taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit ce33080c539678355bba05b0a7312bbcfc85a6b0
parent 2d93886a7506296470f7328b5c3742133b17c33f
Author: Antoine A <>
Date:   Thu, 17 Apr 2025 14:15:08 +0200

wallet-core: improve pay-peer-push

Diffstat:
Mpackages/taler-harness/src/integrationtests/test-peer-push.ts | 14++++----------
Mpackages/taler-wallet-core/src/pay-peer-push-credit.ts | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 63 insertions(+), 12 deletions(-)

diff --git a/packages/taler-harness/src/integrationtests/test-peer-push.ts b/packages/taler-harness/src/integrationtests/test-peer-push.ts @@ -214,14 +214,12 @@ export async function runPeerPushTest(t: GlobalTestState) { minor: TransactionMinorState.Merge }, }), - // FIXME should be aborted wallet4.call(WalletApiOperation.TestingWaitTransactionState, { transactionId: prepare4.transactionId, txState: { - major: TransactionMajorState.Dialog, - minor: TransactionMinorState.Proposed + major: TransactionMajorState.Aborted, }, - }), + }) ]); { @@ -282,12 +280,10 @@ export async function runPeerPushTest(t: GlobalTestState) { minor: TransactionMinorState.Merge }, }), - // FIXME should be aborted wallet3.call(WalletApiOperation.TestingWaitTransactionState, { transactionId: prepare3.transactionId, txState: { - major: TransactionMajorState.Dialog, - minor: TransactionMinorState.Proposed + major: TransactionMajorState.Aborted }, }), ]); @@ -342,12 +338,10 @@ export async function runPeerPushTest(t: GlobalTestState) { minor: TransactionMinorState.Merge }, }), - // FIXME should be aborted wallet3.call(WalletApiOperation.TestingWaitTransactionState, { transactionId: prepare3.transactionId, txState: { - major: TransactionMajorState.Dialog, - minor: TransactionMinorState.Proposed + major: TransactionMajorState.Aborted }, }), ]); diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts @@ -31,6 +31,7 @@ import { PreparePeerPushCreditResponse, TalerErrorDetail, TalerPreciseTimestamp, + TalerProtocolTimestamp, Transaction, TransactionAction, TransactionIdStr, @@ -464,7 +465,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { assertUnreachable(rec.status); } }); - this.wex.taskScheduler.startShepherdTask(this.taskId); + this.wex.taskScheduler.stopShepherdTask(this.taskId); } async resumeTransaction(): Promise<void> { @@ -530,7 +531,6 @@ export class PeerPushCreditTransactionContext implements TransactionContext { } }); this.wex.taskScheduler.stopShepherdTask(this.taskId); - this.wex.taskScheduler.startShepherdTask(this.taskId); } } @@ -694,6 +694,7 @@ export async function preparePeerPushCredit( }, ); notifyTransition(wex, ctx.transactionId, transitionInfo); + wex.taskScheduler.startShepherdTask(ctx.taskId); const currency = Amounts.currencyOf(wi.withdrawalAmountRaw); const scopeInfo = await wex.db.runAllStoresReadOnlyTx( @@ -1072,6 +1073,59 @@ async function handlePendingWithdrawing( } } +async function processPeerPushDebitDialogProposed( + wex: WalletExecutionContext, + pullIni: PeerPushPaymentIncomingRecord, +): Promise<TaskRunResult> { + const purseDepositUrl = new URL( + `purses/${pullIni.pursePub}/merge`, + pullIni.exchangeBaseUrl, + ); + logger.info(`querying purse status via ${purseDepositUrl.href}`); + const resp = await wex.ws.runLongpollQueueing( + wex.cancellationToken, + purseDepositUrl.hostname, + async (timeoutMs) => { + purseDepositUrl.searchParams.set("timeout_ms", `${timeoutMs}`); + return await wex.http.fetch(purseDepositUrl.href, { + cancellationToken: wex.cancellationToken, + }); + }, + ); + const ctx = new PeerPushCreditTransactionContext(wex, pullIni.peerPushCreditId); + + logger.info(`purse status code: HTTP ${resp.status}`); + + switch (resp.status) { + case HttpStatusCode.Gone: { + // Exchange says that purse doesn't exist anymore => expired! + await ctx.transitionStatus(PeerPushCreditStatus.DialogProposed, PeerPushCreditStatus.Aborted); + return TaskRunResult.finished(); + } + case HttpStatusCode.NotFound: + // FIXME: Maybe check error code? 404 could also mean something else. + return TaskRunResult.longpollReturnedPending(); + } + + const result = await readSuccessResponseJsonOrThrow( + resp, + codecForExchangePurseStatus(), + ); + + logger.trace(`purse status: ${j2s(result)}`); + + const mergeTimestamp = result.merge_timestamp; + + if (mergeTimestamp != null && !TalerProtocolTimestamp.isNever(mergeTimestamp)) { + logger.info("purse completed by another wallet"); + await ctx.transitionStatus(PeerPushCreditStatus.DialogProposed, PeerPushCreditStatus.Aborted); + return TaskRunResult.finished(); + } + + return TaskRunResult.longpollReturnedPending(); +} + + export async function processPeerPushCredit( wex: WalletExecutionContext, peerPushCreditId: string, @@ -1115,6 +1169,8 @@ export async function processPeerPushCredit( ); switch (peerInc.status) { + case PeerPushCreditStatus.DialogProposed: + return processPeerPushDebitDialogProposed(wex, peerInc); case PeerPushCreditStatus.PendingMergeKycRequired: { if (!peerInc.kycPaytoHash) { throw Error("invalid state, kycPaytoHash required"); @@ -1259,6 +1315,7 @@ export async function confirmPeerPushCredit( await ctx.transitionStatus(PeerPushCreditStatus.DialogProposed, PeerPushCreditStatus.PendingMerge); + wex.taskScheduler.stopShepherdTask(ctx.taskId); wex.taskScheduler.startShepherdTask(ctx.taskId); return {