wallet-core: handle more p2p abort cases nicely

This commit is contained in:
Florian Dold 2023-06-05 17:58:20 +02:00
parent 6e7c88a620
commit 9fca44893a
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
11 changed files with 385 additions and 100 deletions

View File

@ -45,7 +45,7 @@ export interface HttpResponse {
export const DEFAULT_REQUEST_TIMEOUT_MS = 60000;
export interface HttpRequestOptions {
method?: "POST" | "PUT" | "GET";
method?: "POST" | "PUT" | "GET" | "DELETE";
headers?: { [name: string]: string };
/**

View File

@ -958,6 +958,7 @@ export enum TalerSignaturePurpose {
WALLET_PURSE_MERGE = 1213,
WALLET_ACCOUNT_MERGE = 1214,
WALLET_PURSE_ECONTRACT = 1216,
WALLET_PURSE_DELETE = 1220,
EXCHANGE_CONFIRM_RECOUP = 1039,
EXCHANGE_CONFIRM_RECOUP_REFRESH = 1041,
ANASTASIS_POLICY_UPLOAD = 1400,

View File

@ -83,6 +83,7 @@ export enum TransactionMajorState {
Dialog = "dialog",
SuspendedAborting = "suspended-aborting",
Failed = "failed",
Expired = "expired",
// Only used for the notification, never in the transaction history
Deleted = "deleted",
}

View File

@ -734,6 +734,7 @@ export enum RefreshReason {
Refund = "refund",
AbortPay = "abort-pay",
AbortDeposit = "abort-deposit",
AbortPeerPushDebit = "abort-peer-push-debit",
Recoup = "recoup",
BackupRestored = "backup-restored",
Scheduled = "scheduled",

View File

@ -106,6 +106,8 @@ import {
EncryptContractRequest,
EncryptContractResponse,
EncryptedContract,
SignDeletePurseRequest,
SignDeletePurseResponse,
SignPurseMergeRequest,
SignPurseMergeResponse,
SignRefundRequest,
@ -240,6 +242,8 @@ export interface TalerCryptoInterface {
): Promise<SignReservePurseCreateResponse>;
signRefund(req: SignRefundRequest): Promise<SignRefundResponse>;
signDeletePurse(req: SignDeletePurseRequest): Promise<SignDeletePurseResponse>;
}
/**
@ -419,6 +423,11 @@ export const nullCrypto: TalerCryptoInterface = {
signRefund: function (req: SignRefundRequest): Promise<SignRefundResponse> {
throw new Error("Function not implemented.");
},
signDeletePurse: function (
req: SignDeletePurseRequest,
): Promise<SignDeletePurseResponse> {
throw new Error("Function not implemented.");
},
};
export type WithArg<X> = X extends (req: infer T) => infer R
@ -1671,6 +1680,21 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
sig: refundSigResp.sig,
};
},
async signDeletePurse(
tci: TalerCryptoInterfaceR,
req: SignDeletePurseRequest,
): Promise<SignDeletePurseResponse> {
const deleteSigBlob = buildSigPS(
TalerSignaturePurpose.WALLET_PURSE_DELETE,
).build();
const sigResp = await tci.eddsaSign(tci, {
msg: encodeCrock(deleteSigBlob),
priv: req.pursePriv,
});
return {
sig: sigResp.sig,
}
},
};
function amountToBuffer(amount: AmountLike): Uint8Array {

View File

@ -268,7 +268,14 @@ export interface SignRefundResponse {
sig: string;
}
export interface SignRefundResponse {}
export interface SignDeletePurseRequest {
pursePriv: string;
}
export interface SignDeletePurseResponse {
sig: EddsaSignatureString;
}
export interface SignReservePurseCreateRequest {
mergeTimestamp: TalerProtocolTimestamp;

View File

@ -1787,6 +1787,7 @@ export enum PeerPushPaymentInitiationStatus {
Done = 50 /* DORMANT_START */,
Aborted = 51,
Failed = 52,
Expired = 53,
}
export interface PeerPushPaymentCoinSelection {
@ -1844,6 +1845,8 @@ export interface PeerPushPaymentInitiationRecord {
timestampCreated: TalerPreciseTimestamp;
abortRefreshGroupId?: string;
/**
* Status of the peer push payment initiation.
*/

View File

@ -83,7 +83,6 @@ import {
stopLongpolling,
} from "./transactions.js";
import {
checkWithdrawalKycStatus,
getExchangeWithdrawalInfo,
internalCreateWithdrawalGroup,
processWithdrawalGroup,
@ -241,6 +240,62 @@ async function longpollKycStatus(
};
}
async function processPeerPullCreditAbortingDeletePurse(
ws: InternalWalletState,
peerPullIni: PeerPullPaymentInitiationRecord,
): Promise<OperationAttemptResult> {
const { pursePub, pursePriv } = peerPullIni;
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPushDebit,
pursePub,
});
const sigResp = await ws.cryptoApi.signDeletePurse({
pursePriv,
});
const purseUrl = new URL(
`purses/${pursePub}`,
peerPullIni.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.peerPullPaymentInitiations,
x.refreshGroups,
x.denominations,
x.coinAvailability,
x.coins,
])
.runReadWrite(async (tx) => {
const ppiRec = await tx.peerPullPaymentInitiations.get(pursePub);
if (!ppiRec) {
return undefined;
}
if (
ppiRec.status !== PeerPullPaymentInitiationStatus.AbortingDeletePurse
) {
return undefined;
}
const oldTxState = computePeerPullCreditTransactionState(ppiRec);
ppiRec.status = PeerPullPaymentInitiationStatus.Aborted;
const newTxState = computePeerPullCreditTransactionState(ppiRec);
return {
oldTxState,
newTxState,
};
});
notifyTransition(ws, transactionId, transitionInfo);
return OperationAttemptResult.pendingEmpty();
}
export async function processPeerPullCredit(
ws: InternalWalletState,
pursePub: string,
@ -320,6 +375,8 @@ export async function processPeerPullCredit(
}
case PeerPullPaymentInitiationStatus.PendingCreatePurse:
break;
case PeerPullPaymentInitiationStatus.AbortingDeletePurse:
return await processPeerPullCreditAbortingDeletePurse(ws, pullIni);
default:
throw Error(`unknown PeerPullPaymentInitiationStatus ${pullIni.status}`);
}

View File

@ -15,55 +15,152 @@
*/
import {
ConfirmPeerPullDebitRequest,
AcceptPeerPullPaymentResponse,
Amounts,
j2s,
TalerError,
TalerErrorCode,
TransactionType,
RefreshReason,
ConfirmPeerPullDebitRequest,
ExchangePurseDeposits,
Logger,
PeerContractTerms,
PreparePeerPullDebitRequest,
PreparePeerPullDebitResponse,
RefreshReason,
TalerError,
TalerErrorCode,
TalerPreciseTimestamp,
TransactionAction,
TransactionMajorState,
TransactionMinorState,
TransactionState,
TransactionType,
codecForAny,
codecForExchangeGetContractResponse,
codecForPeerContractTerms,
decodeCrock,
eddsaGetPublic,
encodeCrock,
getRandomBytes,
j2s,
parsePayPullUri,
TransactionAction,
TransactionMajorState,
TransactionMinorState,
TransactionState,
} from "@gnu-taler/taler-util";
import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
import {
InternalWalletState,
PeerPullDebitRecordStatus,
PeerPullPaymentIncomingRecord,
PendingTaskType,
} from "../index.js";
import { TaskIdentifiers, constructTaskIdentifier } from "../util/retries.js";
import { spendCoins, runOperationWithErrorReporting } from "./common.js";
import { assertUnreachable } from "../util/assertUnreachable.js";
import {
OperationAttemptResult,
OperationAttemptResultType,
TaskIdentifiers,
constructTaskIdentifier,
} from "../util/retries.js";
import { runOperationWithErrorReporting, spendCoins } from "./common.js";
import {
codecForExchangePurseStatus,
getTotalPeerPaymentCost,
queryCoinInfosForSelection,
selectPeerCoins,
} from "./pay-peer-common.js";
import { processPeerPullDebit } from "./pay-peer-push-credit.js";
import {
constructTransactionIdentifier,
notifyTransition,
stopLongpolling,
} from "./transactions.js";
import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
import { assertUnreachable } from "../util/assertUnreachable.js";
const logger = new Logger("pay-peer-pull-debit.ts");
async function processPeerPullDebitPendingDeposit(
ws: InternalWalletState,
peerPullInc: PeerPullPaymentIncomingRecord,
): Promise<OperationAttemptResult> {
const peerPullPaymentIncomingId = peerPullInc.peerPullPaymentIncomingId;
const pursePub = peerPullInc.pursePub;
const coinSel = peerPullInc.coinSel;
if (!coinSel) {
throw Error("invalid state, no coins selected");
}
const coins = await queryCoinInfosForSelection(ws, coinSel);
const depositSigsResp = await ws.cryptoApi.signPurseDeposits({
exchangeBaseUrl: peerPullInc.exchangeBaseUrl,
pursePub: peerPullInc.pursePub,
coins,
});
const purseDepositUrl = new URL(
`purses/${pursePub}/deposit`,
peerPullInc.exchangeBaseUrl,
);
const depositPayload: ExchangePurseDeposits = {
deposits: depositSigsResp.deposits,
};
if (logger.shouldLogTrace()) {
logger.trace(`purse deposit payload: ${j2s(depositPayload)}`);
}
const httpResp = await ws.http.postJson(purseDepositUrl.href, depositPayload);
const resp = await readSuccessResponseJsonOrThrow(httpResp, codecForAny());
logger.trace(`purse deposit response: ${j2s(resp)}`);
await ws.db
.mktx((x) => [x.peerPullPaymentIncoming])
.runReadWrite(async (tx) => {
const pi = await tx.peerPullPaymentIncoming.get(
peerPullPaymentIncomingId,
);
if (!pi) {
throw Error("peer pull payment not found anymore");
}
if (pi.status === PeerPullDebitRecordStatus.PendingDeposit) {
pi.status = PeerPullDebitRecordStatus.DonePaid;
}
await tx.peerPullPaymentIncoming.put(pi);
});
return {
type: OperationAttemptResultType.Finished,
result: undefined,
};
}
async function processPeerPullDebitAbortingRefresh(
ws: InternalWalletState,
peerPullInc: PeerPullPaymentIncomingRecord,
): Promise<OperationAttemptResult> {
throw Error("not implemented");
}
export async function processPeerPullDebit(
ws: InternalWalletState,
peerPullPaymentIncomingId: string,
): Promise<OperationAttemptResult> {
const peerPullInc = await ws.db
.mktx((x) => [x.peerPullPaymentIncoming])
.runReadOnly(async (tx) => {
return tx.peerPullPaymentIncoming.get(peerPullPaymentIncomingId);
});
if (!peerPullInc) {
throw Error("peer pull debit not found");
}
switch (peerPullInc.status) {
case PeerPullDebitRecordStatus.PendingDeposit:
return await processPeerPullDebitPendingDeposit(ws, peerPullInc);
case PeerPullDebitRecordStatus.AbortingRefresh:
return await processPeerPullDebitAbortingRefresh(ws, peerPullInc);
}
return {
type: OperationAttemptResultType.Finished,
result: undefined,
}
}
export async function confirmPeerPullDebit(
ws: InternalWalletState,
req: ConfirmPeerPullDebitRequest,

View File

@ -553,76 +553,6 @@ export async function confirmPeerPushCredit(
};
}
export async function processPeerPullDebit(
ws: InternalWalletState,
peerPullPaymentIncomingId: string,
): Promise<OperationAttemptResult> {
const peerPullInc = await ws.db
.mktx((x) => [x.peerPullPaymentIncoming])
.runReadOnly(async (tx) => {
return tx.peerPullPaymentIncoming.get(peerPullPaymentIncomingId);
});
if (!peerPullInc) {
throw Error("peer pull debit not found");
}
if (peerPullInc.status === PeerPullDebitRecordStatus.PendingDeposit) {
const pursePub = peerPullInc.pursePub;
const coinSel = peerPullInc.coinSel;
if (!coinSel) {
throw Error("invalid state, no coins selected");
}
const coins = await queryCoinInfosForSelection(ws, coinSel);
const depositSigsResp = await ws.cryptoApi.signPurseDeposits({
exchangeBaseUrl: peerPullInc.exchangeBaseUrl,
pursePub: peerPullInc.pursePub,
coins,
});
const purseDepositUrl = new URL(
`purses/${pursePub}/deposit`,
peerPullInc.exchangeBaseUrl,
);
const depositPayload: ExchangePurseDeposits = {
deposits: depositSigsResp.deposits,
};
if (logger.shouldLogTrace()) {
logger.trace(`purse deposit payload: ${j2s(depositPayload)}`);
}
const httpResp = await ws.http.postJson(
purseDepositUrl.href,
depositPayload,
);
const resp = await readSuccessResponseJsonOrThrow(httpResp, codecForAny());
logger.trace(`purse deposit response: ${j2s(resp)}`);
}
await ws.db
.mktx((x) => [x.peerPullPaymentIncoming])
.runReadWrite(async (tx) => {
const pi = await tx.peerPullPaymentIncoming.get(
peerPullPaymentIncomingId,
);
if (!pi) {
throw Error("peer pull payment not found anymore");
}
if (pi.status === PeerPullDebitRecordStatus.PendingDeposit) {
pi.status = PeerPullDebitRecordStatus.DonePaid;
}
await tx.peerPullPaymentIncoming.put(pi);
});
return {
type: OperationAttemptResultType.Finished,
result: undefined,
};
}
export async function suspendPeerPushCreditTransaction(
ws: InternalWalletState,
peerPushPaymentIncomingId: string,

View File

@ -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,
};
}
}
}