diff options
author | Florian Dold <florian@dold.me> | 2024-02-19 20:50:03 +0100 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2024-02-19 20:50:03 +0100 |
commit | 20397e3fba3fe4b274354047f76e3a8f3a92d6b8 (patch) | |
tree | e1e79fec4be55b2bb22e6830cea6c2afd5ea6ead /packages/taler-wallet-core/src/withdraw.ts | |
parent | 151ab351f6a2e00caecc9a1dda19b724f90a088e (diff) | |
download | wallet-core-20397e3fba3fe4b274354047f76e3a8f3a92d6b8.tar.xz |
wallet-core: long-poll withdrawal operation status
Diffstat (limited to 'packages/taler-wallet-core/src/withdraw.ts')
-rw-r--r-- | packages/taler-wallet-core/src/withdraw.ts | 190 |
1 files changed, 129 insertions, 61 deletions
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index 45792af41..9cf1ad36d 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -70,7 +70,7 @@ import { WithdrawalExchangeAccountDetails, addPaytoQueryParams, canonicalizeBaseUrl, - codecForAny, + codeForBankWithdrawalOperationPostResponse, codecForCashinConversionResponse, codecForConversionBankConfig, codecForExchangeWithdrawBatchResponse, @@ -1257,7 +1257,7 @@ async function updateWithdrawalDenoms( * and are big enough to withdraw with available denominations, * create a new withdrawal group for the remaining amount. */ -async function queryReserve( +async function processQueryReserve( ws: InternalWalletState, withdrawalGroupId: string, cancellationToken: CancellationToken, @@ -1330,7 +1330,11 @@ async function queryReserve( notifyTransition(ws, transactionId, transitionResult); - return TaskRunResult.backoff(); + if (transitionResult) { + return TaskRunResult.progress(); + } else { + return TaskRunResult.backoff(); + } } /** @@ -1664,9 +1668,13 @@ export async function processWithdrawalGroup( switch (withdrawalGroup.status) { case WithdrawalGroupStatus.PendingRegisteringBank: - return await processReserveBankStatus(ws, withdrawalGroupId); + return await processBankRegisterReserve( + ws, + withdrawalGroupId, + cancellationToken, + ); case WithdrawalGroupStatus.PendingQueryingStatus: - return queryReserve(ws, withdrawalGroupId, cancellationToken); + return processQueryReserve(ws, withdrawalGroupId, cancellationToken); case WithdrawalGroupStatus.PendingWaitConfirmBank: return await processReserveBankStatus(ws, withdrawalGroupId); case WithdrawalGroupStatus.PendingAml: @@ -2064,7 +2072,7 @@ async function registerReserveWithBank( if ( withdrawalGroup.wgInfo.withdrawalType != WithdrawalRecordType.BankIntegrated ) { - throw Error("expecting withdrarwal type = bank integrated"); + throw Error("expecting withdrawal type = bank integrated"); } const bankInfo = withdrawalGroup.wgInfo.bankInfo; if (!bankInfo) { @@ -2081,8 +2089,10 @@ async function registerReserveWithBank( body: reqBody, timeout: getReserveRequestTimeout(withdrawalGroup), }); - // FIXME: libeufin-bank currently doesn't return a response in the right format, so we don't validate at all. - await readSuccessResponseJsonOrThrow(httpResp, codecForAny()); + const status = await readSuccessResponseJsonOrThrow( + httpResp, + codeForBankWithdrawalOperationPostResponse(), + ); const transitionInfo = await ws.db.runReadWriteTx( ["withdrawalGroups"], async (tx) => { @@ -2105,6 +2115,7 @@ async function registerReserveWithBank( ); const oldTxState = computeWithdrawalTransactionStatus(r); r.status = WithdrawalGroupStatus.PendingWaitConfirmBank; + r.wgInfo.bankInfo.confirmUrl = status.confirm_transfer_url; const newTxState = computeWithdrawalTransactionStatus(r); await tx.withdrawalGroups.put(r); return { @@ -2117,25 +2128,109 @@ async function registerReserveWithBank( notifyTransition(ws, transactionId, transitionInfo); } -async function processReserveBankStatus( +async function transitionBankAborted( + ctx: WithdrawTransactionContext, +): Promise<TaskRunResult> { + logger.info("bank aborted the withdrawal"); + const transitionInfo = await ctx.ws.db.runReadWriteTx( + ["withdrawalGroups"], + async (tx) => { + const r = await tx.withdrawalGroups.get(ctx.withdrawalGroupId); + if (!r) { + return; + } + switch (r.status) { + case WithdrawalGroupStatus.PendingRegisteringBank: + case WithdrawalGroupStatus.PendingWaitConfirmBank: + break; + default: + return; + } + if (r.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) { + throw Error("invariant failed"); + } + const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now()); + const oldTxState = computeWithdrawalTransactionStatus(r); + r.wgInfo.bankInfo.timestampBankConfirmed = timestampPreciseToDb(now); + r.status = WithdrawalGroupStatus.FailedBankAborted; + const newTxState = computeWithdrawalTransactionStatus(r); + await tx.withdrawalGroups.put(r); + return { + oldTxState, + newTxState, + }; + }, + ); + notifyTransition(ctx.ws, ctx.transactionId, transitionInfo); + return TaskRunResult.finished(); +} + +async function processBankRegisterReserve( ws: InternalWalletState, withdrawalGroupId: string, + cancellationToken: CancellationToken, ): Promise<TaskRunResult> { + const ctx = new WithdrawTransactionContext(ws, withdrawalGroupId); const withdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, { withdrawalGroupId, }); - const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Withdrawal, + if (!withdrawalGroup) { + return TaskRunResult.finished(); + } + + if ( + withdrawalGroup.wgInfo.withdrawalType != WithdrawalRecordType.BankIntegrated + ) { + throw Error("wrong withdrawal record type"); + } + const bankInfo = withdrawalGroup.wgInfo.bankInfo; + if (!bankInfo) { + throw Error("no bank info in bank-integrated withdrawal"); + } + + const uriResult = parseWithdrawUri(bankInfo.talerWithdrawUri); + if (!uriResult) { + throw Error(`can't parse withdrawal URL ${bankInfo.talerWithdrawUri}`); + } + const url = new URL( + `withdrawal-operation/${uriResult.withdrawalOperationId}`, + uriResult.bankIntegrationApiBaseUrl, + ); + + const statusResp = await ws.http.fetch(url.href, { + timeout: getReserveRequestTimeout(withdrawalGroup), + cancellationToken, + }); + + const status = await readSuccessResponseJsonOrThrow( + statusResp, + codecForWithdrawOperationStatusResponse(), + ); + + if (status.aborted) { + return transitionBankAborted(ctx); + } + + // FIXME: Put confirm transfer URL in the DB! + + await registerReserveWithBank(ws, withdrawalGroupId); + return TaskRunResult.progress(); +} + +async function processReserveBankStatus( + ws: InternalWalletState, + withdrawalGroupId: string, +): Promise<TaskRunResult> { + const withdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, { withdrawalGroupId, }); - switch (withdrawalGroup?.status) { - case WithdrawalGroupStatus.PendingWaitConfirmBank: - case WithdrawalGroupStatus.PendingRegisteringBank: - break; - default: - return TaskRunResult.backoff(); + + if (!withdrawalGroup) { + return TaskRunResult.finished(); } + const ctx = new WithdrawTransactionContext(ws, withdrawalGroupId); + if ( withdrawalGroup.wgInfo.withdrawalType != WithdrawalRecordType.BankIntegrated ) { @@ -2143,58 +2238,35 @@ async function processReserveBankStatus( } const bankInfo = withdrawalGroup.wgInfo.bankInfo; if (!bankInfo) { - return TaskRunResult.backoff(); + throw Error("no bank info in bank-integrated withdrawal"); } - const bankStatusUrl = getBankStatusUrl(bankInfo.talerWithdrawUri); + const uriResult = parseWithdrawUri(bankInfo.talerWithdrawUri); + if (!uriResult) { + throw Error(`can't parse withdrawal URL ${bankInfo.talerWithdrawUri}`); + } + const url = new URL( + `withdrawal-operation/${uriResult.withdrawalOperationId}`, + uriResult.bankIntegrationApiBaseUrl, + ); + url.searchParams.set("timeout_ms", "30000"); - const statusResp = await ws.http.fetch(bankStatusUrl, { + const statusResp = await ws.http.fetch(url.href, { timeout: getReserveRequestTimeout(withdrawalGroup), }); + const status = await readSuccessResponseJsonOrThrow( statusResp, codecForWithdrawOperationStatusResponse(), ); if (status.aborted) { - logger.info("bank aborted the withdrawal"); - const transitionInfo = await ws.db.runReadWriteTx( - ["withdrawalGroups"], - async (tx) => { - const r = await tx.withdrawalGroups.get(withdrawalGroupId); - if (!r) { - return; - } - switch (r.status) { - case WithdrawalGroupStatus.PendingRegisteringBank: - case WithdrawalGroupStatus.PendingWaitConfirmBank: - break; - default: - return; - } - if (r.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) { - throw Error("invariant failed"); - } - const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now()); - const oldTxState = computeWithdrawalTransactionStatus(r); - r.wgInfo.bankInfo.timestampBankConfirmed = timestampPreciseToDb(now); - r.status = WithdrawalGroupStatus.FailedBankAborted; - const newTxState = computeWithdrawalTransactionStatus(r); - await tx.withdrawalGroups.put(r); - return { - oldTxState, - newTxState, - }; - }, - ); - notifyTransition(ws, transactionId, transitionInfo); - return TaskRunResult.finished(); + return transitionBankAborted(ctx); } - // Bank still needs to know our reserve info - if (!status.selection_done) { - await registerReserveWithBank(ws, withdrawalGroupId); - return TaskRunResult.progress(); + if (!status.transfer_done) { + // FIXME: This is a long-poll result + return TaskRunResult.backoff(); } const transitionInfo = await ws.db.runReadWriteTx( @@ -2206,7 +2278,6 @@ async function processReserveBankStatus( } // Re-check reserve status within transaction switch (r.status) { - case WithdrawalGroupStatus.PendingRegisteringBank: case WithdrawalGroupStatus.PendingWaitConfirmBank: break; default: @@ -2222,9 +2293,6 @@ async function processReserveBankStatus( r.wgInfo.bankInfo.timestampBankConfirmed = timestampPreciseToDb(now); r.status = WithdrawalGroupStatus.PendingQueryingStatus; } else { - logger.trace("withdrawal: transfer not yet confirmed by bank"); - r.wgInfo.bankInfo.confirmUrl = status.confirm_transfer_url; - r.senderWire = status.sender_wire; } const newTxState = computeWithdrawalTransactionStatus(r); await tx.withdrawalGroups.put(r); @@ -2235,7 +2303,7 @@ async function processReserveBankStatus( }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(ws, ctx.transactionId, transitionInfo); if (transitionInfo) { return TaskRunResult.progress(); |