aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations/pay-merchant.ts
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-01-11 17:12:08 +0100
committerFlorian Dold <florian@dold.me>2023-01-11 17:14:49 +0100
commit143a4fe4ac5b8724cf6e13a704e88daa99dd4202 (patch)
tree07b2fe6d993ae46838bc4072db8eba609bbb1801 /packages/taler-wallet-core/src/operations/pay-merchant.ts
parent5fc0cb7927e7a81a80129f6165b2027e72b89d33 (diff)
downloadwallet-core-143a4fe4ac5b8724cf6e13a704e88daa99dd4202.tar.xz
wallet-core: refresh when aborting payments
Diffstat (limited to 'packages/taler-wallet-core/src/operations/pay-merchant.ts')
-rw-r--r--packages/taler-wallet-core/src/operations/pay-merchant.ts118
1 files changed, 65 insertions, 53 deletions
diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts b/packages/taler-wallet-core/src/operations/pay-merchant.ts
index c8e3230b9..2f1f3375c 100644
--- a/packages/taler-wallet-core/src/operations/pay-merchant.ts
+++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts
@@ -125,6 +125,7 @@ import {
} from "../util/retries.js";
import {
makeTransactionId,
+ runOperationWithErrorReporting,
spendCoins,
storeOperationError,
storeOperationPending,
@@ -1135,9 +1136,9 @@ export function selectForced(
export type SelectPayCoinsResult =
| {
- type: "failure";
- insufficientBalanceDetails: PayMerchantInsufficientBalanceDetails;
- }
+ type: "failure";
+ insufficientBalanceDetails: PayMerchantInsufficientBalanceDetails;
+ }
| { type: "success"; coinSel: PayCoinSelection };
/**
@@ -1594,7 +1595,12 @@ export async function runPayForConfirmPay(
ws: InternalWalletState,
proposalId: string,
): Promise<ConfirmPayResult> {
- const res = await processPurchasePay(ws, proposalId, { forceNow: true });
+ logger.trace("processing proposal for confirmPay");
+ const opId = RetryTags.byPaymentProposalId(proposalId);
+ const res = await runOperationWithErrorReporting(ws, opId, async () => {
+ return await processPurchasePay(ws, proposalId, { forceNow: true });
+ });
+ logger.trace(`processPurchasePay response type ${res.type}`);
switch (res.type) {
case OperationAttemptResultType.Finished: {
const purchase = await ws.db
@@ -1623,9 +1629,10 @@ export async function runPayForConfirmPay(
const numRetry = opRetry?.retryInfo.retryCounter ?? 0;
if (
res.errorDetail.code ===
- TalerErrorCode.WALLET_PAY_MERCHANT_SERVER_ERROR &&
+ TalerErrorCode.WALLET_PAY_MERCHANT_SERVER_ERROR &&
numRetry < maxRetry
) {
+ logger.trace("hiding transient error from caller");
// Pretend the operation is pending instead of reporting
// an error, but only up to maxRetry attempts.
await storeOperationPending(
@@ -1638,20 +1645,11 @@ export async function runPayForConfirmPay(
transactionId: makeTransactionId(TransactionType.Payment, proposalId),
};
} else {
- // FIXME: allocate error code!
- await storeOperationError(
- ws,
- RetryTags.byPaymentProposalId(proposalId),
- res.errorDetail,
- );
throw Error("payment failed");
}
}
case OperationAttemptResultType.Pending:
- await storeOperationPending(
- ws,
- `${PendingTaskType.Purchase}:${proposalId}`,
- );
+ logger.trace("reporting pending as confirmPay response");
return {
type: ConfirmPayResultType.Pending,
transactionId: makeTransactionId(TransactionType.Payment, proposalId),
@@ -1968,29 +1966,12 @@ export async function processPurchasePay(
result: undefined,
};
}
+ }
- if (resp.status >= 400 && resp.status <= 499) {
- const errDetails = await readUnexpectedResponseDetails(resp);
- logger.warn(`server returned ${resp.status} response for /pay`);
- logger.warn(j2s(errDetails));
- await ws.db
- .mktx((x) => [x.purchases])
- .runReadWrite(async (tx) => {
- const purch = await tx.purchases.get(proposalId);
- if (!purch) {
- return;
- }
- // FIXME: Should be some "PayPermanentlyFailed" and error info should be stored
- purch.purchaseStatus = PurchaseStatus.PaymentAbortFinished;
- await tx.purchases.put(purch);
- });
- throw makePendingOperationFailedError(
- errDetails,
- TransactionType.Payment,
- proposalId,
- );
- }
-
+ if (resp.status >= 400 && resp.status <= 499) {
+ logger.trace("got generic 4xx from merchant");
+ const err = await readTalerErrorResponse(resp);
+ throwUnexpectedRequestError(resp, err);
}
const merchantResp = await readSuccessResponseJsonOrThrow(
@@ -2395,16 +2376,18 @@ async function acceptRefunds(
}
}
- const refreshCoinsPubs = Object.values(refreshCoinsMap);
- logger.info(`refreshCoinMap ${j2s(refreshCoinsMap)}`);
- if (refreshCoinsPubs.length > 0) {
- await createRefreshGroup(
- ws,
- tx,
- Amounts.currencyOf(refreshCoinsPubs[0].amount),
- refreshCoinsPubs,
- RefreshReason.Refund,
- );
+ if (reason === RefundReason.AbortRefund) {
+ const refreshCoinsPubs = Object.values(refreshCoinsMap);
+ logger.info(`refreshCoinMap ${j2s(refreshCoinsMap)}`);
+ if (refreshCoinsPubs.length > 0) {
+ await createRefreshGroup(
+ ws,
+ tx,
+ Amounts.currencyOf(refreshCoinsPubs[0].amount),
+ refreshCoinsPubs,
+ RefreshReason.Refund,
+ );
+ }
}
// Are we done with querying yet, or do we need to do another round
@@ -2808,12 +2791,21 @@ export async function processPurchaseQueryRefund(
return OperationAttemptResult.finishedEmpty();
}
-export async function abortFailedPayWithRefund(
+export async function abortPay(
ws: InternalWalletState,
proposalId: string,
+ cancelImmediately?: boolean,
): Promise<void> {
+ const opId = RetryTags.byPaymentProposalId(proposalId);
await ws.db
- .mktx((x) => [x.purchases])
+ .mktx((x) => [
+ x.purchases,
+ x.refreshGroups,
+ x.denominations,
+ x.coinAvailability,
+ x.coins,
+ x.operationRetries,
+ ])
.runReadWrite(async (tx) => {
const purchase = await tx.purchases.get(proposalId);
if (!purchase) {
@@ -2828,10 +2820,30 @@ export async function abortFailedPayWithRefund(
purchase.purchaseStatus = PurchaseStatus.AbortingWithRefund;
}
await tx.purchases.put(purchase);
+ await tx.operationRetries.delete(opId);
+ if (purchase.payInfo) {
+ const coinSel = purchase.payInfo.payCoinSelection;
+ const currency = Amounts.currencyOf(purchase.payInfo.totalPayCost);
+ const refreshCoins: CoinRefreshRequest[] = [];
+ for (let i = 0; i < coinSel.coinPubs.length; i++) {
+ refreshCoins.push({
+ amount: coinSel.coinContributions[i],
+ coinPub: coinSel.coinPubs[i],
+ });
+ }
+ await createRefreshGroup(
+ ws,
+ tx,
+ currency,
+ refreshCoins,
+ RefreshReason.AbortPay,
+ );
+ }
+ });
+
+ runOperationWithErrorReporting(ws, opId, async () => {
+ return await processPurchaseQueryRefund(ws, proposalId, {
+ forceNow: true,
});
- processPurchaseQueryRefund(ws, proposalId, {
- forceNow: true,
- }).catch((e) => {
- logger.trace(`error during refund processing after abort pay: ${e}`);
});
}