diff options
author | Florian Dold <florian.dold@gmail.com> | 2019-12-06 00:24:34 +0100 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2019-12-06 00:24:34 +0100 |
commit | 65bccbd139c53a2baccec442a680373125488102 (patch) | |
tree | 216860ec3523af33091b8fb52193787034c667f8 | |
parent | 7b54439fd62bd2a5e15b3068a8fbaffeb0a57468 (diff) |
separate operations for pay, refund status query and refund submission
-rw-r--r-- | src/dbTypes.ts | 50 | ||||
-rw-r--r-- | src/wallet-impl/balance.ts | 2 | ||||
-rw-r--r-- | src/wallet-impl/exchanges.ts | 19 | ||||
-rw-r--r-- | src/wallet-impl/history.ts | 4 | ||||
-rw-r--r-- | src/wallet-impl/pay.ts | 418 | ||||
-rw-r--r-- | src/wallet-impl/pending.ts | 66 | ||||
-rw-r--r-- | src/wallet.ts | 14 | ||||
-rw-r--r-- | src/walletTypes.ts | 20 |
8 files changed, 359 insertions, 234 deletions
diff --git a/src/dbTypes.ts b/src/dbTypes.ts index 096c3f04e..e39d73672 100644 --- a/src/dbTypes.ts +++ b/src/dbTypes.ts @@ -970,18 +970,6 @@ export interface WireFee { sig: string; } -export enum PurchaseStatus { - /** - * We're currently paying, either for the first - * time or as a re-play potentially with a different - * session ID. - */ - SubmitPay = "submit-pay", - QueryRefund = "query-refund", - ProcessRefund = "process-refund", - Abort = "abort", - Dormant = "dormant", -} /** * Record that stores status information about one purchase, starting from when @@ -995,11 +983,6 @@ export interface PurchaseRecord { proposalId: string; /** - * Status of this purchase. - */ - status: PurchaseStatus; - - /** * Hash of the contract terms. */ contractTermsHash: string; @@ -1021,10 +1004,9 @@ export interface PurchaseRecord { merchantSig: string; /** - * The purchase isn't active anymore, it's either successfully paid or - * refunded/aborted. + * A successful payment has been made. */ - finished: boolean; + payFinished: boolean; /** * Pending refunds for the purchase. @@ -1046,13 +1028,15 @@ export interface PurchaseRecord { * When was the last refund made? * Set to 0 if no refund was made on the purchase. */ - lastRefundTimestamp: Timestamp | undefined; + lastRefundStatusTimestamp: Timestamp | undefined; /** * Last session signature that we submitted to /pay (if any). */ lastSessionId: string | undefined; + refundStatusRequested: boolean; + /** * An abort (with refund) was requested for this (incomplete!) purchase. */ @@ -1063,9 +1047,29 @@ export interface PurchaseRecord { */ abortDone: boolean; - retryInfo: RetryInfo; + payRetryInfo: RetryInfo; - lastError: OperationError | undefined; + lastPayError: OperationError | undefined; + + /** + * Retry information for querying the refund status with the merchant. + */ + refundStatusRetryInfo: RetryInfo; + + /** + * Last error (or undefined) for querying the refund status with the merchant. + */ + lastRefundStatusError: OperationError | undefined; + + /** + * Retry information for querying the refund status with the merchant. + */ + refundApplyRetryInfo: RetryInfo; + + /** + * Last error (or undefined) for querying the refund status with the merchant. + */ + lastRefundApplyError: OperationError | undefined; } /** diff --git a/src/wallet-impl/balance.ts b/src/wallet-impl/balance.ts index a1351014c..082e62563 100644 --- a/src/wallet-impl/balance.ts +++ b/src/wallet-impl/balance.ts @@ -138,7 +138,7 @@ export async function getBalances( }); await tx.iter(Stores.purchases).forEach(t => { - if (t.finished) { + if (t.payFinished) { return; } for (const c of t.payReq.coins) { diff --git a/src/wallet-impl/exchanges.ts b/src/wallet-impl/exchanges.ts index b6a2f1c8a..3814971a3 100644 --- a/src/wallet-impl/exchanges.ts +++ b/src/wallet-impl/exchanges.ts @@ -44,7 +44,10 @@ import { } from "../util/query"; import * as Amounts from "../util/amounts"; import { parsePaytoUri } from "../util/payto"; -import { OperationFailedAndReportedError } from "./errors"; +import { + OperationFailedAndReportedError, + guardOperationException, +} from "./errors"; async function denominationRecordFromKeys( ws: InternalWalletState, @@ -307,12 +310,24 @@ async function updateExchangeWithWireInfo( }); } +export async function updateExchangeFromUrl( + ws: InternalWalletState, + baseUrl: string, + force: boolean = false, +): Promise<ExchangeRecord> { + const onOpErr = (e: OperationError) => setExchangeError(ws, baseUrl, e); + return await guardOperationException( + () => updateExchangeFromUrlImpl(ws, baseUrl, force), + onOpErr, + ); +} + /** * Update or add exchange DB entry by fetching the /keys and /wire information. * Optionally link the reserve entry to the new or existing * exchange entry in then DB. */ -export async function updateExchangeFromUrl( +async function updateExchangeFromUrlImpl( ws: InternalWalletState, baseUrl: string, force: boolean = false, diff --git a/src/wallet-impl/history.ts b/src/wallet-impl/history.ts index 5e93ab878..23887e895 100644 --- a/src/wallet-impl/history.ts +++ b/src/wallet-impl/history.ts @@ -82,7 +82,7 @@ export async function getHistory( type: "pay", explicit: false, }); - if (p.lastRefundTimestamp) { + if (p.lastRefundStatusTimestamp) { const contractAmount = Amounts.parseOrThrow(p.contractTerms.amount); const amountsPending = Object.keys(p.refundsPending).map(x => Amounts.parseOrThrow(p.refundsPending[x].refund_amount), @@ -103,7 +103,7 @@ export async function getHistory( merchantName: p.contractTerms.merchant.name, refundAmount: amount, }, - timestamp: p.lastRefundTimestamp, + timestamp: p.lastRefundStatusTimestamp, type: "refund", explicit: false, }); diff --git a/src/wallet-impl/pay.ts b/src/wallet-impl/pay.ts index f07b0328c..cec1b6bc5 100644 --- a/src/wallet-impl/pay.ts +++ b/src/wallet-impl/pay.ts @@ -55,7 +55,6 @@ import { ProposalStatus, initRetryInfo, updateRetryInfoTimeout, - PurchaseStatus, } from "../dbTypes"; import * as Amounts from "../util/amounts"; import { @@ -344,18 +343,22 @@ async function recordConfirmPay( abortRequested: false, contractTerms: d.contractTerms, contractTermsHash: d.contractTermsHash, - finished: false, + payFinished: false, lastSessionId: undefined, merchantSig: d.merchantSig, payReq, refundsDone: {}, refundsPending: {}, acceptTimestamp: getTimestampNow(), - lastRefundTimestamp: undefined, + lastRefundStatusTimestamp: undefined, proposalId: proposal.proposalId, - retryInfo: initRetryInfo(), - lastError: undefined, - status: PurchaseStatus.SubmitPay, + lastPayError: undefined, + lastRefundStatusError: undefined, + payRetryInfo: initRetryInfo(), + refundStatusRetryInfo: initRetryInfo(), + refundStatusRequested: false, + lastRefundApplyError: undefined, + refundApplyRetryInfo: initRetryInfo(), }; await runWithWriteTransaction( @@ -402,7 +405,7 @@ export async function abortFailedPayment( if (!purchase) { throw Error("Purchase not found, unable to abort with refund"); } - if (purchase.finished) { + if (purchase.payFinished) { throw Error("Purchase already finished, not aborting"); } if (purchase.abortDone) { @@ -464,23 +467,65 @@ async function incrementProposalRetry( }); } -async function incrementPurchaseRetry( +async function incrementPurchasePayRetry( ws: InternalWalletState, proposalId: string, err: OperationError | undefined, ): Promise<void> { - console.log("incrementing purchase retry with error", err); + console.log("incrementing purchase pay retry with error", err); await runWithWriteTransaction(ws.db, [Stores.purchases], async tx => { const pr = await tx.get(Stores.purchases, proposalId); if (!pr) { return; } - if (!pr.retryInfo) { + if (!pr.payRetryInfo) { return; } - pr.retryInfo.retryCounter++; - updateRetryInfoTimeout(pr.retryInfo); - pr.lastError = err; + pr.payRetryInfo.retryCounter++; + updateRetryInfoTimeout(pr.payRetryInfo); + pr.lastPayError = err; + await tx.put(Stores.purchases, pr); + }); +} + +async function incrementPurchaseQueryRefundRetry( + ws: InternalWalletState, + proposalId: string, + err: OperationError | undefined, +): Promise<void> { + console.log("incrementing purchase refund query retry with error", err); + await runWithWriteTransaction(ws.db, [Stores.purchases], async tx => { + const pr = await tx.get(Stores.purchases, proposalId); + if (!pr) { + return; + } + if (!pr.refundStatusRetryInfo) { + return; + } + pr.refundStatusRetryInfo.retryCounter++; + updateRetryInfoTimeout(pr.refundStatusRetryInfo); + pr.lastRefundStatusError = err; + await tx.put(Stores.purchases, pr); + }); +} + +async function incrementPurchaseApplyRefundRetry( + ws: InternalWalletState, + proposalId: string, + err: OperationError | undefined, +): Promise<void> { + console.log("incrementing purchase refund apply retry with error", err); + await runWithWriteTransaction(ws.db, [Stores.purchases], async tx => { + const pr = await tx.get(Stores.purchases, proposalId); + if (!pr) { + return; + } + if (!pr.refundApplyRetryInfo) { + return; + } + pr.refundApplyRetryInfo.retryCounter++; + updateRetryInfoTimeout(pr.refundStatusRetryInfo); + pr.lastRefundApplyError = err; await tx.put(Stores.purchases, pr); }); } @@ -652,10 +697,9 @@ export async function submitPay( // FIXME: properly display error throw Error("merchant payment signature invalid"); } - purchase.finished = true; - purchase.status = PurchaseStatus.Dormant; - purchase.lastError = undefined; - purchase.retryInfo = initRetryInfo(false); + purchase.payFinished = true; + purchase.lastPayError = undefined; + purchase.payRetryInfo = initRetryInfo(false); const modifiedCoins: CoinRecord[] = []; for (const pc of purchase.payReq.coins) { const c = await oneShotGet(ws.db, Stores.coins, pc.coin_pub); @@ -986,90 +1030,6 @@ export async function getFullRefundFees( return feeAcc; } -async function submitRefundsToExchange( - ws: InternalWalletState, - proposalId: string, -): Promise<void> { - const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId); - if (!purchase) { - console.error("not submitting refunds, payment not found:"); - return; - } - const pendingKeys = Object.keys(purchase.refundsPending); - if (pendingKeys.length === 0) { - console.log("no pending refunds"); - return; - } - for (const pk of pendingKeys) { - const perm = purchase.refundsPending[pk]; - const req: RefundRequest = { - coin_pub: perm.coin_pub, - h_contract_terms: purchase.contractTermsHash, - merchant_pub: purchase.contractTerms.merchant_pub, - merchant_sig: perm.merchant_sig, - refund_amount: perm.refund_amount, - refund_fee: perm.refund_fee, - rtransaction_id: perm.rtransaction_id, - }; - console.log("sending refund permission", perm); - // FIXME: not correct once we support multiple exchanges per payment - const exchangeUrl = purchase.payReq.coins[0].exchange_url; - const reqUrl = new URL("refund", exchangeUrl); - const resp = await ws.http.postJson(reqUrl.href, req); - console.log("sent refund permission"); - if (resp.status !== 200) { - console.error("refund failed", resp); - continue; - } - - let allRefundsProcessed = false; - - await runWithWriteTransaction( - ws.db, - [Stores.purchases, Stores.coins], - async tx => { - const p = await tx.get(Stores.purchases, proposalId); - if (!p) { - return; - } - if (p.refundsPending[pk]) { - p.refundsDone[pk] = p.refundsPending[pk]; - delete p.refundsPending[pk]; - } - if (Object.keys(p.refundsPending).length === 0) { - p.retryInfo = initRetryInfo(); - p.lastError = undefined; - p.status = PurchaseStatus.Dormant; - allRefundsProcessed = true; - } - await tx.put(Stores.purchases, p); - const c = await tx.get(Stores.coins, perm.coin_pub); - if (!c) { - console.warn("coin not found, can't apply refund"); - return; - } - const refundAmount = Amounts.parseOrThrow(perm.refund_amount); - const refundFee = Amounts.parseOrThrow(perm.refund_fee); - c.status = CoinStatus.Dirty; - c.currentAmount = Amounts.add(c.currentAmount, refundAmount).amount; - c.currentAmount = Amounts.sub(c.currentAmount, refundFee).amount; - await tx.put(Stores.coins, c); - }, - ); - if (allRefundsProcessed) { - ws.notify({ - type: NotificationType.RefundFinished, - }) - } - await refresh(ws, perm.coin_pub); - } - - ws.notify({ - type: NotificationType.RefundsSubmitted, - proposalId, - }); -} - async function acceptRefundResponse( ws: InternalWalletState, proposalId: string, @@ -1082,60 +1042,45 @@ async function acceptRefundResponse( throw Error("empty refund"); } - /** - * Add refund to purchase if not already added. - */ - function f(t: PurchaseRecord | undefined): PurchaseRecord | undefined { - if (!t) { + + let numNewRefunds = 0; + + await runWithWriteTransaction(ws.db, [Stores.purchases], async (tx) => { + const p = await tx.get(Stores.purchases, proposalId); + if (!p) { console.error("purchase not found, not adding refunds"); return; } - t.lastRefundTimestamp = getTimestampNow(); - t.status = PurchaseStatus.ProcessRefund; - t.lastError = undefined; - t.retryInfo = initRetryInfo(); + if (!p.refundStatusRequested) { + return; + } + + p.lastRefundStatusTimestamp = getTimestampNow(); + p.lastRefundStatusError = undefined; + p.refundStatusRetryInfo = initRetryInfo(); + p.refundStatusRequested = false; for (const perm of refundPermissions) { if ( - !t.refundsPending[perm.merchant_sig] && - !t.refundsDone[perm.merchant_sig] + !p.refundsPending[perm.merchant_sig] && + !p.refundsDone[perm.merchant_sig] ) { - t.refundsPending[perm.merchant_sig] = perm; + p.refundsPending[perm.merchant_sig] = perm; + numNewRefunds++; } } - return t; - } - // Add the refund permissions to the purchase within a DB transaction - await oneShotMutate(ws.db, Stores.purchases, proposalId, f); - await submitRefundsToExchange(ws, proposalId); -} -async function queryRefund( - ws: InternalWalletState, - proposalId: string, -): Promise<void> { - const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId); - if (purchase?.status !== PurchaseStatus.QueryRefund) { - return; - } + if (numNewRefunds) { + p.lastRefundApplyError = undefined; + p.refundApplyRetryInfo = initRetryInfo(); + } - const refundUrlObj = new URL( - "refund", - purchase.contractTerms.merchant_base_url, - ); - refundUrlObj.searchParams.set("order_id", purchase.contractTerms.order_id); - const refundUrl = refundUrlObj.href; - let resp; - try { - resp = await ws.http.get(refundUrl); - } catch (e) { - console.error("error downloading refund permission", e); - throw e; + await tx.put(Stores.purchases, p); + }); + if (numNewRefunds > 0) { + await processPurchaseApplyRefund(ws, proposalId); } - - const refundResponse = MerchantRefundResponse.checked(resp.responseJson); - await acceptRefundResponse(ws, proposalId, refundResponse); } async function startRefundQuery( @@ -1151,21 +1096,12 @@ async function startRefundQuery( console.log("no purchase found for refund URL"); return false; } - if (p.status === PurchaseStatus.QueryRefund) { - return true; - } - if (p.status === PurchaseStatus.ProcessRefund) { - return true; - } - if (p.status !== PurchaseStatus.Dormant) { - console.log( - `can't apply refund, as payment isn't done (status ${p.status})`, - ); - return false; + if (p.refundStatusRequested) { + } - p.lastError = undefined; - p.status = PurchaseStatus.QueryRefund; - p.retryInfo = initRetryInfo(); + p.refundStatusRequested = true; + p.lastRefundStatusError = undefined; + p.refundStatusRetryInfo = initRetryInfo(); await tx.put(Stores.purchases, p); return true; }, @@ -1175,7 +1111,7 @@ async function startRefundQuery( return; } - await processPurchase(ws, proposalId); + await processPurchaseQueryRefund(ws, proposalId); } /** @@ -1210,19 +1146,19 @@ export async function applyRefund( return purchase.contractTermsHash; } -export async function processPurchase( +export async function processPurchasePay( ws: InternalWalletState, proposalId: string, ): Promise<void> { const onOpErr = (e: OperationError) => - incrementPurchaseRetry(ws, proposalId, e); + incrementPurchasePayRetry(ws, proposalId, e); await guardOperationException( - () => processPurchaseImpl(ws, proposalId), + () => processPurchasePayImpl(ws, proposalId), onOpErr, ); } -async function processPurchaseImpl( +async function processPurchasePayImpl( ws: InternalWalletState, proposalId: string, ): Promise<void> { @@ -1230,24 +1166,146 @@ async function processPurchaseImpl( if (!purchase) { return; } - logger.trace(`processing purchase ${proposalId}`); - switch (purchase.status) { - case PurchaseStatus.Dormant: - return; - case PurchaseStatus.Abort: - // FIXME - break; - case PurchaseStatus.SubmitPay: - break; - case PurchaseStatus.QueryRefund: - await queryRefund(ws, proposalId); - break; - case PurchaseStatus.ProcessRefund: - console.log("submitting refunds to exchange (toplvl)"); - await submitRefundsToExchange(ws, proposalId); - console.log("after submitting refunds to exchange (toplvl)"); - break; - default: - throw assertUnreachable(purchase.status); + logger.trace(`processing purchase pay ${proposalId}`); + if (purchase.payFinished) { + return; + } + await submitPay(ws, proposalId, purchase.lastSessionId); +} + +export async function processPurchaseQueryRefund( + ws: InternalWalletState, + proposalId: string, +): Promise<void> { + const onOpErr = (e: OperationError) => + incrementPurchaseQueryRefundRetry(ws, proposalId, e); + await guardOperationException( + () => processPurchaseQueryRefundImpl(ws, proposalId), + onOpErr, + ); +} + +async function processPurchaseQueryRefundImpl( + ws: InternalWalletState, + proposalId: string, +): Promise<void> { + const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId); + if (!purchase) { + return; + } + if (!purchase.refundStatusRequested) { + return; + } + + const refundUrlObj = new URL( + "refund", + purchase.contractTerms.merchant_base_url, + ); + refundUrlObj.searchParams.set("order_id", purchase.contractTerms.order_id); + const refundUrl = refundUrlObj.href; + let resp; + try { + resp = await ws.http.get(refundUrl); + } catch (e) { + console.error("error downloading refund permission", e); + throw e; + } + + const refundResponse = MerchantRefundResponse.checked(resp.responseJson); + await acceptRefundResponse(ws, proposalId, refundResponse); +} + +export async function processPurchaseApplyRefund( + ws: InternalWalletState, + proposalId: string, +): Promise<void> { + const onOpErr = (e: OperationError) => + incrementPurchaseApplyRefundRetry(ws, proposalId, e); + await guardOperationException( + () => processPurchaseApplyRefundImpl(ws, proposalId), + onOpErr, + ); +} + +async function processPurchaseApplyRefundImpl( + ws: InternalWalletState, + proposalId: string, +): Promise<void> { + const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId); + if (!purchase) { + console.error("not submitting refunds, payment not found:"); + return; + } + const pendingKeys = Object.keys(purchase.refundsPending); + if (pendingKeys.length === 0) { + console.log("no pending refunds"); + return; + } + for (const pk of pendingKeys) { + const perm = purchase.refundsPending[pk]; + const req: RefundRequest = { + coin_pub: perm.coin_pub, + h_contract_terms: purchase.contractTermsHash, + merchant_pub: purchase.contractTerms.merchant_pub, + merchant_sig: perm.merchant_sig, + refund_amount: perm.refund_amount, + refund_fee: perm.refund_fee, + rtransaction_id: perm.rtransaction_id, + }; + console.log("sending refund permission", perm); + // FIXME: not correct once we support multiple exchanges per payment + const exchangeUrl = purchase.payReq.coins[0].exchange_url; + const reqUrl = new URL("refund", exchangeUrl); + const resp = await ws.http.postJson(reqUrl.href, req); + console.log("sent refund permission"); + if (resp.status !== 200) { + console.error("refund failed", resp); + continue; + } + + let allRefundsProcessed = false; + + await runWithWriteTransaction( + ws.db, + [Stores.purchases, Stores.coins], + async tx => { + const p = await tx.get(Stores.purchases, proposalId); + if (!p) { + return; + } + if (p.refundsPending[pk]) { + p.refundsDone[pk] = p.refundsPending[pk]; + delete p.refundsPending[pk]; + } + if (Object.keys(p.refundsPending).length === 0) { + p.refundStatusRetryInfo = initRetryInfo(); + p.lastRefundStatusError = undefined; + allRefundsProcessed = true; + } + await tx.put(Stores.purchases, p); + const c = await tx.get(Stores.coins, perm.coin_pub); + if (!c) { + console.warn("coin not found, can't apply refund"); + return; + } + const refundAmount = Amounts.parseOrThrow(perm.refund_amount); + const refundFee = Amounts.parseOrThrow(perm.refund_fee); + c.status = CoinStatus.Dirty; + c.currentAmount = Amounts.add(c.currentAmount, refundAmount).amount; + c.currentAmount = Amounts.sub(c.currentAmount, refundFee).amount; + await tx.put(Stores.coins, c); + }, + ); + if (allRefundsProcessed) { + ws.notify({ + type: NotificationType.RefundFinished, + }); + } + await refresh(ws, perm.coin_pub); } + + ws.notify({ + type: NotificationType.RefundsSubmitted, + proposalId, + }); } diff --git a/src/wallet-impl/pending.ts b/src/wallet-impl/pending.ts index c86ed6959..169c7e291 100644 --- a/src/wallet-impl/pending.ts +++ b/src/wallet-impl/pending.ts @@ -32,9 +32,7 @@ import { ReserveRecordStatus, CoinStatus, ProposalStatus, - PurchaseStatus, } from "../dbTypes"; -import { assertUnreachable } from "../util/assertUnreachable"; function updateRetryDelay( oldDelay: Duration, @@ -355,28 +353,54 @@ async function gatherPurchasePending( onlyDue: boolean = false, ): Promise<void> { await tx.iter(Stores.purchases).forEach((pr) => { - if (pr.status === PurchaseStatus.Dormant) { - return; + if (!pr.payFinished) { + resp.nextRetryDelay = updateRetryDelay( + resp.nextRetryDelay, + now, + pr.payRetryInfo.nextRetry, + ); + resp.pendingOperations.push({ + type: "pay", + givesLifeness: true, + isReplay: false, + proposalId: pr.proposalId, + retryInfo: pr.payRetryInfo, + lastError: pr.lastPayError, + }); } - resp.nextRetryDelay = updateRetryDelay( - resp.nextRetryDelay, - now, - pr.retryInfo.nextRetry, - ); - if (onlyDue && pr.retryInfo.nextRetry.t_ms > now.t_ms) { - return; + if (pr.refundStatusRequested) { + resp.nextRetryDelay = updateRetryDelay( + resp.nextRetryDelay, + now, + pr.refundStatusRetryInfo.nextRetry, + ); + resp.pendingOperations.push({ + type: "refund-query", + givesLifeness: true, + proposalId: pr.proposalId, + retryInfo: pr.refundStatusRetryInfo, + lastError: pr.lastRefundStatusError, + }); + } + const numRefundsPending = Object.keys(pr.refundsPending).length; + if (numRefundsPending > 0) { + const numRefundsDone = Object.keys(pr.refundsDone).length; + resp.nextRetryDelay = updateRetryDelay( + resp.nextRetryDelay, + now, + pr.refundApplyRetryInfo.nextRetry, + ); + resp.pendingOperations.push({ + type: "refund-apply", + numRefundsDone, + numRefundsPending, + givesLifeness: true, + proposalId: pr.proposalId, + retryInfo: pr.refundApplyRetryInfo, + lastError: pr.lastRefundApplyError, + }); } - resp.pendingOperations.push({ - type: "pay", - givesLifeness: true, - isReplay: false, - proposalId: pr.proposalId, - status: pr.status, - retryInfo: pr.retryInfo, - lastError: pr.lastError, - }); }); - } export async function getPendingOperations( diff --git a/src/wallet.ts b/src/wallet.ts index 489bb2af8..276e3c371 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -49,7 +49,9 @@ import { processDownloadProposal, applyRefund, getFullRefundFees, - processPurchase, + processPurchasePay, + processPurchaseQueryRefund, + processPurchaseApplyRefund, } from "./wallet-impl/pay"; import { @@ -210,7 +212,13 @@ export class Wallet { await processTip(this.ws, pending.tipId); break; case "pay": - await processPurchase(this.ws, pending.proposalId); + await processPurchasePay(this.ws, pending.proposalId); + break; + case "refund-query": + await processPurchaseQueryRefund(this.ws, pending.proposalId); + break; + case "refund-apply": + await processPurchaseApplyRefund(this.ws, pending.proposalId); break; default: assertUnreachable(pending); @@ -710,7 +718,7 @@ export class Wallet { const totalFees = totalRefundFees; return { contractTerms: purchase.contractTerms, - hasRefund: purchase.lastRefundTimestamp !== undefined, + hasRefund: purchase.lastRefundStatusTimestamp !== undefined, totalRefundAmount: totalRefundAmount, totalRefundAndRefreshFees: totalFees, }; diff --git a/src/walletTypes.ts b/src/walletTypes.ts index 2413234eb..f27970330 100644 --- a/src/walletTypes.ts +++ b/src/walletTypes.ts @@ -37,7 +37,6 @@ import { ExchangeWireInfo, WithdrawalSource, RetryInfo, - PurchaseStatus, } from "./dbTypes"; import { CoinPaySig, ContractTerms, PayReq } from "./talerTypes"; @@ -681,11 +680,26 @@ export interface PendingPayOperation { type: "pay"; proposalId: string; isReplay: boolean; - status: PurchaseStatus; retryInfo: RetryInfo, lastError: OperationError | undefined; } +export interface PendingRefundQueryOperation { + type: "refund-query"; + proposalId: string; + retryInfo: RetryInfo, + lastError: OperationError | undefined; +} + +export interface PendingRefundApplyOperation { + type: "refund-apply"; + proposalId: string; + retryInfo: RetryInfo, + lastError: OperationError | undefined; + numRefundsPending: number; + numRefundsDone: number; +} + export interface PendingOperationInfoCommon { type: string; givesLifeness: boolean; @@ -703,6 +717,8 @@ export type PendingOperationInfo = PendingOperationInfoCommon & | PendingProposalDownloadOperation | PendingProposalChoiceOperation | PendingPayOperation + | PendingRefundQueryOperation + | PendingRefundApplyOperation ); export interface PendingOperationsResponse { |