From 7f0edb6a783d9a50f94f65c815c1280baecaac89 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Fri, 5 May 2023 19:03:44 +0200 Subject: wallet-core: refund DD37 refactoring --- .../src/operations/transactions.ts | 310 ++++----------------- 1 file changed, 49 insertions(+), 261 deletions(-) (limited to 'packages/taler-wallet-core/src/operations/transactions.ts') diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 02f11d82d..d9778f0c2 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -19,7 +19,6 @@ */ import { AbsoluteTime, - AmountJson, Amounts, constructPayPullUri, constructPayPushUri, @@ -51,9 +50,7 @@ import { PeerPushPaymentInitiationRecord, PurchaseStatus, PurchaseRecord, - RefundState, TipRecord, - WalletRefundItem, WithdrawalGroupRecord, WithdrawalRecordType, WalletContractData, @@ -66,6 +63,7 @@ import { PeerPushPaymentIncomingRecord, PeerPushPaymentIncomingStatus, PeerPullPaymentInitiationRecord, + RefundGroupRecord, } from "../db.js"; import { InternalWalletState } from "../internal-wallet-state.js"; import { PendingTaskType } from "../pending-types.js"; @@ -89,6 +87,7 @@ import { getExchangeDetails } from "./exchanges.js"; import { abortPayMerchant, computePayMerchantTransactionState, + computeRefundTransactionState, expectProposalDownload, extractContractData, processPurchasePay, @@ -205,40 +204,15 @@ export async function getTransactionById( .runReadWrite(async (tx) => { const purchase = await tx.purchases.get(proposalId); if (!purchase) throw Error("not found"); - - const filteredRefunds = await Promise.all( - Object.values(purchase.refunds).map(async (r) => { - const t = await tx.tombstones.get( - makeTombstoneId( - TombstoneTag.DeleteRefund, - purchase.proposalId, - `${r.executionTime.t_s}`, - ), - ); - if (!t) return r; - return undefined; - }), - ); - const download = await expectProposalDownload(ws, purchase, tx); - - const cleanRefunds = filteredRefunds.filter( - (x): x is WalletRefundItem => !!x, - ); - const contractData = download.contractData; - const refunds = mergeRefundByExecutionTime( - cleanRefunds, - Amounts.zeroOfAmount(contractData.amount), - ); - const payOpId = TaskIdentifiers.forPay(purchase); const payRetryRecord = await tx.operationRetries.get(payOpId); return buildTransactionForPurchase( purchase, contractData, - refunds, + [], // FIXME: Add refunds from refund group records here. payRetryRecord, ); }); @@ -272,66 +246,8 @@ export async function getTransactionById( return buildTransactionForDeposit(depositRecord, retries); }); } else if (type === TransactionType.Refund) { - const proposalId = rest[0]; - const executionTimeStr = rest[1]; - - return await ws.db - .mktx((x) => [ - x.operationRetries, - x.purchases, - x.tombstones, - x.contractTerms, - ]) - .runReadWrite(async (tx) => { - const purchase = await tx.purchases.get(proposalId); - if (!purchase) throw Error("not found"); - - const t = await tx.tombstones.get( - makeTombstoneId( - TombstoneTag.DeleteRefund, - purchase.proposalId, - executionTimeStr, - ), - ); - if (t) throw Error("deleted"); - - const filteredRefunds = await Promise.all( - Object.values(purchase.refunds).map(async (r) => { - const t = await tx.tombstones.get( - makeTombstoneId( - TombstoneTag.DeleteRefund, - purchase.proposalId, - `${r.executionTime.t_s}`, - ), - ); - if (!t) return r; - return undefined; - }), - ); - - const cleanRefunds = filteredRefunds.filter( - (x): x is WalletRefundItem => !!x, - ); - - const download = await expectProposalDownload(ws, purchase, tx); - const contractData = download.contractData; - const refunds = mergeRefundByExecutionTime( - cleanRefunds, - Amounts.zeroOfAmount(contractData.amount), - ); - - const theRefund = refunds.find( - (r) => `${r.executionTime.t_s}` === executionTimeStr, - ); - if (!theRefund) throw Error("not found"); - - return buildTransactionForRefund( - purchase, - contractData, - theRefund, - undefined, - ); - }); + // FIXME! + throw Error("not implemented"); } else if (type === TransactionType.PeerPullDebit) { const peerPullPaymentIncomingId = rest[0]; return await ws.db @@ -730,6 +646,29 @@ function buildTransactionForManualWithdraw( }; } +function buildTransactionForRefund( + refundRecord: RefundGroupRecord, +): Transaction { + return { + type: TransactionType.Refund, + amountEffective: refundRecord.amountEffective, + amountRaw: refundRecord.amountEffective, + refundedTransactionId: constructTransactionIdentifier({ + tag: TransactionType.Payment, + proposalId: refundRecord.proposalId + }), + timestamp: refundRecord.timestampCreated, + transactionId: constructTransactionIdentifier({ + tag: TransactionType.Refund, + refundGroupId: refundRecord.refundGroupId, + }), + txState: computeRefundTransactionState(refundRecord), + extendedStatus: ExtendedStatus.Done, + frozen: false, + pending: false, + } +} + function buildTransactionForRefresh( refreshGroupRecord: RefreshGroupRecord, ort?: OperationRetryRecord, @@ -850,113 +789,11 @@ function buildTransactionForTip( }; } -/** - * For a set of refund with the same executionTime. - */ -interface MergedRefundInfo { - executionTime: TalerProtocolTimestamp; - amountAppliedRaw: AmountJson; - amountAppliedEffective: AmountJson; - firstTimestamp: TalerProtocolTimestamp; -} - -function mergeRefundByExecutionTime( - rs: WalletRefundItem[], - zero: AmountJson, -): MergedRefundInfo[] { - const refundByExecTime = rs.reduce((prev, refund) => { - const key = `${refund.executionTime.t_s}`; - - // refunds count if applied - const effective = - refund.type === RefundState.Applied - ? Amounts.sub( - refund.refundAmount, - refund.refundFee, - refund.totalRefreshCostBound, - ).amount - : zero; - const raw = - refund.type === RefundState.Applied ? refund.refundAmount : zero; - - const v = prev.get(key); - if (!v) { - prev.set(key, { - executionTime: refund.executionTime, - amountAppliedEffective: effective, - amountAppliedRaw: Amounts.parseOrThrow(raw), - firstTimestamp: refund.obtainedTime, - }); - } else { - //v.executionTime is the same - v.amountAppliedEffective = Amounts.add( - v.amountAppliedEffective, - effective, - ).amount; - v.amountAppliedRaw = Amounts.add( - v.amountAppliedRaw, - refund.refundAmount, - ).amount; - v.firstTimestamp = TalerProtocolTimestamp.min( - v.firstTimestamp, - refund.obtainedTime, - ); - } - return prev; - }, new Map()); - - return Array.from(refundByExecTime.values()); -} - -async function buildTransactionForRefund( - purchaseRecord: PurchaseRecord, - contractData: WalletContractData, - refundInfo: MergedRefundInfo, - ort?: OperationRetryRecord, -): Promise { - const info: OrderShortInfo = { - merchant: contractData.merchant, - orderId: contractData.orderId, - products: contractData.products, - summary: contractData.summary, - summary_i18n: contractData.summaryI18n, - contractTermsHash: contractData.contractTermsHash, - }; - if (contractData.fulfillmentUrl !== "") { - info.fulfillmentUrl = contractData.fulfillmentUrl; - } - - return { - type: TransactionType.Refund, - txState: mkTxStateUnknown(), - info, - refundedTransactionId: makeTransactionId( - TransactionType.Payment, - purchaseRecord.proposalId, - ), - transactionId: makeTransactionId( - TransactionType.Refund, - purchaseRecord.proposalId, - `${refundInfo.executionTime.t_s}`, - ), - timestamp: refundInfo.firstTimestamp, - amountEffective: Amounts.stringify(refundInfo.amountAppliedEffective), - amountRaw: Amounts.stringify(refundInfo.amountAppliedRaw), - refundPending: - purchaseRecord.refundAmountAwaiting === undefined - ? undefined - : Amounts.stringify(purchaseRecord.refundAmountAwaiting), - extendedStatus: ExtendedStatus.Done, - pending: false, - frozen: false, - ...(ort?.lastError ? { error: ort.lastError } : {}), - }; -} async function buildTransactionForPurchase( purchaseRecord: PurchaseRecord, contractData: WalletContractData, - refundsInfo: MergedRefundInfo[], + refundsInfo: RefundGroupRecord[], ort?: OperationRetryRecord, ): Promise { const zero = Amounts.zeroOfAmount(contractData.amount); @@ -974,30 +811,7 @@ async function buildTransactionForPurchase( info.fulfillmentUrl = contractData.fulfillmentUrl; } - const totalRefund = refundsInfo.reduce( - (prev, cur) => { - return { - raw: Amounts.add(prev.raw, cur.amountAppliedRaw).amount, - effective: Amounts.add(prev.effective, cur.amountAppliedEffective) - .amount, - }; - }, - { - raw: zero, - effective: zero, - } as { raw: AmountJson; effective: AmountJson }, - ); - - const refunds: RefundInfoShort[] = refundsInfo.map((r) => ({ - amountEffective: Amounts.stringify(r.amountAppliedEffective), - amountRaw: Amounts.stringify(r.amountAppliedRaw), - timestamp: r.executionTime, - transactionId: makeTransactionId( - TransactionType.Refund, - purchaseRecord.proposalId, - `${r.executionTime.t_s}`, - ), - })); + const refunds: RefundInfoShort[] = []; const timestamp = purchaseRecord.timestampAccept; checkDbInvariant(!!timestamp); @@ -1008,7 +822,7 @@ async function buildTransactionForPurchase( case PurchaseStatus.AbortingWithRefund: status = ExtendedStatus.Aborting; break; - case PurchaseStatus.Paid: + case PurchaseStatus.Done: case PurchaseStatus.RepurchaseDetected: status = ExtendedStatus.Done; break; @@ -1018,10 +832,10 @@ async function buildTransactionForPurchase( case PurchaseStatus.Paying: status = ExtendedStatus.Pending; break; - case PurchaseStatus.ProposalDownloadFailed: + case PurchaseStatus.FailedClaim: status = ExtendedStatus.Failed; break; - case PurchaseStatus.PaymentAbortFinished: + case PurchaseStatus.AbortedIncompletePayment: status = ExtendedStatus.Aborted; break; default: @@ -1034,8 +848,8 @@ async function buildTransactionForPurchase( txState: computePayMerchantTransactionState(purchaseRecord), amountRaw: Amounts.stringify(contractData.amount), amountEffective: Amounts.stringify(purchaseRecord.payInfo.totalPayCost), - totalRefundRaw: Amounts.stringify(totalRefund.raw), - totalRefundEffective: Amounts.stringify(totalRefund.effective), + totalRefundRaw: Amounts.stringify(zero), // FIXME! + totalRefundEffective: Amounts.stringify(zero), // FIXME! refundPending: purchaseRecord.refundAmountAwaiting === undefined ? undefined @@ -1057,7 +871,7 @@ async function buildTransactionForPurchase( refundQueryActive: purchaseRecord.purchaseStatus === PurchaseStatus.QueryingRefund, frozen: - purchaseRecord.purchaseStatus === PurchaseStatus.PaymentAbortFinished ?? + purchaseRecord.purchaseStatus === PurchaseStatus.AbortedIncompletePayment ?? false, ...(ort?.lastError ? { error: ort.lastError } : {}), }; @@ -1092,6 +906,7 @@ export async function getTransactions( x.tombstones, x.withdrawalGroups, x.refreshGroups, + x.refundGroups, ]) .runReadOnly(async (tx) => { tx.peerPushPaymentInitiations.iter().forEachAsync(async (pi) => { @@ -1202,6 +1017,14 @@ export async function getTransactions( ); }); + tx.refundGroups.iter().forEachAsync(async (refundGroup) => { + const currency = Amounts.currencyOf(refundGroup.amountRaw); + if (shouldSkipCurrency(transactionsRequest, currency)) { + return; + } + transactions.push(buildTransactionForRefund(refundGroup)) + }); + tx.refreshGroups.iter().forEachAsync(async (rg) => { if (shouldSkipCurrency(transactionsRequest, rg.currency)) { return; @@ -1318,47 +1141,13 @@ export async function getTransactions( download.contractTermsMerchantSig, ); - const filteredRefunds = await Promise.all( - Object.values(purchase.refunds).map(async (r) => { - const t = await tx.tombstones.get( - makeTombstoneId( - TombstoneTag.DeleteRefund, - purchase.proposalId, - `${r.executionTime.t_s}`, - ), - ); - if (!t) return r; - return undefined; - }), - ); - - const cleanRefunds = filteredRefunds.filter( - (x): x is WalletRefundItem => !!x, - ); - - const refunds = mergeRefundByExecutionTime( - cleanRefunds, - Amounts.zeroOfCurrency(download.currency), - ); - - refunds.forEach(async (refundInfo) => { - transactions.push( - await buildTransactionForRefund( - purchase, - contractData, - refundInfo, - undefined, - ), - ); - }); - const payOpId = TaskIdentifiers.forPay(purchase); const payRetryRecord = await tx.operationRetries.get(payOpId); transactions.push( await buildTransactionForPurchase( purchase, contractData, - refunds, + [], // FIXME! payRetryRecord, ), ); @@ -1425,7 +1214,7 @@ export type ParsedTransactionIdentifier = | { tag: TransactionType.PeerPushCredit; peerPushPaymentIncomingId: string } | { tag: TransactionType.PeerPushDebit; pursePub: string } | { tag: TransactionType.Refresh; refreshGroupId: string } - | { tag: TransactionType.Refund; proposalId: string; executionTime: string } + | { tag: TransactionType.Refund; refundGroupId: string } | { tag: TransactionType.Tip; walletTipId: string } | { tag: TransactionType.Withdrawal; withdrawalGroupId: string }; @@ -1448,7 +1237,7 @@ export function constructTransactionIdentifier( case TransactionType.Refresh: return `txn:${pTxId.tag}:${pTxId.refreshGroupId}`; case TransactionType.Refund: - return `txn:${pTxId.tag}:${pTxId.proposalId}:${pTxId.executionTime}`; + return `txn:${pTxId.tag}:${pTxId.refundGroupId}`; case TransactionType.Tip: return `txn:${pTxId.tag}:${pTxId.walletTipId}`; case TransactionType.Withdrawal: @@ -1490,8 +1279,7 @@ export function parseTransactionIdentifier( case TransactionType.Refund: return { tag: TransactionType.Refund, - proposalId: rest[0], - executionTime: rest[1], + refundGroupId: rest[0], }; case TransactionType.Tip: return { -- cgit v1.2.3