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 | 742 |
1 files changed, 742 insertions, 0 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 new file mode 100644 index 000000000..dead6313d --- /dev/null +++ b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts @@ -0,0 +1,742 @@ +/* + This file is part of GNU Taler + (C) 2022-2023 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { + Amounts, + CheckPeerPushDebitRequest, + CheckPeerPushDebitResponse, + ContractTermsUtil, + HttpStatusCode, + InitiatePeerPushDebitRequest, + InitiatePeerPushDebitResponse, + Logger, + RefreshReason, + TalerError, + TalerErrorCode, + TalerPreciseTimestamp, + TransactionAction, + TransactionMajorState, + TransactionMinorState, + TransactionState, + TransactionType, + constructPayPushUri, + j2s, +} from "@gnu-taler/taler-util"; +import { InternalWalletState } from "../internal-wallet-state.js"; +import { + selectPeerCoins, + getTotalPeerPaymentCost, + codecForExchangePurseStatus, + queryCoinInfosForSelection, +} from "./pay-peer-common.js"; +import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; +import { + PeerPushPaymentInitiationRecord, + PeerPushPaymentInitiationStatus, +} from "../index.js"; +import { PendingTaskType } from "../pending-types.js"; +import { + OperationAttemptResult, + OperationAttemptResultType, + constructTaskIdentifier, +} from "../util/retries.js"; +import { + runLongpollAsync, + spendCoins, + runOperationWithErrorReporting, +} from "./common.js"; +import { + constructTransactionIdentifier, + notifyTransition, + stopLongpolling, +} from "./transactions.js"; +import { assertUnreachable } from "../util/assertUnreachable.js"; + +const logger = new Logger("pay-peer-push-debit.ts"); + +export async function checkPeerPushDebit( + ws: InternalWalletState, + req: CheckPeerPushDebitRequest, +): Promise<CheckPeerPushDebitResponse> { + const instructedAmount = Amounts.parseOrThrow(req.amount); + const coinSelRes = await selectPeerCoins(ws, { instructedAmount }); + if (coinSelRes.type === "failure") { + throw TalerError.fromDetail( + TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE, + { + insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails, + }, + ); + } + const totalAmount = await getTotalPeerPaymentCost( + ws, + coinSelRes.result.coins, + ); + return { + amountEffective: Amounts.stringify(totalAmount), + amountRaw: req.amount, + }; +} + +async function processPeerPushDebitCreateReserve( + ws: InternalWalletState, + peerPushInitiation: PeerPushPaymentInitiationRecord, +): Promise<OperationAttemptResult> { + const pursePub = peerPushInitiation.pursePub; + const purseExpiration = peerPushInitiation.purseExpiration; + const hContractTerms = peerPushInitiation.contractTermsHash; + + const purseSigResp = await ws.cryptoApi.signPurseCreation({ + hContractTerms, + mergePub: peerPushInitiation.mergePub, + minAge: 0, + purseAmount: peerPushInitiation.amount, + purseExpiration, + pursePriv: peerPushInitiation.pursePriv, + }); + + const coins = await queryCoinInfosForSelection( + ws, + peerPushInitiation.coinSel, + ); + + const depositSigsResp = await ws.cryptoApi.signPurseDeposits({ + exchangeBaseUrl: peerPushInitiation.exchangeBaseUrl, + pursePub: peerPushInitiation.pursePub, + coins, + }); + + const econtractResp = await ws.cryptoApi.encryptContractForMerge({ + contractTerms: peerPushInitiation.contractTerms, + mergePriv: peerPushInitiation.mergePriv, + pursePriv: peerPushInitiation.pursePriv, + pursePub: peerPushInitiation.pursePub, + contractPriv: peerPushInitiation.contractPriv, + contractPub: peerPushInitiation.contractPub, + }); + + const createPurseUrl = new URL( + `purses/${peerPushInitiation.pursePub}/create`, + peerPushInitiation.exchangeBaseUrl, + ); + + const httpResp = await ws.http.fetch(createPurseUrl.href, { + method: "POST", + body: { + amount: peerPushInitiation.amount, + merge_pub: peerPushInitiation.mergePub, + purse_sig: purseSigResp.sig, + h_contract_terms: hContractTerms, + purse_expiration: purseExpiration, + deposits: depositSigsResp.deposits, + min_age: 0, + econtract: econtractResp.econtract, + }, + }); + + const resp = await httpResp.json(); + + logger.info(`resp: ${j2s(resp)}`); + + if (httpResp.status !== HttpStatusCode.Ok) { + throw Error("got error response from exchange"); + } + + await ws.db + .mktx((x) => [x.peerPushPaymentInitiations]) + .runReadWrite(async (tx) => { + const ppi = await tx.peerPushPaymentInitiations.get(pursePub); + if (!ppi) { + return; + } + ppi.status = PeerPushPaymentInitiationStatus.Done; + await tx.peerPushPaymentInitiations.put(ppi); + }); + + return { + type: OperationAttemptResultType.Finished, + result: undefined, + }; +} + +async function transitionPeerPushDebitFromReadyToDone( + ws: InternalWalletState, + pursePub: string, +): Promise<void> { + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushDebit, + pursePub, + }); + const transitionInfo = await ws.db + .mktx((x) => [x.peerPushPaymentInitiations]) + .runReadWrite(async (tx) => { + const ppiRec = await tx.peerPushPaymentInitiations.get(pursePub); + if (!ppiRec) { + return undefined; + } + if (ppiRec.status !== PeerPushPaymentInitiationStatus.PendingReady) { + return undefined; + } + const oldTxState = computePeerPushDebitTransactionState(ppiRec); + ppiRec.status = PeerPushPaymentInitiationStatus.Done; + const newTxState = computePeerPushDebitTransactionState(ppiRec); + return { + oldTxState, + newTxState, + }; + }); + notifyTransition(ws, transactionId, transitionInfo); +} + +/** + * Process the "pending(ready)" state of a peer-push-debit transaction. + */ +async function processPeerPushDebitReady( + ws: InternalWalletState, + peerPushInitiation: PeerPushPaymentInitiationRecord, +): Promise<OperationAttemptResult> { + const pursePub = peerPushInitiation.pursePub; + const retryTag = constructTaskIdentifier({ + tag: PendingTaskType.PeerPushDebit, + pursePub, + }); + runLongpollAsync(ws, retryTag, async (ct) => { + const mergeUrl = new URL(`purses/${pursePub}/merge`); + mergeUrl.searchParams.set("timeout_ms", "30000"); + const resp = await ws.http.fetch(mergeUrl.href, { + // timeout: getReserveRequestTimeout(withdrawalGroup), + cancellationToken: ct, + }); + if (resp.status === HttpStatusCode.Ok) { + const purseStatus = await readSuccessResponseJsonOrThrow( + resp, + codecForExchangePurseStatus(), + ); + if (purseStatus.deposit_timestamp) { + await transitionPeerPushDebitFromReadyToDone( + ws, + peerPushInitiation.pursePub, + ); + return { + ready: true, + }; + } + } else if (resp.status === HttpStatusCode.Gone) { + // FIXME: transition the reserve into the expired state + } + return { + ready: false, + }; + }); + logger.trace( + "returning early from peer-push-debit for long-polling in background", + ); + return { + type: OperationAttemptResultType.Longpoll, + }; +} + +export async function processPeerPushDebit( + ws: InternalWalletState, + pursePub: string, +): Promise<OperationAttemptResult> { + const peerPushInitiation = await ws.db + .mktx((x) => [x.peerPushPaymentInitiations]) + .runReadOnly(async (tx) => { + return tx.peerPushPaymentInitiations.get(pursePub); + }); + if (!peerPushInitiation) { + throw Error("peer push payment not found"); + } + + const retryTag = constructTaskIdentifier({ + tag: PendingTaskType.PeerPushDebit, + pursePub, + }); + + // We're already running! + if (ws.activeLongpoll[retryTag]) { + logger.info("peer-push-debit task already in long-polling, returning!"); + return { + type: OperationAttemptResultType.Longpoll, + }; + } + + switch (peerPushInitiation.status) { + case PeerPushPaymentInitiationStatus.PendingCreatePurse: + return processPeerPushDebitCreateReserve(ws, peerPushInitiation); + case PeerPushPaymentInitiationStatus.PendingReady: + return processPeerPushDebitReady(ws, peerPushInitiation); + } + + return { + type: OperationAttemptResultType.Finished, + result: undefined, + }; +} + +/** + * Initiate sending a peer-to-peer push payment. + */ +export async function initiatePeerPushDebit( + ws: InternalWalletState, + req: InitiatePeerPushDebitRequest, +): Promise<InitiatePeerPushDebitResponse> { + const instructedAmount = Amounts.parseOrThrow( + req.partialContractTerms.amount, + ); + const purseExpiration = req.partialContractTerms.purse_expiration; + const contractTerms = req.partialContractTerms; + + const pursePair = await ws.cryptoApi.createEddsaKeypair({}); + const mergePair = await ws.cryptoApi.createEddsaKeypair({}); + + const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms); + + const contractKeyPair = await ws.cryptoApi.createEddsaKeypair({}); + + const coinSelRes = await selectPeerCoins(ws, { instructedAmount }); + + if (coinSelRes.type !== "success") { + throw TalerError.fromDetail( + TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE, + { + insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails, + }, + ); + } + + const sel = coinSelRes.result; + + logger.info(`selected p2p coins (push): ${j2s(coinSelRes)}`); + + const totalAmount = await getTotalPeerPaymentCost( + ws, + coinSelRes.result.coins, + ); + + await ws.db + .mktx((x) => [ + x.exchanges, + x.contractTerms, + x.coins, + x.coinAvailability, + x.denominations, + x.refreshGroups, + x.peerPushPaymentInitiations, + ]) + .runReadWrite(async (tx) => { + // FIXME: Instead of directly doing a spendCoin here, + // we might want to mark the coins as used and spend them + // after we've been able to create the purse. + await spendCoins(ws, tx, { + // allocationId: `txn:peer-push-debit:${pursePair.pub}`, + allocationId: constructTransactionIdentifier({ + tag: TransactionType.PeerPushDebit, + pursePub: pursePair.pub, + }), + coinPubs: sel.coins.map((x) => x.coinPub), + contributions: sel.coins.map((x) => + Amounts.parseOrThrow(x.contribution), + ), + refreshReason: RefreshReason.PayPeerPush, + }); + + await tx.peerPushPaymentInitiations.add({ + amount: Amounts.stringify(instructedAmount), + contractPriv: contractKeyPair.priv, + contractPub: contractKeyPair.pub, + contractTermsHash: hContractTerms, + exchangeBaseUrl: sel.exchangeBaseUrl, + mergePriv: mergePair.priv, + mergePub: mergePair.pub, + purseExpiration: purseExpiration, + pursePriv: pursePair.priv, + pursePub: pursePair.pub, + timestampCreated: TalerPreciseTimestamp.now(), + status: PeerPushPaymentInitiationStatus.PendingCreatePurse, + contractTerms: contractTerms, + coinSel: { + coinPubs: sel.coins.map((x) => x.coinPub), + contributions: sel.coins.map((x) => x.contribution), + }, + totalCost: Amounts.stringify(totalAmount), + }); + + await tx.contractTerms.put({ + h: hContractTerms, + contractTermsRaw: contractTerms, + }); + }); + + const taskId = constructTaskIdentifier({ + tag: PendingTaskType.PeerPushDebit, + pursePub: pursePair.pub, + }); + + await runOperationWithErrorReporting(ws, taskId, async () => { + return await processPeerPushDebit(ws, pursePair.pub); + }); + + return { + contractPriv: contractKeyPair.priv, + mergePriv: mergePair.priv, + pursePub: pursePair.pub, + exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl, + talerUri: constructPayPushUri({ + exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl, + contractPriv: contractKeyPair.priv, + }), + transactionId: constructTransactionIdentifier({ + tag: TransactionType.PeerPushDebit, + pursePub: pursePair.pub, + }), + }; +} + +export function computePeerPushDebitTransactionActions( + ppiRecord: PeerPushPaymentInitiationRecord, +): TransactionAction[] { + switch (ppiRecord.status) { + case PeerPushPaymentInitiationStatus.PendingCreatePurse: + return [TransactionAction.Abort, TransactionAction.Suspend]; + case PeerPushPaymentInitiationStatus.PendingReady: + return [TransactionAction.Abort, TransactionAction.Suspend]; + case PeerPushPaymentInitiationStatus.Aborted: + return [TransactionAction.Delete]; + case PeerPushPaymentInitiationStatus.AbortingDeletePurse: + return [TransactionAction.Suspend, TransactionAction.Fail]; + case PeerPushPaymentInitiationStatus.AbortingRefresh: + return [TransactionAction.Suspend, TransactionAction.Fail]; + case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse: + return [TransactionAction.Resume, TransactionAction.Fail]; + case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh: + return [TransactionAction.Resume, TransactionAction.Fail]; + case PeerPushPaymentInitiationStatus.SuspendedCreatePurse: + return [TransactionAction.Resume, TransactionAction.Abort]; + case PeerPushPaymentInitiationStatus.SuspendedReady: + return [TransactionAction.Suspend, TransactionAction.Abort]; + case PeerPushPaymentInitiationStatus.Done: + return [TransactionAction.Delete]; + case PeerPushPaymentInitiationStatus.Failed: + return [TransactionAction.Delete]; + } +} + +export async function abortPeerPushDebitTransaction( + ws: InternalWalletState, + pursePub: string, +) { + const taskId = constructTaskIdentifier({ + tag: PendingTaskType.PeerPushDebit, + pursePub, + }); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushDebit, + pursePub, + }); + stopLongpolling(ws, taskId); + const transitionInfo = await ws.db + .mktx((x) => [x.peerPushPaymentInitiations]) + .runReadWrite(async (tx) => { + const pushDebitRec = await tx.peerPushPaymentInitiations.get(pursePub); + if (!pushDebitRec) { + logger.warn(`peer push debit ${pursePub} not found`); + return; + } + let newStatus: PeerPushPaymentInitiationStatus | undefined = undefined; + switch (pushDebitRec.status) { + case PeerPushPaymentInitiationStatus.PendingReady: + case PeerPushPaymentInitiationStatus.SuspendedReady: + newStatus = PeerPushPaymentInitiationStatus.AbortingDeletePurse; + break; + case PeerPushPaymentInitiationStatus.SuspendedCreatePurse: + case PeerPushPaymentInitiationStatus.PendingCreatePurse: + // Network request might already be in-flight! + newStatus = PeerPushPaymentInitiationStatus.AbortingDeletePurse; + break; + case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh: + case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse: + case PeerPushPaymentInitiationStatus.AbortingRefresh: + case PeerPushPaymentInitiationStatus.Done: + case PeerPushPaymentInitiationStatus.AbortingDeletePurse: + case PeerPushPaymentInitiationStatus.Aborted: + // Do nothing + break; + case PeerPushPaymentInitiationStatus.Failed: + break; + default: + assertUnreachable(pushDebitRec.status); + } + if (newStatus != null) { + const oldTxState = computePeerPushDebitTransactionState(pushDebitRec); + pushDebitRec.status = newStatus; + const newTxState = computePeerPushDebitTransactionState(pushDebitRec); + await tx.peerPushPaymentInitiations.put(pushDebitRec); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }); + notifyTransition(ws, transactionId, transitionInfo); +} + +export async function failPeerPushDebitTransaction( + ws: InternalWalletState, + pursePub: string, +) { + const taskId = constructTaskIdentifier({ + tag: PendingTaskType.PeerPushDebit, + pursePub, + }); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushDebit, + pursePub, + }); + stopLongpolling(ws, taskId); + const transitionInfo = await ws.db + .mktx((x) => [x.peerPushPaymentInitiations]) + .runReadWrite(async (tx) => { + const pushDebitRec = await tx.peerPushPaymentInitiations.get(pursePub); + if (!pushDebitRec) { + logger.warn(`peer push debit ${pursePub} not found`); + return; + } + let newStatus: PeerPushPaymentInitiationStatus | undefined = undefined; + switch (pushDebitRec.status) { + case PeerPushPaymentInitiationStatus.AbortingRefresh: + case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh: + // FIXME: We also need to abort the refresh group! + newStatus = PeerPushPaymentInitiationStatus.Aborted; + break; + case PeerPushPaymentInitiationStatus.AbortingDeletePurse: + case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse: + newStatus = PeerPushPaymentInitiationStatus.Aborted; + break; + case PeerPushPaymentInitiationStatus.PendingReady: + case PeerPushPaymentInitiationStatus.SuspendedReady: + case PeerPushPaymentInitiationStatus.SuspendedCreatePurse: + case PeerPushPaymentInitiationStatus.PendingCreatePurse: + case PeerPushPaymentInitiationStatus.Done: + case PeerPushPaymentInitiationStatus.Aborted: + case PeerPushPaymentInitiationStatus.Failed: + // Do nothing + break; + default: + assertUnreachable(pushDebitRec.status); + } + if (newStatus != null) { + const oldTxState = computePeerPushDebitTransactionState(pushDebitRec); + pushDebitRec.status = newStatus; + const newTxState = computePeerPushDebitTransactionState(pushDebitRec); + await tx.peerPushPaymentInitiations.put(pushDebitRec); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }); + notifyTransition(ws, transactionId, transitionInfo); +} + +export async function suspendPeerPushDebitTransaction( + ws: InternalWalletState, + pursePub: string, +) { + const taskId = constructTaskIdentifier({ + tag: PendingTaskType.PeerPushDebit, + pursePub, + }); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushDebit, + pursePub, + }); + stopLongpolling(ws, taskId); + const transitionInfo = await ws.db + .mktx((x) => [x.peerPushPaymentInitiations]) + .runReadWrite(async (tx) => { + const pushDebitRec = await tx.peerPushPaymentInitiations.get(pursePub); + if (!pushDebitRec) { + logger.warn(`peer push debit ${pursePub} not found`); + return; + } + let newStatus: PeerPushPaymentInitiationStatus | undefined = undefined; + switch (pushDebitRec.status) { + case PeerPushPaymentInitiationStatus.PendingCreatePurse: + newStatus = PeerPushPaymentInitiationStatus.SuspendedCreatePurse; + break; + case PeerPushPaymentInitiationStatus.AbortingRefresh: + newStatus = PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh; + break; + case PeerPushPaymentInitiationStatus.AbortingDeletePurse: + newStatus = + PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse; + break; + case PeerPushPaymentInitiationStatus.PendingReady: + newStatus = PeerPushPaymentInitiationStatus.SuspendedReady; + break; + case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse: + case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh: + case PeerPushPaymentInitiationStatus.SuspendedReady: + case PeerPushPaymentInitiationStatus.SuspendedCreatePurse: + case PeerPushPaymentInitiationStatus.Done: + case PeerPushPaymentInitiationStatus.Aborted: + case PeerPushPaymentInitiationStatus.Failed: + // Do nothing + break; + default: + assertUnreachable(pushDebitRec.status); + } + if (newStatus != null) { + const oldTxState = computePeerPushDebitTransactionState(pushDebitRec); + pushDebitRec.status = newStatus; + const newTxState = computePeerPushDebitTransactionState(pushDebitRec); + await tx.peerPushPaymentInitiations.put(pushDebitRec); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }); + notifyTransition(ws, transactionId, transitionInfo); +} + +export async function resumePeerPushDebitTransaction( + ws: InternalWalletState, + pursePub: string, +) { + const taskId = constructTaskIdentifier({ + tag: PendingTaskType.PeerPushDebit, + pursePub, + }); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushDebit, + pursePub, + }); + stopLongpolling(ws, taskId); + const transitionInfo = await ws.db + .mktx((x) => [x.peerPushPaymentInitiations]) + .runReadWrite(async (tx) => { + const pushDebitRec = await tx.peerPushPaymentInitiations.get(pursePub); + if (!pushDebitRec) { + logger.warn(`peer push debit ${pursePub} not found`); + return; + } + let newStatus: PeerPushPaymentInitiationStatus | undefined = undefined; + switch (pushDebitRec.status) { + case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse: + newStatus = PeerPushPaymentInitiationStatus.AbortingDeletePurse; + break; + case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh: + newStatus = PeerPushPaymentInitiationStatus.AbortingRefresh; + break; + case PeerPushPaymentInitiationStatus.SuspendedReady: + newStatus = PeerPushPaymentInitiationStatus.PendingReady; + break; + case PeerPushPaymentInitiationStatus.SuspendedCreatePurse: + newStatus = PeerPushPaymentInitiationStatus.PendingCreatePurse; + break; + case PeerPushPaymentInitiationStatus.PendingCreatePurse: + case PeerPushPaymentInitiationStatus.AbortingRefresh: + case PeerPushPaymentInitiationStatus.AbortingDeletePurse: + case PeerPushPaymentInitiationStatus.PendingReady: + case PeerPushPaymentInitiationStatus.Done: + case PeerPushPaymentInitiationStatus.Aborted: + case PeerPushPaymentInitiationStatus.Failed: + // Do nothing + break; + default: + assertUnreachable(pushDebitRec.status); + } + if (newStatus != null) { + const oldTxState = computePeerPushDebitTransactionState(pushDebitRec); + pushDebitRec.status = newStatus; + const newTxState = computePeerPushDebitTransactionState(pushDebitRec); + await tx.peerPushPaymentInitiations.put(pushDebitRec); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }); + ws.workAvailable.trigger(); + notifyTransition(ws, transactionId, transitionInfo); +} + + +export function computePeerPushDebitTransactionState( + ppiRecord: PeerPushPaymentInitiationRecord, +): TransactionState { + switch (ppiRecord.status) { + case PeerPushPaymentInitiationStatus.PendingCreatePurse: + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.CreatePurse, + }; + case PeerPushPaymentInitiationStatus.PendingReady: + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.Ready, + }; + case PeerPushPaymentInitiationStatus.Aborted: + return { + major: TransactionMajorState.Aborted, + }; + case PeerPushPaymentInitiationStatus.AbortingDeletePurse: + return { + major: TransactionMajorState.Aborting, + minor: TransactionMinorState.DeletePurse, + }; + case PeerPushPaymentInitiationStatus.AbortingRefresh: + return { + major: TransactionMajorState.Aborting, + minor: TransactionMinorState.Refresh, + }; + case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse: + return { + major: TransactionMajorState.SuspendedAborting, + minor: TransactionMinorState.DeletePurse, + }; + case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh: + return { + major: TransactionMajorState.SuspendedAborting, + minor: TransactionMinorState.Refresh, + }; + case PeerPushPaymentInitiationStatus.SuspendedCreatePurse: + return { + major: TransactionMajorState.Suspended, + minor: TransactionMinorState.CreatePurse, + }; + case PeerPushPaymentInitiationStatus.SuspendedReady: + return { + major: TransactionMajorState.Suspended, + minor: TransactionMinorState.Ready, + }; + case PeerPushPaymentInitiationStatus.Done: + return { + major: TransactionMajorState.Done, + }; + case PeerPushPaymentInitiationStatus.Failed: + return { + major: TransactionMajorState.Failed, + }; + } +}
\ No newline at end of file |