diff options
Diffstat (limited to 'packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts')
-rw-r--r-- | packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts | 188 |
1 files changed, 176 insertions, 12 deletions
diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts index dead6313d..ac0aa9c87 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts @@ -18,6 +18,7 @@ import { Amounts, CheckPeerPushDebitRequest, CheckPeerPushDebitResponse, + CoinRefreshRequest, ContractTermsUtil, HttpStatusCode, InitiatePeerPushDebitRequest, @@ -27,13 +28,14 @@ import { TalerError, TalerErrorCode, TalerPreciseTimestamp, + TalerUriAction, TransactionAction, TransactionMajorState, TransactionMinorState, TransactionState, TransactionType, - constructPayPushUri, j2s, + stringifyTalerUri, } from "@gnu-taler/taler-util"; import { InternalWalletState } from "../internal-wallet-state.js"; import { @@ -46,6 +48,8 @@ import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; import { PeerPushPaymentInitiationRecord, PeerPushPaymentInitiationStatus, + RefreshOperationStatus, + createRefreshGroup, } from "../index.js"; import { PendingTaskType } from "../pending-types.js"; import { @@ -64,6 +68,7 @@ import { stopLongpolling, } from "./transactions.js"; import { assertUnreachable } from "../util/assertUnreachable.js"; +import { checkLogicInvariant } from "../util/invariants.js"; const logger = new Logger("pay-peer-push-debit.ts"); @@ -172,9 +177,89 @@ async function processPeerPushDebitCreateReserve( }; } -async function transitionPeerPushDebitFromReadyToDone( +async function processPeerPushDebitAbortingDeletePurse( + ws: InternalWalletState, + peerPushInitiation: PeerPushPaymentInitiationRecord, +): Promise<OperationAttemptResult> { + const { pursePub, pursePriv } = peerPushInitiation; + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushDebit, + pursePub, + }); + + const sigResp = await ws.cryptoApi.signDeletePurse({ + pursePriv, + }); + const purseUrl = new URL( + `purses/${pursePub}`, + peerPushInitiation.exchangeBaseUrl, + ); + const resp = await ws.http.fetch(purseUrl.href, { + method: "DELETE", + headers: { + "taler-purse-signature": sigResp.sig, + }, + }); + logger.info(`deleted purse with response status ${resp.status}`); + + const transitionInfo = await ws.db + .mktx((x) => [ + x.peerPushPaymentInitiations, + x.refreshGroups, + x.denominations, + x.coinAvailability, + x.coins, + ]) + .runReadWrite(async (tx) => { + const ppiRec = await tx.peerPushPaymentInitiations.get(pursePub); + if (!ppiRec) { + return undefined; + } + if ( + ppiRec.status !== PeerPushPaymentInitiationStatus.AbortingDeletePurse + ) { + return undefined; + } + const currency = Amounts.currencyOf(ppiRec.amount); + const oldTxState = computePeerPushDebitTransactionState(ppiRec); + const coinPubs: CoinRefreshRequest[] = []; + + for (let i = 0; i < ppiRec.coinSel.coinPubs.length; i++) { + coinPubs.push({ + amount: ppiRec.coinSel.contributions[i], + coinPub: ppiRec.coinSel.coinPubs[i], + }); + } + + const refresh = await createRefreshGroup( + ws, + tx, + currency, + coinPubs, + RefreshReason.AbortPeerPushDebit, + ); + ppiRec.status = PeerPushPaymentInitiationStatus.AbortingRefresh; + ppiRec.abortRefreshGroupId = refresh.refreshGroupId; + const newTxState = computePeerPushDebitTransactionState(ppiRec); + return { + oldTxState, + newTxState, + }; + }); + notifyTransition(ws, transactionId, transitionInfo); + + return OperationAttemptResult.pendingEmpty(); +} + +interface SimpleTransition { + stFrom: PeerPushPaymentInitiationStatus; + stTo: PeerPushPaymentInitiationStatus; +} + +async function transitionPeerPushDebitTransaction( ws: InternalWalletState, pursePub: string, + transitionSpec: SimpleTransition, ): Promise<void> { const transactionId = constructTransactionIdentifier({ tag: TransactionType.PeerPushDebit, @@ -187,11 +272,11 @@ async function transitionPeerPushDebitFromReadyToDone( if (!ppiRec) { return undefined; } - if (ppiRec.status !== PeerPushPaymentInitiationStatus.PendingReady) { + if (ppiRec.status !== transitionSpec.stFrom) { return undefined; } const oldTxState = computePeerPushDebitTransactionState(ppiRec); - ppiRec.status = PeerPushPaymentInitiationStatus.Done; + ppiRec.status = transitionSpec.stTo; const newTxState = computePeerPushDebitTransactionState(ppiRec); return { oldTxState, @@ -201,6 +286,54 @@ async function transitionPeerPushDebitFromReadyToDone( notifyTransition(ws, transactionId, transitionInfo); } +async function processPeerPushDebitAbortingRefresh( + ws: InternalWalletState, + peerPushInitiation: PeerPushPaymentInitiationRecord, +): Promise<OperationAttemptResult> { + const pursePub = peerPushInitiation.pursePub; + const abortRefreshGroupId = peerPushInitiation.abortRefreshGroupId; + checkLogicInvariant(!!abortRefreshGroupId); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushDebit, + pursePub: peerPushInitiation.pursePub, + }); + const transitionInfo = await ws.db + .mktx((x) => [x.refreshGroups, x.peerPushPaymentInitiations]) + .runReadWrite(async (tx) => { + const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId); + let newOpState: PeerPushPaymentInitiationStatus | undefined; + if (!refreshGroup) { + // Maybe it got manually deleted? Means that we should + // just go into failed. + logger.warn("no aborting refresh group found for deposit group"); + newOpState = PeerPushPaymentInitiationStatus.Failed; + } else { + if (refreshGroup.operationStatus === RefreshOperationStatus.Finished) { + newOpState = PeerPushPaymentInitiationStatus.Aborted; + } else if ( + refreshGroup.operationStatus === RefreshOperationStatus.Failed + ) { + newOpState = PeerPushPaymentInitiationStatus.Failed; + } + } + if (newOpState) { + const newDg = await tx.peerPushPaymentInitiations.get(pursePub); + if (!newDg) { + return; + } + const oldTxState = computePeerPushDebitTransactionState(newDg); + newDg.status = newOpState; + const newTxState = computePeerPushDebitTransactionState(newDg); + await tx.peerPushPaymentInitiations.put(newDg); + return { oldTxState, newTxState }; + } + return undefined; + }); + notifyTransition(ws, transactionId, transitionInfo); + // FIXME: Shouldn't this be finished in some cases?! + return OperationAttemptResult.pendingEmpty(); +} + /** * Process the "pending(ready)" state of a peer-push-debit transaction. */ @@ -214,7 +347,10 @@ async function processPeerPushDebitReady( pursePub, }); runLongpollAsync(ws, retryTag, async (ct) => { - const mergeUrl = new URL(`purses/${pursePub}/merge`); + const mergeUrl = new URL( + `purses/${pursePub}/merge`, + peerPushInitiation.exchangeBaseUrl, + ); mergeUrl.searchParams.set("timeout_ms", "30000"); const resp = await ws.http.fetch(mergeUrl.href, { // timeout: getReserveRequestTimeout(withdrawalGroup), @@ -226,16 +362,30 @@ async function processPeerPushDebitReady( codecForExchangePurseStatus(), ); if (purseStatus.deposit_timestamp) { - await transitionPeerPushDebitFromReadyToDone( + await transitionPeerPushDebitTransaction( ws, peerPushInitiation.pursePub, + { + stFrom: PeerPushPaymentInitiationStatus.PendingReady, + stTo: PeerPushPaymentInitiationStatus.Done, + }, ); return { ready: true, }; } } else if (resp.status === HttpStatusCode.Gone) { - // FIXME: transition the reserve into the expired state + await transitionPeerPushDebitTransaction( + ws, + peerPushInitiation.pursePub, + { + stFrom: PeerPushPaymentInitiationStatus.PendingReady, + stTo: PeerPushPaymentInitiationStatus.Expired, + }, + ); + return { + ready: true, + }; } return { ready: false, @@ -280,6 +430,10 @@ export async function processPeerPushDebit( return processPeerPushDebitCreateReserve(ws, peerPushInitiation); case PeerPushPaymentInitiationStatus.PendingReady: return processPeerPushDebitReady(ws, peerPushInitiation); + case PeerPushPaymentInitiationStatus.AbortingDeletePurse: + return processPeerPushDebitAbortingDeletePurse(ws, peerPushInitiation); + case PeerPushPaymentInitiationStatus.AbortingRefresh: + return processPeerPushDebitAbortingRefresh(ws, peerPushInitiation); } return { @@ -396,7 +550,8 @@ export async function initiatePeerPushDebit( mergePriv: mergePair.priv, pursePub: pursePair.pub, exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl, - talerUri: constructPayPushUri({ + talerUri: stringifyTalerUri({ + type: TalerUriAction.PayPush, exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl, contractPriv: contractKeyPair.priv, }), @@ -431,6 +586,8 @@ export function computePeerPushDebitTransactionActions( return [TransactionAction.Suspend, TransactionAction.Abort]; case PeerPushPaymentInitiationStatus.Done: return [TransactionAction.Delete]; + case PeerPushPaymentInitiationStatus.Expired: + return [TransactionAction.Delete]; case PeerPushPaymentInitiationStatus.Failed: return [TransactionAction.Delete]; } @@ -474,9 +631,9 @@ export async function abortPeerPushDebitTransaction( case PeerPushPaymentInitiationStatus.Done: case PeerPushPaymentInitiationStatus.AbortingDeletePurse: case PeerPushPaymentInitiationStatus.Aborted: - // Do nothing - break; + case PeerPushPaymentInitiationStatus.Expired: case PeerPushPaymentInitiationStatus.Failed: + // Do nothing break; default: assertUnreachable(pushDebitRec.status); @@ -535,6 +692,7 @@ export async function failPeerPushDebitTransaction( case PeerPushPaymentInitiationStatus.Done: case PeerPushPaymentInitiationStatus.Aborted: case PeerPushPaymentInitiationStatus.Failed: + case PeerPushPaymentInitiationStatus.Expired: // Do nothing break; default: @@ -598,6 +756,7 @@ export async function suspendPeerPushDebitTransaction( case PeerPushPaymentInitiationStatus.Done: case PeerPushPaymentInitiationStatus.Aborted: case PeerPushPaymentInitiationStatus.Failed: + case PeerPushPaymentInitiationStatus.Expired: // Do nothing break; default: @@ -660,6 +819,7 @@ export async function resumePeerPushDebitTransaction( case PeerPushPaymentInitiationStatus.Done: case PeerPushPaymentInitiationStatus.Aborted: case PeerPushPaymentInitiationStatus.Failed: + case PeerPushPaymentInitiationStatus.Expired: // Do nothing break; default: @@ -681,7 +841,6 @@ export async function resumePeerPushDebitTransaction( notifyTransition(ws, transactionId, transitionInfo); } - export function computePeerPushDebitTransactionState( ppiRecord: PeerPushPaymentInitiationRecord, ): TransactionState { @@ -738,5 +897,10 @@ export function computePeerPushDebitTransactionState( return { major: TransactionMajorState.Failed, }; + case PeerPushPaymentInitiationStatus.Expired: + return { + major: TransactionMajorState.Expired, + }; } -}
\ No newline at end of file +} + |