auto-refund
This commit is contained in:
parent
d634626d7f
commit
165486a112
@ -788,7 +788,7 @@ export interface TipRecord {
|
|||||||
/**
|
/**
|
||||||
* Timestamp, the tip can't be picked up anymore after this deadline.
|
* Timestamp, the tip can't be picked up anymore after this deadline.
|
||||||
*/
|
*/
|
||||||
deadline: number;
|
deadline: Timestamp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The exchange that will sign our coins, chosen by the merchant.
|
* The exchange that will sign our coins, chosen by the merchant.
|
||||||
@ -1066,6 +1066,11 @@ export interface PurchaseRecord {
|
|||||||
* Last error (or undefined) for querying the refund status with the merchant.
|
* Last error (or undefined) for querying the refund status with the merchant.
|
||||||
*/
|
*/
|
||||||
lastRefundApplyError: OperationError | undefined;
|
lastRefundApplyError: OperationError | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Continue querying the refund status until this deadline has expired.
|
||||||
|
*/
|
||||||
|
autoRefundDeadline: Timestamp | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
import { AmountJson } from "./amounts";
|
import { AmountJson } from "./amounts";
|
||||||
import * as Amounts from "./amounts";
|
import * as Amounts from "./amounts";
|
||||||
|
|
||||||
import { Timestamp } from "../walletTypes";
|
import { Timestamp, Duration } from "../walletTypes";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show an amount in a form suitable for the user.
|
* Show an amount in a form suitable for the user.
|
||||||
@ -151,6 +151,30 @@ export function extractTalerStampOrThrow(stamp: string): Timestamp {
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract a duration from a Taler duration string.
|
||||||
|
*/
|
||||||
|
export function extractTalerDuration(duration: string): Duration | undefined {
|
||||||
|
const m = duration.match(/\/?Delay\(([0-9]*)\)\/?/);
|
||||||
|
if (!m || !m[1]) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
d_ms: parseInt(m[1], 10) * 1000,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract a duration from a Taler duration string.
|
||||||
|
*/
|
||||||
|
export function extractTalerDurationOrThrow(duration: string): Duration {
|
||||||
|
const r = extractTalerDuration(duration);
|
||||||
|
if (!r) {
|
||||||
|
throw Error("invalid duration");
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a timestamp is in the right format.
|
* Check if a timestamp is in the right format.
|
||||||
*/
|
*/
|
||||||
@ -159,18 +183,6 @@ export function timestampCheck(stamp: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a JavaScript Date object from a Taler date string.
|
|
||||||
* Returns null if input is not in the right format.
|
|
||||||
*/
|
|
||||||
export function getTalerStampDate(stamp: string): Date | null {
|
|
||||||
const sec = getTalerStampSec(stamp);
|
|
||||||
if (sec == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new Date(sec * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the hash function of a JSON object.
|
* Compute the hash function of a JSON object.
|
||||||
*/
|
*/
|
||||||
|
@ -62,6 +62,8 @@ import {
|
|||||||
strcmp,
|
strcmp,
|
||||||
canonicalJson,
|
canonicalJson,
|
||||||
extractTalerStampOrThrow,
|
extractTalerStampOrThrow,
|
||||||
|
extractTalerDurationOrThrow,
|
||||||
|
extractTalerDuration,
|
||||||
} from "../util/helpers";
|
} from "../util/helpers";
|
||||||
import { Logger } from "../util/logging";
|
import { Logger } from "../util/logging";
|
||||||
import { InternalWalletState } from "./state";
|
import { InternalWalletState } from "./state";
|
||||||
@ -359,6 +361,7 @@ async function recordConfirmPay(
|
|||||||
lastRefundApplyError: undefined,
|
lastRefundApplyError: undefined,
|
||||||
refundApplyRetryInfo: initRetryInfo(),
|
refundApplyRetryInfo: initRetryInfo(),
|
||||||
firstSuccessfulPayTimestamp: undefined,
|
firstSuccessfulPayTimestamp: undefined,
|
||||||
|
autoRefundDeadline: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
await runWithWriteTransaction(
|
await runWithWriteTransaction(
|
||||||
@ -704,9 +707,23 @@ export async function submitPay(
|
|||||||
// FIXME: properly display error
|
// FIXME: properly display error
|
||||||
throw Error("merchant payment signature invalid");
|
throw Error("merchant payment signature invalid");
|
||||||
}
|
}
|
||||||
|
const isFirst = purchase.firstSuccessfulPayTimestamp === undefined;
|
||||||
purchase.firstSuccessfulPayTimestamp = getTimestampNow();
|
purchase.firstSuccessfulPayTimestamp = getTimestampNow();
|
||||||
purchase.lastPayError = undefined;
|
purchase.lastPayError = undefined;
|
||||||
purchase.payRetryInfo = initRetryInfo(false);
|
purchase.payRetryInfo = initRetryInfo(false);
|
||||||
|
if (isFirst) {
|
||||||
|
const ar = purchase.contractTerms.auto_refund;
|
||||||
|
if (ar) {
|
||||||
|
const autoRefundDelay = extractTalerDuration(ar);
|
||||||
|
if (autoRefundDelay) {
|
||||||
|
purchase.refundStatusRequested = true;
|
||||||
|
purchase.autoRefundDeadline = {
|
||||||
|
t_ms: getTimestampNow().t_ms + autoRefundDelay.d_ms,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const modifiedCoins: CoinRecord[] = [];
|
const modifiedCoins: CoinRecord[] = [];
|
||||||
for (const pc of purchase.payReq.coins) {
|
for (const pc of purchase.payReq.coins) {
|
||||||
const c = await oneShotGet(ws.db, Stores.coins, pc.coin_pub);
|
const c = await oneShotGet(ws.db, Stores.coins, pc.coin_pub);
|
||||||
@ -1064,11 +1081,6 @@ async function acceptRefundResponse(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.lastRefundStatusTimestamp = getTimestampNow();
|
|
||||||
p.lastRefundStatusError = undefined;
|
|
||||||
p.refundStatusRetryInfo = initRetryInfo();
|
|
||||||
p.refundStatusRequested = false;
|
|
||||||
|
|
||||||
for (const perm of refundPermissions) {
|
for (const perm of refundPermissions) {
|
||||||
if (
|
if (
|
||||||
!p.refundsPending[perm.merchant_sig] &&
|
!p.refundsPending[perm.merchant_sig] &&
|
||||||
@ -1079,6 +1091,29 @@ async function acceptRefundResponse(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Are we done with querying yet, or do we need to do another round
|
||||||
|
// after a retry delay?
|
||||||
|
let queryDone = true;
|
||||||
|
|
||||||
|
if (numNewRefunds === 0) {
|
||||||
|
if (p.autoRefundDeadline && p.autoRefundDeadline.t_ms < getTimestampNow().t_ms) {
|
||||||
|
queryDone = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryDone) {
|
||||||
|
p.lastRefundStatusTimestamp = getTimestampNow();
|
||||||
|
p.lastRefundStatusError = undefined;
|
||||||
|
p.refundStatusRetryInfo = initRetryInfo();
|
||||||
|
p.refundStatusRequested = false;
|
||||||
|
} else {
|
||||||
|
// No error, but we need to try again!
|
||||||
|
p.lastRefundStatusTimestamp = getTimestampNow();
|
||||||
|
p.refundStatusRetryInfo.retryCounter++;
|
||||||
|
updateRetryInfoTimeout(p.refundStatusRetryInfo);
|
||||||
|
p.lastRefundStatusError = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (numNewRefunds) {
|
if (numNewRefunds) {
|
||||||
p.lastRefundApplyError = undefined;
|
p.lastRefundApplyError = undefined;
|
||||||
p.refundApplyRetryInfo = initRetryInfo();
|
p.refundApplyRetryInfo = initRetryInfo();
|
||||||
|
@ -23,7 +23,7 @@ import { TipPickupGetResponse, TipPlanchetDetail, TipResponse } from "../talerTy
|
|||||||
import * as Amounts from "../util/amounts";
|
import * as Amounts from "../util/amounts";
|
||||||
import { Stores, PlanchetRecord, WithdrawalSessionRecord, initRetryInfo, updateRetryInfoTimeout } from "../dbTypes";
|
import { Stores, PlanchetRecord, WithdrawalSessionRecord, initRetryInfo, updateRetryInfoTimeout } from "../dbTypes";
|
||||||
import { getWithdrawDetailsForAmount, getVerifiedWithdrawDenomList, processWithdrawSession } from "./withdraw";
|
import { getWithdrawDetailsForAmount, getVerifiedWithdrawDenomList, processWithdrawSession } from "./withdraw";
|
||||||
import { getTalerStampSec } from "../util/helpers";
|
import { getTalerStampSec, extractTalerStampOrThrow } from "../util/helpers";
|
||||||
import { updateExchangeFromUrl } from "./exchanges";
|
import { updateExchangeFromUrl } from "./exchanges";
|
||||||
import { getRandomBytes, encodeCrock } from "../crypto/talerCrypto";
|
import { getRandomBytes, encodeCrock } from "../crypto/talerCrypto";
|
||||||
import { guardOperationException } from "./errors";
|
import { guardOperationException } from "./errors";
|
||||||
@ -68,7 +68,7 @@ export async function getTipStatus(
|
|||||||
tipId,
|
tipId,
|
||||||
accepted: false,
|
accepted: false,
|
||||||
amount,
|
amount,
|
||||||
deadline: getTalerStampSec(tipPickupStatus.stamp_expire)!,
|
deadline: extractTalerStampOrThrow(tipPickupStatus.stamp_expire),
|
||||||
exchangeUrl: tipPickupStatus.exchange_url,
|
exchangeUrl: tipPickupStatus.exchange_url,
|
||||||
merchantBaseUrl: res.merchantBaseUrl,
|
merchantBaseUrl: res.merchantBaseUrl,
|
||||||
nextUrl: undefined,
|
nextUrl: undefined,
|
||||||
|
Loading…
Reference in New Issue
Block a user