aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/withdraw.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core/src/withdraw.ts')
-rw-r--r--packages/taler-wallet-core/src/withdraw.ts301
1 files changed, 173 insertions, 128 deletions
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts
index 24e2861e1..0e7ed144c 100644
--- a/packages/taler-wallet-core/src/withdraw.ts
+++ b/packages/taler-wallet-core/src/withdraw.ts
@@ -344,9 +344,11 @@ export class WithdrawTransactionContext implements TransactionContext {
"exchanges" as const,
"exchangeDetails" as const,
];
- let stores = opts.extraStores
+ const stores = opts.extraStores
? [...baseStores, ...opts.extraStores]
: baseStores;
+
+ let errorThrown: Error | undefined;
const transitionInfo = await this.wex.db.runReadWriteTx(
{ storeNames: stores },
async (tx) => {
@@ -359,7 +361,17 @@ export class WithdrawTransactionContext implements TransactionContext {
major: TransactionMajorState.None,
};
}
- const res = await f(wgRec, tx);
+ let res: TransitionResult<WithdrawalGroupRecord> | undefined;
+ try {
+ res = await f(wgRec, tx);
+ } catch (error) {
+ if (error instanceof Error) {
+ errorThrown = error;
+ }
+ return undefined;
+ }
+
+ // const res = await f(wgRec, tx);
switch (res.type) {
case TransitionResultType.Transition: {
await tx.withdrawalGroups.put(res.rec);
@@ -384,6 +396,9 @@ export class WithdrawTransactionContext implements TransactionContext {
}
},
);
+ if (errorThrown) {
+ throw errorThrown;
+ }
notifyTransition(this.wex, this.transactionId, transitionInfo);
return transitionInfo;
}
@@ -929,6 +944,10 @@ async function processPlanchetGenerate(
withdrawalGroup.denomsSel !== undefined,
"can't process uninitialized exchange",
);
+ checkDbInvariant(
+ withdrawalGroup.exchangeBaseUrl !== undefined,
+ "can't get funding uri from uninitialized wg",
+ );
const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl;
let planchet = await wex.db.runReadOnlyTx(
{ storeNames: ["planchets"] },
@@ -1133,6 +1152,10 @@ async function processPlanchetExchangeBatchRequest(
logger.info(
`processing planchet exchange batch request ${withdrawalGroup.withdrawalGroupId}, start=${args.coinStartIndex}, len=${args.batchSize}`,
);
+ checkDbInvariant(
+ withdrawalGroup.exchangeBaseUrl !== undefined,
+ "can't get funding uri from uninitialized wg",
+ );
const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl;
const batchReq: ExchangeBatchWithdrawRequest = { planchets: [] };
@@ -1268,6 +1291,10 @@ async function processPlanchetVerifyAndStoreCoin(
resp: ExchangeWithdrawResponse,
): Promise<void> {
const withdrawalGroup = wgContext.wgRecord;
+ checkDbInvariant(
+ withdrawalGroup.exchangeBaseUrl !== undefined,
+ "can't get funding uri from uninitialized wg",
+ );
const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl;
logger.trace(`checking and storing planchet idx=${coinIdx}`);
@@ -1517,6 +1544,10 @@ async function processQueryReserve(
return TaskRunResult.backoff();
}
checkDbInvariant(
+ withdrawalGroup.exchangeBaseUrl !== undefined,
+ "can't get funding uri from uninitialized wg",
+ );
+ checkDbInvariant(
withdrawalGroup.denomsSel !== undefined,
"can't process uninitialized exchange",
);
@@ -1752,6 +1783,10 @@ async function redenominateWithdrawal(
return;
}
checkDbInvariant(
+ wg.exchangeBaseUrl !== undefined,
+ "can't get funding uri from uninitialized wg",
+ );
+ checkDbInvariant(
wg.denomsSel !== undefined,
"can't process uninitialized exchange",
);
@@ -1894,6 +1929,10 @@ async function processWithdrawalGroupPendingReady(
withdrawalGroup.denomsSel !== undefined,
"can't process uninitialized exchange",
);
+ checkDbInvariant(
+ withdrawalGroup.exchangeBaseUrl !== undefined,
+ "can't get funding uri from uninitialized wg",
+ );
const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl;
await fetchFreshExchange(wex, withdrawalGroup.exchangeBaseUrl);
@@ -2321,6 +2360,10 @@ export async function getFundingPaytoUris(
const withdrawalGroup = await tx.withdrawalGroups.get(withdrawalGroupId);
checkDbInvariant(!!withdrawalGroup, `no withdrawal for ${withdrawalGroupId}`);
checkDbInvariant(
+ withdrawalGroup.exchangeBaseUrl !== undefined,
+ "can't get funding uri from uninitialized wg",
+ );
+ checkDbInvariant(
withdrawalGroup.instructedAmount !== undefined,
"can't get funding uri from uninitialized wg",
);
@@ -2675,7 +2718,7 @@ export async function internalPrepareCreateWithdrawalGroup(
args: {
reserveStatus: WithdrawalGroupStatus;
amount?: AmountJson;
- exchangeBaseUrl: string;
+ exchangeBaseUrl: string | undefined;
forcedWithdrawalGroupId?: string;
forcedDenomSel?: ForcedDenomSel;
reserveKeyPair?: EddsaKeypair;
@@ -2716,7 +2759,7 @@ export async function internalPrepareCreateWithdrawalGroup(
let initialDenomSel: DenomSelectionState | undefined;
const denomSelUid = encodeCrock(getRandomBytes(16));
- if (amount !== undefined) {
+ if (amount !== undefined && exchangeBaseUrl !== undefined) {
initialDenomSel = await getInitialDenomsSelection(
wex,
exchangeBaseUrl,
@@ -2747,7 +2790,9 @@ export async function internalPrepareCreateWithdrawalGroup(
wgInfo: args.wgInfo,
};
- await fetchFreshExchange(wex, exchangeBaseUrl);
+ if (exchangeBaseUrl !== undefined) {
+ await fetchFreshExchange(wex, exchangeBaseUrl);
+ }
const transactionId = constructTransactionIdentifier({
tag: TransactionType.Withdrawal,
@@ -2757,12 +2802,13 @@ export async function internalPrepareCreateWithdrawalGroup(
return {
withdrawalGroup,
transactionId,
- creationInfo: !amount
- ? undefined
- : {
- amount,
- canonExchange: exchangeBaseUrl,
- },
+ creationInfo:
+ !amount || !exchangeBaseUrl
+ ? undefined
+ : {
+ amount,
+ canonExchange: exchangeBaseUrl,
+ },
};
}
@@ -2792,8 +2838,8 @@ export async function internalPerformCreateWithdrawalGroup(
if (existingWg) {
return {
withdrawalGroup: existingWg,
- exchangeNotif: undefined,
transitionInfo: undefined,
+ exchangeNotif: undefined,
};
}
await tx.withdrawalGroups.add(withdrawalGroup);
@@ -2809,7 +2855,21 @@ export async function internalPerformCreateWithdrawalGroup(
exchangeNotif: undefined,
};
}
- const exchange = await tx.exchanges.get(prep.creationInfo.canonExchange);
+ return internalPerformExchangeWasUsed(
+ wex,
+ tx,
+ prep.creationInfo.canonExchange,
+ withdrawalGroup,
+ );
+}
+
+export async function internalPerformExchangeWasUsed(
+ wex: WalletExecutionContext,
+ tx: WalletDbReadWriteTransaction<["exchanges"]>,
+ canonExchange: string,
+ withdrawalGroup: WithdrawalGroupRecord,
+): Promise<PerformCreateWithdrawalGroupResult> {
+ const exchange = await tx.exchanges.get(canonExchange);
if (exchange) {
exchange.lastWithdrawal = timestampPreciseToDb(TalerPreciseTimestamp.now());
await tx.exchanges.put(exchange);
@@ -2825,11 +2885,7 @@ export async function internalPerformCreateWithdrawalGroup(
newTxState,
};
- const exchangeUsedRes = await markExchangeUsed(
- wex,
- tx,
- prep.creationInfo.canonExchange,
- );
+ const exchangeUsedRes = await markExchangeUsed(wex, tx, canonExchange);
const ctx = new WithdrawTransactionContext(
wex,
@@ -2857,7 +2913,7 @@ export async function internalCreateWithdrawalGroup(
wex: WalletExecutionContext,
args: {
reserveStatus: WithdrawalGroupStatus;
- exchangeBaseUrl: string;
+ exchangeBaseUrl: string | undefined;
amount?: AmountJson;
forcedWithdrawalGroupId?: string;
forcedDenomSel?: ForcedDenomSel;
@@ -2903,7 +2959,6 @@ export async function prepareBankIntegratedWithdrawal(
wex: WalletExecutionContext,
req: {
talerWithdrawUri: string;
- selectedExchange?: string;
},
): Promise<PrepareBankIntegratedWithdrawalResponse> {
const existingWithdrawalGroup = await wex.db.runReadOnlyTx(
@@ -2932,12 +2987,6 @@ export async function prepareBankIntegratedWithdrawal(
const info = await getWithdrawalDetailsForUri(wex, req.talerWithdrawUri);
- const exchangeBaseUrl =
- req.selectedExchange ?? withdrawInfo.suggestedExchange;
- if (!exchangeBaseUrl) {
- return { info };
- }
-
/**
* Withdrawal group without exchange and amount
* this is an special case when the user haven't yet
@@ -2946,7 +2995,7 @@ export async function prepareBankIntegratedWithdrawal(
* same URI
*/
const withdrawalGroup = await internalCreateWithdrawalGroup(wex, {
- exchangeBaseUrl,
+ exchangeBaseUrl: undefined,
wgInfo: {
withdrawalType: WithdrawalRecordType.BankIntegrated,
bankInfo: {
@@ -2955,6 +3004,7 @@ export async function prepareBankIntegratedWithdrawal(
timestampBankConfirmed: undefined,
timestampReserveInfoPosted: undefined,
wireTypes: withdrawInfo.wireTypes,
+ currency: withdrawInfo.currency,
},
},
reserveStatus: WithdrawalGroupStatus.DialogProposed,
@@ -2977,6 +3027,9 @@ export async function confirmWithdrawal(
req: ConfirmWithdrawalRequest,
): Promise<void> {
const parsedTx = parseTransactionIdentifier(req.transactionId);
+ const selectedExchange = req.exchangeBaseUrl;
+ const instructedAmount = Amounts.parseOrThrow(req.amount);
+
if (parsedTx?.tag !== TransactionType.Withdrawal) {
throw Error("invalid withdrawal transaction ID");
}
@@ -2998,7 +3051,6 @@ export async function confirmWithdrawal(
throw Error("not a bank integrated withdrawal");
}
- const selectedExchange = req.exchangeBaseUrl;
const exchange = await fetchFreshExchange(wex, selectedExchange);
const talerWithdrawUri = withdrawalGroup.wgInfo.bankInfo.talerWithdrawUri;
@@ -3006,30 +3058,36 @@ export async function confirmWithdrawal(
/**
* The only reason this could be undefined is because it is an old wallet
- * database before adding the wireType field was added
+ * database before adding the prepareWithdrawal feature
*/
- let wtypes: string[];
- if (withdrawalGroup.wgInfo.bankInfo.wireTypes === undefined) {
+ let bankWireTypes: string[];
+ let bankCurrency: string;
+ if (
+ withdrawalGroup.wgInfo.bankInfo.wireTypes === undefined ||
+ withdrawalGroup.wgInfo.bankInfo.currency === undefined
+ ) {
const withdrawInfo = await getBankWithdrawalInfo(
wex.http,
talerWithdrawUri,
);
- wtypes = withdrawInfo.wireTypes;
+ bankWireTypes = withdrawInfo.wireTypes;
+ bankCurrency = withdrawInfo.currency;
} else {
- wtypes = withdrawalGroup.wgInfo.bankInfo.wireTypes;
+ bankWireTypes = withdrawalGroup.wgInfo.bankInfo.wireTypes;
+ bankCurrency = withdrawalGroup.wgInfo.bankInfo.currency;
}
const exchangePaytoUri = await getExchangePaytoUri(
wex,
selectedExchange,
- wtypes,
+ bankWireTypes,
);
const withdrawalAccountList = await fetchWithdrawalAccountInfo(
wex,
{
exchange,
- instructedAmount: Amounts.parseOrThrow(req.amount),
+ instructedAmount,
},
wex.cancellationToken,
);
@@ -3040,23 +3098,34 @@ export async function confirmWithdrawal(
);
const initalDenoms = await getInitialDenomsSelection(
wex,
- req.exchangeBaseUrl,
- Amounts.parseOrThrow(req.amount),
+ exchange.exchangeBaseUrl,
+ instructedAmount,
req.forcedDenomSel,
);
+ let pending = false;
await ctx.transition({}, async (rec) => {
if (!rec) {
return TransitionResult.stay();
}
switch (rec.status) {
+ case WithdrawalGroupStatus.PendingWaitConfirmBank: {
+ pending = true;
+ return TransitionResult.stay();
+ }
+ case WithdrawalGroupStatus.AbortedOtherWallet: {
+ throw TalerError.fromDetail(
+ TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK,
+ {},
+ );
+ }
case WithdrawalGroupStatus.DialogProposed: {
- rec.exchangeBaseUrl = req.exchangeBaseUrl;
+ rec.exchangeBaseUrl = exchange.exchangeBaseUrl;
rec.instructedAmount = req.amount;
+ rec.restrictAge = req.restrictAge;
rec.denomsSel = initalDenoms;
rec.rawWithdrawalAmount = initalDenoms.totalWithdrawCost;
rec.effectiveWithdrawalAmount = initalDenoms.totalCoinValue;
- rec.restrictAge = req.restrictAge;
rec.wgInfo = {
withdrawalType: WithdrawalRecordType.BankIntegrated,
@@ -3067,19 +3136,50 @@ export async function confirmWithdrawal(
confirmUrl: confirmUrl,
timestampBankConfirmed: undefined,
timestampReserveInfoPosted: undefined,
- wireTypes: wtypes,
+ wireTypes: bankWireTypes,
+ currency: bankCurrency,
},
};
-
+ pending = true;
rec.status = WithdrawalGroupStatus.PendingRegisteringBank;
return TransitionResult.transition(rec);
}
- default:
- throw Error("unable to confirm withdrawal in current state");
+ default: {
+ throw Error(
+ `unable to confirm withdrawal in current state: ${rec.status}`,
+ );
+ }
}
});
await wex.taskScheduler.resetTaskRetries(ctx.taskId);
+
+ wex.ws.notify({
+ type: NotificationType.BalanceChange,
+ hintTransactionId: ctx.transactionId,
+ });
+
+ const res = await wex.db.runReadWriteTx(
+ {
+ storeNames: ["exchanges"],
+ },
+ async (tx) => {
+ const r = await internalPerformExchangeWasUsed(
+ wex,
+ tx,
+ exchange.exchangeBaseUrl,
+ withdrawalGroup,
+ );
+ return r;
+ },
+ );
+ if (res.exchangeNotif) {
+ wex.ws.notify(res.exchangeNotif);
+ }
+
+ if (pending) {
+ await waitWithdrawalRegistered(wex, ctx);
+ }
}
/**
@@ -3104,112 +3204,57 @@ export async function acceptWithdrawalFromUri(
): Promise<AcceptWithdrawalResponse> {
const selectedExchange = req.selectedExchange;
logger.info(
- `accepting withdrawal via ${req.talerWithdrawUri}, canonicalized selected exchange ${selectedExchange}`,
- );
- const existingWithdrawalGroup = await wex.db.runReadOnlyTx(
- { storeNames: ["withdrawalGroups"] },
- async (tx) => {
- return await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get(
- req.talerWithdrawUri,
- );
- },
+ `preparing withdrawal via ${req.talerWithdrawUri}, canonicalized selected exchange ${selectedExchange}`,
);
- if (existingWithdrawalGroup) {
- let url: string | undefined;
- if (
- existingWithdrawalGroup.wgInfo.withdrawalType ===
- WithdrawalRecordType.BankIntegrated
- ) {
- url = existingWithdrawalGroup.wgInfo.bankInfo.confirmUrl;
- }
- return {
- reservePub: existingWithdrawalGroup.reservePub,
- confirmTransferUrl: url,
- transactionId: constructTransactionIdentifier({
- tag: TransactionType.Withdrawal,
- withdrawalGroupId: existingWithdrawalGroup.withdrawalGroupId,
- }),
- };
- }
-
- const exchange = await fetchFreshExchange(wex, selectedExchange);
- const withdrawInfo = await getBankWithdrawalInfo(
- wex.http,
- req.talerWithdrawUri,
- );
- const exchangePaytoUri = await getExchangePaytoUri(
- wex,
- selectedExchange,
- withdrawInfo.wireTypes,
- );
+ const p = await prepareBankIntegratedWithdrawal(wex, {
+ talerWithdrawUri: req.talerWithdrawUri,
+ });
- let amount: AmountJson;
- if (withdrawInfo.amount == null) {
+ let amount: AmountString;
+ if (p.info.amount == null) {
if (req.amount == null) {
throw Error(
"amount required, as withdrawal operation has flexible amount",
);
}
- amount = Amounts.parseOrThrow(req.amount);
+ amount = req.amount as AmountString;
} else {
- if (
- req.amount != null &&
- Amounts.cmp(req.amount, withdrawInfo.amount) != 0
- ) {
+ if (req.amount != null && Amounts.cmp(req.amount, p.info.amount) != 0) {
throw Error(
"mismatched amount, amount is fixed by bank but client provided different amount",
);
}
- amount = withdrawInfo.amount;
+ amount = p.info.amount;
}
- const withdrawalAccountList = await fetchWithdrawalAccountInfo(
- wex,
- {
- exchange,
- instructedAmount: amount,
- },
- CancellationToken.CONTINUE,
- );
-
- const withdrawalGroup = await internalCreateWithdrawalGroup(wex, {
- amount,
- exchangeBaseUrl: req.selectedExchange,
- wgInfo: {
- withdrawalType: WithdrawalRecordType.BankIntegrated,
- exchangeCreditAccounts: withdrawalAccountList,
- bankInfo: {
- exchangePaytoUri,
- talerWithdrawUri: req.talerWithdrawUri,
- confirmUrl: withdrawInfo.confirmTransferUrl,
- timestampBankConfirmed: undefined,
- timestampReserveInfoPosted: undefined,
- wireTypes: withdrawInfo.wireTypes,
- },
- },
+ logger.info(`confirming withdrawal with tx ${p.transactionId}`);
+ await confirmWithdrawal(wex, {
+ amount: Amounts.stringify(amount),
+ exchangeBaseUrl: selectedExchange,
+ transactionId: p.transactionId,
restrictAge: req.restrictAge,
forcedDenomSel: req.forcedDenomSel,
- reserveStatus: WithdrawalGroupStatus.PendingRegisteringBank,
- });
-
- const withdrawalGroupId = withdrawalGroup.withdrawalGroupId;
-
- const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId);
-
- wex.ws.notify({
- type: NotificationType.BalanceChange,
- hintTransactionId: ctx.transactionId,
});
- wex.taskScheduler.startShepherdTask(ctx.taskId);
+ const newWithdrawralGroup = await wex.db.runReadOnlyTx(
+ { storeNames: ["withdrawalGroups"] },
+ async (tx) => {
+ return await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get(
+ req.talerWithdrawUri,
+ );
+ },
+ );
- await waitWithdrawalRegistered(wex, ctx);
+ checkDbInvariant(
+ newWithdrawralGroup !== undefined,
+ "withdrawal don't exist after confirm",
+ );
return {
- reservePub: withdrawalGroup.reservePub,
- confirmTransferUrl: withdrawInfo.confirmTransferUrl,
- transactionId: ctx.transactionId,
+ reservePub: newWithdrawralGroup.reservePub,
+ confirmTransferUrl: p.info.confirmTransferUrl,
+ transactionId: p.transactionId,
};
}
@@ -3434,7 +3479,7 @@ export async function createManualWithdrawal(
);
const withdrawalGroup = await internalCreateWithdrawalGroup(wex, {
- amount: Amounts.jsonifyAmount(req.amount),
+ amount: amount,
wgInfo: {
withdrawalType: WithdrawalRecordType.BankManual,
exchangeCreditAccounts: withdrawalAccountsList,