implement freezing for payments
This commit is contained in:
parent
408d8e9fc8
commit
a09359bd39
@ -80,6 +80,12 @@ export interface TransactionCommon {
|
|||||||
// but its transactionId will remain unchanged
|
// but its transactionId will remain unchanged
|
||||||
pending: boolean;
|
pending: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the transaction encountered a problem that might be
|
||||||
|
* permanent. A frozen transaction won't be automatically retried.
|
||||||
|
*/
|
||||||
|
frozen: boolean;
|
||||||
|
|
||||||
// Raw amount of the transaction (exclusive of fees or other extra costs)
|
// Raw amount of the transaction (exclusive of fees or other extra costs)
|
||||||
amountRaw: AmountString;
|
amountRaw: AmountString;
|
||||||
|
|
||||||
|
@ -112,13 +112,6 @@ export async function runDenomUnofferedTest(t: GlobalTestState) {
|
|||||||
merchantErrorCode,
|
merchantErrorCode,
|
||||||
TalerErrorCode.MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_NOT_FOUND,
|
TalerErrorCode.MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_NOT_FOUND,
|
||||||
);
|
);
|
||||||
|
|
||||||
const purchId = makeEventId(TransactionType.Payment, preparePayResult.proposalId);
|
|
||||||
await wallet.client.call(WalletApiOperation.DeleteTransaction, {
|
|
||||||
transactionId: purchId,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Now, delete the purchase and refresh operation.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await wallet.client.call(WalletApiOperation.AddExchange, {
|
await wallet.client.call(WalletApiOperation.AddExchange, {
|
||||||
|
@ -1276,6 +1276,12 @@ export interface PurchaseRecord {
|
|||||||
* Continue querying the refund status until this deadline has expired.
|
* Continue querying the refund status until this deadline has expired.
|
||||||
*/
|
*/
|
||||||
autoRefundDeadline: Timestamp | undefined;
|
autoRefundDeadline: Timestamp | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the payment frozen? I.e. did we encounter
|
||||||
|
* an error where it doesn't make sense to retry.
|
||||||
|
*/
|
||||||
|
payFrozen?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WALLET_BACKUP_STATE_KEY = "walletBackupState";
|
export const WALLET_BACKUP_STATE_KEY = "walletBackupState";
|
||||||
|
@ -93,6 +93,7 @@ import {
|
|||||||
readSuccessResponseJsonOrErrorCode,
|
readSuccessResponseJsonOrErrorCode,
|
||||||
readSuccessResponseJsonOrThrow,
|
readSuccessResponseJsonOrThrow,
|
||||||
readTalerErrorResponse,
|
readTalerErrorResponse,
|
||||||
|
readUnexpectedResponseDetails,
|
||||||
throwUnexpectedRequestError,
|
throwUnexpectedRequestError,
|
||||||
} from "../util/http.js";
|
} from "../util/http.js";
|
||||||
import {
|
import {
|
||||||
@ -1209,6 +1210,26 @@ async function submitPay(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (resp.status === HttpResponseStatus.BadRequest) {
|
||||||
|
const errDetails = await readUnexpectedResponseDetails(resp);
|
||||||
|
logger.warn("unexpected 400 response for /pay");
|
||||||
|
logger.warn(j2s(errDetails));
|
||||||
|
await ws.db
|
||||||
|
.mktx((x) => ({ purchases: x.purchases }))
|
||||||
|
.runReadWrite(async (tx) => {
|
||||||
|
const purch = await tx.purchases.get(proposalId);
|
||||||
|
if (!purch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
purch.payFrozen = true;
|
||||||
|
purch.lastPayError = errDetails;
|
||||||
|
delete purch.payRetryInfo;
|
||||||
|
await tx.purchases.put(purch);
|
||||||
|
});
|
||||||
|
// FIXME: Maybe introduce a new return type for this instead of throwing?
|
||||||
|
throw new OperationFailedAndReportedError(errDetails);
|
||||||
|
}
|
||||||
|
|
||||||
if (resp.status === HttpResponseStatus.Conflict) {
|
if (resp.status === HttpResponseStatus.Conflict) {
|
||||||
const err = await readTalerErrorResponse(resp);
|
const err = await readTalerErrorResponse(resp);
|
||||||
if (
|
if (
|
||||||
|
@ -235,7 +235,11 @@ async function gatherPurchasePending(
|
|||||||
resp: PendingOperationsResponse,
|
resp: PendingOperationsResponse,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await tx.purchases.iter().forEach((pr) => {
|
await tx.purchases.iter().forEach((pr) => {
|
||||||
if (pr.paymentSubmitPending && pr.abortStatus === AbortStatus.None) {
|
if (
|
||||||
|
pr.paymentSubmitPending &&
|
||||||
|
pr.abortStatus === AbortStatus.None &&
|
||||||
|
!pr.payFrozen
|
||||||
|
) {
|
||||||
const timestampDue = pr.payRetryInfo?.nextRetry ?? getTimestampNow();
|
const timestampDue = pr.payRetryInfo?.nextRetry ?? getTimestampNow();
|
||||||
resp.pendingOperations.push({
|
resp.pendingOperations.push({
|
||||||
type: PendingTaskType.Pay,
|
type: PendingTaskType.Pay,
|
||||||
|
@ -168,6 +168,7 @@ export async function getTransactions(
|
|||||||
TransactionType.Withdrawal,
|
TransactionType.Withdrawal,
|
||||||
wsr.withdrawalGroupId,
|
wsr.withdrawalGroupId,
|
||||||
),
|
),
|
||||||
|
frozen: false,
|
||||||
...(wsr.lastError ? { error: wsr.lastError } : {}),
|
...(wsr.lastError ? { error: wsr.lastError } : {}),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -215,6 +216,7 @@ export async function getTransactions(
|
|||||||
TransactionType.Withdrawal,
|
TransactionType.Withdrawal,
|
||||||
r.initialWithdrawalGroupId,
|
r.initialWithdrawalGroupId,
|
||||||
),
|
),
|
||||||
|
frozen: false,
|
||||||
...(r.lastError ? { error: r.lastError } : {}),
|
...(r.lastError ? { error: r.lastError } : {}),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -230,6 +232,7 @@ export async function getTransactions(
|
|||||||
amountRaw: Amounts.stringify(dg.effectiveDepositAmount),
|
amountRaw: Amounts.stringify(dg.effectiveDepositAmount),
|
||||||
amountEffective: Amounts.stringify(dg.totalPayCost),
|
amountEffective: Amounts.stringify(dg.totalPayCost),
|
||||||
pending: !dg.timestampFinished,
|
pending: !dg.timestampFinished,
|
||||||
|
frozen: false,
|
||||||
timestamp: dg.timestampCreated,
|
timestamp: dg.timestampCreated,
|
||||||
targetPaytoUri: dg.wire.payto_uri,
|
targetPaytoUri: dg.wire.payto_uri,
|
||||||
transactionId: makeEventId(
|
transactionId: makeEventId(
|
||||||
@ -288,6 +291,7 @@ export async function getTransactions(
|
|||||||
transactionId: paymentTransactionId,
|
transactionId: paymentTransactionId,
|
||||||
proposalId: pr.proposalId,
|
proposalId: pr.proposalId,
|
||||||
info: info,
|
info: info,
|
||||||
|
frozen: pr.payFrozen ?? false,
|
||||||
...(err ? { error: err } : {}),
|
...(err ? { error: err } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -351,6 +355,7 @@ export async function getTransactions(
|
|||||||
amountEffective: Amounts.stringify(amountEffective),
|
amountEffective: Amounts.stringify(amountEffective),
|
||||||
amountRaw: Amounts.stringify(amountRaw),
|
amountRaw: Amounts.stringify(amountRaw),
|
||||||
pending: false,
|
pending: false,
|
||||||
|
frozen: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -372,6 +377,7 @@ export async function getTransactions(
|
|||||||
amountEffective: Amounts.stringify(tipRecord.tipAmountEffective),
|
amountEffective: Amounts.stringify(tipRecord.tipAmountEffective),
|
||||||
amountRaw: Amounts.stringify(tipRecord.tipAmountRaw),
|
amountRaw: Amounts.stringify(tipRecord.tipAmountRaw),
|
||||||
pending: !tipRecord.pickedUpTimestamp,
|
pending: !tipRecord.pickedUpTimestamp,
|
||||||
|
frozen: false,
|
||||||
timestamp: tipRecord.acceptedTimestamp,
|
timestamp: tipRecord.acceptedTimestamp,
|
||||||
transactionId: makeEventId(
|
transactionId: makeEventId(
|
||||||
TransactionType.Tip,
|
TransactionType.Tip,
|
||||||
|
@ -64,6 +64,7 @@ export enum HttpResponseStatus {
|
|||||||
NoContent = 204,
|
NoContent = 204,
|
||||||
Gone = 210,
|
Gone = 210,
|
||||||
NotModified = 304,
|
NotModified = 304,
|
||||||
|
BadRequest = 400,
|
||||||
PaymentRequired = 402,
|
PaymentRequired = 402,
|
||||||
NotFound = 404,
|
NotFound = 404,
|
||||||
Conflict = 409,
|
Conflict = 409,
|
||||||
|
Loading…
Reference in New Issue
Block a user