/* 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 { AmountString, ConfirmPayResultType, MerchantApiClient, NotificationType, PreparePayResultType, j2s, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { GlobalTestState } from "../harness/harness.js"; import { createSimpleTestkudosEnvironmentV3, createWalletDaemonWithClient, withdrawViaBankV3, } from "../harness/helpers.js"; /** * Run test for basic, bank-integrated withdrawal and payment. */ export async function runPaymentShareTest(t: GlobalTestState) { // Set up test environment const { walletClient: firstWallet, bankClient, exchange, merchant, } = await createSimpleTestkudosEnvironmentV3(t); const merchantClient = new MerchantApiClient(merchant.makeInstanceBaseUrl()); // Withdraw digital cash into the wallet. await withdrawViaBankV3(t, { walletClient: firstWallet, bankClient, exchange, amount: "TESTKUDOS:20", }); await firstWallet.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); const { walletClient: secondWallet } = await createWalletDaemonWithClient(t, { name: "wallet2", }); await withdrawViaBankV3(t, { walletClient: secondWallet, bankClient, exchange, amount: "TESTKUDOS:20", }); await secondWallet.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); { const first = await firstWallet.call(WalletApiOperation.GetBalances, {}); const second = await secondWallet.call(WalletApiOperation.GetBalances, {}); t.assertAmountEquals(first.balances[0].available, "TESTKUDOS:19.53"); t.assertAmountEquals(second.balances[0].available, "TESTKUDOS:19.53"); } t.logStep("setup-done"); // create two orders to pay async function createOrder(amount: string) { const order = { summary: "Buy me!", amount: amount as AmountString, fulfillment_url: "taler://fulfillment-success/thx", }; const args = { order }; const orderResp = await merchantClient.createOrder({ order: args.order, }); const orderStatus = await merchantClient.queryPrivateOrderStatus({ orderId: orderResp.order_id, }); t.assertTrue(orderStatus.order_status === "unpaid"); return { id: orderResp.order_id, uri: orderStatus.taler_pay_uri }; } t.logStep("orders-created"); /** * Case 1: * - Claim with first wallet and pay in the second wallet. * - First wallet should be notified. */ { const order = await createOrder("TESTKUDOS:5"); // Claim the order with the first wallet const claimFirstWallet = await firstWallet.call( WalletApiOperation.PreparePayForUri, { talerPayUri: order.uri }, ); t.assertTrue( claimFirstWallet.status === PreparePayResultType.PaymentPossible, ); t.logStep("w1-payment-possible"); // share order from the first wallet const { privatePayUri } = await firstWallet.call( WalletApiOperation.SharePayment, { merchantBaseUrl: merchant.makeInstanceBaseUrl(), orderId: order.id, }, ); t.logStep("w1-payment-shared"); // claim from the second wallet const claimSecondWallet = await secondWallet.call( WalletApiOperation.PreparePayForUri, { talerPayUri: privatePayUri }, ); t.assertTrue( claimSecondWallet.status === PreparePayResultType.PaymentPossible, ); t.logStep("w2-claimed"); // pay from the second wallet const r2 = await secondWallet.call(WalletApiOperation.ConfirmPay, { transactionId: claimSecondWallet.transactionId, }); t.assertTrue(r2.type === ConfirmPayResultType.Done); t.logStep("w2-confirmed"); // Wait for refresh to settle before we do checks await secondWallet.call( WalletApiOperation.TestingWaitTransactionsFinal, {}, ); t.logStep("w2-refresh-settled"); { const first = await firstWallet.call(WalletApiOperation.GetBalances, {}); const second = await secondWallet.call( WalletApiOperation.GetBalances, {}, ); t.assertAmountEquals(first.balances[0].available, "TESTKUDOS:19.53"); t.assertAmountEquals(second.balances[0].available, "TESTKUDOS:14.23"); } t.logStep("wait-for-payment"); // firstWallet.waitForNotificationCond(n => // n.type === NotificationType.TransactionStateTransition && // n.transactionId === claimFirstWallet.transactionId // ) // Claim the order with the first wallet const claimFirstWalletAgain = await firstWallet.call( WalletApiOperation.PreparePayForUri, { talerPayUri: order.uri }, ); t.assertTrue( claimFirstWalletAgain.status === PreparePayResultType.AlreadyConfirmed, ); t.assertTrue( claimFirstWalletAgain.paid ); t.logStep("w1-prepared-again"); const r1 = await firstWallet.call(WalletApiOperation.ConfirmPay, { transactionId: claimFirstWallet.transactionId, }); //t.assertTrue(r1.type === ConfirmPayResultType.Pending); t.logStep("w1-confirmed-shared"); await firstWallet.call( WalletApiOperation.TestingWaitTransactionsFinal, {}, ); await secondWallet.call( WalletApiOperation.TestingWaitTransactionsFinal, {}, ); /** * only the second wallet balance was affected */ { const first = await firstWallet.call(WalletApiOperation.GetBalances, {}); const second = await secondWallet.call( WalletApiOperation.GetBalances, {}, ); t.assertAmountEquals(first.balances[0].available, "TESTKUDOS:19.53"); t.assertAmountEquals(second.balances[0].available, "TESTKUDOS:14.23"); } } t.logStep("first-case-done"); /** * Case 2: * - Claim with first wallet and share with the second wallet * - Pay with the first wallet, second wallet should be notified */ { const order = await createOrder("TESTKUDOS:3"); // Claim the order with the first wallet const claimFirstWallet = await firstWallet.call( WalletApiOperation.PreparePayForUri, { talerPayUri: order.uri }, ); t.assertTrue( claimFirstWallet.status === PreparePayResultType.PaymentPossible, ); t.logStep("case2-w1-claimed"); // share order from the first wallet const { privatePayUri } = await firstWallet.call( WalletApiOperation.SharePayment, { merchantBaseUrl: merchant.makeInstanceBaseUrl(), orderId: order.id, }, ); t.logStep("case2-w1-shared"); // claim from the second wallet const claimSecondWallet = await secondWallet.call( WalletApiOperation.PreparePayForUri, { talerPayUri: privatePayUri }, ); t.logStep("case2-w2-prepared"); t.assertTrue( claimSecondWallet.status === PreparePayResultType.PaymentPossible, ); // pay from the first wallet const r2 = await firstWallet.call(WalletApiOperation.ConfirmPay, { transactionId: claimFirstWallet.transactionId, }); t.assertTrue(r2.type === ConfirmPayResultType.Done); // Wait for refreshes to settle before doing checks await firstWallet.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); /** * only the first wallet balance was affected */ const bal1 = await firstWallet.call(WalletApiOperation.GetBalances, {}); const bal2 = await secondWallet.call(WalletApiOperation.GetBalances, {}); t.assertAmountEquals(bal1.balances[0].available, "TESTKUDOS:16.18"); t.assertAmountEquals(bal2.balances[0].available, "TESTKUDOS:14.23"); t.logStep("wait-for-payment"); // secondWallet.waitForNotificationCond(n => // n.type === NotificationType.TransactionStateTransition && // n.transactionId === claimSecondWallet.transactionId // ) // Claim the order with the first wallet const claimSecondWalletAgain = await secondWallet.call( WalletApiOperation.PreparePayForUri, { talerPayUri: order.uri }, ); t.assertTrue( claimSecondWalletAgain.status === PreparePayResultType.AlreadyConfirmed, ); t.assertTrue( claimSecondWalletAgain.paid, ); } t.logStep("second-case-done"); } runPaymentShareTest.suites = ["wallet"];