From 7deefd5b2d58a9dc2ba91b2b824d4135a4e0837e Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 24 Aug 2020 20:00:15 +0530 Subject: fix wallet DB --- .../src/test-merchant-refund-api.ts | 189 ++++++++++++++++++--- packages/taler-wallet-core/src/db.ts | 2 +- packages/taler-wallet-core/src/operations/pay.ts | 2 +- .../src/operations/transactions.ts | 4 +- packages/taler-wallet-core/src/types/dbTypes.ts | 9 +- 5 files changed, 182 insertions(+), 24 deletions(-) (limited to 'packages') diff --git a/packages/taler-integrationtests/src/test-merchant-refund-api.ts b/packages/taler-integrationtests/src/test-merchant-refund-api.ts index 61f08780c..92d635b2c 100644 --- a/packages/taler-integrationtests/src/test-merchant-refund-api.ts +++ b/packages/taler-integrationtests/src/test-merchant-refund-api.ts @@ -17,35 +17,150 @@ /** * Imports. */ -import { runTest, GlobalTestState, MerchantPrivateApi } from "./harness"; -import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers"; +import { + runTest, + GlobalTestState, + MerchantPrivateApi, + MerchantService, + BankServiceInterface, + MerchantServiceInterface, + WalletCli, + ExchangeServiceInterface, +} from "./harness"; +import { + createSimpleTestkudosEnvironment, + withdrawViaBank, + SimpleTestEnvironment, +} from "./helpers"; import { PreparePayResultType, URL } from "taler-wallet-core"; import axios from "axios"; -/** - * Test case for the refund API of the merchant backend. - */ -runTest(async (t: GlobalTestState) => { - // Set up test environment +async function testRefundApiWithFulfillmentUrl( + t: GlobalTestState, + env: { + merchant: MerchantServiceInterface; + bank: BankServiceInterface; + wallet: WalletCli; + exchange: ExchangeServiceInterface; + }, +): Promise { + const { wallet, bank, exchange, merchant } = env; - const { - wallet, - bank, - exchange, - merchant, - } = await createSimpleTestkudosEnvironment(t); + // Set up order. + const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", { + order: { + summary: "Buy me!", + amount: "TESTKUDOS:5", + fulfillment_url: "https://example.com/fulfillment", + }, + }); - // Withdraw digital cash into the wallet. + let orderStatus = await MerchantPrivateApi.queryPrivateOrderStatus(merchant, { + orderId: orderResp.order_id, + }); - await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); + t.assertTrue(orderStatus.order_status === "unpaid"); - // Set up order. + const talerPayUri = orderStatus.taler_pay_uri; + const orderId = orderResp.order_id; + + // Make wallet pay for the order + + let preparePayResult = await wallet.preparePay({ + talerPayUri, + }); + + t.assertTrue( + preparePayResult.status === PreparePayResultType.PaymentPossible, + ); + + const r2 = await wallet.apiRequest("confirmPay", { + proposalId: preparePayResult.proposalId, + }); + t.assertTrue(r2.type === "response"); + + // Check if payment was successful. + + orderStatus = await MerchantPrivateApi.queryPrivateOrderStatus(merchant, { + orderId: orderResp.order_id, + }); + + t.assertTrue(orderStatus.order_status === "paid"); + + preparePayResult = await wallet.preparePay({ + talerPayUri, + }); + + t.assertTrue( + preparePayResult.status === PreparePayResultType.AlreadyConfirmed, + ); + + await MerchantPrivateApi.giveRefund(merchant, { + amount: "TESTKUDOS:5", + instance: "default", + justification: "foo", + orderId: orderResp.order_id, + }); + + orderStatus = await MerchantPrivateApi.queryPrivateOrderStatus(merchant, { + 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 axios.get(publicOrderStatusUrl.href, { + validateStatus: () => true, + }); + console.log(publicOrderStatusResp.data); + t.assertTrue(publicOrderStatusResp.status === 200); + t.assertAmountEquals(publicOrderStatusResp.data.refund_amount, "TESTKUDOS:5"); + + publicOrderStatusUrl = new URL( + `orders/${orderId}`, + merchant.makeInstanceBaseUrl(), + ); + + publicOrderStatusResp = await axios.get(publicOrderStatusUrl.href, { + validateStatus: () => true, + }); + console.log(publicOrderStatusResp.data); + // We didn't give any authentication, so we should get a fulfillment URL back + t.assertTrue(publicOrderStatusResp.status === 202); + const fu = publicOrderStatusResp.data.fulfillment_url; + t.assertTrue(typeof fu === "string" && fu.startsWith("https://example.com")); +} + +async function testRefundApiWithFulfillmentMessage( + t: GlobalTestState, + env: { + merchant: MerchantServiceInterface; + bank: BankServiceInterface; + wallet: WalletCli; + exchange: ExchangeServiceInterface; + }, +): Promise { + const { wallet, bank, exchange, merchant } = env; + + // Set up order. const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", { order: { summary: "Buy me!", amount: "TESTKUDOS:5", - fulfillment_url: "taler://fulfillment-success/thx", + fulfillment_message: "Thank you for buying foobar", }, }); @@ -131,7 +246,41 @@ runTest(async (t: GlobalTestState) => { publicOrderStatusResp = await axios.get(publicOrderStatusUrl.href, { validateStatus: () => true, }); - console.log(publicOrderStatusResp.data) - // We didn't give any authentication, so this should be forbidden - t.assertTrue(publicOrderStatusResp.status === 403); + console.log(publicOrderStatusResp.data); + // We didn't give any authentication, so we should get a fulfillment URL back + t.assertTrue(publicOrderStatusResp.status === 202); + const fu = publicOrderStatusResp.data.fulfillment_message; + t.assertTrue(typeof fu === "string" && fu.startsWith("Thank you")); +} + +/** + * Test case for the refund API of the merchant backend. + */ +runTest(async (t: GlobalTestState) => { + // Set up test environment + + const { + wallet, + bank, + exchange, + merchant, + } = await createSimpleTestkudosEnvironment(t); + + // Withdraw digital cash into the wallet. + + await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); + + await testRefundApiWithFulfillmentUrl(t, { + wallet, + bank, + exchange, + merchant, + }); + + await testRefundApiWithFulfillmentMessage(t, { + wallet, + bank, + exchange, + merchant, + }); }); diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index f4d0b911e..a55d9bb16 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -8,7 +8,7 @@ import { IDBFactory, IDBDatabase } from "idb-bridge"; * with each major change. When incrementing the major version, * the wallet should import data from the previous version. */ -const TALER_DB_NAME = "taler-walletdb-v7"; +const TALER_DB_NAME = "taler-walletdb-v8"; /** * Current database minor version, should be incremented diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts index 6b45e3da2..7b8a1efac 100644 --- a/packages/taler-wallet-core/src/operations/pay.ts +++ b/packages/taler-wallet-core/src/operations/pay.ts @@ -686,7 +686,7 @@ async function processDownloadProposalImpl( contractData: { amount, contractTermsHash: contractTermsHash, - fulfillmentUrl: parsedContractTerms.fulfillment_url, + fulfillmentUrl: parsedContractTerms.fulfillment_url ?? "", merchantBaseUrl: parsedContractTerms.merchant_base_url, merchantPub: parsedContractTerms.merchant_pub, merchantSig: proposalResp.sig, diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 7b42b9a5f..d869ed770 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -235,7 +235,6 @@ export async function getTransactions( return; } const info: OrderShortInfo = { - fulfillmentUrl: pr.contractData.fulfillmentUrl, merchant: pr.contractData.merchant, orderId: pr.contractData.orderId, products: pr.contractData.products, @@ -243,6 +242,9 @@ export async function getTransactions( summary_i18n: pr.contractData.summaryI18n, contractTermsHash: pr.contractData.contractTermsHash, }; + if (pr.contractData.fulfillmentUrl !== "") { + info.fulfillmentUrl = pr.contractData.fulfillmentUrl; + } const paymentTransactionId = makeEventId( TransactionType.Payment, pr.proposalId, diff --git a/packages/taler-wallet-core/src/types/dbTypes.ts b/packages/taler-wallet-core/src/types/dbTypes.ts index 79100b69f..0b2de8b0c 100644 --- a/packages/taler-wallet-core/src/types/dbTypes.ts +++ b/packages/taler-wallet-core/src/types/dbTypes.ts @@ -1271,7 +1271,14 @@ export interface AllowedExchangeInfo { export interface WalletContractData { products?: Product[]; summaryI18n: { [lang_tag: string]: string } | undefined; - fulfillmentUrl?: string; + + /** + * Fulfillment URL, or the empty string if the order has no fulfillment URL. + * + * Stored as a non-nullable string as we use this field for IndexedDB indexing. + */ + fulfillmentUrl: string; + contractTermsHash: string; fulfillmentMessage?: string; fulfillmentMessageI18n?: InternationalizedString; -- cgit v1.2.3