/* This file is part of GNU Taler (C) 2020 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ /** * Imports. */ import { Duration, MerchantApiClient, PreparePayResultType, URL, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { BankServiceHandle, ExchangeServiceInterface, GlobalTestState, MerchantServiceInterface, WalletClient, harnessHttpLib, } from "../harness/harness.js"; import { createSimpleTestkudosEnvironmentV2, withdrawViaBankV2, } from "../harness/helpers.js"; async function testRefundApiWithFulfillmentUrl( t: GlobalTestState, env: { merchant: MerchantServiceInterface; bank: BankServiceHandle; walletClient: WalletClient; exchange: ExchangeServiceInterface; }, ): Promise { const { walletClient, merchant } = env; const merchantClient = new MerchantApiClient(merchant.makeInstanceBaseUrl()); // Set up order. const orderResp = await merchantClient.createOrder({ order: { summary: "Buy me!", amount: "TESTKUDOS:5", fulfillment_url: "https://example.com/fulfillment", }, refund_delay: Duration.toTalerProtocolDuration( Duration.fromSpec({ minutes: 5 }), ), }); let orderStatus = await merchantClient.queryPrivateOrderStatus({ orderId: orderResp.order_id, }); t.assertTrue(orderStatus.order_status === "unpaid"); const talerPayUri = orderStatus.taler_pay_uri; const orderId = orderResp.order_id; // Make wallet pay for the order let preparePayResult = await walletClient.call( WalletApiOperation.PreparePayForUri, { talerPayUri, }, ); t.assertTrue( preparePayResult.status === PreparePayResultType.PaymentPossible, ); await walletClient.call(WalletApiOperation.ConfirmPay, { proposalId: preparePayResult.proposalId, }); // Check if payment was successful. orderStatus = await merchantClient.queryPrivateOrderStatus({ orderId: orderResp.order_id, }); t.assertTrue(orderStatus.order_status === "paid"); preparePayResult = await walletClient.call( WalletApiOperation.PreparePayForUri, { talerPayUri, }, ); t.assertTrue( preparePayResult.status === PreparePayResultType.AlreadyConfirmed, ); await merchantClient.giveRefund({ amount: "TESTKUDOS:5", instance: "default", justification: "foo", orderId: orderResp.order_id, }); orderStatus = await merchantClient.queryPrivateOrderStatus({ orderId: orderResp.order_id, }); t.assertTrue(orderStatus.order_status === "paid"); t.assertAmountEquals(orderStatus.refund_amount, "TESTKUDOS:5"); // Now test what the merchant gives as a response for various requests to the // public order status URL! let publicOrderStatusUrl = new URL( `orders/${orderId}`, merchant.makeInstanceBaseUrl(), ); publicOrderStatusUrl.searchParams.set( "h_contract", preparePayResult.contractTermsHash, ); let publicOrderStatusResp = await harnessHttpLib.fetch( publicOrderStatusUrl.href, ); const respData = await publicOrderStatusResp.json(); t.assertTrue(publicOrderStatusResp.status === 200); t.assertAmountEquals(respData.refund_amount, "TESTKUDOS:5"); publicOrderStatusUrl = new URL( `orders/${orderId}`, merchant.makeInstanceBaseUrl(), ); console.log(`requesting order status via '${publicOrderStatusUrl.href}'`); publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href); console.log(publicOrderStatusResp.status); console.log(await publicOrderStatusResp.json()); // We didn't give any authentication, so we should get a fulfillment URL back t.assertTrue(publicOrderStatusResp.status === 403); } async function testRefundApiWithFulfillmentMessage( t: GlobalTestState, env: { merchant: MerchantServiceInterface; bank: BankServiceHandle; walletClient: WalletClient; exchange: ExchangeServiceInterface; }, ): Promise { const { walletClient, merchant } = env; const merchantClient = new MerchantApiClient(merchant.makeInstanceBaseUrl()); // Set up order. const orderResp = await merchantClient.createOrder({ order: { summary: "Buy me!", amount: "TESTKUDOS:5", fulfillment_message: "Thank you for buying foobar", }, refund_delay: Duration.toTalerProtocolDuration( Duration.fromSpec({ minutes: 5 }), ), }); let orderStatus = await merchantClient.queryPrivateOrderStatus({ orderId: orderResp.order_id, }); t.assertTrue(orderStatus.order_status === "unpaid"); const talerPayUri = orderStatus.taler_pay_uri; const orderId = orderResp.order_id; // Make wallet pay for the order let preparePayResult = await walletClient.call( WalletApiOperation.PreparePayForUri, { talerPayUri, }, ); t.assertTrue( preparePayResult.status === PreparePayResultType.PaymentPossible, ); await walletClient.call(WalletApiOperation.ConfirmPay, { proposalId: preparePayResult.proposalId, }); // Check if payment was successful. orderStatus = await merchantClient.queryPrivateOrderStatus({ orderId: orderResp.order_id, }); t.assertTrue(orderStatus.order_status === "paid"); preparePayResult = await walletClient.call( WalletApiOperation.PreparePayForUri, { talerPayUri, }, ); t.assertTrue( preparePayResult.status === PreparePayResultType.AlreadyConfirmed, ); await merchantClient.giveRefund({ amount: "TESTKUDOS:5", instance: "default", justification: "foo", orderId: orderResp.order_id, }); orderStatus = await merchantClient.queryPrivateOrderStatus({ orderId: orderResp.order_id, }); t.assertTrue(orderStatus.order_status === "paid"); t.assertAmountEquals(orderStatus.refund_amount, "TESTKUDOS:5"); // Now test what the merchant gives as a response for various requests to the // public order status URL! let publicOrderStatusUrl = new URL( `orders/${orderId}`, merchant.makeInstanceBaseUrl(), ); publicOrderStatusUrl.searchParams.set( "h_contract", preparePayResult.contractTermsHash, ); let publicOrderStatusResp = await harnessHttpLib.fetch( publicOrderStatusUrl.href, ); let respData = await publicOrderStatusResp.json(); console.log(respData); t.assertTrue(publicOrderStatusResp.status === 200); t.assertAmountEquals(respData.refund_amount, "TESTKUDOS:5"); publicOrderStatusUrl = new URL( `orders/${orderId}`, merchant.makeInstanceBaseUrl(), ); publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href); respData = await publicOrderStatusResp.json(); console.log(respData); // We didn't give any authentication, so we should get a fulfillment URL back t.assertTrue(publicOrderStatusResp.status === 403); } /** * Test case for the refund API of the merchant backend. */ export async function runMerchantRefundApiTest(t: GlobalTestState) { // Set up test environment const { walletClient, bank, exchange, merchant } = await createSimpleTestkudosEnvironmentV2(t); // Withdraw digital cash into the wallet. const wres = await withdrawViaBankV2(t, { walletClient, bank, exchange, amount: "TESTKUDOS:20", }); await wres.withdrawalFinishedCond; await testRefundApiWithFulfillmentUrl(t, { walletClient, bank, exchange, merchant, }); await testRefundApiWithFulfillmentMessage(t, { walletClient, bank, exchange, merchant, }); } runMerchantRefundApiTest.suites = ["merchant"];