diff options
Diffstat (limited to 'packages/taler-wallet-core/src/transactions.ts')
-rw-r--r-- | packages/taler-wallet-core/src/transactions.ts | 234 |
1 files changed, 158 insertions, 76 deletions
diff --git a/packages/taler-wallet-core/src/transactions.ts b/packages/taler-wallet-core/src/transactions.ts index f6216d641..0f7b1c3ca 100644 --- a/packages/taler-wallet-core/src/transactions.ts +++ b/packages/taler-wallet-core/src/transactions.ts @@ -243,21 +243,29 @@ export async function getTransactionById( const opId = TaskIdentifiers.forWithdrawal(withdrawalGroupRecord); const ort = await tx.operationRetries.get(opId); + const exchangeDetails = + withdrawalGroupRecord.exchangeBaseUrl === undefined + ? undefined + : await getExchangeWireDetailsInTx( + tx, + withdrawalGroupRecord.exchangeBaseUrl, + ); + // if (!exchangeDetails) throw Error("not exchange details"); + if ( withdrawalGroupRecord.wgInfo.withdrawalType === WithdrawalRecordType.BankIntegrated ) { return buildTransactionForBankIntegratedWithdraw( withdrawalGroupRecord, + exchangeDetails, ort, ); } - const exchangeDetails = await getExchangeWireDetailsInTx( - tx, - withdrawalGroupRecord.exchangeBaseUrl, + checkDbInvariant( + exchangeDetails !== undefined, + "manual withdrawal without exchange", ); - if (!exchangeDetails) throw Error("not exchange details"); - return buildTransactionForManualWithdraw( withdrawalGroupRecord, exchangeDetails, @@ -402,7 +410,10 @@ export async function getTransactionById( const debit = await tx.peerPushDebit.get(parsedTx.pursePub); if (!debit) throw Error("not found"); const ct = await tx.contractTerms.get(debit.contractTermsHash); - checkDbInvariant(!!ct); + checkDbInvariant( + !!ct, + `no contract terms for p2p push ${parsedTx.pursePub}`, + ); return buildTransactionForPushPaymentDebit( debit, ct.contractTermsRaw, @@ -426,7 +437,10 @@ export async function getTransactionById( const pushInc = await tx.peerPushCredit.get(peerPushCreditId); if (!pushInc) throw Error("not found"); const ct = await tx.contractTerms.get(pushInc.contractTermsHash); - checkDbInvariant(!!ct); + checkDbInvariant( + !!ct, + `no contract terms for p2p push ${peerPushCreditId}`, + ); let wg: WithdrawalGroupRecord | undefined = undefined; let wgOrt: OperationRetryRecord | undefined = undefined; @@ -438,7 +452,7 @@ export async function getTransactionById( } } const pushIncOpId = TaskIdentifiers.forPeerPushCredit(pushInc); - let pushIncOrt = await tx.operationRetries.get(pushIncOpId); + const pushIncOrt = await tx.operationRetries.get(pushIncOpId); return buildTransactionForPeerPushCredit( pushInc, @@ -466,7 +480,7 @@ export async function getTransactionById( const pushInc = await tx.peerPullCredit.get(pursePub); if (!pushInc) throw Error("not found"); const ct = await tx.contractTerms.get(pushInc.contractTermsHash); - checkDbInvariant(!!ct); + checkDbInvariant(!!ct, `no contract terms for p2p push ${pursePub}`); let wg: WithdrawalGroupRecord | undefined = undefined; let wgOrt: OperationRetryRecord | undefined = undefined; @@ -589,6 +603,9 @@ function buildTransactionForPeerPullCredit( ); }); const txState = computePeerPullCreditTransactionState(pullCredit); + checkDbInvariant(wsr.instructedAmount !== undefined, "wg uninitialized"); + checkDbInvariant(wsr.denomsSel !== undefined, "wg uninitialized"); + checkDbInvariant(wsr.exchangeBaseUrl !== undefined, "wg uninitialized"); return { type: TransactionType.PeerPullCredit, txState, @@ -654,13 +671,16 @@ function buildTransactionForPeerPushCredit( pushInc: PeerPushPaymentIncomingRecord, pushOrt: OperationRetryRecord | undefined, peerContractTerms: PeerContractTerms, - wsr: WithdrawalGroupRecord | undefined, + wg: WithdrawalGroupRecord | undefined, wsrOrt: OperationRetryRecord | undefined, ): Transaction { - if (wsr) { - if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPushCredit) { + if (wg) { + if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPushCredit) { throw Error("invalid withdrawal group type for push payment credit"); } + checkDbInvariant(wg.instructedAmount !== undefined, "wg uninitialized"); + checkDbInvariant(wg.denomsSel !== undefined, "wg uninitialized"); + checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg uninitialized"); const txState = computePeerPushCreditTransactionState(pushInc); return { @@ -668,15 +688,15 @@ function buildTransactionForPeerPushCredit( txState, txActions: computePeerPushCreditTransactionActions(pushInc), amountEffective: isUnsuccessfulTransaction(txState) - ? Amounts.stringify(Amounts.zeroOfAmount(wsr.instructedAmount)) - : Amounts.stringify(wsr.denomsSel.totalCoinValue), - amountRaw: Amounts.stringify(wsr.instructedAmount), - exchangeBaseUrl: wsr.exchangeBaseUrl, + ? Amounts.stringify(Amounts.zeroOfAmount(wg.instructedAmount)) + : Amounts.stringify(wg.denomsSel.totalCoinValue), + amountRaw: Amounts.stringify(wg.instructedAmount), + exchangeBaseUrl: wg.exchangeBaseUrl, info: { expiration: peerContractTerms.purse_expiration, summary: peerContractTerms.summary, }, - timestamp: timestampPreciseFromDb(wsr.timestampStart), + timestamp: timestampPreciseFromDb(wg.timestampStart), transactionId: constructTransactionIdentifier({ tag: TransactionType.PeerPushCredit, peerPushCreditId: pushInc.peerPushCreditId, @@ -712,37 +732,49 @@ function buildTransactionForPeerPushCredit( } function buildTransactionForBankIntegratedWithdraw( - wgRecord: WithdrawalGroupRecord, + wg: WithdrawalGroupRecord, + exchangeDetails: ExchangeWireDetails | undefined, ort?: OperationRetryRecord, ): TransactionWithdrawal { - if (wgRecord.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) + if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) { throw Error(""); - - const txState = computeWithdrawalTransactionStatus(wgRecord); + } + const instructedCurrency = + wg.instructedAmount === undefined + ? undefined + : Amounts.currencyOf(wg.instructedAmount); + const currency = wg.wgInfo.bankInfo.currency ?? instructedCurrency; + checkDbInvariant(currency !== undefined, "wg uninitialized (missing currency)"); + const txState = computeWithdrawalTransactionStatus(wg); + + const zero = Amounts.stringify(Amounts.zeroOfCurrency(currency)); return { type: TransactionType.Withdrawal, txState, - txActions: computeWithdrawalTransactionActions(wgRecord), - amountEffective: isUnsuccessfulTransaction(txState) - ? Amounts.stringify(Amounts.zeroOfAmount(wgRecord.instructedAmount)) - : Amounts.stringify(wgRecord.denomsSel.totalCoinValue), - amountRaw: Amounts.stringify(wgRecord.instructedAmount), + txActions: computeWithdrawalTransactionActions(wg), + exchangeBaseUrl: wg.exchangeBaseUrl, + amountEffective: + isUnsuccessfulTransaction(txState) || !wg.denomsSel + ? zero + : Amounts.stringify(wg.denomsSel.totalCoinValue), + amountRaw: !wg.instructedAmount + ? zero + : Amounts.stringify(wg.instructedAmount), withdrawalDetails: { type: WithdrawalType.TalerBankIntegrationApi, - confirmed: wgRecord.wgInfo.bankInfo.timestampBankConfirmed ? true : false, - exchangeCreditAccountDetails: wgRecord.wgInfo.exchangeCreditAccounts, - reservePub: wgRecord.reservePub, - bankConfirmationUrl: wgRecord.wgInfo.bankInfo.confirmUrl, + confirmed: wg.wgInfo.bankInfo.timestampBankConfirmed ? true : false, + exchangeCreditAccountDetails: wg.wgInfo.exchangeCreditAccounts, + reservePub: wg.reservePub, + bankConfirmationUrl: wg.wgInfo.bankInfo.confirmUrl, reserveIsReady: - wgRecord.status === WithdrawalGroupStatus.Done || - wgRecord.status === WithdrawalGroupStatus.PendingReady, + wg.status === WithdrawalGroupStatus.Done || + wg.status === WithdrawalGroupStatus.PendingReady, }, - kycUrl: wgRecord.kycUrl, - exchangeBaseUrl: wgRecord.exchangeBaseUrl, - timestamp: timestampPreciseFromDb(wgRecord.timestampStart), + kycUrl: wg.kycUrl, + timestamp: timestampPreciseFromDb(wg.timestampStart), transactionId: constructTransactionIdentifier({ tag: TransactionType.Withdrawal, - withdrawalGroupId: wgRecord.withdrawalGroupId, + withdrawalGroupId: wg.withdrawalGroupId, }), ...(ort?.lastError ? { error: ort.lastError } : {}), }; @@ -759,50 +791,50 @@ export function isUnsuccessfulTransaction(state: TransactionState): boolean { } function buildTransactionForManualWithdraw( - withdrawalGroup: WithdrawalGroupRecord, + wg: WithdrawalGroupRecord, exchangeDetails: ExchangeWireDetails, ort?: OperationRetryRecord, ): TransactionWithdrawal { - if (withdrawalGroup.wgInfo.withdrawalType !== WithdrawalRecordType.BankManual) + if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankManual) throw Error(""); const plainPaytoUris = exchangeDetails.wireInfo?.accounts.map((x) => x.payto_uri) ?? []; + checkDbInvariant(wg.instructedAmount !== undefined, "wg uninitialized"); + checkDbInvariant(wg.denomsSel !== undefined, "wg uninitialized"); + checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg uninitialized"); const exchangePaytoUris = augmentPaytoUrisForWithdrawal( plainPaytoUris, - withdrawalGroup.reservePub, - withdrawalGroup.instructedAmount, + wg.reservePub, + wg.instructedAmount, ); - const txState = computeWithdrawalTransactionStatus(withdrawalGroup); + const txState = computeWithdrawalTransactionStatus(wg); return { type: TransactionType.Withdrawal, txState, - txActions: computeWithdrawalTransactionActions(withdrawalGroup), + txActions: computeWithdrawalTransactionActions(wg), amountEffective: isUnsuccessfulTransaction(txState) - ? Amounts.stringify( - Amounts.zeroOfAmount(withdrawalGroup.instructedAmount), - ) - : Amounts.stringify(withdrawalGroup.denomsSel.totalCoinValue), - amountRaw: Amounts.stringify(withdrawalGroup.instructedAmount), + ? Amounts.stringify(Amounts.zeroOfAmount(wg.instructedAmount)) + : Amounts.stringify(wg.denomsSel.totalCoinValue), + amountRaw: Amounts.stringify(wg.instructedAmount), withdrawalDetails: { type: WithdrawalType.ManualTransfer, - reservePub: withdrawalGroup.reservePub, + reservePub: wg.reservePub, exchangePaytoUris, - exchangeCreditAccountDetails: - withdrawalGroup.wgInfo.exchangeCreditAccounts, + exchangeCreditAccountDetails: wg.wgInfo.exchangeCreditAccounts, reserveIsReady: - withdrawalGroup.status === WithdrawalGroupStatus.Done || - withdrawalGroup.status === WithdrawalGroupStatus.PendingReady, + wg.status === WithdrawalGroupStatus.Done || + wg.status === WithdrawalGroupStatus.PendingReady, }, - kycUrl: withdrawalGroup.kycUrl, - exchangeBaseUrl: withdrawalGroup.exchangeBaseUrl, - timestamp: timestampPreciseFromDb(withdrawalGroup.timestampStart), + kycUrl: wg.kycUrl, + exchangeBaseUrl: wg.exchangeBaseUrl, + timestamp: timestampPreciseFromDb(wg.timestampStart), transactionId: constructTransactionIdentifier({ tag: TransactionType.Withdrawal, - withdrawalGroupId: withdrawalGroup.withdrawalGroupId, + withdrawalGroupId: wg.withdrawalGroupId, }), ...(ort?.lastError ? { error: ort.lastError } : {}), }; @@ -983,16 +1015,22 @@ async function lookupMaybeContractData( return contractData; } -async function buildTransactionForPurchase( +function buildTransactionForPurchase( purchaseRecord: PurchaseRecord, contractData: WalletContractData, refundsInfo: RefundGroupRecord[], ort?: OperationRetryRecord, -): Promise<Transaction> { +): Transaction { const zero = Amounts.zeroOfAmount(contractData.amount); const info: OrderShortInfo = { - merchant: contractData.merchant, + merchant: { + name: contractData.merchant.name, + address: contractData.merchant.address, + email: contractData.merchant.email, + jurisdiction: contractData.merchant.jurisdiction, + website: contractData.merchant.website, + }, orderId: contractData.orderId, summary: contractData.summary, summary_i18n: contractData.summaryI18n, @@ -1016,8 +1054,14 @@ async function buildTransactionForPurchase( })); const timestamp = purchaseRecord.timestampAccept; - checkDbInvariant(!!timestamp); - checkDbInvariant(!!purchaseRecord.payInfo); + checkDbInvariant( + !!timestamp, + `purchase ${purchaseRecord.orderId} without accepted time`, + ); + checkDbInvariant( + !!purchaseRecord.payInfo, + `purchase ${purchaseRecord.orderId} without payinfo`, + ); const txState = computePayMerchantTransactionState(purchaseRecord); return { @@ -1071,24 +1115,30 @@ export async function getWithdrawalTransactionByUri( if (!withdrawalGroupRecord) { return undefined; } + if (withdrawalGroupRecord.exchangeBaseUrl === undefined) { + // prepared and unconfirmed withdrawals are hidden + return undefined; + } const opId = TaskIdentifiers.forWithdrawal(withdrawalGroupRecord); const ort = await tx.operationRetries.get(opId); + const exchangeDetails = await getExchangeWireDetailsInTx( + tx, + withdrawalGroupRecord.exchangeBaseUrl, + ); + if (!exchangeDetails) throw Error("not exchange details"); + if ( withdrawalGroupRecord.wgInfo.withdrawalType === WithdrawalRecordType.BankIntegrated ) { return buildTransactionForBankIntegratedWithdraw( withdrawalGroupRecord, + exchangeDetails, ort, ); } - const exchangeDetails = await getExchangeWireDetailsInTx( - tx, - withdrawalGroupRecord.exchangeBaseUrl, - ); - if (!exchangeDetails) throw Error("not exchange details"); return buildTransactionForManualWithdraw( withdrawalGroupRecord, @@ -1155,7 +1205,7 @@ export async function getTransactions( return; } const ct = await tx.contractTerms.get(pi.contractTermsHash); - checkDbInvariant(!!ct); + checkDbInvariant(!!ct, `no contract terms for p2p push ${pi.pursePub}`); transactions.push( buildTransactionForPushPaymentDebit(pi, ct.contractTermsRaw), ); @@ -1229,9 +1279,9 @@ export async function getTransactions( } } const pushIncOpId = TaskIdentifiers.forPeerPushCredit(pi); - let pushIncOrt = await tx.operationRetries.get(pushIncOpId); + const pushIncOrt = await tx.operationRetries.get(pushIncOpId); - checkDbInvariant(!!ct); + checkDbInvariant(!!ct, `no contract terms for p2p push ${pi.pursePub}`); transactions.push( buildTransactionForPeerPushCredit( pi, @@ -1263,9 +1313,9 @@ export async function getTransactions( } } const pushIncOpId = TaskIdentifiers.forPeerPullPaymentInitiation(pi); - let pushIncOrt = await tx.operationRetries.get(pushIncOpId); + const pushIncOrt = await tx.operationRetries.get(pushIncOpId); - checkDbInvariant(!!ct); + checkDbInvariant(!!ct, `no contract terms for p2p push ${pi.pursePub}`); transactions.push( buildTransactionForPeerPullCredit( pi, @@ -1331,6 +1381,13 @@ export async function getTransactions( }); await iterRecordsForWithdrawal(tx, filter, async (wsr) => { + if ( + wsr.rawWithdrawalAmount === undefined || + wsr.exchangeBaseUrl == undefined + ) { + // skip prepared withdrawals which has not been confirmed + return; + } const exchangesInTx = [wsr.exchangeBaseUrl]; if ( shouldSkipCurrency( @@ -1360,11 +1417,26 @@ export async function getTransactions( // FIXME: If this is an orphan withdrawal, still report it as a withdrawal! // FIXME: Still report if requested with verbose option? return; - case WithdrawalRecordType.BankIntegrated: + case WithdrawalRecordType.BankIntegrated: { + const exchangeDetails = await getExchangeWireDetailsInTx( + tx, + wsr.exchangeBaseUrl, + ); + if (!exchangeDetails) { + // FIXME: report somehow + return; + } + transactions.push( - buildTransactionForBankIntegratedWithdraw(wsr, ort), + buildTransactionForBankIntegratedWithdraw( + wsr, + exchangeDetails, + ort, + ), ); return; + } + case WithdrawalRecordType.BankManual: { const exchangeDetails = await getExchangeWireDetailsInTx( tx, @@ -1374,7 +1446,6 @@ export async function getTransactions( // FIXME: report somehow return; } - transactions.push( buildTransactionForManualWithdraw(wsr, exchangeDetails, ort), ); @@ -1475,7 +1546,7 @@ export async function getTransactions( ); transactions.push( - await buildTransactionForPurchase( + buildTransactionForPurchase( purchase, contractData, refunds, @@ -1726,7 +1797,18 @@ export async function retryTransaction( logger.info(`resetting retry timeout for ${transactionId}`); const taskId = maybeTaskFromTransaction(transactionId); if (taskId) { - wex.taskScheduler.resetTaskRetries(taskId); + await wex.taskScheduler.resetTaskRetries(taskId); + } +} + +/** + * Reset the task retry counter for all tasks. + */ +export async function retryAll(wex: WalletExecutionContext): Promise<void> { + await wex.taskScheduler.ensureRunning(); + const tasks = wex.taskScheduler.getActiveTasks(); + for (const task of tasks) { + await wex.taskScheduler.resetTaskRetries(task); } } |