gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (35c83414 -> 79d0c2f9)


From: gnunet
Subject: [taler-wallet-core] branch master updated (35c83414 -> 79d0c2f9)
Date: Fri, 15 May 2020 13:09:46 +0200

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

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

    from 35c83414 fix issues in the webextension refund UI
     new 3eb88574 address first batch of transaction list issues
     new 79d0c2f9 include refund fees in effective refund amount calculation

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/db.ts                      |   2 +-
 src/operations/pay.ts          |  11 ++--
 src/operations/refund.ts       |  31 ++++++++++
 src/operations/reserves.ts     |  51 ++++++++--------
 src/operations/tip.ts          |  16 ++----
 src/operations/transactions.ts |  63 ++++++--------------
 src/operations/withdraw.ts     |  20 ++++++-
 src/types/dbTypes.ts           |  27 ++++++---
 src/types/talerTypes.ts        |  94 +++++++++++++++++++++++++++---
 src/types/transactions.ts      | 128 +++++++++++++++++++++++++++++------------
 10 files changed, 302 insertions(+), 141 deletions(-)

diff --git a/src/db.ts b/src/db.ts
index 098767b5..19737202 100644
--- a/src/db.ts
+++ b/src/db.ts
@@ -7,7 +7,7 @@ import { openDatabase, Database, Store, Index } from 
"./util/query";
  * with each major change.  When incrementing the major version,
  * the wallet should import data from the previous version.
  */
-const TALER_DB_NAME = "taler-walletdb-v3";
+const TALER_DB_NAME = "taler-walletdb-v4";
 
 /**
  * Current database minor version, should be incremented
diff --git a/src/operations/pay.ts b/src/operations/pay.ts
index 45caa958..372d012d 100644
--- a/src/operations/pay.ts
+++ b/src/operations/pay.ts
@@ -137,11 +137,7 @@ export async function getTotalPaymentCost(
   ws: InternalWalletState,
   pcs: PayCoinSelection,
 ): Promise<PayCostInfo> {
-  const costs = [
-    pcs.paymentAmount,
-    pcs.customerDepositFees,
-    pcs.customerWireFees,
-  ];
+  const costs = [];
   for (let i = 0; i < pcs.coinPubs.length; i++) {
     const coin = await ws.db.get(Stores.coins, pcs.coinPubs[i]);
     if (!coin) {
@@ -165,6 +161,7 @@ export async function getTotalPaymentCost(
     const amountLeft = Amounts.sub(denom.value, pcs.coinContributions[i])
       .amount;
     const refreshCost = getTotalRefreshCost(allDenoms, denom, amountLeft);
+    costs.push(pcs.coinContributions[i]);
     costs.push(refreshCost);
   }
   return {
@@ -459,6 +456,7 @@ async function recordConfirmPay(
     refundsDone: {},
     refundsFailed: {},
     refundsPending: {},
+    refundsRefreshCost: {},
   };
 
   await ws.db.runWithWriteTransaction(
@@ -670,6 +668,9 @@ async function processDownloadProposalImpl(
           wireMethod: parsedContractTerms.wire_method,
           wireInfoHash: parsedContractTerms.h_wire,
           maxDepositFee: Amounts.parseOrThrow(parsedContractTerms.max_fee),
+          merchant: parsedContractTerms.merchant,
+          products: parsedContractTerms.products,
+          summaryI18n: parsedContractTerms.summary_i18n,
         },
         contractTermsRaw: JSON.stringify(proposalResp.contract_terms),
       };
diff --git a/src/operations/refund.ts b/src/operations/refund.ts
index cbcb9644..b5d611b0 100644
--- a/src/operations/refund.ts
+++ b/src/operations/refund.ts
@@ -144,6 +144,8 @@ async function acceptRefundResponse(
   const unfinishedRefunds: MerchantRefundDetails[] = [];
   const failedRefunds: MerchantRefundDetails[] = [];
 
+  const refundsRefreshCost: { [refundKey: string]: AmountJson } = {};
+
   for (const rd of refunds) {
     if (rd.exchange_http_status === 200) {
       // FIXME: also verify signature if necessary.
@@ -158,6 +160,33 @@ async function acceptRefundResponse(
     }
   }
 
+  for (const rd of [...finishedRefunds, ...unfinishedRefunds]) {
+    const key = getRefundKey(rd);
+    const coin = await ws.db.get(Stores.coins, rd.coin_pub);
+    if (!coin) {
+      continue;
+    }
+    const denom = await ws.db.getIndexed(
+      Stores.denominations.denomPubHashIndex,
+      coin.denomPubHash,
+    );
+    if (!denom) {
+      throw Error("inconsistent database");
+    }
+    const amountLeft = Amounts.sub(
+      Amounts.add(coin.currentAmount, Amounts.parseOrThrow(rd.refund_amount))
+        .amount,
+      Amounts.parseOrThrow(rd.refund_fee),
+    ).amount;
+    const allDenoms = await ws.db
+      .iterIndex(
+        Stores.denominations.exchangeBaseUrlIndex,
+        coin.exchangeBaseUrl,
+      )
+      .toArray();
+    refundsRefreshCost[key] = getTotalRefreshCost(allDenoms, denom, 
amountLeft);
+  }
+
   const now = getTimestampNow();
 
   await ws.db.runWithWriteTransaction(
@@ -282,6 +311,8 @@ async function acceptRefundResponse(
         console.log("refund query not done");
       }
 
+      p.refundsRefreshCost = {...p.refundsRefreshCost, ...refundsRefreshCost };
+
       await tx.put(Stores.purchases, p);
 
       const coinsPubsToBeRefreshed = Object.values(refreshCoinsMap);
diff --git a/src/operations/reserves.ts b/src/operations/reserves.ts
index 347f6e89..3d45d8d5 100644
--- a/src/operations/reserves.ts
+++ b/src/operations/reserves.ts
@@ -35,6 +35,7 @@ import {
   WalletReserveHistoryItemType,
   WithdrawalSourceType,
   ReserveHistoryRecord,
+  ReserveBankInfo,
 } from "../types/dbTypes";
 import { Logger } from "../util/logging";
 import { Amounts } from "../util/amounts";
@@ -48,9 +49,11 @@ import { assertUnreachable } from 
"../util/assertUnreachable";
 import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto";
 import { randomBytes } from "../crypto/primitives/nacl-fast";
 import {
-  getVerifiedWithdrawDenomList,
+  selectWithdrawalDenoms,
   processWithdrawGroup,
   getBankWithdrawalInfo,
+  denomSelectionInfoToState,
+  getWithdrawDenomList,
 } from "./withdraw";
 import {
   guardOperationException,
@@ -100,6 +103,20 @@ export async function createReserve(
     reserveStatus = ReserveRecordStatus.UNCONFIRMED;
   }
 
+  let bankInfo: ReserveBankInfo | undefined;
+
+  if (req.bankWithdrawStatusUrl) {
+    const denomSelInfo = await selectWithdrawalDenoms(ws, canonExchange, 
req.amount);
+    const denomSel = denomSelectionInfoToState(denomSelInfo);
+    bankInfo = {
+      statusUrl: req.bankWithdrawStatusUrl,
+      amount: req.amount,
+      bankWithdrawalGroupId: encodeCrock(getRandomBytes(32)),
+      withdrawalStarted: false,
+      denomSel,
+    };
+  }
+
   const reserveRecord: ReserveRecord = {
     timestampCreated: now,
     exchangeBaseUrl: canonExchange,
@@ -108,14 +125,7 @@ export async function createReserve(
     senderWire: req.senderWire,
     timestampConfirmed: undefined,
     timestampReserveInfoPosted: undefined,
-    bankInfo: req.bankWithdrawStatusUrl
-      ? {
-          statusUrl: req.bankWithdrawStatusUrl,
-          amount: req.amount,
-          bankWithdrawalGroupId: encodeCrock(getRandomBytes(32)),
-          withdrawalStarted: false,
-        }
-      : undefined,
+    bankInfo,
     exchangeWire: req.exchangeWire,
     reserveStatus,
     lastSuccessfulStatusQuery: undefined,
@@ -286,10 +296,11 @@ async function registerReserveWithBank(
     default:
       return;
   }
-  const bankStatusUrl = reserve.bankInfo?.statusUrl;
-  if (!bankStatusUrl) {
+  const bankInfo = reserve.bankInfo;
+  if (!bankInfo) {
     return;
   }
+  const bankStatusUrl = bankInfo.statusUrl;
   console.log("making selection");
   if (reserve.timestampReserveInfoPosted) {
     throw Error("bank claims that reserve info selection is not done");
@@ -309,6 +320,9 @@ async function registerReserveWithBank(
     }
     r.timestampReserveInfoPosted = getTimestampNow();
     r.reserveStatus = ReserveRecordStatus.WAIT_CONFIRM_BANK;
+    if (!r.bankInfo) {
+      throw Error("invariant failed");
+    }
     r.retryInfo = initRetryInfo();
     return r;
   });
@@ -657,7 +671,7 @@ async function depleteReserve(
 
   logger.trace(`getting denom list`);
 
-  const denomsForWithdraw = await getVerifiedWithdrawDenomList(
+  const denomsForWithdraw = await selectWithdrawalDenoms(
     ws,
     reserve.exchangeBaseUrl,
     withdrawAmount,
@@ -752,17 +766,8 @@ async function depleteReserve(
         retryInfo: initRetryInfo(),
         lastErrorPerCoin: {},
         lastError: undefined,
-        denomsSel: {
-          totalCoinValue: denomsForWithdraw.totalCoinValue,
-          totalWithdrawCost: denomsForWithdraw.totalWithdrawCost,
-          selectedDenoms: denomsForWithdraw.selectedDenoms.map((x) => {
-            return {
-              count: x.count,
-              denomPubHash: x.denom.denomPubHash,
-            };
-          }),
-        },
-      };    
+        denomsSel: denomSelectionInfoToState(denomsForWithdraw),
+      };
 
       await tx.put(Stores.reserves, newReserve);
       await tx.put(Stores.reserveHistory, newHist);
diff --git a/src/operations/tip.ts b/src/operations/tip.ts
index f584fc50..15d2339b 100644
--- a/src/operations/tip.ts
+++ b/src/operations/tip.ts
@@ -34,8 +34,9 @@ import {
 } from "../types/dbTypes";
 import {
   getExchangeWithdrawalInfo,
-  getVerifiedWithdrawDenomList,
+  selectWithdrawalDenoms,
   processWithdrawGroup,
+  denomSelectionInfoToState,
 } from "./withdraw";
 import { updateExchangeFromUrl } from "./exchanges";
 import { getRandomBytes, encodeCrock } from "../crypto/talerCrypto";
@@ -81,7 +82,7 @@ export async function getTipStatus(
     );
 
     const tipId = encodeCrock(getRandomBytes(32));
-    const selectedDenoms = await getVerifiedWithdrawDenomList(
+    const selectedDenoms = await selectWithdrawalDenoms(
       ws,
       tipPickupStatus.exchange_url,
       amount,
@@ -107,16 +108,7 @@ export async function getTipStatus(
       ).amount,
       retryInfo: initRetryInfo(),
       lastError: undefined,
-      denomsSel: {
-        totalCoinValue: selectedDenoms.totalCoinValue,
-        totalWithdrawCost: selectedDenoms.totalWithdrawCost,
-        selectedDenoms: selectedDenoms.selectedDenoms.map((x) => {
-          return {
-            count: x.count,
-            denomPubHash: x.denom.denomPubHash,
-          };
-        }),
-      },
+      denomsSel: denomSelectionInfoToState(selectedDenoms),
     };
     await ws.db.put(Stores.tips, tipRecord);
   }
diff --git a/src/operations/transactions.ts b/src/operations/transactions.ts
index 4cc6154b..9e07d4ff 100644
--- a/src/operations/transactions.ts
+++ b/src/operations/transactions.ts
@@ -18,7 +18,7 @@
  * Imports.
  */
 import { InternalWalletState } from "./state";
-import { Stores, ReserveRecordStatus, PurchaseRecord, ProposalStatus } from 
"../types/dbTypes";
+import { Stores, ReserveRecordStatus, PurchaseRecord } from "../types/dbTypes";
 import { Amounts, AmountJson } from "../util/amounts";
 import { timestampCmp } from "../util/time";
 import {
@@ -63,6 +63,8 @@ function getRefundStats(
       .amount;
   }
 
+  // Subtract fees from effective refund amount
+
   for (const rk of Object.keys(pr.refundsDone)) {
     const perm = pr.refundsDone[rk].perm;
     if (pr.refundsDone[rk].refundGroupId !== refundGroupId) {
@@ -72,6 +74,12 @@ function getRefundStats(
       amountEffective,
       Amounts.parseOrThrow(perm.refund_fee),
     ).amount;
+    if (pr.refundsRefreshCost[rk]) {
+      amountEffective = Amounts.sub(
+        amountEffective,
+        pr.refundsRefreshCost[rk],
+      ).amount;
+    }
   }
 
   for (const rk of Object.keys(pr.refundsFailed)) {
@@ -131,10 +139,8 @@ export async function getTransactions(
           if (wsr.timestampFinish) {
             transactions.push({
               type: TransactionType.Withdrawal,
-              amountEffective: Amounts.stringify(
-                wsr.denomsSel.totalWithdrawCost,
-              ),
-              amountRaw: Amounts.stringify(wsr.denomsSel.totalCoinValue),
+              amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
+              amountRaw: Amounts.stringify(wsr.denomsSel.totalWithdrawCost),
               confirmed: true,
               exchangeBaseUrl: wsr.exchangeBaseUrl,
               pending: !wsr.timestampFinish,
@@ -163,9 +169,9 @@ export async function getTransactions(
         transactions.push({
           type: TransactionType.Withdrawal,
           confirmed: false,
-          amountRaw: Amounts.stringify(r.bankInfo.amount),
-          amountEffective: undefined,
-          exchangeBaseUrl: undefined,
+          amountRaw: Amounts.stringify(r.bankInfo.denomSel.totalWithdrawCost),
+          amountEffective: 
Amounts.stringify(r.bankInfo.denomSel.totalCoinValue),
+          exchangeBaseUrl: r.exchangeBaseUrl,
           pending: true,
           timestamp: r.timestampCreated,
           bankConfirmationUrl: r.bankInfo.confirmUrl,
@@ -176,38 +182,6 @@ export async function getTransactions(
         });
       });
 
-      tx.iter(Stores.proposals).forEachAsync(async (proposal) => {
-        if (!proposal.download) {
-          return;
-        }
-        if (proposal.proposalStatus !== ProposalStatus.PROPOSED) {
-          return;
-        }
-        const dl = proposal.download;
-        const purchase = await tx.get(Stores.purchases, proposal.proposalId);
-        if (purchase) {
-          return;
-        }
-
-        transactions.push({
-          type: TransactionType.Payment,
-          amountRaw: Amounts.stringify(dl.contractData.amount),
-          amountEffective: undefined,
-          status: PaymentStatus.Offered,
-          pending: true,
-          timestamp: proposal.timestamp,
-          transactionId: makeEventId(TransactionType.Payment, 
proposal.proposalId),
-          info: {
-            fulfillmentUrl: dl.contractData.fulfillmentUrl,
-            merchant: {},
-            orderId: dl.contractData.orderId,
-            products: [],
-            summary: dl.contractData.summary,
-            summary_i18n: {},
-          },
-        });
-      });
-
       tx.iter(Stores.purchases).forEachAsync(async (pr) => {
         if (
           transactionsRequest?.currency &&
@@ -231,11 +205,11 @@ export async function getTransactions(
           transactionId: makeEventId(TransactionType.Payment, pr.proposalId),
           info: {
             fulfillmentUrl: pr.contractData.fulfillmentUrl,
-            merchant: {},
+            merchant: pr.contractData.merchant,
             orderId: pr.contractData.orderId,
-            products: [],
+            products: pr.contractData.products,
             summary: pr.contractData.summary,
-            summary_i18n: {},
+            summary_i18n: pr.contractData.summaryI18n,
           },
         });
 
@@ -258,7 +232,8 @@ export async function getTransactions(
             timestamp: rg.timestampQueried,
             transactionId: makeEventId(
               TransactionType.Refund,
-              `{rg.timestampQueried.t_ms}`,
+              pr.proposalId,
+              `${rg.timestampQueried.t_ms}`,
             ),
             refundedTransactionId: makeEventId(
               TransactionType.Payment,
diff --git a/src/operations/withdraw.ts b/src/operations/withdraw.ts
index 21c30d7a..14071be7 100644
--- a/src/operations/withdraw.ts
+++ b/src/operations/withdraw.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2019-2029 Taler Systems SA
+ (C) 2019-2020 Taler Systems SA
 
  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
@@ -27,6 +27,7 @@ import {
   DenominationSelectionInfo,
   PlanchetRecord,
   WithdrawalSourceType,
+  DenomSelectionState,
 } from "../types/dbTypes";
 import {
   BankWithdrawDetails,
@@ -419,6 +420,19 @@ async function processPlanchet(
   }
 }
 
+export function denomSelectionInfoToState(dsi: DenominationSelectionInfo): 
DenomSelectionState {
+  return {
+    selectedDenoms: dsi.selectedDenoms.map((x) => {
+      return {
+        count: x.count,
+        denomPubHash: x.denom.denomPubHash
+      };
+    }),
+    totalCoinValue: dsi.totalCoinValue,
+    totalWithdrawCost: dsi.totalWithdrawCost,
+  }
+}
+
 /**
  * Get a list of denominations to withdraw from the given exchange for the
  * given amount, making sure that all denominations' signatures are verified.
@@ -426,7 +440,7 @@ async function processPlanchet(
  * Writes to the DB in order to record the result from verifying
  * denominations.
  */
-export async function getVerifiedWithdrawDenomList(
+export async function selectWithdrawalDenoms(
   ws: InternalWalletState,
   exchangeBaseUrl: string,
   amount: AmountJson,
@@ -603,7 +617,7 @@ export async function getExchangeWithdrawalInfo(
     throw Error(`exchange ${exchangeInfo.baseUrl} wire details not available`);
   }
 
-  const selectedDenoms = await getVerifiedWithdrawDenomList(
+  const selectedDenoms = await selectWithdrawalDenoms(
     ws,
     baseUrl,
     amount,
diff --git a/src/types/dbTypes.ts b/src/types/dbTypes.ts
index 37a66251..82649d9f 100644
--- a/src/types/dbTypes.ts
+++ b/src/types/dbTypes.ts
@@ -31,6 +31,8 @@ import {
   PayReq,
   TipResponse,
   ExchangeSignKeyJson,
+  MerchantInfo,
+  Product,
 } from "./talerTypes";
 
 import { Index, Store } from "../util/query";
@@ -216,6 +218,15 @@ export interface ReserveHistoryRecord {
   reserveTransactions: WalletReserveHistoryItem[];
 }
 
+export interface ReserveBankInfo {
+  statusUrl: string;
+  confirmUrl?: string;
+  amount: AmountJson;
+  bankWithdrawalGroupId: string;
+  withdrawalStarted: boolean;
+  denomSel: DenomSelectionState;
+}
+
 /**
  * A reserve record as stored in the wallet's database.
  */
@@ -278,13 +289,7 @@ export interface ReserveRecord {
    * Extra state for when this is a withdrawal involving
    * a Taler-integrated bank.
    */
-  bankInfo?: {
-    statusUrl: string;
-    confirmUrl?: string;
-    amount: AmountJson;
-    bankWithdrawalGroupId: string;
-    withdrawalStarted: boolean;
-  };
+  bankInfo?: ReserveBankInfo;
 
   reserveStatus: ReserveRecordStatus;
 
@@ -1179,10 +1184,13 @@ export interface AllowedExchangeInfo {
  * processing in the wallet.
  */
 export interface WalletContractData {
+  products?: Product[];
+  summaryI18n: { [lang_tag: string]: string } | undefined;
   fulfillmentUrl: string;
   contractTermsHash: string;
   merchantSig: string;
   merchantPub: string;
+  merchant: MerchantInfo;
   amount: AmountJson;
   orderId: string;
   merchantBaseUrl: string;
@@ -1261,6 +1269,11 @@ export interface PurchaseRecord {
    */
   refundsFailed: { [refundKey: string]: RefundInfo };
 
+  /**
+   * Refresh cost for each refund permission.
+   */
+  refundsRefreshCost: { [refundKey: string]: AmountJson };
+
   /**
    * When was the last refund made?
    * Set to 0 if no refund was made on the purchase.
diff --git a/src/types/talerTypes.ts b/src/types/talerTypes.ts
index 17d11eea..eb10d6e1 100644
--- a/src/types/talerTypes.ts
+++ b/src/types/talerTypes.ts
@@ -260,6 +260,55 @@ export class AuditorHandle {
   url: string;
 }
 
+export interface MerchantInfo {
+  name: string;
+  jurisdiction: string | undefined;
+  address: string | undefined;
+}
+
+export interface Tax {
+  // the name of the tax
+  name: string;
+
+  // amount paid in tax
+  tax: AmountString;
+}
+
+export interface Product {
+  // merchant-internal identifier for the product.
+  product_id?: string;
+
+  // Human-readable product description.
+  description: string;
+
+  // Map from IETF BCP 47 language tags to localized descriptions
+  description_i18n?: { [lang_tag: string]: string };
+
+  // The number of units of the product to deliver to the customer.
+  quantity?: number;
+
+  // The unit in which the product is measured (liters, kilograms, packages, 
etc.)
+  unit?: string;
+
+  // The price of the product; this is the total price for quantity times unit 
of this product.
+  price?: AmountString;
+
+  // An optional base64-encoded product image
+  image?: string;
+
+  // a list of taxes paid by the merchant for this product. Can be empty.
+  taxes?: Tax[];
+
+  // time indicating when this product should be delivered
+  delivery_date?: Timestamp;
+
+  // where to deliver this product. This may be an URL for online delivery
+  // (i.e. 'http://example.com/download' or 'mailto:address@hidden'),
+  // or a location label defined inside the proposition's 'locations'.
+  // The presence of a colon (':') indicates the use of an URL.
+  delivery_location?: string;
+}
+
 /**
  * Contract terms from a merchant.
  */
@@ -284,6 +333,8 @@ export class ContractTerms {
    */
   summary: string;
 
+  summary_i18n?: { [lang_tag: string]: string };
+
   /**
    * Nonce used to ensure freshness.
    */
@@ -317,7 +368,7 @@ export class ContractTerms {
   /**
    * Information about the merchant.
    */
-  merchant: any;
+  merchant: MerchantInfo;
 
   /**
    * Public key of the merchant.
@@ -332,7 +383,7 @@ export class ContractTerms {
   /**
    * Products that are sold in this contract.
    */
-  products?: any[];
+  products?: Product[];
 
   /**
    * Deadline for refunds.
@@ -805,6 +856,35 @@ export const codecForAuditorHandle = (): 
Codec<AuditorHandle> =>
     .property("url", codecForString)
     .build("AuditorHandle");
 
+export const codecForMerchantInfo = (): Codec<MerchantInfo> =>
+  makeCodecForObject<MerchantInfo>()
+    .property("name", codecForString)
+    .property("address", makeCodecOptional(codecForString))
+    .property("jurisdiction", makeCodecOptional(codecForString))
+    .build("MerchantInfo");
+
+export const codecForTax = (): Codec<Tax> =>
+  makeCodecForObject<Tax>()
+    .property("name", codecForString)
+    .property("tax", codecForString)
+    .build("Tax");
+
+
+export const codecForI18n = (): Codec<{ [lang_tag: string]: string }> =>
+  makeCodecForMap(codecForString)
+
+export const codecForProduct = (): Codec<Product> =>
+  makeCodecForObject<Product>()
+    .property("product_id", makeCodecOptional(codecForString))
+    .property("description", codecForString)
+    .property("description_i18n", makeCodecOptional(codecForI18n()))
+    .property("quantity", makeCodecOptional(codecForNumber))
+    .property("unit", makeCodecOptional(codecForString))
+    .property("price", makeCodecOptional(codecForString))
+    .property("delivery_date", makeCodecOptional(codecForTimestamp))
+    .property("delivery_location", makeCodecOptional(codecForString))
+    .build("Tax");
+
 export const codecForContractTerms = (): Codec<ContractTerms> =>
   makeCodecForObject<ContractTerms>()
     .property("order_id", codecForString)
@@ -814,6 +894,7 @@ export const codecForContractTerms = (): 
Codec<ContractTerms> =>
     .property("auto_refund", makeCodecOptional(codecForDuration))
     .property("wire_method", codecForString)
     .property("summary", codecForString)
+    .property("summary_i18n", makeCodecOptional(codecForI18n()))
     .property("nonce", codecForString)
     .property("amount", codecForString)
     .property("auditors", makeCodecForList(codecForAuditorHandle()))
@@ -824,10 +905,10 @@ export const codecForContractTerms = (): 
Codec<ContractTerms> =>
     .property("locations", codecForAny)
     .property("max_fee", codecForString)
     .property("max_wire_fee", makeCodecOptional(codecForString))
-    .property("merchant", codecForAny)
+    .property("merchant", codecForMerchantInfo())
     .property("merchant_pub", codecForString)
     .property("exchanges", makeCodecForList(codecForExchangeHandle()))
-    .property("products", makeCodecOptional(makeCodecForList(codecForAny)))
+    .property("products", 
makeCodecOptional(makeCodecForList(codecForProduct())))
     .property("extra", codecForAny)
     .build("ContractTerms");
 
@@ -852,10 +933,7 @@ export const codecForMerchantRefundResponse = (): Codec<
   makeCodecForObject<MerchantRefundResponse>()
     .property("merchant_pub", codecForString)
     .property("h_contract_terms", codecForString)
-    .property(
-      "refunds",
-      makeCodecForList(codecForMerchantRefundPermission()),
-    )
+    .property("refunds", makeCodecForList(codecForMerchantRefundPermission()))
     .build("MerchantRefundResponse");
 
 export const codecForReserveSigSingleton = (): Codec<ReserveSigSingleton> =>
diff --git a/src/types/transactions.ts b/src/types/transactions.ts
index 263e08a4..b1d033c0 100644
--- a/src/types/transactions.ts
+++ b/src/types/transactions.ts
@@ -16,13 +16,16 @@
 
 /**
  * Type and schema definitions for the wallet's transaction list.
+ * 
+ * @author Florian Dold
+ * @author Torsten Grote
  */
 
 /**
  * Imports.
  */
 import { Timestamp } from "../util/time";
-import { AmountString } from "./talerTypes";
+import { AmountString, Product } from "./talerTypes";
 
 export interface TransactionsRequest {
   /**
@@ -44,6 +47,24 @@ export interface TransactionsResponse {
   transactions: Transaction[];
 }
 
+interface TransactionError {
+  /**
+   * TALER_EC_* unique error code.
+   * The action(s) offered and message displayed on the transaction item 
depend on this code.
+   */
+  ec: number;
+
+  /**
+   * English-only error hint, if available.
+   */
+  hint?: string;
+
+  /**
+   * Error details specific to "ec", if applicable/available
+   */
+  details?: any;
+}
+
 export interface TransactionCommon {
   // opaque unique ID for the transaction, used as a starting point for 
paginating queries
   // and for invoking actions on the transaction (e.g. deleting/hiding it from 
the history)
@@ -64,16 +85,17 @@ export interface TransactionCommon {
   amountRaw: AmountString;
 
   // Amount added or removed from the wallet's balance (including all fees and 
other costs)
-  amountEffective?: AmountString;
+  amountEffective: AmountString;
+
+  error?: TransactionError;
 }
 
-export type Transaction = (
-  TransactionWithdrawal |
-  TransactionPayment |
-  TransactionRefund |
-  TransactionTip |
-  TransactionRefresh
-)
+export type Transaction =
+  | TransactionWithdrawal
+  | TransactionPayment
+  | TransactionRefund
+  | TransactionTip
+  | TransactionRefresh;
 
 export const enum TransactionType {
   Withdrawal = "withdrawal",
@@ -93,79 +115,109 @@ interface TransactionWithdrawal extends TransactionCommon 
{
    */
   exchangeBaseUrl?: string;
 
-  // true if the bank has confirmed the withdrawal, false if not.
-  // An unconfirmed withdrawal usually requires user-input and should be 
highlighted in the UI.
-  // See also bankConfirmationUrl below.
+  /**
+   * true if the bank has confirmed the withdrawal, false if not.
+   * An unconfirmed withdrawal usually requires user-input and should be 
highlighted in the UI.
+   * See also bankConfirmationUrl below.
+   */
   confirmed: boolean;
 
-  // If the withdrawal is unconfirmed, this can include a URL for user 
initiated confirmation.
+  /**
+   * If the withdrawal is unconfirmed, this can include a URL for user
+   * initiated confirmation.
+   */
   bankConfirmationUrl?: string;
 
-  // Amount that has been subtracted from the reserve's balance for this 
withdrawal.
+  /**
+   * Amount that got subtracted from the reserve balance.
+   */
   amountRaw: AmountString;
 
   /**
    * Amount that actually was (or will be) added to the wallet's balance.
-   * Only present if an exchange has already been selected.
    */
-  amountEffective?: AmountString;
+  amountEffective: AmountString;
 }
 
 export const enum PaymentStatus {
-  // Explicitly aborted after timeout / failure
+  /**
+   * Explicitly aborted after timeout / failure
+   */
   Aborted = "aborted",
 
-  // Payment failed, wallet will auto-retry.
-  // User should be given the option to retry now / abort.
+  /**
+   * Payment failed, wallet will auto-retry.
+   * User should be given the option to retry now / abort.
+   */
   Failed = "failed",
 
-  // Paid successfully
+  /**
+   * Paid successfully
+   */
   Paid = "paid",
 
-  // Only offered, user must accept / decline
-  Offered = "offered",
-
-  // User accepted, payment is processing.
+  /**
+   * User accepted, payment is processing.
+   */
   Accepted = "accepted",
 }
 
 export interface TransactionPayment extends TransactionCommon {
   type: TransactionType.Payment;
 
-  // Additional information about the payment.
+  /**
+   * Additional information about the payment.
+   */
   info: PaymentShortInfo;
 
+  /**
+   * How far did the wallet get with processing the payment?
+   */
   status: PaymentStatus;
 
-  // Amount that must be paid for the contract
+  /**
+   * Amount that must be paid for the contract
+   */
   amountRaw: AmountString;
 
-  // Amount that was paid, including deposit, wire and refresh fees.
-  amountEffective?: AmountString;
+  /**
+   * Amount that was paid, including deposit, wire and refresh fees.
+   */
+  amountEffective: AmountString;
 }
 
-
 interface PaymentShortInfo {
-  // Order ID, uniquely identifies the order within a merchant instance
+  /**
+   * Order ID, uniquely identifies the order within a merchant instance
+   */
   orderId: string;
 
-  // More information about the merchant
+  /**
+   * More information about the merchant
+   */
   merchant: any;
 
-  // Summary of the order, given by the merchant
+  /**
+   * Summary of the order, given by the merchant
+   */
   summary: string;
 
-  // Map from IETF BCP 47 language tags to localized summaries
+  /**
+   * Map from IETF BCP 47 language tags to localized summaries
+   */
   summary_i18n?: { [lang_tag: string]: string };
 
-  // List of products that are part of the order
-  products: any[];
+  /**
+   * List of products that are part of the order
+   */
+  products: Product[] | undefined;
 
-  // URL of the fulfillment, given by the merchant
+  /**
+   * URL of the fulfillment, given by the merchant
+   */
   fulfillmentUrl: string;
 }
 
-
 interface TransactionRefund extends TransactionCommon {
   type: TransactionType.Refund;
 
@@ -221,4 +273,4 @@ interface TransactionRefresh extends TransactionCommon {
 
   // Amount that will be paid as fees for the refresh
   amountEffective: AmountString;
-}
\ No newline at end of file
+}

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



reply via email to

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