aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-06-06 17:07:09 +0200
committerFlorian Dold <florian@dold.me>2023-06-06 17:07:09 +0200
commit002ab0dab7b83c5999b0f82c430e716c718251e6 (patch)
tree324f81160453ae9ea5a0bc609be0ec3f0725b772
parentf9c33136b4885827a18d0565c35d5854b44bb5ca (diff)
wallet-core: try to abort withdrawals wallet-side with the bank
-rw-r--r--packages/taler-wallet-core/src/db.ts2
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts216
2 files changed, 147 insertions, 71 deletions
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index 195760831..9905fa370 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -210,6 +210,8 @@ export enum WithdrawalGroupStatus {
* wired or not.
*/
AbortedExchange = 60,
+
+ AbortedBank = 61,
}
/**
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts
index 7db6dcd2a..61cab6fbb 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -316,6 +316,7 @@ export async function abortWithdrawalTransaction(
case WithdrawalGroupStatus.Finished:
case WithdrawalGroupStatus.FailedBankAborted:
case WithdrawalGroupStatus.AbortedExchange:
+ case WithdrawalGroupStatus.AbortedBank:
case WithdrawalGroupStatus.FailedAbortingBank:
// Not allowed
throw Error("abort not allowed in current state");
@@ -481,6 +482,12 @@ export function computeWithdrawalTransactionStatus(
major: TransactionMajorState.Aborted,
minor: TransactionMinorState.Exchange,
};
+
+ case WithdrawalGroupStatus.AbortedBank:
+ return {
+ major: TransactionMajorState.Aborted,
+ minor: TransactionMinorState.Bank,
+ };
}
}
@@ -507,7 +514,7 @@ export function computeWithdrawalTransactionActions(
case WithdrawalGroupStatus.SuspendedQueryingStatus:
return [TransactionAction.Resume, TransactionAction.Abort];
case WithdrawalGroupStatus.SuspendedRegisteringBank:
- return [TransactionAction.Resume, TransactionAction.Abort]
+ return [TransactionAction.Resume, TransactionAction.Abort];
case WithdrawalGroupStatus.SuspendedWaitConfirmBank:
return [TransactionAction.Resume, TransactionAction.Abort];
case WithdrawalGroupStatus.SuspendedReady:
@@ -519,11 +526,13 @@ export function computeWithdrawalTransactionActions(
case WithdrawalGroupStatus.SuspendedAml:
return [TransactionAction.Resume, TransactionAction.Abort];
case WithdrawalGroupStatus.SuspendedKyc:
- return [TransactionAction.Resume, TransactionAction.Abort]
+ return [TransactionAction.Resume, TransactionAction.Abort];
case WithdrawalGroupStatus.FailedAbortingBank:
return [TransactionAction.Delete];
case WithdrawalGroupStatus.AbortedExchange:
return [TransactionAction.Delete];
+ case WithdrawalGroupStatus.AbortedBank:
+ return [TransactionAction.Delete];
}
}
@@ -1270,87 +1279,61 @@ export interface WithdrawalGroupContext {
wgRecord: WithdrawalGroupRecord;
}
-export async function processWithdrawalGroup(
+async function processWithdrawalGroupAbortingBank(
ws: InternalWalletState,
- withdrawalGroupId: string,
+ withdrawalGroup: WithdrawalGroupRecord,
): Promise<OperationAttemptResult> {
- logger.trace("processing withdrawal group", withdrawalGroupId);
- const withdrawalGroup = await ws.db
- .mktx((x) => [x.withdrawalGroups])
- .runReadOnly(async (tx) => {
- return tx.withdrawalGroups.get(withdrawalGroupId);
- });
-
- if (!withdrawalGroup) {
- throw Error(`withdrawal group ${withdrawalGroupId} not found`);
- }
-
- const retryTag = TaskIdentifiers.forWithdrawal(withdrawalGroup);
+ const { withdrawalGroupId } = withdrawalGroup;
const transactionId = constructTransactionIdentifier({
tag: TransactionType.Withdrawal,
withdrawalGroupId,
});
- // We're already running!
- if (ws.activeLongpoll[retryTag]) {
- logger.info("withdrawal group already in long-polling, returning!");
- return {
- type: OperationAttemptResultType.Longpoll,
- };
+ const wgInfo = withdrawalGroup.wgInfo;
+ if (wgInfo.withdrawalType != WithdrawalRecordType.BankIntegrated) {
+ throw Error("invalid state (aborting(bank) without bank info");
}
+ const abortUrl = getBankAbortUrl(wgInfo.bankInfo.talerWithdrawUri);
+ logger.info(`aborting withdrawal at ${abortUrl}`);
+ const abortResp = await ws.http.fetch(abortUrl, {
+ method: "POST",
+ body: {},
+ });
+ logger.info(`abort response status: ${abortResp.status}`);
- switch (withdrawalGroup.status) {
- case WithdrawalGroupStatus.PendingRegisteringBank:
- await processReserveBankStatus(ws, withdrawalGroupId);
- // FIXME: This will get called by the main task loop, why call it here?!
- return await processWithdrawalGroup(ws, withdrawalGroupId);
- case WithdrawalGroupStatus.PendingQueryingStatus: {
- runLongpollAsync(ws, retryTag, (ct) => {
- return queryReserve(ws, withdrawalGroupId, ct);
- });
- logger.trace(
- "returning early from withdrawal for long-polling in background",
- );
- return {
- type: OperationAttemptResultType.Longpoll,
- };
- }
- case WithdrawalGroupStatus.PendingWaitConfirmBank: {
- const res = await processReserveBankStatus(ws, withdrawalGroupId);
- switch (res.status) {
- case BankStatusResultCode.Aborted:
- case BankStatusResultCode.Done:
- return {
- type: OperationAttemptResultType.Finished,
- result: undefined,
- };
- case BankStatusResultCode.Waiting: {
- return {
- type: OperationAttemptResultType.Pending,
- result: undefined,
- };
- }
+ const transitionInfo = await ws.db
+ .mktx((x) => [x.withdrawalGroups])
+ .runReadWrite(async (tx) => {
+ const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
+ if (!wg) {
+ return undefined;
}
- break;
- }
- case WithdrawalGroupStatus.FailedBankAborted: {
- // FIXME
+ const txStatusOld = computeWithdrawalTransactionStatus(wg);
+ wg.status = WithdrawalGroupStatus.AbortedBank;
+ wg.timestampFinish = TalerPreciseTimestamp.now();
+ const txStatusNew = computeWithdrawalTransactionStatus(wg);
+ await tx.withdrawalGroups.put(wg);
return {
- type: OperationAttemptResultType.Pending,
- result: undefined,
+ oldTxState: txStatusOld,
+ newTxState: txStatusNew,
};
- }
- case WithdrawalGroupStatus.Finished:
- // We can try to withdraw, nothing needs to be done with the reserve.
- break;
- case WithdrawalGroupStatus.PendingReady:
- // Continue with the actual withdrawal!
- break;
- default:
- throw new InvariantViolatedError(
- `unknown reserve record status: ${withdrawalGroup.status}`,
- );
- }
+ });
+ notifyTransition(ws, transactionId, transitionInfo);
+ return {
+ type: OperationAttemptResultType.Finished,
+ result: undefined,
+ };
+}
+
+async function processWithdrawalGroupPendingReady(
+ ws: InternalWalletState,
+ withdrawalGroup: WithdrawalGroupRecord,
+): Promise<OperationAttemptResult> {
+ const { withdrawalGroupId } = withdrawalGroup;
+ const transactionId = constructTransactionIdentifier({
+ tag: TransactionType.Withdrawal,
+ withdrawalGroupId,
+ });
await ws.exchangeOps.updateExchangeFromUrl(
ws,
@@ -1544,6 +1527,85 @@ export async function processWithdrawalGroup(
};
}
+export async function processWithdrawalGroup(
+ ws: InternalWalletState,
+ withdrawalGroupId: string,
+): Promise<OperationAttemptResult> {
+ logger.trace("processing withdrawal group", withdrawalGroupId);
+ const withdrawalGroup = await ws.db
+ .mktx((x) => [x.withdrawalGroups])
+ .runReadOnly(async (tx) => {
+ return tx.withdrawalGroups.get(withdrawalGroupId);
+ });
+
+ if (!withdrawalGroup) {
+ throw Error(`withdrawal group ${withdrawalGroupId} not found`);
+ }
+
+ const retryTag = TaskIdentifiers.forWithdrawal(withdrawalGroup);
+
+ // We're already running!
+ if (ws.activeLongpoll[retryTag]) {
+ logger.info("withdrawal group already in long-polling, returning!");
+ return {
+ type: OperationAttemptResultType.Longpoll,
+ };
+ }
+
+ switch (withdrawalGroup.status) {
+ case WithdrawalGroupStatus.PendingRegisteringBank:
+ await processReserveBankStatus(ws, withdrawalGroupId);
+ // FIXME: This will get called by the main task loop, why call it here?!
+ return await processWithdrawalGroup(ws, withdrawalGroupId);
+ case WithdrawalGroupStatus.PendingQueryingStatus: {
+ runLongpollAsync(ws, retryTag, (ct) => {
+ return queryReserve(ws, withdrawalGroupId, ct);
+ });
+ logger.trace(
+ "returning early from withdrawal for long-polling in background",
+ );
+ return {
+ type: OperationAttemptResultType.Longpoll,
+ };
+ }
+ case WithdrawalGroupStatus.PendingWaitConfirmBank: {
+ const res = await processReserveBankStatus(ws, withdrawalGroupId);
+ switch (res.status) {
+ case BankStatusResultCode.Aborted:
+ case BankStatusResultCode.Done:
+ return {
+ type: OperationAttemptResultType.Finished,
+ result: undefined,
+ };
+ case BankStatusResultCode.Waiting: {
+ return {
+ type: OperationAttemptResultType.Pending,
+ result: undefined,
+ };
+ }
+ }
+ break;
+ }
+ case WithdrawalGroupStatus.Finished:
+ case WithdrawalGroupStatus.FailedBankAborted: {
+ // FIXME
+ return {
+ type: OperationAttemptResultType.Pending,
+ result: undefined,
+ };
+ }
+ case WithdrawalGroupStatus.PendingReady:
+ // Continue with the actual withdrawal!
+ return await processWithdrawalGroupPendingReady(ws, withdrawalGroup);
+ case WithdrawalGroupStatus.AbortingBank:
+ return await processWithdrawalGroupAbortingBank(ws, withdrawalGroup);
+ default:
+ throw new InvariantViolatedError(
+ `unknown withdrawal group status: ${withdrawalGroup.status}`,
+ );
+ }
+}
+
export async function checkWithdrawalKycStatus(
ws: InternalWalletState,
exchangeUrl: string,
@@ -1890,6 +1952,18 @@ export function getBankStatusUrl(talerWithdrawUri: string): string {
return url.href;
}
+export function getBankAbortUrl(talerWithdrawUri: string): string {
+ const uriResult = parseWithdrawUri(talerWithdrawUri);
+ if (!uriResult) {
+ throw Error(`can't parse withdrawal URL ${talerWithdrawUri}`);
+ }
+ const url = new URL(
+ `withdrawal-operation/${uriResult.withdrawalOperationId}/abort`,
+ uriResult.bankIntegrationApiBaseUrl,
+ );
+ return url.href;
+}
+
async function registerReserveWithBank(
ws: InternalWalletState,
withdrawalGroupId: string,