aboutsummaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-05-30 13:16:15 -0300
committerSebastian <sebasjm@gmail.com>2024-06-06 13:03:13 -0300
commit60a4640a35be584ee004de5e362f21ed03fa239e (patch)
tree1df828dd3c5f5898434efb5df301d4f116196581 /packages
parent920595aa3864615c07aba0ecfc233acf05f3e3e6 (diff)
downloadwallet-core-60a4640a35be584ee004de5e362f21ed03fa239e.tar.xz
working #8882
Diffstat (limited to 'packages')
-rw-r--r--packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts10
-rw-r--r--packages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts21
-rw-r--r--packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts10
-rw-r--r--packages/taler-util/src/transactions-types.ts2
-rw-r--r--packages/taler-util/src/wallet-types.ts4
-rw-r--r--packages/taler-wallet-core/src/balance.ts12
-rw-r--r--packages/taler-wallet-core/src/db.ts4
-rw-r--r--packages/taler-wallet-core/src/transactions.ts51
-rw-r--r--packages/taler-wallet-core/src/wallet.ts5
-rw-r--r--packages/taler-wallet-core/src/withdraw.ts301
-rw-r--r--packages/taler-wallet-webextension/src/components/HistoryItem.tsx4
-rw-r--r--packages/taler-wallet-webextension/src/cta/Withdraw/state.ts33
-rw-r--r--packages/taler-wallet-webextension/src/cta/Withdraw/test.ts5
13 files changed, 277 insertions, 185 deletions
diff --git a/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts b/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts
index f3e3802e5..046bd5aed 100644
--- a/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts
+++ b/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts
@@ -24,6 +24,7 @@ import {
NotificationType,
PreparePayResultType,
TalerCorebankApiClient,
+ j2s,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { makeNoFeeCoinConfig } from "../harness/denomStructures.js";
@@ -149,12 +150,15 @@ export async function runTimetravelAutorefreshTest(t: GlobalTestState) {
});
t.logStep("wait");
await wres.withdrawalFinishedCond;
-
const exchangeUpdated1Cond = walletClient.waitForNotificationCond(
(x) =>
- x.type === NotificationType.ExchangeStateTransition &&
- x.exchangeBaseUrl === exchange.baseUrl,
+ {
+ t.logStep(`EXCHANGE UPDATE, ${j2s(x)}`)
+ return x.type === NotificationType.ExchangeStateTransition &&
+ x.exchangeBaseUrl === exchange.baseUrl
+ }
);
+
t.logStep("waiting tx");
await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
{
diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts
index a13095883..3ec2a3bcd 100644
--- a/packages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts
+++ b/packages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts
@@ -46,7 +46,7 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) {
"TESTKUDOS:10",
);
- // Hand it to the wallet
+ t.logStep("Hand it to the wallet")
const r1 = await walletClient.client.call(
WalletApiOperation.GetWithdrawalDetailsForUri,
@@ -55,7 +55,7 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) {
},
);
- // Withdraw
+ t.logStep("Withdraw")
const r2 = await walletClient.client.call(
WalletApiOperation.AcceptBankIntegratedWithdrawal,
@@ -65,6 +65,7 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) {
},
);
+ t.logStep("wait confirmed")
const withdrawalBankConfirmedCond = walletClient.waitForNotificationCond(
(x) => {
return (
@@ -76,6 +77,7 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) {
},
);
+ t.logStep("wait finished")
const withdrawalFinishedCond = walletClient.waitForNotificationCond((x) => {
return (
x.type === NotificationType.TransactionStateTransition &&
@@ -84,6 +86,7 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) {
);
});
+ t.logStep("wait withdraw coins")
const withdrawalReserveReadyCond = walletClient.waitForNotificationCond(
(x) => {
return (
@@ -95,7 +98,7 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) {
},
);
- // Do it twice to check idempotency
+ t.logStep("Do it twice to check idempotency")
const r3 = await walletClient.client.call(
WalletApiOperation.AcceptBankIntegratedWithdrawal,
{
@@ -104,9 +107,10 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) {
},
);
+ t.logStep("stop wirewatch")
await exchange.stopWirewatch();
- // Check status before withdrawal is confirmed by bank.
+ t.logStep("Check status before withdrawal is confirmed by bank.")
{
const txn = await walletClient.client.call(
WalletApiOperation.GetTransactions,
@@ -122,7 +126,7 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) {
t.assertTrue(tx0.withdrawalDetails.reserveIsReady === false);
}
- // Confirm it
+ t.logStep("Confirm it")
await bankClient.confirmWithdrawalOperation(user.username, {
withdrawalOperationId: wop.withdrawal_id,
@@ -132,6 +136,7 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) {
// Check status after withdrawal is confirmed by bank,
// but before funds are wired to the exchange.
+ t.logStep("Check status after withdrawal")
{
const txn = await walletClient.client.call(
WalletApiOperation.GetTransactions,
@@ -147,11 +152,13 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) {
t.assertTrue(tx0.withdrawalDetails.reserveIsReady === false);
}
+ t.logStep("start wirewatch")
await exchange.startWirewatch();
+ t.logStep("wait reserve")
await withdrawalReserveReadyCond;
- // Check status after funds were wired.
+ t.logStep("Check status after funds were wired.")
{
const txn = await walletClient.client.call(
WalletApiOperation.GetTransactions,
@@ -169,7 +176,7 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) {
await withdrawalFinishedCond;
- // Check balance
+ t.logStep("Check balance")
const balResp = await walletClient.client.call(
WalletApiOperation.GetBalances,
diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts
index 8a2268231..0657d2da7 100644
--- a/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts
+++ b/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts
@@ -138,7 +138,7 @@ export async function runWithdrawalFeesTest(t: GlobalTestState) {
bankClient.setAuth(user);
const wop = await bankClient.createWithdrawalOperation(user.username, amount);
- // Hand it to the wallet
+ t.logStep("Hand it to the wallet")
const details = await wallet.client.call(
WalletApiOperation.GetWithdrawalDetailsForUri,
@@ -165,23 +165,25 @@ export async function runWithdrawalFeesTest(t: GlobalTestState) {
t.assertAmountEquals(amountDetails.amountEffective, "TESTKUDOS:5");
t.assertAmountEquals(amountDetails.amountRaw, "TESTKUDOS:7.5");
+ t.logStep("Complete all pending operations")
+
await wallet.runPending();
- // Withdraw (AKA select)
+ t.logStep("Withdraw (AKA select)")
await wallet.client.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, {
exchangeBaseUrl: exchange.baseUrl,
talerWithdrawUri: wop.taler_withdraw_uri,
});
- // Confirm it
+ t.logStep("Confirm it")
await bankClient.confirmWithdrawalOperation(user.username, {
withdrawalOperationId: wop.withdrawal_id,
});
await wallet.runUntilDone();
- // Check balance
+ t.logStep("Check balance")
const balResp = await wallet.client.call(WalletApiOperation.GetBalances, {});
console.log(j2s(balResp));
diff --git a/packages/taler-util/src/transactions-types.ts b/packages/taler-util/src/transactions-types.ts
index cee3de9fa..db2133944 100644
--- a/packages/taler-util/src/transactions-types.ts
+++ b/packages/taler-util/src/transactions-types.ts
@@ -324,7 +324,7 @@ export interface TransactionWithdrawal extends TransactionCommon {
/**
* Exchange of the withdrawal.
*/
- exchangeBaseUrl: string;
+ exchangeBaseUrl: string | undefined;
/**
* Amount that got subtracted from the reserve balance.
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts
index a7cd65fa2..66b1e9769 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -1850,18 +1850,16 @@ export interface GetWithdrawalDetailsForAmountRequest {
export interface PrepareBankIntegratedWithdrawalRequest {
talerWithdrawUri: string;
- selectedExchange?: string;
}
export const codecForPrepareBankIntegratedWithdrawalRequest =
(): Codec<PrepareBankIntegratedWithdrawalRequest> =>
buildCodecForObject<PrepareBankIntegratedWithdrawalRequest>()
.property("talerWithdrawUri", codecForString())
- .property("selectedExchange", codecOptional(codecForString()))
.build("PrepareBankIntegratedWithdrawalRequest");
export interface PrepareBankIntegratedWithdrawalResponse {
- transactionId?: string;
+ transactionId: TransactionIdStr;
info: WithdrawUriInfoResponse;
}
diff --git a/packages/taler-wallet-core/src/balance.ts b/packages/taler-wallet-core/src/balance.ts
index 76e604324..4f06e3756 100644
--- a/packages/taler-wallet-core/src/balance.ts
+++ b/packages/taler-wallet-core/src/balance.ts
@@ -379,6 +379,10 @@ export async function getBalancesInsideTransaction(
wg.denomsSel !== undefined,
"wg in kyc state should have been initialized",
);
+ checkDbInvariant(
+ wg.exchangeBaseUrl !== undefined,
+ "wg in kyc state should have been initialized",
+ );
const currency = Amounts.currencyOf(wg.denomsSel.totalCoinValue);
await balanceStore.setFlagIncomingKyc(currency, wg.exchangeBaseUrl);
break;
@@ -389,6 +393,10 @@ export async function getBalancesInsideTransaction(
wg.denomsSel !== undefined,
"wg in aml state should have been initialized",
);
+ checkDbInvariant(
+ wg.exchangeBaseUrl !== undefined,
+ "wg in kyc state should have been initialized",
+ );
const currency = Amounts.currencyOf(wg.denomsSel.totalCoinValue);
await balanceStore.setFlagIncomingAml(currency, wg.exchangeBaseUrl);
break;
@@ -408,6 +416,10 @@ export async function getBalancesInsideTransaction(
wg.denomsSel !== undefined,
"wg in confirmed state should have been initialized",
);
+ checkDbInvariant(
+ wg.exchangeBaseUrl !== undefined,
+ "wg in kyc state should have been initialized",
+ );
const currency = Amounts.currencyOf(wg.denomsSel.totalCoinValue);
await balanceStore.setFlagIncomingConfirmation(
currency,
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index ad9b4f1cb..4ec83a783 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -395,6 +395,8 @@ export interface ReserveBankInfo {
timestampBankConfirmed: DbPreciseTimestamp | undefined;
wireTypes: string[] | undefined;
+
+ currency: string | undefined;
}
/**
@@ -1531,7 +1533,7 @@ export interface WithdrawalGroupRecord {
* The exchange base URL that we're withdrawing from.
* (Redundantly stored, as the reserve record also has this info.)
*/
- exchangeBaseUrl: string;
+ exchangeBaseUrl?: string;
/**
* When was the withdrawal operation started started?
diff --git a/packages/taler-wallet-core/src/transactions.ts b/packages/taler-wallet-core/src/transactions.ts
index 72aff319a..bcf3fcaf6 100644
--- a/packages/taler-wallet-core/src/transactions.ts
+++ b/packages/taler-wallet-core/src/transactions.ts
@@ -243,11 +243,14 @@ export async function getTransactionById(
const opId = TaskIdentifiers.forWithdrawal(withdrawalGroupRecord);
const ort = await tx.operationRetries.get(opId);
- const exchangeDetails = await getExchangeWireDetailsInTx(
- tx,
- withdrawalGroupRecord.exchangeBaseUrl,
- );
- if (!exchangeDetails) throw Error("not exchange details");
+ const exchangeDetails =
+ withdrawalGroupRecord.exchangeBaseUrl === undefined
+ ? undefined
+ : await getExchangeWireDetailsInTx(
+ tx,
+ withdrawalGroupRecord.exchangeBaseUrl,
+ );
+ // if (!exchangeDetails) throw Error("not exchange details");
if (
withdrawalGroupRecord.wgInfo.withdrawalType ===
@@ -259,7 +262,10 @@ export async function getTransactionById(
ort,
);
}
-
+ checkDbInvariant(
+ exchangeDetails !== undefined,
+ "manual withdrawal without exchange",
+ );
return buildTransactionForManualWithdraw(
withdrawalGroupRecord,
exchangeDetails,
@@ -404,7 +410,10 @@ export async function getTransactionById(
const debit = await tx.peerPushDebit.get(parsedTx.pursePub);
if (!debit) throw Error("not found");
const ct = await tx.contractTerms.get(debit.contractTermsHash);
- checkDbInvariant(!!ct, `no contract terms for p2p push ${parsedTx.pursePub}`);
+ checkDbInvariant(
+ !!ct,
+ `no contract terms for p2p push ${parsedTx.pursePub}`,
+ );
return buildTransactionForPushPaymentDebit(
debit,
ct.contractTermsRaw,
@@ -428,7 +437,10 @@ export async function getTransactionById(
const pushInc = await tx.peerPushCredit.get(peerPushCreditId);
if (!pushInc) throw Error("not found");
const ct = await tx.contractTerms.get(pushInc.contractTermsHash);
- checkDbInvariant(!!ct, `no contract terms for p2p push ${peerPushCreditId}`);
+ checkDbInvariant(
+ !!ct,
+ `no contract terms for p2p push ${peerPushCreditId}`,
+ );
let wg: WithdrawalGroupRecord | undefined = undefined;
let wgOrt: OperationRetryRecord | undefined = undefined;
@@ -593,6 +605,7 @@ function buildTransactionForPeerPullCredit(
const txState = computePeerPullCreditTransactionState(pullCredit);
checkDbInvariant(wsr.instructedAmount !== undefined, "wg uninitialized");
checkDbInvariant(wsr.denomsSel !== undefined, "wg uninitialized");
+ checkDbInvariant(wsr.exchangeBaseUrl !== undefined, "wg uninitialized");
return {
type: TransactionType.PeerPullCredit,
txState,
@@ -667,6 +680,7 @@ function buildTransactionForPeerPushCredit(
}
checkDbInvariant(wg.instructedAmount !== undefined, "wg uninitialized");
checkDbInvariant(wg.denomsSel !== undefined, "wg uninitialized");
+ checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg uninitialized");
const txState = computePeerPushCreditTransactionState(pushInc);
return {
@@ -719,15 +733,17 @@ function buildTransactionForPeerPushCredit(
function buildTransactionForBankIntegratedWithdraw(
wg: WithdrawalGroupRecord,
- exchangeDetails: ExchangeWireDetails,
+ exchangeDetails: ExchangeWireDetails | undefined,
ort?: OperationRetryRecord,
): TransactionWithdrawal {
if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) {
throw Error("");
}
+ checkDbInvariant(wg.wgInfo.bankInfo.currency !== undefined, "wg uninitialized");
const txState = computeWithdrawalTransactionStatus(wg);
+
const zero = Amounts.stringify(
- Amounts.zeroOfCurrency(exchangeDetails.currency),
+ Amounts.zeroOfCurrency(wg.wgInfo.bankInfo.currency),
);
return {
type: TransactionType.Withdrawal,
@@ -784,6 +800,7 @@ function buildTransactionForManualWithdraw(
checkDbInvariant(wg.instructedAmount !== undefined, "wg uninitialized");
checkDbInvariant(wg.denomsSel !== undefined, "wg uninitialized");
+ checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg uninitialized");
const exchangePaytoUris = augmentPaytoUrisForWithdrawal(
plainPaytoUris,
wg.reservePub,
@@ -1034,8 +1051,14 @@ function buildTransactionForPurchase(
}));
const timestamp = purchaseRecord.timestampAccept;
- checkDbInvariant(!!timestamp, `purchase ${purchaseRecord.orderId} without accepted time`);
- checkDbInvariant(!!purchaseRecord.payInfo, `purchase ${purchaseRecord.orderId} without payinfo`);
+ checkDbInvariant(
+ !!timestamp,
+ `purchase ${purchaseRecord.orderId} without accepted time`,
+ );
+ checkDbInvariant(
+ !!purchaseRecord.payInfo,
+ `purchase ${purchaseRecord.orderId} without payinfo`,
+ );
const txState = computePayMerchantTransactionState(purchaseRecord);
return {
@@ -1089,6 +1112,10 @@ export async function getWithdrawalTransactionByUri(
if (!withdrawalGroupRecord) {
return undefined;
}
+ if (withdrawalGroupRecord.exchangeBaseUrl === undefined) {
+ // prepared and unconfirmed withdrawals are hidden
+ return undefined;
+ }
const opId = TaskIdentifiers.forWithdrawal(withdrawalGroupRecord);
const ort = await tx.operationRetries.get(opId);
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index d98106d1f..f1d53b7d5 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -1010,10 +1010,7 @@ async function dispatchRequestInternal(
case WalletApiOperation.PrepareBankIntegratedWithdrawal: {
const req =
codecForPrepareBankIntegratedWithdrawalRequest().decode(payload);
- return prepareBankIntegratedWithdrawal(wex, {
- talerWithdrawUri: req.talerWithdrawUri,
- selectedExchange: req.selectedExchange,
- });
+ return prepareBankIntegratedWithdrawal(wex, req);
}
case WalletApiOperation.GetExchangeTos: {
const req = codecForGetExchangeTosRequest().decode(payload);
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,
diff --git a/packages/taler-wallet-webextension/src/components/HistoryItem.tsx b/packages/taler-wallet-webextension/src/components/HistoryItem.tsx
index 9be9326b2..8e48a2e9f 100644
--- a/packages/taler-wallet-webextension/src/components/HistoryItem.tsx
+++ b/packages/taler-wallet-webextension/src/components/HistoryItem.tsx
@@ -26,7 +26,7 @@ import {
DenomLossEventType,
parsePaytoUri,
} from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
+import { Fragment, h, VNode } from "preact";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Avatar } from "../mui/Avatar.js";
import { Pages } from "../NavigationBar.js";
@@ -49,6 +49,8 @@ export function HistoryItem(props: { tx: Transaction }): VNode {
*/
switch (tx.type) {
case TransactionType.Withdrawal:
+ //withdrawal that has not been confirmed are hidden
+ if (!tx.exchangeBaseUrl) return <Fragment />
return (
<Layout
id={tx.transactionId}
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts
index 90ad65d6e..da3b1eeb2 100644
--- a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts
+++ b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts
@@ -213,10 +213,7 @@ export function useComponentStateFromURI({
const uriInfo = await api.wallet.call(
WalletApiOperation.PrepareBankIntegratedWithdrawal,
- {
- talerWithdrawUri,
- selectedExchange: updatedExchangeByUser,
- },
+ { talerWithdrawUri },
);
const {
amount,
@@ -225,12 +222,12 @@ export function useComponentStateFromURI({
confirmTransferUrl,
status,
} = uriInfo.info;
- const txInfo =
- uriInfo.transactionId === undefined
- ? undefined
- : await api.wallet.call(WalletApiOperation.GetTransactionById, {
- transactionId: uriInfo.transactionId,
- });
+ const txInfo = await api.wallet.call(
+ WalletApiOperation.GetTransactionById,
+ {
+ transactionId: uriInfo.transactionId,
+ },
+ );
return {
talerWithdrawUri,
status,
@@ -292,9 +289,6 @@ export function useComponentStateFromURI({
transactionId: string;
confirmTransferUrl: string | undefined;
}> {
- if (!txId) {
- throw Error("can't confirm transaction");
- }
const res = await api.wallet.call(WalletApiOperation.ConfirmWithdrawal, {
exchangeBaseUrl: exchange,
amount,
@@ -370,15 +364,16 @@ function exchangeSelectionState(
onExchangeUpdated(current);
}
}, [current]);
-
- const safeAmount = !infoAmount ? Amounts.zeroOfCurrency(currency) : infoAmount
- const [choosenAmount, setChoosenAmount] = useState(safeAmount)
+
+ const safeAmount = !infoAmount
+ ? Amounts.zeroOfCurrency(currency)
+ : infoAmount;
+ const [choosenAmount, setChoosenAmount] = useState(safeAmount);
if (selectedExchange.status !== "ready") {
return selectedExchange;
}
-
return useCallback(():
| State.Success
| State.LoadingUriError
@@ -520,8 +515,8 @@ function exchangeSelectionState(
amount: {
value: choosenAmount,
onInput: pushAlertOnError(async (v) => {
- setChoosenAmount(v)
- })
+ setChoosenAmount(v);
+ }),
},
talerWithdrawUri,
ageRestriction,
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts
index 785fab996..5bbf5f6c8 100644
--- a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts
+++ b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts
@@ -26,6 +26,7 @@ import {
ExchangeListItem,
ExchangeTosStatus,
ScopeType,
+ TransactionIdStr,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai";
@@ -111,7 +112,7 @@ describe("Withdraw CTA states", () => {
WalletApiOperation.PrepareBankIntegratedWithdrawal,
undefined,
{
- transactionId: "123",
+ transactionId: "123" as TransactionIdStr,
info: {
status: "pending",
operationId: "123",
@@ -153,7 +154,7 @@ describe("Withdraw CTA states", () => {
WalletApiOperation.PrepareBankIntegratedWithdrawal,
undefined,
{
- transactionId: "123",
+ transactionId: "123" as TransactionIdStr,
info: {
status: "pending",
operationId: "123",