wallet-core: add missing resume/suspend implementations

This commit is contained in:
Florian Dold 2023-05-30 09:33:32 +02:00
parent 246f914ca6
commit 0323067c07
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
7 changed files with 755 additions and 58 deletions

View File

@ -863,11 +863,22 @@ export interface TipRecord {
* The url to be redirected after the tip is accepted. * The url to be redirected after the tip is accepted.
*/ */
next_url: string | undefined; next_url: string | undefined;
/** /**
* Timestamp for when the wallet finished picking up the tip * Timestamp for when the wallet finished picking up the tip
* from the merchant. * from the merchant.
*/ */
pickedUpTimestamp: TalerPreciseTimestamp | undefined; pickedUpTimestamp: TalerPreciseTimestamp | undefined;
status: TipRecordStatus;
}
export enum TipRecordStatus {
PendingPickup = 10,
SuspendidPickup = 21,
Done = 50,
} }
export enum RefreshCoinStatus { export enum RefreshCoinStatus {
@ -1078,12 +1089,12 @@ export enum PurchaseStatus {
/** /**
* Not downloaded yet. * Not downloaded yet.
*/ */
DownloadingProposal = 10, PendingDownloadingProposal = 10,
/** /**
* The user has accepted the proposal. * The user has accepted the proposal.
*/ */
Paying = 11, PendingPaying = 11,
/** /**
* Currently in the process of aborting with a refund. * Currently in the process of aborting with a refund.
@ -1093,17 +1104,17 @@ export enum PurchaseStatus {
/** /**
* Paying a second time, likely with different session ID * Paying a second time, likely with different session ID
*/ */
PayingReplay = 13, PendingPayingReplay = 13,
/** /**
* Query for refunds (until query succeeds). * Query for refunds (until query succeeds).
*/ */
QueryingRefund = 14, PendingQueryingRefund = 14,
/** /**
* Query for refund (until auto-refund deadline is reached). * Query for refund (until auto-refund deadline is reached).
*/ */
QueryingAutoRefund = 15, PendingQueryingAutoRefund = 15,
PendingAcceptRefund = 16, PendingAcceptRefund = 16,
@ -1902,7 +1913,7 @@ export interface PeerPullPaymentInitiationRecord {
export enum PeerPushPaymentIncomingStatus { export enum PeerPushPaymentIncomingStatus {
PendingMerge = 10 /* ACTIVE_START */, PendingMerge = 10 /* ACTIVE_START */,
MergeKycRequired = 11 /* ACTIVE_START + 1 */, PendingMergeKycRequired = 11 /* ACTIVE_START + 1 */,
/** /**
* Merge was successful and withdrawal group has been created, now * Merge was successful and withdrawal group has been created, now
* everything is in the hand of the withdrawal group. * everything is in the hand of the withdrawal group.
@ -2829,6 +2840,22 @@ export const walletDbFixups: FixupDescription[] = [
}); });
}, },
}, },
{
name: "TipRecordRecord_status_add",
async fn(tx): Promise<void> {
await tx.tips.iter().forEachAsync(async (r) => {
// Remove legacy transactions that don't have the totalCost field yet.
if (r.status == null) {
if (r.pickedUpTimestamp) {
r.status = TipRecordStatus.Done;
} else {
r.status = TipRecordStatus.PendingPickup;
}
await tx.tips.put(r);
}
});
},
},
]; ];
const logger = new Logger("db.ts"); const logger = new Logger("db.ts");

View File

@ -412,14 +412,14 @@ export async function exportBackup(
let propStatus: BackupProposalStatus; let propStatus: BackupProposalStatus;
switch (purch.purchaseStatus) { switch (purch.purchaseStatus) {
case PurchaseStatus.Done: case PurchaseStatus.Done:
case PurchaseStatus.QueryingAutoRefund: case PurchaseStatus.PendingQueryingAutoRefund:
case PurchaseStatus.QueryingRefund: case PurchaseStatus.PendingQueryingRefund:
propStatus = BackupProposalStatus.Paid; propStatus = BackupProposalStatus.Paid;
break; break;
case PurchaseStatus.PayingReplay: case PurchaseStatus.PendingPayingReplay:
case PurchaseStatus.DownloadingProposal: case PurchaseStatus.PendingDownloadingProposal:
case PurchaseStatus.Proposed: case PurchaseStatus.Proposed:
case PurchaseStatus.Paying: case PurchaseStatus.PendingPaying:
propStatus = BackupProposalStatus.Proposed; propStatus = BackupProposalStatus.Proposed;
break; break;
case PurchaseStatus.FailedClaim: case PurchaseStatus.FailedClaim:

View File

@ -131,6 +131,7 @@ import {
import { import {
constructTransactionIdentifier, constructTransactionIdentifier,
notifyTransition, notifyTransition,
stopLongpolling,
} from "./transactions.js"; } from "./transactions.js";
/** /**
@ -339,7 +340,7 @@ async function processDownloadProposal(
}; };
} }
if (proposal.purchaseStatus != PurchaseStatus.DownloadingProposal) { if (proposal.purchaseStatus != PurchaseStatus.PendingDownloadingProposal) {
return { return {
type: OperationAttemptResultType.Finished, type: OperationAttemptResultType.Finished,
result: undefined, result: undefined,
@ -516,7 +517,7 @@ async function processDownloadProposal(
if (!p) { if (!p) {
return; return;
} }
if (p.purchaseStatus !== PurchaseStatus.DownloadingProposal) { if (p.purchaseStatus !== PurchaseStatus.PendingDownloadingProposal) {
return; return;
} }
const oldTxState = computePayMerchantTransactionState(p); const oldTxState = computePayMerchantTransactionState(p);
@ -626,7 +627,7 @@ async function createPurchase(
merchantBaseUrl, merchantBaseUrl,
orderId, orderId,
proposalId: proposalId, proposalId: proposalId,
purchaseStatus: PurchaseStatus.DownloadingProposal, purchaseStatus: PurchaseStatus.PendingDownloadingProposal,
repurchaseProposalId: undefined, repurchaseProposalId: undefined,
downloadSessionId: sessionId, downloadSessionId: sessionId,
autoRefundDeadline: undefined, autoRefundDeadline: undefined,
@ -699,7 +700,7 @@ async function storeFirstPaySuccess(
return; return;
} }
const oldTxState = computePayMerchantTransactionState(purchase); const oldTxState = computePayMerchantTransactionState(purchase);
if (purchase.purchaseStatus === PurchaseStatus.Paying) { if (purchase.purchaseStatus === PurchaseStatus.PendingPaying) {
purchase.purchaseStatus = PurchaseStatus.Done; purchase.purchaseStatus = PurchaseStatus.Done;
} }
purchase.timestampFirstSuccessfulPay = now; purchase.timestampFirstSuccessfulPay = now;
@ -721,7 +722,7 @@ async function storeFirstPaySuccess(
if (protoAr) { if (protoAr) {
const ar = Duration.fromTalerProtocolDuration(protoAr); const ar = Duration.fromTalerProtocolDuration(protoAr);
logger.info("auto_refund present"); logger.info("auto_refund present");
purchase.purchaseStatus = PurchaseStatus.QueryingAutoRefund; purchase.purchaseStatus = PurchaseStatus.PendingQueryingAutoRefund;
purchase.autoRefundDeadline = AbsoluteTime.toProtocolTimestamp( purchase.autoRefundDeadline = AbsoluteTime.toProtocolTimestamp(
AbsoluteTime.addDuration(AbsoluteTime.now(), ar), AbsoluteTime.addDuration(AbsoluteTime.now(), ar),
); );
@ -760,8 +761,8 @@ async function storePayReplaySuccess(
} }
const oldTxState = computePayMerchantTransactionState(purchase); const oldTxState = computePayMerchantTransactionState(purchase);
if ( if (
purchase.purchaseStatus === PurchaseStatus.Paying || purchase.purchaseStatus === PurchaseStatus.PendingPaying ||
purchase.purchaseStatus === PurchaseStatus.PayingReplay purchase.purchaseStatus === PurchaseStatus.PendingPayingReplay
) { ) {
purchase.purchaseStatus = PurchaseStatus.Done; purchase.purchaseStatus = PurchaseStatus.Done;
} }
@ -1058,7 +1059,7 @@ export async function checkPaymentByProposalId(
} }
const oldTxState = computePayMerchantTransactionState(p); const oldTxState = computePayMerchantTransactionState(p);
p.lastSessionId = sessionId; p.lastSessionId = sessionId;
p.purchaseStatus = PurchaseStatus.PayingReplay; p.purchaseStatus = PurchaseStatus.PendingPayingReplay;
await tx.purchases.put(p); await tx.purchases.put(p);
const newTxState = computePayMerchantTransactionState(p); const newTxState = computePayMerchantTransactionState(p);
return { oldTxState, newTxState }; return { oldTxState, newTxState };
@ -1098,8 +1099,8 @@ export async function checkPaymentByProposalId(
} else { } else {
const paid = const paid =
purchase.purchaseStatus === PurchaseStatus.Done || purchase.purchaseStatus === PurchaseStatus.Done ||
purchase.purchaseStatus === PurchaseStatus.QueryingRefund || purchase.purchaseStatus === PurchaseStatus.PendingQueryingRefund ||
purchase.purchaseStatus === PurchaseStatus.QueryingAutoRefund; purchase.purchaseStatus === PurchaseStatus.PendingQueryingAutoRefund;
const download = await expectProposalDownload(ws, purchase); const download = await expectProposalDownload(ws, purchase);
return { return {
status: PreparePayResultType.AlreadyConfirmed, status: PreparePayResultType.AlreadyConfirmed,
@ -1348,7 +1349,7 @@ export async function confirmPay(
logger.trace(`changing session ID to ${sessionIdOverride}`); logger.trace(`changing session ID to ${sessionIdOverride}`);
purchase.lastSessionId = sessionIdOverride; purchase.lastSessionId = sessionIdOverride;
if (purchase.purchaseStatus === PurchaseStatus.Done) { if (purchase.purchaseStatus === PurchaseStatus.Done) {
purchase.purchaseStatus = PurchaseStatus.PayingReplay; purchase.purchaseStatus = PurchaseStatus.PendingPayingReplay;
} }
await tx.purchases.put(purchase); await tx.purchases.put(purchase);
} }
@ -1424,7 +1425,7 @@ export async function confirmPay(
}; };
p.lastSessionId = sessionId; p.lastSessionId = sessionId;
p.timestampAccept = TalerPreciseTimestamp.now(); p.timestampAccept = TalerPreciseTimestamp.now();
p.purchaseStatus = PurchaseStatus.Paying; p.purchaseStatus = PurchaseStatus.PendingPaying;
await tx.purchases.put(p); await tx.purchases.put(p);
await spendCoins(ws, tx, { await spendCoins(ws, tx, {
//`txn:proposal:${p.proposalId}` //`txn:proposal:${p.proposalId}`
@ -1440,7 +1441,7 @@ export async function confirmPay(
}); });
break; break;
case PurchaseStatus.Done: case PurchaseStatus.Done:
case PurchaseStatus.Paying: case PurchaseStatus.PendingPaying:
default: default:
break; break;
} }
@ -1481,14 +1482,14 @@ export async function processPurchase(
} }
switch (purchase.purchaseStatus) { switch (purchase.purchaseStatus) {
case PurchaseStatus.DownloadingProposal: case PurchaseStatus.PendingDownloadingProposal:
return processDownloadProposal(ws, proposalId); return processDownloadProposal(ws, proposalId);
case PurchaseStatus.Paying: case PurchaseStatus.PendingPaying:
case PurchaseStatus.PayingReplay: case PurchaseStatus.PendingPayingReplay:
return processPurchasePay(ws, proposalId); return processPurchasePay(ws, proposalId);
case PurchaseStatus.QueryingRefund: case PurchaseStatus.PendingQueryingRefund:
return processPurchaseQueryRefund(ws, purchase); return processPurchaseQueryRefund(ws, purchase);
case PurchaseStatus.QueryingAutoRefund: case PurchaseStatus.PendingQueryingAutoRefund:
return processPurchaseAutoRefund(ws, purchase); return processPurchaseAutoRefund(ws, purchase);
case PurchaseStatus.AbortingWithRefund: case PurchaseStatus.AbortingWithRefund:
return processPurchaseAbortingRefund(ws, purchase); return processPurchaseAbortingRefund(ws, purchase);
@ -1540,8 +1541,8 @@ export async function processPurchasePay(
}; };
} }
switch (purchase.purchaseStatus) { switch (purchase.purchaseStatus) {
case PurchaseStatus.Paying: case PurchaseStatus.PendingPaying:
case PurchaseStatus.PayingReplay: case PurchaseStatus.PendingPayingReplay:
break; break;
default: default:
return OperationAttemptResult.finishedEmpty(); return OperationAttemptResult.finishedEmpty();
@ -1757,11 +1758,11 @@ export async function abortPayMerchant(
logger.warn(`tried to abort successful payment`); logger.warn(`tried to abort successful payment`);
return; return;
} }
if (oldStatus === PurchaseStatus.Paying) { if (oldStatus === PurchaseStatus.PendingPaying) {
purchase.purchaseStatus = PurchaseStatus.AbortingWithRefund; purchase.purchaseStatus = PurchaseStatus.AbortingWithRefund;
} }
await tx.purchases.put(purchase); await tx.purchases.put(purchase);
if (oldStatus === PurchaseStatus.Paying) { if (oldStatus === PurchaseStatus.PendingPaying) {
if (purchase.payInfo) { if (purchase.payInfo) {
const coinSel = purchase.payInfo.payCoinSelection; const coinSel = purchase.payInfo.payCoinSelection;
const currency = Amounts.currencyOf(purchase.payInfo.totalPayCost); const currency = Amounts.currencyOf(purchase.payInfo.totalPayCost);
@ -1789,32 +1790,146 @@ export async function abortPayMerchant(
ws.workAvailable.trigger(); ws.workAvailable.trigger();
} }
const transitionSuspend: { [x in PurchaseStatus]?: {
next: PurchaseStatus | undefined,
} } = {
[PurchaseStatus.PendingDownloadingProposal]: {
next: PurchaseStatus.SuspendedDownloadingProposal,
},
[PurchaseStatus.AbortingWithRefund]: {
next: PurchaseStatus.SuspendedAbortingWithRefund,
},
[PurchaseStatus.PendingPaying]: {
next: PurchaseStatus.SuspendedPaying,
},
[PurchaseStatus.PendingPayingReplay]: {
next: PurchaseStatus.SuspendedPayingReplay,
},
[PurchaseStatus.PendingQueryingAutoRefund]: {
next: PurchaseStatus.SuspendedQueryingAutoRefund,
}
}
const transitionResume: { [x in PurchaseStatus]?: {
next: PurchaseStatus | undefined,
} } = {
[PurchaseStatus.SuspendedDownloadingProposal]: {
next: PurchaseStatus.PendingDownloadingProposal,
},
[PurchaseStatus.SuspendedAbortingWithRefund]: {
next: PurchaseStatus.AbortingWithRefund,
},
[PurchaseStatus.SuspendedPaying]: {
next: PurchaseStatus.PendingPaying,
},
[PurchaseStatus.SuspendedPayingReplay]: {
next: PurchaseStatus.PendingPayingReplay,
},
[PurchaseStatus.SuspendedQueryingAutoRefund]: {
next: PurchaseStatus.PendingQueryingAutoRefund,
}
}
export async function suspendPayMerchant(
ws: InternalWalletState,
proposalId: string,
): Promise<void> {
const transactionId = constructTransactionIdentifier({
tag: TransactionType.Payment,
proposalId,
});
const opId = constructTaskIdentifier({
tag: PendingTaskType.Purchase,
proposalId,
});
stopLongpolling(ws, opId);
const transitionInfo = await ws.db
.mktx((x) => [
x.purchases,
])
.runReadWrite(async (tx) => {
const purchase = await tx.purchases.get(proposalId);
if (!purchase) {
throw Error("purchase not found");
}
const oldTxState = computePayMerchantTransactionState(purchase);
let newStatus = transitionSuspend[purchase.purchaseStatus];
if (!newStatus) {
return undefined;
}
await tx.purchases.put(purchase);
const newTxState = computePayMerchantTransactionState(purchase);
return { oldTxState, newTxState };
});
notifyTransition(ws, transactionId, transitionInfo);
ws.workAvailable.trigger();
}
export async function resumePayMerchant(
ws: InternalWalletState,
proposalId: string,
): Promise<void> {
const transactionId = constructTransactionIdentifier({
tag: TransactionType.Payment,
proposalId,
});
const opId = constructTaskIdentifier({
tag: PendingTaskType.Purchase,
proposalId,
});
stopLongpolling(ws, opId);
const transitionInfo = await ws.db
.mktx((x) => [
x.purchases,
])
.runReadWrite(async (tx) => {
const purchase = await tx.purchases.get(proposalId);
if (!purchase) {
throw Error("purchase not found");
}
const oldTxState = computePayMerchantTransactionState(purchase);
let newStatus = transitionResume[purchase.purchaseStatus];
if (!newStatus) {
return undefined;
}
await tx.purchases.put(purchase);
const newTxState = computePayMerchantTransactionState(purchase);
return { oldTxState, newTxState };
});
ws.workAvailable.trigger();
notifyTransition(ws, transactionId, transitionInfo);
ws.workAvailable.trigger();
}
export function computePayMerchantTransactionState( export function computePayMerchantTransactionState(
purchaseRecord: PurchaseRecord, purchaseRecord: PurchaseRecord,
): TransactionState { ): TransactionState {
switch (purchaseRecord.purchaseStatus) { switch (purchaseRecord.purchaseStatus) {
// Pending States // Pending States
case PurchaseStatus.DownloadingProposal: case PurchaseStatus.PendingDownloadingProposal:
return { return {
major: TransactionMajorState.Pending, major: TransactionMajorState.Pending,
minor: TransactionMinorState.ClaimProposal, minor: TransactionMinorState.ClaimProposal,
}; };
case PurchaseStatus.Paying: case PurchaseStatus.PendingPaying:
return { return {
major: TransactionMajorState.Pending, major: TransactionMajorState.Pending,
minor: TransactionMinorState.SubmitPayment, minor: TransactionMinorState.SubmitPayment,
}; };
case PurchaseStatus.PayingReplay: case PurchaseStatus.PendingPayingReplay:
return { return {
major: TransactionMajorState.Pending, major: TransactionMajorState.Pending,
minor: TransactionMinorState.RebindSession, minor: TransactionMinorState.RebindSession,
}; };
case PurchaseStatus.QueryingAutoRefund: case PurchaseStatus.PendingQueryingAutoRefund:
return { return {
major: TransactionMajorState.Pending, major: TransactionMajorState.Pending,
minor: TransactionMinorState.AutoRefund, minor: TransactionMinorState.AutoRefund,
}; };
case PurchaseStatus.QueryingRefund: case PurchaseStatus.PendingQueryingRefund:
return { return {
major: TransactionMajorState.Pending, major: TransactionMajorState.Pending,
minor: TransactionMinorState.CheckRefund, minor: TransactionMinorState.CheckRefund,
@ -1937,7 +2052,7 @@ async function processPurchaseAutoRefund(
logger.warn("purchase does not exist anymore"); logger.warn("purchase does not exist anymore");
return; return;
} }
if (p.purchaseStatus !== PurchaseStatus.QueryingRefund) { if (p.purchaseStatus !== PurchaseStatus.PendingQueryingRefund) {
return; return;
} }
const oldTxState = computePayMerchantTransactionState(p); const oldTxState = computePayMerchantTransactionState(p);
@ -1982,7 +2097,7 @@ async function processPurchaseAutoRefund(
logger.warn("purchase does not exist anymore"); logger.warn("purchase does not exist anymore");
return; return;
} }
if (p.purchaseStatus !== PurchaseStatus.QueryingAutoRefund) { if (p.purchaseStatus !== PurchaseStatus.PendingQueryingAutoRefund) {
return; return;
} }
const oldTxState = computePayMerchantTransactionState(p); const oldTxState = computePayMerchantTransactionState(p);
@ -2118,7 +2233,7 @@ async function processPurchaseQueryRefund(
logger.warn("purchase does not exist anymore"); logger.warn("purchase does not exist anymore");
return undefined; return undefined;
} }
if (p.purchaseStatus !== PurchaseStatus.QueryingRefund) { if (p.purchaseStatus !== PurchaseStatus.PendingQueryingRefund) {
return undefined; return undefined;
} }
const oldTxState = computePayMerchantTransactionState(p); const oldTxState = computePayMerchantTransactionState(p);
@ -2143,7 +2258,7 @@ async function processPurchaseQueryRefund(
logger.warn("purchase does not exist anymore"); logger.warn("purchase does not exist anymore");
return; return;
} }
if (p.purchaseStatus !== PurchaseStatus.QueryingRefund) { if (p.purchaseStatus !== PurchaseStatus.PendingQueryingRefund) {
return; return;
} }
const oldTxState = computePayMerchantTransactionState(p); const oldTxState = computePayMerchantTransactionState(p);
@ -2242,7 +2357,7 @@ export async function startQueryRefund(
return; return;
} }
const oldTxState = computePayMerchantTransactionState(p); const oldTxState = computePayMerchantTransactionState(p);
p.purchaseStatus = PurchaseStatus.QueryingRefund; p.purchaseStatus = PurchaseStatus.PendingQueryingRefund;
const newTxState = computePayMerchantTransactionState(p); const newTxState = computePayMerchantTransactionState(p);
await tx.purchases.put(p); await tx.purchases.put(p);
return { oldTxState, newTxState }; return { oldTxState, newTxState };

View File

@ -1008,7 +1008,7 @@ export async function processPeerPushCredit(
const amount = Amounts.parseOrThrow(contractTerms.amount); const amount = Amounts.parseOrThrow(contractTerms.amount);
if ( if (
peerInc.status === PeerPushPaymentIncomingStatus.MergeKycRequired && peerInc.status === PeerPushPaymentIncomingStatus.PendingMergeKycRequired &&
peerInc.kycInfo peerInc.kycInfo
) { ) {
const txId = constructTransactionIdentifier({ const txId = constructTransactionIdentifier({
@ -1080,7 +1080,7 @@ export async function processPeerPushCredit(
paytoHash: kycPending.h_payto, paytoHash: kycPending.h_payto,
requirementRow: kycPending.requirement_row, requirementRow: kycPending.requirement_row,
}; };
peerInc.status = PeerPushPaymentIncomingStatus.MergeKycRequired; peerInc.status = PeerPushPaymentIncomingStatus.PendingMergeKycRequired;
await tx.peerPushPaymentIncoming.put(peerInc); await tx.peerPushPaymentIncoming.put(peerInc);
}); });
return { return {
@ -1122,7 +1122,7 @@ export async function processPeerPushCredit(
} }
if ( if (
peerInc.status === PeerPushPaymentIncomingStatus.PendingMerge || peerInc.status === PeerPushPaymentIncomingStatus.PendingMerge ||
peerInc.status === PeerPushPaymentIncomingStatus.MergeKycRequired peerInc.status === PeerPushPaymentIncomingStatus.PendingMergeKycRequired
) { ) {
peerInc.status = PeerPushPaymentIncomingStatus.Done; peerInc.status = PeerPushPaymentIncomingStatus.Done;
} }
@ -2186,6 +2186,345 @@ export async function suspendPeerPushDebitTransaction(
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
} }
export async function suspendPeerPullDebitTransaction(
ws: InternalWalletState,
peerPullPaymentIncomingId: string,
) {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPullDebit,
peerPullPaymentIncomingId,
});
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPullDebit,
peerPullPaymentIncomingId,
});
stopLongpolling(ws, taskId);
const transitionInfo = await ws.db
.mktx((x) => [x.peerPullPaymentIncoming])
.runReadWrite(async (tx) => {
const pullDebitRec = await tx.peerPullPaymentIncoming.get(
peerPullPaymentIncomingId,
);
if (!pullDebitRec) {
logger.warn(`peer pull debit ${peerPullPaymentIncomingId} not found`);
return;
}
let newStatus: PeerPullDebitRecordStatus | undefined = undefined;
switch (pullDebitRec.status) {
case PeerPullDebitRecordStatus.DialogProposed:
break;
case PeerPullDebitRecordStatus.DonePaid:
break;
case PeerPullDebitRecordStatus.PendingDeposit:
newStatus = PeerPullDebitRecordStatus.SuspendedDeposit;
break;
case PeerPullDebitRecordStatus.SuspendedDeposit:
break;
default:
assertUnreachable(pullDebitRec.status);
}
if (newStatus != null) {
const oldTxState = computePeerPullDebitTransactionState(pullDebitRec);
pullDebitRec.status = newStatus;
const newTxState = computePeerPullDebitTransactionState(pullDebitRec);
await tx.peerPullPaymentIncoming.put(pullDebitRec);
return {
oldTxState,
newTxState,
};
}
return undefined;
});
notifyTransition(ws, transactionId, transitionInfo);
}
export async function resumePeerPullDebitTransaction(
ws: InternalWalletState,
peerPullPaymentIncomingId: string,
) {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPullDebit,
peerPullPaymentIncomingId,
});
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPullDebit,
peerPullPaymentIncomingId,
});
stopLongpolling(ws, taskId);
const transitionInfo = await ws.db
.mktx((x) => [x.peerPullPaymentIncoming])
.runReadWrite(async (tx) => {
const pullDebitRec = await tx.peerPullPaymentIncoming.get(
peerPullPaymentIncomingId,
);
if (!pullDebitRec) {
logger.warn(`peer pull debit ${peerPullPaymentIncomingId} not found`);
return;
}
let newStatus: PeerPullDebitRecordStatus | undefined = undefined;
switch (pullDebitRec.status) {
case PeerPullDebitRecordStatus.DialogProposed:
case PeerPullDebitRecordStatus.DonePaid:
case PeerPullDebitRecordStatus.PendingDeposit:
break;
case PeerPullDebitRecordStatus.SuspendedDeposit:
newStatus = PeerPullDebitRecordStatus.PendingDeposit;
break;
default:
assertUnreachable(pullDebitRec.status);
}
if (newStatus != null) {
const oldTxState = computePeerPullDebitTransactionState(pullDebitRec);
pullDebitRec.status = newStatus;
const newTxState = computePeerPullDebitTransactionState(pullDebitRec);
await tx.peerPullPaymentIncoming.put(pullDebitRec);
return {
oldTxState,
newTxState,
};
}
return undefined;
});
ws.workAvailable.trigger();
notifyTransition(ws, transactionId, transitionInfo);
}
export async function suspendPeerPushCreditTransaction(
ws: InternalWalletState,
peerPushPaymentIncomingId: string,
) {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPushCredit,
peerPushPaymentIncomingId,
});
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPushCredit,
peerPushPaymentIncomingId,
});
stopLongpolling(ws, taskId);
const transitionInfo = await ws.db
.mktx((x) => [x.peerPushPaymentIncoming])
.runReadWrite(async (tx) => {
const pushCreditRec = await tx.peerPushPaymentIncoming.get(
peerPushPaymentIncomingId,
);
if (!pushCreditRec) {
logger.warn(`peer push credit ${peerPushPaymentIncomingId} not found`);
return;
}
let newStatus: PeerPushPaymentIncomingStatus | undefined = undefined;
switch (pushCreditRec.status) {
case PeerPushPaymentIncomingStatus.DialogProposed:
case PeerPushPaymentIncomingStatus.Done:
case PeerPushPaymentIncomingStatus.SuspendedMerge:
case PeerPushPaymentIncomingStatus.SuspendedMergeKycRequired:
case PeerPushPaymentIncomingStatus.SuspendedWithdrawing:
break;
case PeerPushPaymentIncomingStatus.PendingMergeKycRequired:
newStatus = PeerPushPaymentIncomingStatus.SuspendedMergeKycRequired;
break;
case PeerPushPaymentIncomingStatus.PendingMerge:
newStatus = PeerPushPaymentIncomingStatus.SuspendedMerge;
break;
case PeerPushPaymentIncomingStatus.PendingWithdrawing:
// FIXME: Suspend internal withdrawal transaction!
newStatus = PeerPushPaymentIncomingStatus.SuspendedWithdrawing;
break;
default:
assertUnreachable(pushCreditRec.status);
}
if (newStatus != null) {
const oldTxState = computePeerPushCreditTransactionState(pushCreditRec);
pushCreditRec.status = newStatus;
const newTxState = computePeerPushCreditTransactionState(pushCreditRec);
await tx.peerPushPaymentIncoming.put(pushCreditRec);
return {
oldTxState,
newTxState,
};
}
return undefined;
});
notifyTransition(ws, transactionId, transitionInfo);
}
export async function resumePeerPushCreditTransaction(
ws: InternalWalletState,
peerPushPaymentIncomingId: string,
) {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPushCredit,
peerPushPaymentIncomingId,
});
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPushCredit,
peerPushPaymentIncomingId,
});
stopLongpolling(ws, taskId);
const transitionInfo = await ws.db
.mktx((x) => [x.peerPushPaymentIncoming])
.runReadWrite(async (tx) => {
const pushCreditRec = await tx.peerPushPaymentIncoming.get(
peerPushPaymentIncomingId,
);
if (!pushCreditRec) {
logger.warn(`peer push credit ${peerPushPaymentIncomingId} not found`);
return;
}
let newStatus: PeerPushPaymentIncomingStatus | undefined = undefined;
switch (pushCreditRec.status) {
case PeerPushPaymentIncomingStatus.DialogProposed:
case PeerPushPaymentIncomingStatus.Done:
case PeerPushPaymentIncomingStatus.PendingMergeKycRequired:
case PeerPushPaymentIncomingStatus.PendingMerge:
case PeerPushPaymentIncomingStatus.PendingWithdrawing:
case PeerPushPaymentIncomingStatus.SuspendedMerge:
newStatus = PeerPushPaymentIncomingStatus.PendingMerge;
break;
case PeerPushPaymentIncomingStatus.SuspendedMergeKycRequired:
newStatus = PeerPushPaymentIncomingStatus.PendingMergeKycRequired;
break;
case PeerPushPaymentIncomingStatus.SuspendedWithdrawing:
// FIXME: resume underlying "internal-withdrawal" transaction.
newStatus = PeerPushPaymentIncomingStatus.PendingWithdrawing;
break;
default:
assertUnreachable(pushCreditRec.status);
}
if (newStatus != null) {
const oldTxState = computePeerPushCreditTransactionState(pushCreditRec);
pushCreditRec.status = newStatus;
const newTxState = computePeerPushCreditTransactionState(pushCreditRec);
await tx.peerPushPaymentIncoming.put(pushCreditRec);
return {
oldTxState,
newTxState,
};
}
return undefined;
});
ws.workAvailable.trigger();
notifyTransition(ws, transactionId, transitionInfo);
}
export async function suspendPeerPullCreditTransaction(
ws: InternalWalletState,
pursePub: string,
) {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPullCredit,
pursePub,
});
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPullCredit,
pursePub,
});
stopLongpolling(ws, taskId);
const transitionInfo = await ws.db
.mktx((x) => [x.peerPullPaymentInitiations])
.runReadWrite(async (tx) => {
const pullCreditRec = await tx.peerPullPaymentInitiations.get(pursePub);
if (!pullCreditRec) {
logger.warn(`peer pull credit ${pursePub} not found`);
return;
}
let newStatus: PeerPullPaymentInitiationStatus | undefined = undefined;
switch (pullCreditRec.status) {
case PeerPullPaymentInitiationStatus.PendingCreatePurse:
newStatus = PeerPullPaymentInitiationStatus.SuspendedCreatePurse;
break;
case PeerPullPaymentInitiationStatus.PendingMergeKycRequired:
newStatus = PeerPullPaymentInitiationStatus.SuspendedMergeKycRequired;
break;
case PeerPullPaymentInitiationStatus.PendingWithdrawing:
newStatus = PeerPullPaymentInitiationStatus.SuspendedWithdrawing;
break;
case PeerPullPaymentInitiationStatus.PendingReady:
newStatus = PeerPullPaymentInitiationStatus.SuspendedReady;
break;
case PeerPullPaymentInitiationStatus.DonePurseDeposited:
case PeerPullPaymentInitiationStatus.SuspendedCreatePurse:
case PeerPullPaymentInitiationStatus.SuspendedMergeKycRequired:
case PeerPullPaymentInitiationStatus.SuspendedReady:
case PeerPullPaymentInitiationStatus.SuspendedWithdrawing:
break;
default:
assertUnreachable(pullCreditRec.status);
}
if (newStatus != null) {
const oldTxState = computePeerPullCreditTransactionState(pullCreditRec);
pullCreditRec.status = newStatus;
const newTxState = computePeerPullCreditTransactionState(pullCreditRec);
await tx.peerPullPaymentInitiations.put(pullCreditRec);
return {
oldTxState,
newTxState,
};
}
return undefined;
});
notifyTransition(ws, transactionId, transitionInfo);
}
export async function resumePeerPullCreditTransaction(
ws: InternalWalletState,
pursePub: string,
) {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPullCredit,
pursePub,
});
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPullCredit,
pursePub,
});
stopLongpolling(ws, taskId);
const transitionInfo = await ws.db
.mktx((x) => [x.peerPullPaymentInitiations])
.runReadWrite(async (tx) => {
const pullCreditRec = await tx.peerPullPaymentInitiations.get(pursePub);
if (!pullCreditRec) {
logger.warn(`peer pull credit ${pursePub} not found`);
return;
}
let newStatus: PeerPullPaymentInitiationStatus | undefined = undefined;
switch (pullCreditRec.status) {
case PeerPullPaymentInitiationStatus.PendingCreatePurse:
case PeerPullPaymentInitiationStatus.PendingMergeKycRequired:
case PeerPullPaymentInitiationStatus.PendingWithdrawing:
case PeerPullPaymentInitiationStatus.PendingReady:
case PeerPullPaymentInitiationStatus.DonePurseDeposited:
case PeerPullPaymentInitiationStatus.SuspendedCreatePurse:
newStatus = PeerPullPaymentInitiationStatus.PendingCreatePurse;
break;
case PeerPullPaymentInitiationStatus.SuspendedMergeKycRequired:
newStatus = PeerPullPaymentInitiationStatus.PendingMergeKycRequired;
break;
case PeerPullPaymentInitiationStatus.SuspendedReady:
newStatus = PeerPullPaymentInitiationStatus.PendingReady;
break;
case PeerPullPaymentInitiationStatus.SuspendedWithdrawing:
newStatus = PeerPullPaymentInitiationStatus.PendingWithdrawing;
break;
default:
assertUnreachable(pullCreditRec.status);
}
if (newStatus != null) {
const oldTxState = computePeerPullCreditTransactionState(pullCreditRec);
pullCreditRec.status = newStatus;
const newTxState = computePeerPullCreditTransactionState(pullCreditRec);
await tx.peerPullPaymentInitiations.put(pullCreditRec);
return {
oldTxState,
newTxState,
};
}
return undefined;
});
ws.workAvailable.trigger();
notifyTransition(ws, transactionId, transitionInfo);
}
export async function resumePeerPushDebitTransaction( export async function resumePeerPushDebitTransaction(
ws: InternalWalletState, ws: InternalWalletState,
pursePub: string, pursePub: string,
@ -2244,6 +2583,7 @@ export async function resumePeerPushDebitTransaction(
} }
return undefined; return undefined;
}); });
ws.workAvailable.trigger();
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
} }
@ -2265,7 +2605,7 @@ export function computePeerPushCreditTransactionState(
return { return {
major: TransactionMajorState.Done, major: TransactionMajorState.Done,
}; };
case PeerPushPaymentIncomingStatus.MergeKycRequired: case PeerPushPaymentIncomingStatus.PendingMergeKycRequired:
return { return {
major: TransactionMajorState.Pending, major: TransactionMajorState.Pending,
minor: TransactionMinorState.KycRequired, minor: TransactionMinorState.KycRequired,

View File

@ -48,6 +48,7 @@ import {
CoinSourceType, CoinSourceType,
DenominationRecord, DenominationRecord,
TipRecord, TipRecord,
TipRecordStatus,
} from "../db.js"; } from "../db.js";
import { makeErrorDetail } from "@gnu-taler/taler-util"; import { makeErrorDetail } from "@gnu-taler/taler-util";
import { InternalWalletState } from "../internal-wallet-state.js"; import { InternalWalletState } from "../internal-wallet-state.js";
@ -57,6 +58,7 @@ import {
} from "@gnu-taler/taler-util/http"; } from "@gnu-taler/taler-util/http";
import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js"; import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
import { import {
constructTaskIdentifier,
OperationAttemptResult, OperationAttemptResult,
OperationAttemptResultType, OperationAttemptResultType,
} from "../util/retries.js"; } from "../util/retries.js";
@ -68,7 +70,13 @@ import {
updateWithdrawalDenoms, updateWithdrawalDenoms,
} from "./withdraw.js"; } from "./withdraw.js";
import { selectWithdrawalDenominations } from "../util/coinSelection.js"; import { selectWithdrawalDenominations } from "../util/coinSelection.js";
import { constructTransactionIdentifier } from "./transactions.js"; import {
constructTransactionIdentifier,
notifyTransition,
stopLongpolling,
} from "./transactions.js";
import { PendingTaskType } from "../pending-types.js";
import { assertUnreachable } from "../util/assertUnreachable.js";
const logger = new Logger("operations/tip.ts"); const logger = new Logger("operations/tip.ts");
@ -156,6 +164,7 @@ export async function prepareTip(
const newTipRecord: TipRecord = { const newTipRecord: TipRecord = {
walletTipId: walletTipId, walletTipId: walletTipId,
acceptedTimestamp: undefined, acceptedTimestamp: undefined,
status: TipRecordStatus.PendingPickup,
tipAmountRaw: Amounts.stringify(amount), tipAmountRaw: Amounts.stringify(amount),
tipExpiration: tipPickupStatus.expiration, tipExpiration: tipPickupStatus.expiration,
exchangeBaseUrl: tipPickupStatus.exchange_url, exchangeBaseUrl: tipPickupStatus.exchange_url,
@ -180,7 +189,7 @@ export async function prepareTip(
const transactionId = constructTransactionIdentifier({ const transactionId = constructTransactionIdentifier({
tag: TransactionType.Tip, tag: TransactionType.Tip,
walletTipId: tipRecord.walletTipId, walletTipId: tipRecord.walletTipId,
}) });
const tipStatus: PrepareTipResult = { const tipStatus: PrepareTipResult = {
accepted: !!tipRecord && !!tipRecord.acceptedTimestamp, accepted: !!tipRecord && !!tipRecord.acceptedTimestamp,
@ -410,3 +419,98 @@ export async function acceptTip(
next_url: found?.next_url, next_url: found?.next_url,
}; };
} }
export async function suspendTipTransaction(
ws: InternalWalletState,
walletTipId: string,
): Promise<void> {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.TipPickup,
walletTipId,
});
const transactionId = constructTransactionIdentifier({
tag: TransactionType.Tip,
walletTipId,
});
stopLongpolling(ws, taskId);
const transitionInfo = await ws.db
.mktx((x) => [x.tips])
.runReadWrite(async (tx) => {
const tipRec = await tx.tips.get(walletTipId);
if (!tipRec) {
logger.warn(`transaction tip ${walletTipId} not found`);
return;
}
let newStatus: TipRecordStatus | undefined = undefined;
switch (tipRec.status) {
case TipRecordStatus.Done:
case TipRecordStatus.SuspendidPickup:
break;
case TipRecordStatus.PendingPickup:
newStatus = TipRecordStatus.SuspendidPickup;
break;
default:
assertUnreachable(tipRec.status);
}
if (newStatus != null) {
const oldTxState = computeTipTransactionStatus(tipRec);
tipRec.status = newStatus;
const newTxState = computeTipTransactionStatus(tipRec);
await tx.tips.put(tipRec);
return {
oldTxState,
newTxState,
};
}
return undefined;
});
ws.workAvailable.trigger();
notifyTransition(ws, transactionId, transitionInfo);
}
export async function resumeTipTransaction(
ws: InternalWalletState,
walletTipId: string,
): Promise<void> {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.TipPickup,
walletTipId,
});
const transactionId = constructTransactionIdentifier({
tag: TransactionType.Tip,
walletTipId,
});
stopLongpolling(ws, taskId);
const transitionInfo = await ws.db
.mktx((x) => [x.tips])
.runReadWrite(async (tx) => {
const tipRec = await tx.tips.get(walletTipId);
if (!tipRec) {
logger.warn(`transaction tip ${walletTipId} not found`);
return;
}
let newStatus: TipRecordStatus | undefined = undefined;
switch (tipRec.status) {
case TipRecordStatus.Done:
case TipRecordStatus.SuspendidPickup:
newStatus = TipRecordStatus.PendingPickup;
break;
case TipRecordStatus.PendingPickup:
break;
default:
assertUnreachable(tipRec.status);
}
if (newStatus != null) {
const oldTxState = computeTipTransactionStatus(tipRec);
tipRec.status = newStatus;
const newTxState = computeTipTransactionStatus(tipRec);
await tx.tips.put(tipRec);
return {
oldTxState,
newTxState,
};
}
return undefined;
});
notifyTransition(ws, transactionId, transitionInfo);
}

View File

@ -86,20 +86,40 @@ import {
computeRefundTransactionState, computeRefundTransactionState,
expectProposalDownload, expectProposalDownload,
extractContractData, extractContractData,
resumePayMerchant,
suspendPayMerchant,
} from "./pay-merchant.js"; } from "./pay-merchant.js";
import { import {
computePeerPullCreditTransactionState, computePeerPullCreditTransactionState,
computePeerPullDebitTransactionState, computePeerPullDebitTransactionState,
computePeerPushCreditTransactionState, computePeerPushCreditTransactionState,
computePeerPushDebitTransactionState, computePeerPushDebitTransactionState,
resumePeerPullCreditTransaction,
resumePeerPullDebitTransaction,
resumePeerPushCreditTransaction,
resumePeerPushDebitTransaction,
suspendPeerPullCreditTransaction,
suspendPeerPullDebitTransaction,
suspendPeerPushCreditTransaction,
suspendPeerPushDebitTransaction,
} from "./pay-peer.js"; } from "./pay-peer.js";
import { computeRefreshTransactionState } from "./refresh.js"; import {
import { computeTipTransactionStatus } from "./tip.js"; computeRefreshTransactionState,
resumeRefreshGroup,
suspendRefreshGroup,
} from "./refresh.js";
import {
computeTipTransactionStatus,
resumeTipTransaction,
suspendTipTransaction,
} from "./tip.js";
import { import {
abortWithdrawalTransaction, abortWithdrawalTransaction,
augmentPaytoUrisForWithdrawal, augmentPaytoUrisForWithdrawal,
cancelAbortingWithdrawalTransaction, cancelAbortingWithdrawalTransaction,
computeWithdrawalTransactionStatus, computeWithdrawalTransactionStatus,
resumeWithdrawalTransaction,
suspendWithdrawalTransaction,
} from "./withdraw.js"; } from "./withdraw.js";
const logger = new Logger("taler-wallet-core:transactions.ts"); const logger = new Logger("taler-wallet-core:transactions.ts");
@ -159,6 +179,7 @@ export async function getTransactionById(
} }
switch (parsedTx.tag) { switch (parsedTx.tag) {
case TransactionType.InternalWithdrawal:
case TransactionType.Withdrawal: { case TransactionType.Withdrawal: {
const withdrawalGroupId = parsedTx.withdrawalGroupId; const withdrawalGroupId = parsedTx.withdrawalGroupId;
return await ws.db return await ws.db
@ -844,7 +865,7 @@ async function buildTransactionForPurchase(
proposalId: purchaseRecord.proposalId, proposalId: purchaseRecord.proposalId,
info, info,
refundQueryActive: refundQueryActive:
purchaseRecord.purchaseStatus === PurchaseStatus.QueryingRefund, purchaseRecord.purchaseStatus === PurchaseStatus.PendingQueryingRefund,
...(ort?.lastError ? { error: ort.lastError } : {}), ...(ort?.lastError ? { error: ort.lastError } : {}),
}; };
} }
@ -1197,7 +1218,8 @@ export type ParsedTransactionIdentifier =
| { tag: TransactionType.Refresh; refreshGroupId: string } | { tag: TransactionType.Refresh; refreshGroupId: string }
| { tag: TransactionType.Refund; refundGroupId: string } | { tag: TransactionType.Refund; refundGroupId: string }
| { tag: TransactionType.Tip; walletTipId: string } | { tag: TransactionType.Tip; walletTipId: string }
| { tag: TransactionType.Withdrawal; withdrawalGroupId: string }; | { tag: TransactionType.Withdrawal; withdrawalGroupId: string }
| { tag: TransactionType.InternalWithdrawal; withdrawalGroupId: string };
export function constructTransactionIdentifier( export function constructTransactionIdentifier(
pTxId: ParsedTransactionIdentifier, pTxId: ParsedTransactionIdentifier,
@ -1223,6 +1245,8 @@ export function constructTransactionIdentifier(
return `txn:${pTxId.tag}:${pTxId.walletTipId}` as TransactionIdStr; return `txn:${pTxId.tag}:${pTxId.walletTipId}` as TransactionIdStr;
case TransactionType.Withdrawal: case TransactionType.Withdrawal:
return `txn:${pTxId.tag}:${pTxId.withdrawalGroupId}` as TransactionIdStr; return `txn:${pTxId.tag}:${pTxId.withdrawalGroupId}` as TransactionIdStr;
case TransactionType.InternalWithdrawal:
return `txn:${pTxId.tag}:${pTxId.withdrawalGroupId}` as TransactionIdStr;
default: default:
assertUnreachable(pTxId); assertUnreachable(pTxId);
} }
@ -1242,6 +1266,10 @@ export function parseTransactionIdentifier(
const [prefix, type, ...rest] = txnParts; const [prefix, type, ...rest] = txnParts;
if (prefix != "txn") {
throw Error("invalid transaction identifier");
}
switch (type) { switch (type) {
case TransactionType.Deposit: case TransactionType.Deposit:
return { tag: TransactionType.Deposit, depositGroupId: rest[0] }; return { tag: TransactionType.Deposit, depositGroupId: rest[0] };
@ -1329,6 +1357,7 @@ export async function retryTransaction(
stopLongpolling(ws, taskId); stopLongpolling(ws, taskId);
break; break;
} }
case TransactionType.InternalWithdrawal:
case TransactionType.Withdrawal: { case TransactionType.Withdrawal: {
// FIXME: Abort current long-poller! // FIXME: Abort current long-poller!
const taskId = constructTaskIdentifier({ const taskId = constructTaskIdentifier({
@ -1366,8 +1395,38 @@ export async function retryTransaction(
stopLongpolling(ws, taskId); stopLongpolling(ws, taskId);
break; break;
} }
default: case TransactionType.PeerPullDebit: {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPullDebit,
peerPullPaymentIncomingId: parsedTx.peerPullPaymentIncomingId,
});
await resetOperationTimeout(ws, taskId);
stopLongpolling(ws, taskId);
break; break;
}
case TransactionType.PeerPushCredit: {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPushCredit,
peerPushPaymentIncomingId: parsedTx.peerPushPaymentIncomingId,
});
await resetOperationTimeout(ws, taskId);
stopLongpolling(ws, taskId);
break;
}
case TransactionType.PeerPushDebit: {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPushDebit,
pursePub: parsedTx.pursePub,
});
await resetOperationTimeout(ws, taskId);
stopLongpolling(ws, taskId);
break;
}
case TransactionType.Refund:
// Nothing to do for a refund transaction.
break;
default:
assertUnreachable(parsedTx);
} }
} }
@ -1389,8 +1448,35 @@ export async function suspendTransaction(
case TransactionType.Deposit: case TransactionType.Deposit:
await suspendDepositGroup(ws, tx.depositGroupId); await suspendDepositGroup(ws, tx.depositGroupId);
return; return;
case TransactionType.Refresh:
await suspendRefreshGroup(ws, tx.refreshGroupId);
return;
case TransactionType.InternalWithdrawal:
case TransactionType.Withdrawal:
await suspendWithdrawalTransaction(ws, tx.withdrawalGroupId);
return;
case TransactionType.Payment:
await suspendPayMerchant(ws, tx.proposalId);
return;
case TransactionType.PeerPullCredit:
await suspendPeerPullCreditTransaction(ws, tx.pursePub);
break;
case TransactionType.PeerPushDebit:
await suspendPeerPushDebitTransaction(ws, tx.pursePub);
break;
case TransactionType.PeerPullDebit:
await suspendPeerPullDebitTransaction(ws, tx.peerPullPaymentIncomingId);
break;
case TransactionType.PeerPushCredit:
await suspendPeerPushCreditTransaction(ws, tx.peerPushPaymentIncomingId);
break;
case TransactionType.Refund:
throw Error("refund transactions can't be suspended or resumed");
case TransactionType.Tip:
await suspendTipTransaction(ws, tx.walletTipId);
break;
default: default:
logger.warn(`unable to suspend transaction of type '${tx.tag}'`); assertUnreachable(tx);
} }
} }
@ -1429,8 +1515,33 @@ export async function resumeTransaction(
case TransactionType.Deposit: case TransactionType.Deposit:
await resumeDepositGroup(ws, tx.depositGroupId); await resumeDepositGroup(ws, tx.depositGroupId);
return; return;
default: case TransactionType.Refresh:
logger.warn(`unable to resume transaction of type '${tx.tag}'`); await resumeRefreshGroup(ws, tx.refreshGroupId);
return;
case TransactionType.InternalWithdrawal:
case TransactionType.Withdrawal:
await resumeWithdrawalTransaction(ws, tx.withdrawalGroupId);
return;
case TransactionType.Payment:
await resumePayMerchant(ws, tx.proposalId);
return;
case TransactionType.PeerPullCredit:
await resumePeerPullCreditTransaction(ws, tx.pursePub);
break;
case TransactionType.PeerPushDebit:
await resumePeerPushDebitTransaction(ws, tx.pursePub);
break;
case TransactionType.PeerPullDebit:
await resumePeerPullDebitTransaction(ws, tx.peerPullPaymentIncomingId);
break;
case TransactionType.PeerPushCredit:
await resumePeerPushCreditTransaction(ws, tx.peerPushPaymentIncomingId);
break;
case TransactionType.Refund:
throw Error("refund transactions can't be suspended or resumed");
case TransactionType.Tip:
await resumeTipTransaction(ws, tx.walletTipId);
break;
} }
} }

View File

@ -259,7 +259,7 @@ export async function resumeWithdrawalTransaction(
} }
return undefined; return undefined;
}); });
ws.workAvailable.trigger();
const transactionId = constructTransactionIdentifier({ const transactionId = constructTransactionIdentifier({
tag: TransactionType.Withdrawal, tag: TransactionType.Withdrawal,
withdrawalGroupId, withdrawalGroupId,