From 66d76a35912d7687d76b349f1cac462306306d3f Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 10 Aug 2020 16:48:38 +0530 Subject: simplify refunds a bit, show in transaction history, add integration tests --- .../taler-wallet-core/src/operations/refund.ts | 60 +++++++-------- .../src/operations/transactions.ts | 86 +++++++++++++++++++--- 2 files changed, 102 insertions(+), 44 deletions(-) (limited to 'packages/taler-wallet-core/src/operations') diff --git a/packages/taler-wallet-core/src/operations/refund.ts b/packages/taler-wallet-core/src/operations/refund.ts index 9792d2268..2b6ee97ae 100644 --- a/packages/taler-wallet-core/src/operations/refund.ts +++ b/packages/taler-wallet-core/src/operations/refund.ts @@ -203,7 +203,7 @@ async function acceptRefunds( refunds: MerchantCoinRefundStatus[], reason: RefundReason, ): Promise { - console.log("handling refunds", refunds); + logger.trace("handling refunds", refunds); const now = getTimestampNow(); await ws.db.runWithWriteTransaction( @@ -302,37 +302,6 @@ async function acceptRefunds( }); } -async function startRefundQuery( - ws: InternalWalletState, - proposalId: string, -): Promise { - const success = await ws.db.runWithWriteTransaction( - [Stores.purchases], - async (tx) => { - const p = await tx.get(Stores.purchases, proposalId); - if (!p) { - logger.error("no purchase found for refund URL"); - return false; - } - p.refundStatusRequested = true; - p.lastRefundStatusError = undefined; - p.refundStatusRetryInfo = initRetryInfo(); - await tx.put(Stores.purchases, p); - return true; - }, - ); - - if (!success) { - return; - } - - ws.notify({ - type: NotificationType.RefundStarted, - }); - - await processPurchaseQueryRefund(ws, proposalId); -} - /** * Accept a refund, return the contract hash for the contract * that was involved in the refund. @@ -360,8 +329,31 @@ export async function applyRefund( ); } + const proposalId = purchase.proposalId; + logger.info("processing purchase for refund"); - await startRefundQuery(ws, purchase.proposalId); + const success = await ws.db.runWithWriteTransaction( + [Stores.purchases], + async (tx) => { + const p = await tx.get(Stores.purchases, proposalId); + if (!p) { + logger.error("no purchase found for refund URL"); + return false; + } + p.refundStatusRequested = true; + p.lastRefundStatusError = undefined; + p.refundStatusRetryInfo = initRetryInfo(); + await tx.put(Stores.purchases, p); + return true; + }, + ); + + if (success) { + ws.notify({ + type: NotificationType.RefundStarted, + }); + await processPurchaseQueryRefund(ws, proposalId); + } return { contractTermsHash: purchase.contractData.contractTermsHash, @@ -422,7 +414,7 @@ async function processPurchaseQueryRefundImpl( const request = await ws.http.get(requestUrl.href); - console.log("got json", JSON.stringify(await request.json(), undefined, 2)); + logger.trace("got json", JSON.stringify(await request.json(), undefined, 2)); const refundResponse = await readSuccessResponseJsonOrThrow( request, diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 2d66b5e9d..8de204d49 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -18,7 +18,12 @@ * Imports. */ import { InternalWalletState } from "./state"; -import { Stores, WithdrawalSourceType } from "../types/dbTypes"; +import { + Stores, + WithdrawalSourceType, + WalletRefundItem, + RefundState, +} from "../types/dbTypes"; import { Amounts, AmountJson } from "../util/amounts"; import { timestampCmp } from "../util/time"; import { @@ -29,8 +34,10 @@ import { PaymentStatus, WithdrawalType, WithdrawalDetails, + PaymentShortInfo, } from "../types/transactions"; import { getFundingPaytoUris } from "./reserves"; +import { ResultLevel } from "idb-bridge"; /** * Create an event ID from the type and the primary key for the event. @@ -224,6 +231,18 @@ export async function getTransactions( if (!proposal) { return; } + const info: PaymentShortInfo = { + fulfillmentUrl: pr.contractData.fulfillmentUrl, + merchant: pr.contractData.merchant, + orderId: pr.contractData.orderId, + products: pr.contractData.products, + summary: pr.contractData.summary, + summary_i18n: pr.contractData.summaryI18n, + }; + const paymentTransactionId = makeEventId( + TransactionType.Payment, + pr.proposalId, + ); transactions.push({ type: TransactionType.Payment, amountRaw: Amounts.stringify(pr.contractData.amount), @@ -233,15 +252,62 @@ export async function getTransactions( : PaymentStatus.Accepted, pending: !pr.timestampFirstSuccessfulPay, timestamp: pr.timestampAccept, - transactionId: makeEventId(TransactionType.Payment, pr.proposalId), - info: { - fulfillmentUrl: pr.contractData.fulfillmentUrl, - merchant: pr.contractData.merchant, - orderId: pr.contractData.orderId, - products: pr.contractData.products, - summary: pr.contractData.summary, - summary_i18n: pr.contractData.summaryI18n, - }, + transactionId: paymentTransactionId, + info: info, + }); + + const refundGroupKeys = new Set(); + + for (const rk of Object.keys(pr.refunds)) { + const refund = pr.refunds[rk]; + const groupKey = `${refund.executionTime.t_ms}`; + refundGroupKeys.add(groupKey); + } + + refundGroupKeys.forEach((groupKey: string) => { + const refundTransactionId = makeEventId( + TransactionType.Payment, + pr.proposalId, + groupKey, + ); + let r0: WalletRefundItem | undefined; + let amountEffective = Amounts.getZero( + pr.contractData.amount.currency, + ); + let amountRaw = Amounts.getZero(pr.contractData.amount.currency); + for (const rk of Object.keys(pr.refunds)) { + const refund = pr.refunds[rk]; + if (!r0) { + r0 = refund; + } + if (refund.type === RefundState.Applied) { + amountEffective = Amounts.add( + amountEffective, + refund.refundAmount, + ).amount; + amountRaw = Amounts.add( + amountRaw, + Amounts.sub( + refund.refundAmount, + refund.refundFee, + refund.totalRefreshCostBound, + ).amount, + ).amount; + } + } + if (!r0) { + throw Error("invariant violated"); + } + transactions.push({ + type: TransactionType.Refund, + info, + refundedTransactionId: paymentTransactionId, + transactionId: refundTransactionId, + timestamp: r0.executionTime, + amountEffective: Amounts.stringify(amountEffective), + amountRaw: Amounts.stringify(amountRaw), + pending: false, + }); }); // for (const rg of pr.refundGroups) { -- cgit v1.2.3