gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 02/02: new transactions API: purchases and refunds


From: gnunet
Subject: [taler-wallet-core] 02/02: new transactions API: purchases and refunds
Date: Tue, 12 May 2020 12:14:58 +0200

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

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

commit 67dd0eb06e04466ca01a03955ff8f75d40429c79
Author: Florian Dold <address@hidden>
AuthorDate: Tue May 12 15:44:48 2020 +0530

    new transactions API: purchases and refunds
---
 src/operations/pay.ts          |  15 ++--
 src/operations/refund.ts       |  18 +++--
 src/operations/transactions.ts | 160 +++++++++++++++++++++++++++++++++--------
 src/types/dbTypes.ts           |   4 ++
 src/types/transactions.ts      |   8 +--
 5 files changed, 161 insertions(+), 44 deletions(-)

diff --git a/src/operations/pay.ts b/src/operations/pay.ts
index a7528439..30ccb56c 100644
--- a/src/operations/pay.ts
+++ b/src/operations/pay.ts
@@ -122,6 +122,10 @@ export interface AvailableCoinInfo {
   feeDeposit: AmountJson;
 }
 
+export interface PayCostInfo {
+  totalCost: AmountJson;
+}
+
 /**
  * Compute the total cost of a payment to the customer.
  *
@@ -132,7 +136,7 @@ export interface AvailableCoinInfo {
 export async function getTotalPaymentCost(
   ws: InternalWalletState,
   pcs: PayCoinSelection,
-): Promise<AmountJson> {
+): Promise<PayCostInfo> {
   const costs = [
     pcs.paymentAmount,
     pcs.customerDepositFees,
@@ -163,7 +167,9 @@ export async function getTotalPaymentCost(
     const refreshCost = getTotalRefreshCost(allDenoms, denom, amountLeft);
     costs.push(refreshCost);
   }
-  return Amounts.sum(costs).amount;
+  return {
+    totalCost: Amounts.sum(costs).amount
+  };
 }
 
 /**
@@ -434,6 +440,7 @@ async function recordConfirmPay(
     contractTermsRaw: d.contractTermsRaw,
     contractData: d.contractData,
     lastSessionId: sessionId,
+    payCoinSelection: coinSelection,
     payReq,
     timestampAccept: getTimestampNow(),
     timestampLastRefundStatus: undefined,
@@ -903,8 +910,8 @@ export async function preparePayForUri(
       };
     }
 
-    const totalCost = await getTotalPaymentCost(ws, res);
-    const totalFees = Amounts.sub(totalCost, res.paymentAmount).amount;
+    const costInfo = await getTotalPaymentCost(ws, res);
+    const totalFees = Amounts.sub(costInfo.totalCost, 
res.paymentAmount).amount;
 
     return {
       status: "payment-possible",
diff --git a/src/operations/refund.ts b/src/operations/refund.ts
index 9b18cafd..1ffcd2da 100644
--- a/src/operations/refund.ts
+++ b/src/operations/refund.ts
@@ -36,7 +36,6 @@ import {
   CoinStatus,
   RefundReason,
   RefundEventRecord,
-  RefundInfo,
 } from "../types/dbTypes";
 import { NotificationType } from "../types/notifications";
 import { parseRefundUri } from "../util/taleruri";
@@ -48,7 +47,7 @@ import {
   codecForMerchantRefundResponse,
 } from "../types/talerTypes";
 import { AmountJson } from "../util/amounts";
-import { guardOperationException, OperationFailedError } from "./errors";
+import { guardOperationException } from "./errors";
 import { randomBytes } from "../crypto/primitives/nacl-fast";
 import { encodeCrock } from "../crypto/talerCrypto";
 import { getTimestampNow } from "../util/time";
@@ -159,6 +158,8 @@ async function acceptRefundResponse(
     }
   }
 
+  const now = getTimestampNow();
+
   await ws.db.runWithWriteTransaction(
     [Stores.purchases, Stores.coins, Stores.refreshGroups, 
Stores.refundEvents],
     async (tx) => {
@@ -253,10 +254,16 @@ async function acceptRefundResponse(
       if (numNewRefunds === 0) {
         if (
           p.autoRefundDeadline &&
-          p.autoRefundDeadline.t_ms > getTimestampNow().t_ms
+          p.autoRefundDeadline.t_ms > now.t_ms
         ) {
           queryDone = false;
         }
+      } else {
+        p.refundGroups.push({
+          reason: RefundReason.NormalRefund,
+          refundGroupId,
+          timestampQueried: getTimestampNow(),
+        });
       }
 
       if (Object.keys(unfinishedRefunds).length != 0) {
@@ -264,14 +271,14 @@ async function acceptRefundResponse(
       }
 
       if (queryDone) {
-        p.timestampLastRefundStatus = getTimestampNow();
+        p.timestampLastRefundStatus = now;
         p.lastRefundStatusError = undefined;
         p.refundStatusRetryInfo = initRetryInfo(false);
         p.refundStatusRequested = false;
         console.log("refund query done");
       } else {
         // No error, but we need to try again!
-        p.timestampLastRefundStatus = getTimestampNow();
+        p.timestampLastRefundStatus = now;
         p.refundStatusRetryInfo.retryCounter++;
         updateRetryInfoTimeout(p.refundStatusRetryInfo);
         p.lastRefundStatusError = undefined;
@@ -291,7 +298,6 @@ async function acceptRefundResponse(
 
       // Check if any of the refund groups are done, and we
       // can emit an corresponding event.
-      const now = getTimestampNow();
       for (const g of Object.keys(changedGroups)) {
         let groupDone = true;
         for (const pk of Object.keys(p.refundsPending)) {
diff --git a/src/operations/transactions.ts b/src/operations/transactions.ts
index 8333b66c..e5c704b0 100644
--- a/src/operations/transactions.ts
+++ b/src/operations/transactions.ts
@@ -18,8 +18,8 @@
  * Imports.
  */
 import { InternalWalletState } from "./state";
-import { Stores, ProposalRecord, ReserveRecordStatus } from "../types/dbTypes";
-import { Amounts } from "../util/amounts";
+import { Stores, ReserveRecordStatus, PurchaseRecord } from "../types/dbTypes";
+import { Amounts, AmountJson } from "../util/amounts";
 import { timestampCmp } from "../util/time";
 import {
   TransactionsRequest,
@@ -27,7 +27,7 @@ import {
   Transaction,
   TransactionType,
 } from "../types/transactions";
-import { OrderShortInfo } from "../types/history";
+import { getTotalPaymentCost } from "./pay";
 
 /**
  * Create an event ID from the type and the primary key for the event.
@@ -36,21 +36,49 @@ function makeEventId(type: TransactionType, ...args: 
string[]): string {
   return type + ";" + args.map((x) => encodeURIComponent(x)).join(";");
 }
 
-function getOrderShortInfo(
-  proposal: ProposalRecord,
-): OrderShortInfo | undefined {
-  const download = proposal.download;
-  if (!download) {
-    return undefined;
+
+interface RefundStats {
+  amountInvalid: AmountJson;
+  amountEffective: AmountJson;
+  amountRaw: AmountJson;
+}
+
+function getRefundStats(pr: PurchaseRecord, refundGroupId: string): 
RefundStats {
+  let amountEffective = Amounts.getZero(pr.contractData.amount.currency);
+  let amountInvalid = Amounts.getZero(pr.contractData.amount.currency);
+  let amountRaw = Amounts.getZero(pr.contractData.amount.currency);
+
+  for (const rk of Object.keys(pr.refundsDone)) {
+    const perm = pr.refundsDone[rk].perm;
+    if (pr.refundsDone[rk].refundGroupId !== refundGroupId) {
+      continue;
+    }
+    amountEffective = Amounts.add(amountEffective, 
Amounts.parseOrThrow(perm.refund_amount)).amount;
+    amountRaw = Amounts.add(amountRaw, 
Amounts.parseOrThrow(perm.refund_amount)).amount;
+  }
+
+  for (const rk of Object.keys(pr.refundsDone)) {
+    const perm = pr.refundsDone[rk].perm;
+    if (pr.refundsDone[rk].refundGroupId !== refundGroupId) {
+      continue;
+    }
+    amountEffective = Amounts.sub(amountEffective, 
Amounts.parseOrThrow(perm.refund_fee)).amount;
+  }
+
+  for (const rk of Object.keys(pr.refundsFailed)) {
+    const perm = pr.refundsDone[rk].perm;
+    if (pr.refundsDone[rk].refundGroupId !== refundGroupId) {
+      continue;
+    }
+    amountInvalid = Amounts.add(amountInvalid, 
Amounts.parseOrThrow(perm.refund_fee)).amount;
   }
+
   return {
-    amount: Amounts.stringify(download.contractData.amount),
-    fulfillmentUrl: download.contractData.fulfillmentUrl,
-    orderId: download.contractData.orderId,
-    merchantBaseUrl: download.contractData.merchantBaseUrl,
-    proposalId: proposal.proposalId,
-    summary: download.contractData.summary,
-  };
+    amountEffective,
+    amountInvalid,
+    amountRaw,
+  }
+
 }
 
 /**
@@ -82,24 +110,39 @@ export async function getTransactions(
     ],
     async (tx) => {
       tx.iter(Stores.withdrawalGroups).forEach((wsr) => {
-        if (wsr.timestampFinish) {
-          transactions.push({
-            type: TransactionType.Withdrawal,
-            amountEffective: 
Amounts.stringify(wsr.denomsSel.totalWithdrawCost),
-            amountRaw: Amounts.stringify(wsr.denomsSel.totalCoinValue),
-            confirmed: true,
-            exchangeBaseUrl: wsr.exchangeBaseUrl,
-            pending: !wsr.timestampFinish,
-            timestamp: wsr.timestampStart,
-            transactionId: makeEventId(
-              TransactionType.Withdrawal,
-              wsr.withdrawalGroupId,
-            ),
-          });
+        if (
+          transactionsRequest?.currency &&
+          wsr.rawWithdrawalAmount.currency != transactionsRequest.currency
+        ) {
+          return;
         }
+        if (wsr.rawWithdrawalAmount.currency)
+          if (wsr.timestampFinish) {
+            transactions.push({
+              type: TransactionType.Withdrawal,
+              amountEffective: Amounts.stringify(
+                wsr.denomsSel.totalWithdrawCost,
+              ),
+              amountRaw: Amounts.stringify(wsr.denomsSel.totalCoinValue),
+              confirmed: true,
+              exchangeBaseUrl: wsr.exchangeBaseUrl,
+              pending: !wsr.timestampFinish,
+              timestamp: wsr.timestampStart,
+              transactionId: makeEventId(
+                TransactionType.Withdrawal,
+                wsr.withdrawalGroupId,
+              ),
+            });
+          }
       });
 
       tx.iter(Stores.reserves).forEach((r) => {
+        if (
+          transactionsRequest?.currency &&
+          r.currency != transactionsRequest.currency
+        ) {
+          return;
+        }
         if (r.reserveStatus !== ReserveRecordStatus.WAIT_CONFIRM_BANK) {
           return;
         }
@@ -121,6 +164,63 @@ export async function getTransactions(
           ),
         });
       });
+
+      tx.iter(Stores.purchases).forEachAsync(async (pr) => {
+        if (
+          transactionsRequest?.currency &&
+          pr.contractData.amount.currency != transactionsRequest.currency
+        ) {
+          return;
+        }
+        const proposal = await tx.get(Stores.proposals, pr.proposalId);
+        if (!proposal) {
+          return;
+        }
+        const cost = await getTotalPaymentCost(ws, pr.payCoinSelection);
+        transactions.push({
+          type: TransactionType.Payment,
+          amountRaw: Amounts.stringify(pr.contractData.amount),
+          amountEffective: Amounts.stringify(cost.totalCost),
+          failed: false,
+          pending: !pr.timestampFirstSuccessfulPay,
+          timestamp: pr.timestampAccept,
+          transactionId: makeEventId(TransactionType.Payment, pr.proposalId),
+          info: {
+            fulfillmentUrl: pr.contractData.fulfillmentUrl,
+            merchant: {},
+            orderId: pr.contractData.orderId,
+            products: [],
+            summary: pr.contractData.summary,
+            summary_i18n: {},
+          },
+        });
+
+        for (const rg of pr.refundGroups) {
+          const pending = Object.keys(pr.refundsDone).length > 0;
+
+          const stats = getRefundStats(pr, rg.refundGroupId);
+          
+          transactions.push({
+            type: TransactionType.Refund,
+            pending,
+            info: {
+              fulfillmentUrl: pr.contractData.fulfillmentUrl,
+              merchant: {},
+              orderId: pr.contractData.orderId,
+              products: [],
+              summary: pr.contractData.summary,
+              summary_i18n: {},
+            },
+            timestamp: rg.timestampQueried,
+            transactionId: makeEventId(TransactionType.Refund, 
`{rg.timestampQueried.t_ms}`),
+            refundedTransactionId: makeEventId(TransactionType.Payment, 
pr.proposalId),
+            amountEffective: Amounts.stringify(stats.amountEffective),
+            amountInvalid: Amounts.stringify(stats.amountInvalid),
+            amountRaw: Amounts.stringify(stats.amountRaw),
+
+          });
+        }
+      });
     },
   );
 
diff --git a/src/types/dbTypes.ts b/src/types/dbTypes.ts
index 07c59d4d..eae39fff 100644
--- a/src/types/dbTypes.ts
+++ b/src/types/dbTypes.ts
@@ -43,6 +43,7 @@ import {
   ReserveRecoupTransaction,
 } from "./ReserveTransaction";
 import { Timestamp, Duration, getTimestampNow } from "../util/time";
+import { PayCoinSelection } from "../operations/pay";
 
 export enum ReserveRecordStatus {
   /**
@@ -1133,6 +1134,7 @@ export const enum RefundReason {
 }
 
 export interface RefundGroupInfo {
+  refundGroupId: string;
   timestampQueried: Timestamp;
   reason: RefundReason;
 }
@@ -1222,6 +1224,8 @@ export interface PurchaseRecord {
    */
   payReq: PayReq;
 
+  payCoinSelection: PayCoinSelection;
+
   /**
    * Timestamp of the first time that sending a payment to the merchant
    * for this purchase was successful.
diff --git a/src/types/transactions.ts b/src/types/transactions.ts
index d2f0f6cb..7dda46f7 100644
--- a/src/types/transactions.ts
+++ b/src/types/transactions.ts
@@ -115,7 +115,7 @@ interface TransactionPayment extends TransactionCommon {
   type: TransactionType.Payment;
 
   // Additional information about the payment.
-  info: TransactionInfo;
+  info: PaymentShortInfo;
 
   // true if the payment failed, false otherwise.
   // Note that failed payments with zero effective amount will not be returned 
by the API.
@@ -125,11 +125,11 @@ interface TransactionPayment extends TransactionCommon {
   amountRaw: AmountString;
 
   // Amount that was paid, including deposit, wire and refresh fees.
-  amountEffective: AmountString;
+  amountEffective?: AmountString;
 }
 
 
-interface TransactionInfo {
+interface PaymentShortInfo {
   // Order ID, uniquely identifies the order within a merchant instance
   orderId: string;
 
@@ -157,7 +157,7 @@ interface TransactionRefund extends TransactionCommon {
   refundedTransactionId: string;
 
   // Additional information about the refunded payment
-  info: TransactionInfo;
+  info: PaymentShortInfo;
 
   // Part of the refund that couldn't be applied because the refund 
permissions were expired
   amountInvalid: AmountString;

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

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