diff options
Diffstat (limited to 'packages/taler-wallet-core/src/operations/pay-merchant.ts')
-rw-r--r-- | packages/taler-wallet-core/src/operations/pay-merchant.ts | 102 |
1 files changed, 69 insertions, 33 deletions
diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts b/packages/taler-wallet-core/src/operations/pay-merchant.ts index d590177c2..e805c0ea1 100644 --- a/packages/taler-wallet-core/src/operations/pay-merchant.ts +++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts @@ -115,6 +115,7 @@ import { throwUnexpectedRequestError, } from "../util/http.js"; import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js"; +import { GetReadOnlyAccess } from "../util/query.js"; import { OperationAttemptResult, OperationAttemptResultType, @@ -256,12 +257,34 @@ function getPayRequestTimeout(purchase: PurchaseRecord): Duration { * (Async since in the future this will query the DB.) */ export async function expectProposalDownload( + ws: InternalWalletState, p: PurchaseRecord, -): Promise<ProposalDownload> { +): Promise<{ + contractData: WalletContractData; + contractTermsRaw: any; +}> { if (!p.download) { throw Error("expected proposal to be downloaded"); } - return p.download; + const download = p.download; + return await ws.db + .mktx((x) => [x.contractTerms]) + .runReadOnly(async (tx) => { + const contractTerms = await tx.contractTerms.get( + download.contractTermsHash, + ); + if (!contractTerms) { + throw Error("contract terms not found"); + } + return { + contractData: extractContractData( + contractTerms.contractTermsRaw, + download.contractTermsHash, + download.contractTermsMerchantSig, + ), + contractTermsRaw: contractTerms.contractTermsRaw, + }; + }); } export function extractContractData( @@ -494,7 +517,7 @@ export async function processDownloadProposal( logger.trace(`extracted contract data: ${j2s(contractData)}`); await ws.db - .mktx((x) => [x.purchases]) + .mktx((x) => [x.purchases, x.contractTerms]) .runReadWrite(async (tx) => { const p = await tx.purchases.get(proposalId); if (!p) { @@ -504,9 +527,15 @@ export async function processDownloadProposal( return; } p.download = { - contractData, - contractTermsRaw: proposalResp.contract_terms, + contractTermsHash, + contractTermsMerchantSig: contractData.merchantSig, + currency: contractData.amount.currency, + fulfillmentUrl: contractData.fulfillmentUrl, }; + await tx.contractTerms.put({ + h: contractTermsHash, + contractTermsRaw: proposalResp.contract_terms, + }); if ( fulfillmentUrl && (fulfillmentUrl.startsWith("http://") || @@ -636,7 +665,7 @@ async function storeFirstPaySuccess( ): Promise<void> { const now = AbsoluteTime.toTimestamp(AbsoluteTime.now()); await ws.db - .mktx((x) => [x.purchases]) + .mktx((x) => [x.purchases, x.contractTerms]) .runReadWrite(async (tx) => { const purchase = await tx.purchases.get(proposalId); @@ -655,7 +684,18 @@ async function storeFirstPaySuccess( purchase.timestampFirstSuccessfulPay = now; purchase.lastSessionId = sessionId; purchase.merchantPaySig = paySig; - const protoAr = purchase.download!.contractData.autoRefund; + const dl = purchase.download; + checkDbInvariant(!!dl); + const contractTermsRecord = await tx.contractTerms.get( + dl.contractTermsHash, + ); + checkDbInvariant(!!contractTermsRecord); + const contractData = extractContractData( + contractTermsRecord.contractTermsRaw, + dl.contractTermsHash, + dl.contractTermsMerchantSig, + ); + const protoAr = contractData.autoRefund; if (protoAr) { const ar = Duration.fromTalerProtocolDuration(protoAr); logger.info("auto_refund present"); @@ -739,7 +779,7 @@ async function handleInsufficientFunds( throw new TalerProtocolViolationError(); } - const { contractData } = proposal.download!; + const { contractData } = await expectProposalDownload(ws, proposal); const prevPayCoins: PreviousPayCoins = []; @@ -1254,11 +1294,7 @@ export async function checkPaymentByProposalId( throw Error("existing proposal is in wrong state"); } } - const d = proposal.download; - if (!d) { - logger.error("bad proposal", proposal); - throw Error("proposal is in invalid state"); - } + const d = await expectProposalDownload(ws, proposal); const contractData = d.contractData; const merchantSig = d.contractData.merchantSig; if (!merchantSig) { @@ -1338,7 +1374,7 @@ export async function checkPaymentByProposalId( // FIXME: This does not surface the original error throw Error("submitting pay failed"); } - const download = await expectProposalDownload(purchase); + const download = await expectProposalDownload(ws, purchase); return { status: PreparePayResultType.AlreadyConfirmed, contractTerms: download.contractTermsRaw, @@ -1349,7 +1385,7 @@ export async function checkPaymentByProposalId( proposalId, }; } else if (!purchase.timestampFirstSuccessfulPay) { - const download = await expectProposalDownload(purchase); + const download = await expectProposalDownload(ws, purchase); return { status: PreparePayResultType.AlreadyConfirmed, contractTerms: download.contractTermsRaw, @@ -1364,7 +1400,7 @@ export async function checkPaymentByProposalId( purchase.purchaseStatus === PurchaseStatus.Paid || purchase.purchaseStatus === PurchaseStatus.QueryingRefund || purchase.purchaseStatus === PurchaseStatus.QueryingAutoRefund; - const download = await expectProposalDownload(purchase); + const download = await expectProposalDownload(ws, purchase); return { status: PreparePayResultType.AlreadyConfirmed, contractTerms: download.contractTermsRaw, @@ -1392,11 +1428,9 @@ export async function getContractTermsDetails( throw Error(`proposal with id ${proposalId} not found`); } - if (!proposal.download || !proposal.download.contractData) { - throw Error("proposal is in invalid state"); - } + const d = await expectProposalDownload(ws, proposal); - return proposal.download.contractData; + return d.contractData; } /** @@ -1516,12 +1550,13 @@ export async function runPayForConfirmPay( .runReadOnly(async (tx) => { return tx.purchases.get(proposalId); }); - if (!purchase?.download) { + if (!purchase) { throw Error("purchase record not available anymore"); } + const d = await expectProposalDownload(ws, purchase); return { type: ConfirmPayResultType.Done, - contractTerms: purchase.download.contractTermsRaw, + contractTerms: d.contractTermsRaw, transactionId: makeEventId(TransactionType.Payment, proposalId), }; } @@ -1599,7 +1634,7 @@ export async function confirmPay( throw Error(`proposal with id ${proposalId} not found`); } - const d = proposal.download; + const d = await expectProposalDownload(ws, proposal); if (!d) { throw Error("proposal is in invalid state"); } @@ -1810,7 +1845,7 @@ export async function processPurchasePay( const payInfo = purchase.payInfo; checkDbInvariant(!!payInfo, "payInfo"); - const download = await expectProposalDownload(purchase); + const download = await expectProposalDownload(ws, purchase); if (!purchase.merchantPaySig) { const payUrl = new URL( `orders/${download.contractData.orderId}/pay`, @@ -2007,7 +2042,7 @@ export async function prepareRefund( const purchase = await ws.db .mktx((x) => [x.purchases]) .runReadOnly(async (tx) => { - return tx.purchases.indexes.byMerchantUrlAndOrderId.get([ + return tx.purchases.indexes.byUrlAndOrderId.get([ parseResult.merchantBaseUrl, parseResult.orderId, ]); @@ -2020,10 +2055,10 @@ export async function prepareRefund( } const awaiting = await queryAndSaveAwaitingRefund(ws, purchase); - const summary = await calculateRefundSummary(purchase); + const summary = await calculateRefundSummary(ws, purchase); const proposalId = purchase.proposalId; - const { contractData: c } = await expectProposalDownload(purchase); + const { contractData: c } = await expectProposalDownload(ws, purchase); return { proposalId, @@ -2380,9 +2415,10 @@ async function acceptRefunds( } async function calculateRefundSummary( + ws: InternalWalletState, p: PurchaseRecord, ): Promise<RefundSummary> { - const download = await expectProposalDownload(p); + const download = await expectProposalDownload(ws, p); let amountRefundGranted = Amounts.getZero( download.contractData.amount.currency, ); @@ -2456,7 +2492,7 @@ export async function applyRefund( const purchase = await ws.db .mktx((x) => [x.purchases]) .runReadOnly(async (tx) => { - return tx.purchases.indexes.byMerchantUrlAndOrderId.get([ + return tx.purchases.indexes.byUrlAndOrderId.get([ parseResult.merchantBaseUrl, parseResult.orderId, ]); @@ -2513,8 +2549,8 @@ export async function applyRefundFromPurchaseId( throw Error("purchase no longer exists"); } - const summary = await calculateRefundSummary(purchase); - const download = await expectProposalDownload(purchase); + const summary = await calculateRefundSummary(ws, purchase); + const download = await expectProposalDownload(ws, purchase); return { contractTermsHash: download.contractData.contractTermsHash, @@ -2542,7 +2578,7 @@ async function queryAndSaveAwaitingRefund( purchase: PurchaseRecord, waitForAutoRefund?: boolean, ): Promise<AmountJson> { - const download = await expectProposalDownload(purchase); + const download = await expectProposalDownload(ws, purchase); const requestUrl = new URL( `orders/${download.contractData.orderId}`, download.contractData.merchantBaseUrl, @@ -2621,7 +2657,7 @@ export async function processPurchaseQueryRefund( return OperationAttemptResult.finishedEmpty(); } - const download = await expectProposalDownload(purchase); + const download = await expectProposalDownload(ws, purchase); if (purchase.timestampFirstSuccessfulPay) { if ( |