summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-04-30 11:50:59 +0200
committerFlorian Dold <florian@dold.me>2024-04-30 11:51:07 +0200
commit8b5d1276b9d9043e85cba91704c908ff544916e0 (patch)
treebc558f2ec7fb7b77731c5afd3838567e32c7eb4f
parent22709ff4e2918a8d0e528539d11d761381920b45 (diff)
downloadwallet-core-8b5d1276b9d9043e85cba91704c908ff544916e0.tar.gz
wallet-core-8b5d1276b9d9043e85cba91704c908ff544916e0.tar.bz2
wallet-core-8b5d1276b9d9043e85cba91704c908ff544916e0.zip
wallet-core: new states for withdrawal, prepare/confirm requests
-rw-r--r--packages/taler-harness/src/integrationtests/test-currency-scope.ts3
-rw-r--r--packages/taler-util/src/notifications.ts3
-rw-r--r--packages/taler-util/src/transactions-types.ts1
-rw-r--r--packages/taler-util/src/wallet-types.ts30
-rw-r--r--packages/taler-wallet-core/src/balance.ts3
-rw-r--r--packages/taler-wallet-core/src/db.ts20
-rw-r--r--packages/taler-wallet-core/src/wallet-api-types.ts27
-rw-r--r--packages/taler-wallet-core/src/wallet.ts18
-rw-r--r--packages/taler-wallet-core/src/withdraw.ts272
9 files changed, 332 insertions, 45 deletions
diff --git a/packages/taler-harness/src/integrationtests/test-currency-scope.ts b/packages/taler-harness/src/integrationtests/test-currency-scope.ts
index e07a8f47b..058941e16 100644
--- a/packages/taler-harness/src/integrationtests/test-currency-scope.ts
+++ b/packages/taler-harness/src/integrationtests/test-currency-scope.ts
@@ -18,7 +18,7 @@
* Imports.
*/
import { Duration, j2s } from "@gnu-taler/taler-util";
-import { Wallet, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { defaultCoinConfig } from "../harness/denomStructures.js";
import {
BankService,
@@ -30,7 +30,6 @@ import {
} from "../harness/harness.js";
import {
createWalletDaemonWithClient,
- makeTestPaymentV2,
withdrawViaBankV2,
} from "../harness/helpers.js";
diff --git a/packages/taler-util/src/notifications.ts b/packages/taler-util/src/notifications.ts
index b60fb267c..d4dfe7589 100644
--- a/packages/taler-util/src/notifications.ts
+++ b/packages/taler-util/src/notifications.ts
@@ -30,6 +30,9 @@ export enum NotificationType {
BalanceChange = "balance-change",
BackupOperationError = "backup-error",
TransactionStateTransition = "transaction-state-transition",
+ /**
+ * @deprecated
+ */
WithdrawalOperationTransition = "withdrawal-operation-transition",
ExchangeStateTransition = "exchange-state-transition",
Idle = "idle",
diff --git a/packages/taler-util/src/transactions-types.ts b/packages/taler-util/src/transactions-types.ts
index ac4c3d717..cee3de9fa 100644
--- a/packages/taler-util/src/transactions-types.ts
+++ b/packages/taler-util/src/transactions-types.ts
@@ -151,6 +151,7 @@ export enum TransactionMinorState {
RefundAvailable = "refund-available",
AcceptRefund = "accept-refund",
PaidByOther = "paid-by-other",
+ CompletedByOtherWallet = "completed-by-other-wallet",
}
export enum TransactionAction {
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts
index 9575e6d7d..ccf3c230a 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -1883,6 +1883,36 @@ export interface GetWithdrawalDetailsForAmountRequest {
clientCancellationId?: string;
}
+export interface PrepareBankIntegratedWithdrawalRequest {
+ talerWithdrawUri: string;
+ exchangeBaseUrl: string;
+ forcedDenomSel?: ForcedDenomSel;
+ restrictAge?: number;
+}
+
+export const codecForPrepareBankIntegratedWithdrawalRequest =
+ (): Codec<PrepareBankIntegratedWithdrawalRequest> =>
+ buildCodecForObject<PrepareBankIntegratedWithdrawalRequest>()
+ .property("exchangeBaseUrl", codecForString())
+ .property("talerWithdrawUri", codecForString())
+ .property("forcedDenomSel", codecForAny())
+ .property("restrictAge", codecOptional(codecForNumber()))
+ .build("PrepareBankIntegratedWithdrawalRequest");
+
+export interface PrepareBankIntegratedWithdrawalResponse {
+ transactionId: string;
+}
+
+export interface ConfirmWithdrawalRequest {
+ transactionId: string;
+}
+
+export const codecForConfirmWithdrawalRequestRequest =
+ (): Codec<ConfirmWithdrawalRequest> =>
+ buildCodecForObject<ConfirmWithdrawalRequest>()
+ .property("transactionId", codecForString())
+ .build("ConfirmWithdrawalRequest");
+
export interface AcceptBankIntegratedWithdrawalRequest {
talerWithdrawUri: string;
exchangeBaseUrl: string;
diff --git a/packages/taler-wallet-core/src/balance.ts b/packages/taler-wallet-core/src/balance.ts
index 1fef9876e..5a805b477 100644
--- a/packages/taler-wallet-core/src/balance.ts
+++ b/packages/taler-wallet-core/src/balance.ts
@@ -357,6 +357,9 @@ export async function getBalancesInsideTransaction(
case WithdrawalGroupStatus.AbortedExchange:
case WithdrawalGroupStatus.FailedAbortingBank:
case WithdrawalGroupStatus.FailedBankAborted:
+ case WithdrawalGroupStatus.AbortedOtherWallet:
+ case WithdrawalGroupStatus.AbortedUserRefused:
+ case WithdrawalGroupStatus.DialogProposed:
case WithdrawalGroupStatus.Done:
// Does not count as pendingIncoming
return;
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index 085e909cf..1edafb315 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -298,6 +298,11 @@ export enum WithdrawalGroupStatus {
SuspendedReady = 0x0110_0004,
/**
+ * Proposed to the user, has can choose to accept/refuse.
+ */
+ DialogProposed = 0x0101_0000,
+
+ /**
* We are telling the bank that we don't want to complete
* the withdrawal!
*/
@@ -338,6 +343,21 @@ export enum WithdrawalGroupStatus {
AbortedExchange = 0x0503_0001,
AbortedBank = 0x0503_0002,
+
+ /**
+ * User didn't refused the withdrawal.
+ */
+ AbortedUserRefused = 0x0503_0003,
+
+ /**
+ * Another wallet confirmed the withdrawal
+ * (by POSTing the reseve pub to the bank)
+ * before we had the chance.
+ *
+ * In this situation, we'll let the other wallet continue
+ * and give up ourselves.
+ */
+ AbortedOtherWallet = 0x0503_0004,
}
/**
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts
index e876d8aea..f83db6039 100644
--- a/packages/taler-wallet-core/src/wallet-api-types.ts
+++ b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -47,6 +47,7 @@ import {
ConfirmPayResult,
ConfirmPeerPullDebitRequest,
ConfirmPeerPushCreditRequest,
+ ConfirmWithdrawalRequest,
ConvertAmountRequest,
CreateDepositGroupRequest,
CreateDepositGroupResponse,
@@ -91,6 +92,8 @@ import {
ListGlobalCurrencyAuditorsResponse,
ListGlobalCurrencyExchangesResponse,
ListKnownBankAccountsRequest,
+ PrepareBankIntegratedWithdrawalRequest,
+ PrepareBankIntegratedWithdrawalResponse,
PrepareDepositRequest,
PrepareDepositResponse,
PreparePayRequest,
@@ -195,6 +198,8 @@ export enum WalletApiOperation {
SetExchangeTosForgotten = "SetExchangeTosForgotten",
StartRefundQueryForUri = "startRefundQueryForUri",
StartRefundQuery = "startRefundQuery",
+ PrepareBankIntegratedWithdrawal = "prepareBankIntegratedWithdrawal",
+ ConfirmWithdrawal = "confirmWithdrawal",
AcceptBankIntegratedWithdrawal = "acceptBankIntegratedWithdrawal",
GetExchangeTos = "getExchangeTos",
GetExchangeDetailedInfo = "getExchangeDetailedInfo",
@@ -476,7 +481,27 @@ export type GetWithdrawalDetailsForUriOp = {
};
/**
+ * Prepare a bank-integrated withdrawal operation.
+ */
+export type PrepareBankIntegratedWithdrawalOp = {
+ op: WalletApiOperation.PrepareBankIntegratedWithdrawal;
+ request: PrepareBankIntegratedWithdrawalRequest;
+ response: PrepareBankIntegratedWithdrawalResponse;
+};
+
+/**
+ * Confirm a withdrawal transaction.
+ */
+export type ConfirmWithdrawalOp = {
+ op: WalletApiOperation.ConfirmWithdrawal;
+ request: ConfirmWithdrawalRequest;
+ response: EmptyObject;
+};
+
+/**
* Accept a bank-integrated withdrawal.
+ *
+ * @deprecated in favor of prepare/confirm withdrawal.
*/
export type AcceptBankIntegratedWithdrawalOp = {
op: WalletApiOperation.AcceptBankIntegratedWithdrawal;
@@ -1292,6 +1317,8 @@ export type WalletOperations = {
[WalletApiOperation.TestingGetDenomStats]: TestingGetDenomStatsOp;
[WalletApiOperation.TestingPing]: TestingPingOp;
[WalletApiOperation.Shutdown]: ShutdownOp;
+ [WalletApiOperation.PrepareBankIntegratedWithdrawal]: PrepareBankIntegratedWithdrawalOp;
+ [WalletApiOperation.ConfirmWithdrawal]: ConfirmWithdrawalOp;
};
export type WalletCoreRequestType<
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index dd6ce96f4..8bfb6772f 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -86,6 +86,7 @@ import {
codecForCheckPeerPushDebitRequest,
codecForConfirmPayRequest,
codecForConfirmPeerPushPaymentRequest,
+ codecForConfirmWithdrawalRequestRequest,
codecForConvertAmountRequest,
codecForCreateDepositGroupRequest,
codecForDeleteExchangeRequest,
@@ -111,6 +112,7 @@ import {
codecForIntegrationTestV2Args,
codecForListExchangesForScopedCurrencyRequest,
codecForListKnownBankAccounts,
+ codecForPrepareBankIntegratedWithdrawalRequest,
codecForPrepareDepositRequest,
codecForPreparePayRequest,
codecForPreparePayTemplateRequest,
@@ -295,9 +297,11 @@ import {
} from "./wallet-api-types.js";
import {
acceptWithdrawalFromUri,
+ confirmWithdrawal,
createManualWithdrawal,
getWithdrawalDetailsForAmount,
getWithdrawalDetailsForUri,
+ prepareBankIntegratedWithdrawal,
} from "./withdraw.js";
const logger = new Logger("wallet.ts");
@@ -965,6 +969,20 @@ async function dispatchRequestInternal(
restrictAge: req.restrictAge,
});
}
+ case WalletApiOperation.ConfirmWithdrawal: {
+ const req = codecForConfirmWithdrawalRequestRequest().decode(payload);
+ return confirmWithdrawal(wex, req.transactionId);
+ }
+ case WalletApiOperation.PrepareBankIntegratedWithdrawal: {
+ const req =
+ codecForPrepareBankIntegratedWithdrawalRequest().decode(payload);
+ return prepareBankIntegratedWithdrawal(wex, {
+ selectedExchange: req.exchangeBaseUrl,
+ talerWithdrawUri: req.talerWithdrawUri,
+ forcedDenomSel: req.forcedDenomSel,
+ restrictAge: req.restrictAge,
+ });
+ }
case WalletApiOperation.GetExchangeTos: {
const req = codecForGetExchangeTosRequest().decode(payload);
return getExchangeTos(
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts
index a55ada796..0597c051f 100644
--- a/packages/taler-wallet-core/src/withdraw.ts
+++ b/packages/taler-wallet-core/src/withdraw.ts
@@ -56,6 +56,7 @@ import {
Logger,
NotificationType,
ObservabilityEventType,
+ PrepareBankIntegratedWithdrawalResponse,
TalerBankIntegrationHttpClient,
TalerError,
TalerErrorCode,
@@ -79,7 +80,9 @@ import {
assertUnreachable,
canonicalizeBaseUrl,
checkDbInvariant,
+ checkLogicInvariant,
codeForBankWithdrawalOperationPostResponse,
+ codecForBankWithdrawalOperationStatus,
codecForCashinConversionResponse,
codecForConversionBankConfig,
codecForExchangeWithdrawBatchResponse,
@@ -154,6 +157,7 @@ import {
constructTransactionIdentifier,
isUnsuccessfulTransaction,
notifyTransition,
+ parseTransactionIdentifier,
} from "./transactions.js";
import {
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
@@ -164,7 +168,7 @@ import { WalletExecutionContext, getDenomInfo } from "./wallet.js";
/**
* Logger for this file.
*/
-const logger = new Logger("operations/withdraw.ts");
+const logger = new Logger("withdraw.ts");
/**
* Update the materialized withdrawal transaction based
@@ -466,13 +470,18 @@ export class WithdrawTransactionContext implements TransactionContext {
break;
case WithdrawalGroupStatus.SuspendedAbortingBank:
case WithdrawalGroupStatus.AbortingBank:
+ case WithdrawalGroupStatus.AbortedUserRefused:
// No transition needed, but not an error
return TransitionResult.stay();
+ case WithdrawalGroupStatus.DialogProposed:
+ newStatus = WithdrawalGroupStatus.AbortedUserRefused;
+ break;
case WithdrawalGroupStatus.Done:
case WithdrawalGroupStatus.FailedBankAborted:
case WithdrawalGroupStatus.AbortedExchange:
case WithdrawalGroupStatus.AbortedBank:
case WithdrawalGroupStatus.FailedAbortingBank:
+ case WithdrawalGroupStatus.AbortedOtherWallet:
// Not allowed
throw Error("abort not allowed in current state");
default:
@@ -658,6 +667,21 @@ export function computeWithdrawalTransactionStatus(
major: TransactionMajorState.Aborted,
minor: TransactionMinorState.Bank,
};
+ case WithdrawalGroupStatus.AbortedUserRefused:
+ return {
+ major: TransactionMajorState.Aborted,
+ minor: TransactionMinorState.Refused,
+ };
+ case WithdrawalGroupStatus.DialogProposed:
+ return {
+ major: TransactionMajorState.Dialog,
+ minor: TransactionMinorState.Proposed,
+ };
+ case WithdrawalGroupStatus.AbortedOtherWallet:
+ return {
+ major: TransactionMajorState.Aborted,
+ minor: TransactionMinorState.CompletedByOtherWallet,
+ };
}
}
@@ -702,14 +726,78 @@ export function computeWithdrawalTransactionActions(
case WithdrawalGroupStatus.SuspendedKyc:
return [TransactionAction.Resume, TransactionAction.Abort];
case WithdrawalGroupStatus.FailedAbortingBank:
- return [TransactionAction.Delete];
case WithdrawalGroupStatus.AbortedExchange:
- return [TransactionAction.Delete];
case WithdrawalGroupStatus.AbortedBank:
+ case WithdrawalGroupStatus.AbortedOtherWallet:
+ case WithdrawalGroupStatus.AbortedUserRefused:
return [TransactionAction.Delete];
+ case WithdrawalGroupStatus.DialogProposed:
+ return [TransactionAction.Abort];
}
}
+async function processWithdrawalGroupDialogProposed(
+ ctx: WithdrawTransactionContext,
+ withdrawalGroup: WithdrawalGroupRecord,
+): Promise<TaskRunResult> {
+ if (
+ withdrawalGroup.wgInfo.withdrawalType !==
+ WithdrawalRecordType.BankIntegrated
+ ) {
+ throw new Error(
+ "processWithdrawalGroupDialogProposed called in unexpected state",
+ );
+ }
+
+ const talerWithdrawUri = withdrawalGroup.wgInfo.bankInfo.talerWithdrawUri;
+
+ const parsedUri = parseWithdrawUri(talerWithdrawUri);
+
+ checkLogicInvariant(!!parsedUri);
+
+ const wopid = parsedUri.withdrawalOperationId;
+
+ const url = new URL(
+ `withdrawal-operation/${wopid}`,
+ parsedUri.bankIntegrationApiBaseUrl,
+ );
+
+ url.searchParams.set("old_state", "pending");
+ url.searchParams.set("long_poll_ms", "30000");
+
+ const resp = await ctx.wex.http.fetch(url.href, {
+ method: "GET",
+ cancellationToken: ctx.wex.cancellationToken,
+ });
+
+ // If the bank claims that the withdrawal operation is already
+ // pending, but we're still in DialogProposed, some other wallet
+ // must've completed the withdrawal, we're giving up.
+
+ switch (resp.status) {
+ case HttpStatusCode.Ok: {
+ const body = await readSuccessResponseJsonOrThrow(
+ resp,
+ codecForBankWithdrawalOperationStatus(),
+ );
+ if (body.status !== "pending") {
+ await ctx.transition({}, async (rec) => {
+ switch (rec?.status) {
+ case WithdrawalGroupStatus.DialogProposed: {
+ rec.status = WithdrawalGroupStatus.AbortedOtherWallet;
+ return TransitionResult.transition(rec);
+ }
+ }
+ return TransitionResult.stay();
+ });
+ }
+ break;
+ }
+ }
+
+ return TaskRunResult.longpollReturnedPending();
+}
+
/**
* Get information about a withdrawal from
* a taler://withdraw URI by asking the bank.
@@ -1907,6 +1995,8 @@ export async function processWithdrawalGroup(
throw Error(`withdrawal group ${withdrawalGroupId} not found`);
}
+ const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId);
+
switch (withdrawalGroup.status) {
case WithdrawalGroupStatus.PendingRegisteringBank:
return await processBankRegisterReserve(wex, withdrawalGroupId);
@@ -1924,6 +2014,8 @@ export async function processWithdrawalGroup(
return await processWithdrawalGroupPendingReady(wex, withdrawalGroup);
case WithdrawalGroupStatus.AbortingBank:
return await processWithdrawalGroupAbortingBank(wex, withdrawalGroup);
+ case WithdrawalGroupStatus.DialogProposed:
+ return await processWithdrawalGroupDialogProposed(ctx, withdrawalGroup);
case WithdrawalGroupStatus.AbortedBank:
case WithdrawalGroupStatus.AbortedExchange:
case WithdrawalGroupStatus.FailedAbortingBank:
@@ -1936,6 +2028,8 @@ export async function processWithdrawalGroup(
case WithdrawalGroupStatus.SuspendedWaitConfirmBank:
case WithdrawalGroupStatus.Done:
case WithdrawalGroupStatus.FailedBankAborted:
+ case WithdrawalGroupStatus.AbortedUserRefused:
+ case WithdrawalGroupStatus.AbortedOtherWallet:
// Nothing to do.
return TaskRunResult.finished();
default:
@@ -2073,12 +2167,6 @@ export interface GetWithdrawalDetailsForUriOpts {
notifyChangeFromPendingTimeoutMs?: number;
}
-type WithdrawalOperationMemoryMap = {
- [uri: string]: boolean | undefined;
-};
-
-const ongoingChecks: WithdrawalOperationMemoryMap = {};
-
/**
* Get more information about a taler://withdraw URI.
*
@@ -2119,37 +2207,6 @@ export async function getWithdrawalDetailsForUri(
);
});
- // FIXME: this should be removed after the extended version of
- // withdrawal state machine. issue #8099
- if (
- info.status === "pending" &&
- opts.notifyChangeFromPendingTimeoutMs !== undefined &&
- !ongoingChecks[talerWithdrawUri]
- ) {
- ongoingChecks[talerWithdrawUri] = true;
- const bankApi = new TalerBankIntegrationHttpClient(
- info.apiBaseUrl,
- wex.http,
- );
-
- bankApi
- .getWithdrawalOperationById(info.operationId, {
- old_state: "pending",
- timeoutMs: opts.notifyChangeFromPendingTimeoutMs,
- })
- .then((resp) => {
- if (resp.type === "ok" && resp.body.status !== "pending") {
- wex.ws.notify({
- type: NotificationType.WithdrawalOperationTransition,
- uri: talerWithdrawUri,
- });
- }
- })
- .finally(() => {
- ongoingChecks[talerWithdrawUri] = false;
- });
- }
-
return {
operationId: info.operationId,
confirmTransferUrl: info.confirmTransferUrl,
@@ -2731,6 +2788,135 @@ export async function internalCreateWithdrawalGroup(
return res.withdrawalGroup;
}
+export async function prepareBankIntegratedWithdrawal(
+ wex: WalletExecutionContext,
+ req: {
+ talerWithdrawUri: string;
+ selectedExchange: string;
+ forcedDenomSel?: ForcedDenomSel;
+ restrictAge?: number;
+ },
+): Promise<PrepareBankIntegratedWithdrawalResponse> {
+ const existingWithdrawalGroup = await wex.db.runReadOnlyTx(
+ { storeNames: ["withdrawalGroups"] },
+ async (tx) => {
+ return await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get(
+ req.talerWithdrawUri,
+ );
+ },
+ );
+
+ if (existingWithdrawalGroup) {
+ let url: string | undefined;
+ if (
+ existingWithdrawalGroup.wgInfo.withdrawalType ===
+ WithdrawalRecordType.BankIntegrated
+ ) {
+ url = existingWithdrawalGroup.wgInfo.bankInfo.confirmUrl;
+ }
+ return {
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.Withdrawal,
+ withdrawalGroupId: existingWithdrawalGroup.withdrawalGroupId,
+ }),
+ };
+ }
+
+ const selectedExchange = canonicalizeBaseUrl(req.selectedExchange);
+ const exchange = await fetchFreshExchange(wex, selectedExchange);
+
+ const withdrawInfo = await getBankWithdrawalInfo(
+ wex.http,
+ req.talerWithdrawUri,
+ );
+ const exchangePaytoUri = await getExchangePaytoUri(
+ wex,
+ selectedExchange,
+ withdrawInfo.wireTypes,
+ );
+
+ const withdrawalAccountList = await fetchWithdrawalAccountInfo(
+ wex,
+ {
+ exchange,
+ instructedAmount: withdrawInfo.amount,
+ },
+ wex.cancellationToken,
+ );
+
+ const withdrawalGroup = await internalCreateWithdrawalGroup(wex, {
+ amount: withdrawInfo.amount,
+ exchangeBaseUrl: req.selectedExchange,
+ wgInfo: {
+ withdrawalType: WithdrawalRecordType.BankIntegrated,
+ exchangeCreditAccounts: withdrawalAccountList,
+ bankInfo: {
+ exchangePaytoUri,
+ talerWithdrawUri: req.talerWithdrawUri,
+ confirmUrl: withdrawInfo.confirmTransferUrl,
+ timestampBankConfirmed: undefined,
+ timestampReserveInfoPosted: undefined,
+ },
+ },
+ restrictAge: req.restrictAge,
+ forcedDenomSel: req.forcedDenomSel,
+ reserveStatus: WithdrawalGroupStatus.DialogProposed,
+ });
+
+ const withdrawalGroupId = withdrawalGroup.withdrawalGroupId;
+
+ const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId);
+
+ wex.taskScheduler.startShepherdTask(ctx.taskId);
+
+ return {
+ transactionId: ctx.transactionId,
+ };
+}
+
+export async function confirmWithdrawal(
+ wex: WalletExecutionContext,
+ transactionId: string,
+): Promise<void> {
+ const parsedTx = parseTransactionIdentifier(transactionId);
+ if (parsedTx?.tag !== TransactionType.Withdrawal) {
+ throw Error("invalid withdrawal transaction ID");
+ }
+ const withdrawalGroup = await wex.db.runReadOnlyTx(
+ { storeNames: ["withdrawalGroups"] },
+ async (tx) => {
+ return await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get(
+ parsedTx.withdrawalGroupId,
+ );
+ },
+ );
+
+ if (!withdrawalGroup) {
+ throw Error("withdrawal group not found");
+ }
+
+ const ctx = new WithdrawTransactionContext(
+ wex,
+ withdrawalGroup.withdrawalGroupId,
+ );
+ ctx.transition({}, async (rec) => {
+ if (!rec) {
+ return TransitionResult.stay();
+ }
+ switch (rec.status) {
+ case WithdrawalGroupStatus.DialogProposed: {
+ rec.status = WithdrawalGroupStatus.PendingRegisteringBank;
+ return TransitionResult.transition(rec);
+ }
+ default:
+ throw Error("unable to confirm withdrawal in current state");
+ }
+ });
+
+ await wex.taskScheduler.resetTaskRetries(ctx.taskId);
+ wex.taskScheduler.startShepherdTask(ctx.taskId);
+}
+
/**
* Accept a bank-integrated withdrawal.
*
@@ -2738,6 +2924,8 @@ export async function internalCreateWithdrawalGroup(
*
* Thus after this call returns, the withdrawal operation can be confirmed
* with the bank.
+ *
+ * @deprecated in favor of prepare/accept
*/
export async function acceptWithdrawalFromUri(
wex: WalletExecutionContext,
@@ -2779,7 +2967,7 @@ export async function acceptWithdrawalFromUri(
};
}
- await fetchFreshExchange(wex, selectedExchange);
+ const exchange = await fetchFreshExchange(wex, selectedExchange);
const withdrawInfo = await getBankWithdrawalInfo(
wex.http,
req.talerWithdrawUri,
@@ -2790,8 +2978,6 @@ export async function acceptWithdrawalFromUri(
withdrawInfo.wireTypes,
);
- const exchange = await fetchFreshExchange(wex, selectedExchange);
-
const withdrawalAccountList = await fetchWithdrawalAccountInfo(
wex,
{