gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: wallet-core: DD37 fixes and F


From: gnunet
Subject: [taler-wallet-core] branch master updated: wallet-core: DD37 fixes and FIXME comments for merchant payments
Date: Thu, 25 May 2023 11:13:28 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new 040616086 wallet-core: DD37 fixes and FIXME comments for merchant 
payments
040616086 is described below

commit 0406160869e7f9aa9e863acad58a160a14014467
Author: Florian Dold <florian@dold.me>
AuthorDate: Thu May 25 11:13:19 2023 +0200

    wallet-core: DD37 fixes and FIXME comments for merchant payments
---
 packages/taler-util/src/transactions-types.ts      |   2 -
 .../src/operations/pay-merchant.ts                 | 128 +++++++++++++++------
 2 files changed, 96 insertions(+), 34 deletions(-)

diff --git a/packages/taler-util/src/transactions-types.ts 
b/packages/taler-util/src/transactions-types.ts
index 84c60a847..03c894fe6 100644
--- a/packages/taler-util/src/transactions-types.ts
+++ b/packages/taler-util/src/transactions-types.ts
@@ -86,8 +86,6 @@ export enum TransactionMajorState {
   Failed = "failed",
   // Only used for the notification, never in the transaction history
   Deleted = "deleted",
-  // Placeholder until D37 is fully implemented
-  Unknown = "unknown",
 }
 
 export enum TransactionMinorState {
diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts 
b/packages/taler-wallet-core/src/operations/pay-merchant.ts
index 54953246d..13fb2cb18 100644
--- a/packages/taler-wallet-core/src/operations/pay-merchant.ts
+++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2019-2022 Taler Systems S.A.
+ (C) 2019-2023 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -40,7 +40,6 @@ import {
   CoinRefreshRequest,
   ConfirmPayResult,
   ConfirmPayResultType,
-  constructPayUri,
   ContractTermsUtil,
   Duration,
   encodeCrock,
@@ -63,6 +62,7 @@ import {
   randomBytes,
   RefreshReason,
   StartRefundQueryForUriResponse,
+  stringifyTalerUri,
   TalerError,
   TalerErrorCode,
   TalerErrorDetail,
@@ -197,16 +197,25 @@ async function failProposalPermanently(
   proposalId: string,
   err: TalerErrorDetail,
 ): Promise<void> {
-  await ws.db
+  const transactionId = constructTransactionIdentifier({
+    tag: TransactionType.Payment,
+    proposalId,
+  });
+  const transitionInfo = await ws.db
     .mktx((x) => [x.purchases])
     .runReadWrite(async (tx) => {
       const p = await tx.purchases.get(proposalId);
       if (!p) {
         return;
       }
+      // FIXME: We don't store the error detail here?!
+      const oldTxState = computePayMerchantTransactionState(p);
       p.purchaseStatus = PurchaseStatus.FailedClaim;
+      const newTxState = computePayMerchantTransactionState(p);
       await tx.purchases.put(p);
+      return { oldTxState, newTxState };
     });
+  notifyTransition(ws, transactionId, transitionInfo);
 }
 
 function getProposalRequestTimeout(retryInfo?: RetryInfo): Duration {
@@ -226,8 +235,6 @@ function getPayRequestTimeout(purchase: PurchaseRecord): 
Duration {
 
 /**
  * Return the proposal download data for a purchase, throw if not available.
- *
- * (Async since in the future this will query the DB.)
  */
 export async function expectProposalDownload(
   ws: InternalWalletState,
@@ -314,10 +321,9 @@ export function extractContractData(
   };
 }
 
-export async function processDownloadProposal(
+async function processDownloadProposal(
   ws: InternalWalletState,
   proposalId: string,
-  options: object = {},
 ): Promise<OperationAttemptResult> {
   const proposal = await ws.db
     .mktx((x) => [x.purchases])
@@ -339,6 +345,11 @@ export async function processDownloadProposal(
     };
   }
 
+  const transactionId = constructTransactionIdentifier({
+    tag: TransactionType.Payment,
+    proposalId,
+  });
+
   const orderClaimUrl = new URL(
     `orders/${proposal.orderId}/claim`,
     proposal.merchantBaseUrl,
@@ -363,7 +374,8 @@ export async function processDownloadProposal(
     });
 
   // FIXME: Do this in the background using the new return value
-  const httpResponse = await ws.http.postJson(orderClaimUrl, requestBody, {
+  const httpResponse = await ws.http.fetch(orderClaimUrl, {
+    body: requestBody,
     timeout: getProposalRequestTimeout(retryRecord?.retryInfo),
   });
   const r = await readSuccessResponseJsonOrErrorCode(
@@ -388,7 +400,7 @@ export async function processDownloadProposal(
   const proposalResp = r.response;
 
   // The proposalResp contains the contract terms as raw JSON,
-  // as the coded to parse them doesn't necessarily round-trip.
+  // as the code to parse them doesn't necessarily round-trip.
   // We need this raw JSON to compute the contract terms hash.
 
   // FIXME: Do better error handling, check if the
@@ -496,7 +508,7 @@ export async function processDownloadProposal(
 
   logger.trace(`extracted contract data: ${j2s(contractData)}`);
 
-  await ws.db
+  const transitionInfo = await ws.db
     .mktx((x) => [x.purchases, x.contractTerms])
     .runReadWrite(async (tx) => {
       const p = await tx.purchases.get(proposalId);
@@ -506,6 +518,7 @@ export async function processDownloadProposal(
       if (p.purchaseStatus !== PurchaseStatus.DownloadingProposal) {
         return;
       }
+      const oldTxState = computePayMerchantTransactionState(p);
       p.download = {
         contractTermsHash,
         contractTermsMerchantSig: contractData.merchantSig,
@@ -523,18 +536,28 @@ export async function processDownloadProposal(
       ) {
         const differentPurchase =
           await tx.purchases.indexes.byFulfillmentUrl.get(fulfillmentUrl);
+        // FIXME: Adjust this to account for refunds, don't count as repurchase
+        // if original order is refunded.
         if (differentPurchase) {
           logger.warn("repurchase detected");
           p.purchaseStatus = PurchaseStatus.RepurchaseDetected;
           p.repurchaseProposalId = differentPurchase.proposalId;
           await tx.purchases.put(p);
-          return;
         }
+      } else {
+        p.purchaseStatus = PurchaseStatus.Proposed;
+        await tx.purchases.put(p);
+      }
+      const newTxState = computePayMerchantTransactionState(p);
+      return {
+        oldTxState,
+        newTxState,
       }
-      p.purchaseStatus = PurchaseStatus.Proposed;
-      await tx.purchases.put(p);
     });
 
+  notifyTransition(ws, transactionId, transitionInfo);
+
+  // FIXME: Deprecated pre-DD37 notification, remove eventually
   ws.notify({
     type: NotificationType.ProposalDownloaded,
     proposalId: proposal.proposalId,
@@ -547,13 +570,11 @@ export async function processDownloadProposal(
 }
 
 /**
- * Download a proposal and store it in the database.
- * Returns an id for it to retrieve it later.
- *
- * @param sessionId Current session ID, if the proposal is being
- *  downloaded in the context of a session ID.
+ * Create a new purchase transaction if necessary.  If a purchase
+ * record for the provided arguments already exists,
+ * return the old proposal ID.
  */
-async function startDownloadProposal(
+async function createPurchase(
   ws: InternalWalletState,
   merchantBaseUrl: string,
   orderId: string,
@@ -619,7 +640,7 @@ async function startDownloadProposal(
     posConfirmation: undefined,
   };
 
-  await ws.db
+  const transitionInfo = await ws.db
     .mktx((x) => [x.purchases])
     .runReadWrite(async (tx) => {
       const existingRecord = await tx.purchases.indexes.byUrlAndOrderId.get([
@@ -628,11 +649,25 @@ async function startDownloadProposal(
       ]);
       if (existingRecord) {
         // Created concurrently
-        return;
+        return undefined;
       }
       await tx.purchases.put(proposalRecord);
+      const oldTxState: TransactionState = {
+        major: TransactionMajorState.None,
+      };
+      const newTxState = computePayMerchantTransactionState(proposalRecord);
+      return {
+        oldTxState,
+        newTxState,
+      }
     });
 
+  const transactionId = constructTransactionIdentifier({
+    tag: TransactionType.Payment,
+    proposalId,
+  });
+  notifyTransition(ws, transactionId, transitionInfo);
+
   await processDownloadProposal(ws, proposalId);
   return proposalId;
 }
@@ -643,8 +678,12 @@ async function storeFirstPaySuccess(
   sessionId: string | undefined,
   payResponse: MerchantPayResponse,
 ): Promise<void> {
+  const transactionId = constructTransactionIdentifier({
+    tag: TransactionType.Payment,
+    proposalId,
+  });
   const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
-  await ws.db
+  const transitionInfo = await ws.db
     .mktx((x) => [x.purchases, x.contractTerms])
     .runReadWrite(async (tx) => {
       const purchase = await tx.purchases.get(proposalId);
@@ -658,6 +697,7 @@ async function storeFirstPaySuccess(
         logger.warn("payment success already stored");
         return;
       }
+      const oldTxState = computePayMerchantTransactionState(purchase);
       if (purchase.purchaseStatus === PurchaseStatus.Paying) {
         purchase.purchaseStatus = PurchaseStatus.Done;
       }
@@ -686,7 +726,13 @@ async function storeFirstPaySuccess(
         );
       }
       await tx.purchases.put(purchase);
+      const newTxState = computePayMerchantTransactionState(purchase);
+      return {
+        oldTxState,
+        newTxState,
+      }
     });
+  notifyTransition(ws, transactionId, transitionInfo);
 }
 
 async function storePayReplaySuccess(
@@ -694,7 +740,11 @@ async function storePayReplaySuccess(
   proposalId: string,
   sessionId: string | undefined,
 ): Promise<void> {
-  await ws.db
+  const transactionId = constructTransactionIdentifier({
+    tag: TransactionType.Payment,
+    proposalId,
+  });
+  const transitionInfo = await ws.db
     .mktx((x) => [x.purchases])
     .runReadWrite(async (tx) => {
       const purchase = await tx.purchases.get(proposalId);
@@ -707,6 +757,7 @@ async function storePayReplaySuccess(
       if (isFirst) {
         throw Error("invalid payment state");
       }
+      const oldTxState = computePayMerchantTransactionState(purchase);
       if (
         purchase.purchaseStatus === PurchaseStatus.Paying ||
         purchase.purchaseStatus === PurchaseStatus.PayingReplay
@@ -715,7 +766,10 @@ async function storePayReplaySuccess(
       }
       purchase.lastSessionId = sessionId;
       await tx.purchases.put(purchase);
+      const newTxState = computePayMerchantTransactionState(purchase);
+      return { oldTxState, newTxState };
     });
+  notifyTransition(ws, transactionId, transitionInfo);
 }
 
 /**
@@ -876,6 +930,10 @@ async function unblockBackup(
     });
 }
 
+// FIXME: Should probably not be exported in its current state
+// FIXME: Should take a transaction ID instead of a proposal ID
+// FIXME: Does way more than checking the payment
+// FIXME: Should return immediately.
 export async function checkPaymentByProposalId(
   ws: InternalWalletState,
   proposalId: string,
@@ -918,13 +976,14 @@ export async function checkPaymentByProposalId(
     proposalId,
   });
 
-  const talerUri = constructPayUri(
-    proposal.merchantBaseUrl,
-    proposal.orderId,
-    proposal.lastSessionId ?? proposal.downloadSessionId ?? "",
-    proposal.claimToken,
-    proposal.noncePriv,
-  );
+  const talerUri = stringifyTalerUri({
+    type: TalerUriAction.Pay,
+    merchantBaseUrl: proposal.merchantBaseUrl,
+    orderId: proposal.orderId,
+    sessionId: proposal.lastSessionId ?? proposal.downloadSessionId ?? "",
+    claimToken: proposal.claimToken,
+    noncePriv: proposal.noncePriv,
+  });
 
   // First check if we already paid for it.
   const purchase = await ws.db
@@ -989,17 +1048,22 @@ export async function checkPaymentByProposalId(
       "automatically re-submitting payment with different session ID",
     );
     logger.trace(`last: ${purchase.lastSessionId}, current: ${sessionId}`);
-    await ws.db
+    const transitionInfo = await ws.db
       .mktx((x) => [x.purchases])
       .runReadWrite(async (tx) => {
         const p = await tx.purchases.get(proposalId);
         if (!p) {
           return;
         }
+        const oldTxState = computePayMerchantTransactionState(p);
         p.lastSessionId = sessionId;
         p.purchaseStatus = PurchaseStatus.PayingReplay;
         await tx.purchases.put(p);
+        const newTxState = computePayMerchantTransactionState(p);
+        return { oldTxState, newTxState };
       });
+    notifyTransition(ws, transactionId, transitionInfo);
+    // FIXME: What about error handling?! This doesn't properly store errors 
in the DB.
     const r = await processPurchasePay(ws, proposalId, { forceNow: true });
     if (r.type !== OperationAttemptResultType.Finished) {
       // FIXME: This does not surface the original error
@@ -1092,7 +1156,7 @@ export async function preparePayForUri(
     );
   }
 
-  const proposalId = await startDownloadProposal(
+  const proposalId = await createPurchase(
     ws,
     uriResult.merchantBaseUrl,
     uriResult.orderId,

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]