gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: implement fulfillment_message


From: gnunet
Subject: [taler-wallet-core] branch master updated: implement fulfillment_message and make fulfillment_url optional
Date: Mon, 24 Aug 2020 16:09:14 +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 0e88ef9b implement fulfillment_message and make fulfillment_url 
optional
0e88ef9b is described below

commit 0e88ef9bd2ea76e5b44cc0d4459b9a2e553b8d24
Author: Florian Dold <florian.dold@gmail.com>
AuthorDate: Mon Aug 24 19:39:09 2020 +0530

    implement fulfillment_message and make fulfillment_url optional
---
 packages/taler-integrationtests/src/harness.ts     |  2 +-
 packages/taler-wallet-core/src/i18n/index.ts       |  9 ++++
 packages/taler-wallet-core/src/operations/pay.ts   | 33 ++++---------
 packages/taler-wallet-core/src/operations/state.ts |  3 +-
 .../src/operations/transactions.ts                 |  4 +-
 packages/taler-wallet-core/src/types/dbTypes.ts    |  5 +-
 packages/taler-wallet-core/src/types/talerTypes.ts | 28 ++++++++---
 .../taler-wallet-core/src/types/transactions.ts    | 51 ++++++++++++++++---
 .../taler-wallet-core/src/types/walletTypes.ts     | 29 +++--------
 .../taler-wallet-webextension/src/pages/pay.tsx    | 57 +++++++++++++++-------
 10 files changed, 140 insertions(+), 81 deletions(-)

diff --git a/packages/taler-integrationtests/src/harness.ts 
b/packages/taler-integrationtests/src/harness.ts
index 5fd642e3..fd96c316 100644
--- a/packages/taler-integrationtests/src/harness.ts
+++ b/packages/taler-integrationtests/src/harness.ts
@@ -349,7 +349,7 @@ export class GlobalTestState {
     args: string[],
     logName: string,
   ): ProcessWrapper {
-    console.log(`spawning process (${command})`);
+    console.log(`spawning process ${command} with arguments ${args})`);
     const proc = spawn(command, args, {
       stdio: ["inherit", "pipe", "pipe"],
     });
diff --git a/packages/taler-wallet-core/src/i18n/index.ts 
b/packages/taler-wallet-core/src/i18n/index.ts
index c5b70b1f..b8788115 100644
--- a/packages/taler-wallet-core/src/i18n/index.ts
+++ b/packages/taler-wallet-core/src/i18n/index.ts
@@ -79,3 +79,12 @@ export function str(stringSeq: TemplateStringsArray, 
...values: any[]): string {
     .fetch(...values);
   return tr;
 }
+
+/**
+ * Get an internationalized string (based on the globally set, current 
language)
+ * from a JSON object.  Fall back to the default language of the JSON object
+ * if no match exists.
+ */
+export function getJsonI18n<K extends string>(obj: Record<K, string>, key: K): 
string {
+  return obj[key];
+}
\ No newline at end of file
diff --git a/packages/taler-wallet-core/src/operations/pay.ts 
b/packages/taler-wallet-core/src/operations/pay.ts
index 0d1d4f99..6b45e3da 100644
--- a/packages/taler-wallet-core/src/operations/pay.ts
+++ b/packages/taler-wallet-core/src/operations/pay.ts
@@ -513,17 +513,6 @@ async function recordConfirmPay(
   return t;
 }
 
-function getNextUrl(contractData: WalletContractData): string {
-  const f = contractData.fulfillmentUrl;
-  if (f.startsWith("http://";) || f.startsWith("https://";)) {
-    const fu = new URL(contractData.fulfillmentUrl);
-    fu.searchParams.set("order_id", contractData.orderId);
-    return fu.href;
-  } else {
-    return f;
-  }
-}
-
 async function incrementProposalRetry(
   ws: InternalWalletState,
   proposalId: string,
@@ -642,7 +631,10 @@ async function processDownloadProposalImpl(
   const httpResponse = await ws.http.postJson(orderClaimUrl, requestBody, {
     timeout: getProposalRequestTimeout(proposal),
   });
-  const r = await readSuccessResponseJsonOrErrorCode(httpResponse, 
codecForProposal());
+  const r = await readSuccessResponseJsonOrErrorCode(
+    httpResponse,
+    codecForProposal(),
+  );
   if (r.isError) {
     switch (r.talerErrorResponse.code) {
       case TalerErrorCode.ORDERS_ALREADY_CLAIMED:
@@ -652,7 +644,8 @@ async function processDownloadProposalImpl(
           {
             orderId: proposal.orderId,
             claimUrl: orderClaimUrl,
-          });
+          },
+        );
       default:
         throwUnexpectedRequestError(httpResponse, r.talerErrorResponse);
     }
@@ -723,8 +716,9 @@ async function processDownloadProposalImpl(
         contractTermsRaw: JSON.stringify(proposalResp.contract_terms),
       };
       if (
-        fulfillmentUrl.startsWith("http://";) ||
-        fulfillmentUrl.startsWith("https://";)
+        fulfillmentUrl &&
+        (fulfillmentUrl.startsWith("http://";) ||
+          fulfillmentUrl.startsWith("https://";))
       ) {
         const differentPurchase = await tx.getIndexed(
           Stores.purchases.fulfillmentUrlIndex,
@@ -968,15 +962,9 @@ export async function submitPay(
     await storePayReplaySuccess(ws, proposalId, sessionId);
   }
 
-  const nextUrl = getNextUrl(purchase.contractData);
-  ws.cachedNextUrl[purchase.contractData.fulfillmentUrl] = {
-    nextUrl,
-    lastSessionId: sessionId,
-  };
-
   return {
     type: ConfirmPayResultType.Done,
-    nextUrl,
+    contractTerms: JSON.parse(purchase.contractTermsRaw),
   };
 }
 
@@ -1089,7 +1077,6 @@ export async function preparePayForUri(
       contractTerms: JSON.parse(purchase.contractTermsRaw),
       contractTermsHash: purchase.contractData.contractTermsHash,
       paid: true,
-      nextUrl: r.nextUrl,
       amountRaw: Amounts.stringify(purchase.contractData.amount),
       amountEffective: Amounts.stringify(purchase.payCostInfo.totalCost),
     };
diff --git a/packages/taler-wallet-core/src/operations/state.ts 
b/packages/taler-wallet-core/src/operations/state.ts
index 582dd92d..131e9083 100644
--- a/packages/taler-wallet-core/src/operations/state.ts
+++ b/packages/taler-wallet-core/src/operations/state.ts
@@ -15,7 +15,7 @@
  */
 
 import { HttpRequestLibrary } from "../util/http";
-import { NextUrlResult, BalancesResponse } from "../types/walletTypes";
+import { BalancesResponse } from "../types/walletTypes";
 import { CryptoApi, CryptoWorkerFactory } from "../crypto/workers/cryptoApi";
 import { AsyncOpMemoMap, AsyncOpMemoSingle } from "../util/asyncMemo";
 import { Logger } from "../util/logging";
@@ -32,7 +32,6 @@ export const EXCHANGE_COINS_LOCK = "exchange-coins-lock";
 export const EXCHANGE_RESERVES_LOCK = "exchange-reserves-lock";
 
 export class InternalWalletState {
-  cachedNextUrl: { [fulfillmentUrl: string]: NextUrlResult } = {};
   memoProcessReserve: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
   memoMakePlanchet: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
   memoGetPending: AsyncOpMemoSingle<
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts 
b/packages/taler-wallet-core/src/operations/transactions.ts
index 8300864b..7b42b9a5 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -35,7 +35,7 @@ import {
   PaymentStatus,
   WithdrawalType,
   WithdrawalDetails,
-  PaymentShortInfo,
+  OrderShortInfo,
 } from "../types/transactions";
 import { getFundingPaytoUris } from "./reserves";
 
@@ -234,7 +234,7 @@ export async function getTransactions(
         if (!proposal) {
           return;
         }
-        const info: PaymentShortInfo = {
+        const info: OrderShortInfo = {
           fulfillmentUrl: pr.contractData.fulfillmentUrl,
           merchant: pr.contractData.merchant,
           orderId: pr.contractData.orderId,
diff --git a/packages/taler-wallet-core/src/types/dbTypes.ts 
b/packages/taler-wallet-core/src/types/dbTypes.ts
index e36e322d..79100b69 100644
--- a/packages/taler-wallet-core/src/types/dbTypes.ts
+++ b/packages/taler-wallet-core/src/types/dbTypes.ts
@@ -31,6 +31,7 @@ import {
   ExchangeSignKeyJson,
   MerchantInfo,
   Product,
+  InternationalizedString,
 } from "./talerTypes";
 
 import { Index, Store } from "../util/query";
@@ -1270,8 +1271,10 @@ export interface AllowedExchangeInfo {
 export interface WalletContractData {
   products?: Product[];
   summaryI18n: { [lang_tag: string]: string } | undefined;
-  fulfillmentUrl: string;
+  fulfillmentUrl?: string;
   contractTermsHash: string;
+  fulfillmentMessage?: string;
+  fulfillmentMessageI18n?: InternationalizedString;
   merchantSig: string;
   merchantPub: string;
   merchant: MerchantInfo;
diff --git a/packages/taler-wallet-core/src/types/talerTypes.ts 
b/packages/taler-wallet-core/src/types/talerTypes.ts
index f14e2a2a..14e1b575 100644
--- a/packages/taler-wallet-core/src/types/talerTypes.ts
+++ b/packages/taler-wallet-core/src/types/talerTypes.ts
@@ -314,6 +314,10 @@ export interface Product {
   delivery_location?: string;
 }
 
+export interface InternationalizedString {
+  [lang_tag: string]: string;
+}
+
 /**
  * Contract terms from a merchant.
  */
@@ -338,7 +342,7 @@ export class ContractTerms {
    */
   summary: string;
 
-  summary_i18n?: { [lang_tag: string]: string };
+  summary_i18n?: InternationalizedString;
 
   /**
    * Nonce used to ensure freshness.
@@ -420,7 +424,17 @@ export class ContractTerms {
    * Fulfillment URL to view the product or
    * delivery status.
    */
-  fulfillment_url: string;
+  fulfillment_url?: string;
+
+  /**
+   * Plain text fulfillment message in the merchant's default language.
+   */
+  fulfillment_message?: string;
+
+  /**
+   * Internationalized fulfillment messages.
+   */
+  fulfillment_message_i18n?: InternationalizedString;
 
   /**
    * Share of the wire fee that must be settled with one payment.
@@ -1032,14 +1046,14 @@ export const codecForTax = (): Codec<Tax> =>
     .property("tax", codecForString())
     .build("Tax");
 
-export const codecForI18n = (): Codec<{ [lang_tag: string]: string }> =>
+export const codecForInternationalizedString = (): 
Codec<InternationalizedString> =>
   codecForMap(codecForString());
 
 export const codecForProduct = (): Codec<Product> =>
   buildCodecForObject<Product>()
     .property("product_id", codecOptional(codecForString()))
     .property("description", codecForString())
-    .property("description_i18n", codecOptional(codecForI18n()))
+    .property("description_i18n", 
codecOptional(codecForInternationalizedString()))
     .property("quantity", codecOptional(codecForNumber()))
     .property("unit", codecOptional(codecForString()))
     .property("price", codecOptional(codecForString()))
@@ -1050,13 +1064,15 @@ export const codecForProduct = (): Codec<Product> =>
 export const codecForContractTerms = (): Codec<ContractTerms> =>
   buildCodecForObject<ContractTerms>()
     .property("order_id", codecForString())
-    .property("fulfillment_url", codecForString())
+    .property("fulfillment_url", codecOptional(codecForString()))
+    .property("fulfillment_message", codecOptional(codecForString()))
+    .property("fulfillment_message_i18n", 
codecOptional(codecForInternationalizedString()))
     .property("merchant_base_url", codecForString())
     .property("h_wire", codecForString())
     .property("auto_refund", codecOptional(codecForDuration))
     .property("wire_method", codecForString())
     .property("summary", codecForString())
-    .property("summary_i18n", codecOptional(codecForI18n()))
+    .property("summary_i18n", codecOptional(codecForInternationalizedString()))
     .property("nonce", codecForString())
     .property("amount", codecForString())
     .property("auditors", codecForList(codecForAuditorHandle()))
diff --git a/packages/taler-wallet-core/src/types/transactions.ts 
b/packages/taler-wallet-core/src/types/transactions.ts
index 5ee09384..061ce28f 100644
--- a/packages/taler-wallet-core/src/types/transactions.ts
+++ b/packages/taler-wallet-core/src/types/transactions.ts
@@ -25,7 +25,15 @@
  * Imports.
  */
 import { Timestamp } from "../util/time";
-import { AmountString, Product } from "./talerTypes";
+import {
+  AmountString,
+  Product,
+  InternationalizedString,
+  MerchantInfo,
+  codecForInternationalizedString,
+  codecForMerchantInfo,
+  codecForProduct,
+} from "./talerTypes";
 import {
   Codec,
   buildCodecForObject,
@@ -202,7 +210,7 @@ export interface TransactionPayment extends 
TransactionCommon {
   /**
    * Additional information about the payment.
    */
-  info: PaymentShortInfo;
+  info: OrderShortInfo;
 
   /**
    * How far did the wallet get with processing the payment?
@@ -220,7 +228,7 @@ export interface TransactionPayment extends 
TransactionCommon {
   amountEffective: AmountString;
 }
 
-export interface PaymentShortInfo {
+export interface OrderShortInfo {
   /**
    * Order ID, uniquely identifies the order within a merchant instance
    */
@@ -234,7 +242,7 @@ export interface PaymentShortInfo {
   /**
    * More information about the merchant
    */
-  merchant: any;
+  merchant: MerchantInfo;
 
   /**
    * Summary of the order, given by the merchant
@@ -244,7 +252,7 @@ export interface PaymentShortInfo {
   /**
    * Map from IETF BCP 47 language tags to localized summaries
    */
-  summary_i18n?: { [lang_tag: string]: string };
+  summary_i18n?: InternationalizedString;
 
   /**
    * List of products that are part of the order
@@ -254,7 +262,18 @@ export interface PaymentShortInfo {
   /**
    * URL of the fulfillment, given by the merchant
    */
-  fulfillmentUrl: string;
+  fulfillmentUrl?: string;
+
+  /**
+   * Plain text message that should be shown to the user
+   * when the payment is complete.
+   */
+  fulfillmentMessage?: string;
+
+  /**
+   * Translations of fulfillmentMessage.
+   */
+  fulfillmentMessage_i18n?: InternationalizedString;
 }
 
 interface TransactionRefund extends TransactionCommon {
@@ -264,7 +283,7 @@ interface TransactionRefund extends TransactionCommon {
   refundedTransactionId: string;
 
   // Additional information about the refunded payment
-  info: PaymentShortInfo;
+  info: OrderShortInfo;
 
   // Amount that has been refunded by the merchant
   amountRaw: AmountString;
@@ -321,4 +340,20 @@ export const codecForTransactionsRequest = (): 
Codec<TransactionsRequest> =>
 export const codecForTransactionsResponse = (): Codec<TransactionsResponse> =>
   buildCodecForObject<TransactionsResponse>()
     .property("transactions", codecForList(codecForAny()))
-    .build("TransactionsResponse");
\ No newline at end of file
+    .build("TransactionsResponse");
+
+export const codecForOrderShortInfo = (): Codec<OrderShortInfo> =>
+  buildCodecForObject<OrderShortInfo>()
+    .property("contractTermsHash", codecForString())
+    .property("fulfillmentMessage", codecOptional(codecForString()))
+    .property(
+      "fulfillmentMessage_i18n",
+      codecOptional(codecForInternationalizedString()),
+    )
+    .property("fulfillmentUrl", codecOptional(codecForString()))
+    .property("merchant", codecForMerchantInfo())
+    .property("orderId", codecForString())
+    .property("products", codecOptional(codecForList(codecForProduct())))
+    .property("summary", codecForString())
+    .property("summary_i18n", codecOptional(codecForInternationalizedString()))
+    .build("OrderShortInfo");
diff --git a/packages/taler-wallet-core/src/types/walletTypes.ts 
b/packages/taler-wallet-core/src/types/walletTypes.ts
index 921c63a1..2cf3c7fb 100644
--- a/packages/taler-wallet-core/src/types/walletTypes.ts
+++ b/packages/taler-wallet-core/src/types/walletTypes.ts
@@ -50,8 +50,8 @@ import {
   codecForAny,
   buildCodecForUnion,
 } from "../util/codec";
-import { AmountString, codecForContractTerms } from "./talerTypes";
-import { TransactionError } from "./transactions";
+import { AmountString, codecForContractTerms, ContractTerms } from 
"./talerTypes";
+import { TransactionError, OrderShortInfo, codecForOrderShortInfo } from 
"./transactions";
 
 /**
  * Response for the create reserve request to the wallet.
@@ -209,8 +209,7 @@ export const enum ConfirmPayResultType {
  */
 export interface ConfirmPayResultDone {
   type: ConfirmPayResultType.Done;
-
-  nextUrl: string;
+  contractTerms: ContractTerms;
 }
 
 export interface ConfirmPayResultPending {
@@ -232,7 +231,7 @@ export const codecForConfirmPayResultPending = (): Codec<
 export const codecForConfirmPayResultDone = (): Codec<ConfirmPayResultDone> =>
   buildCodecForObject<ConfirmPayResultDone>()
     .property("type", codecForConstString(ConfirmPayResultType.Done))
-    .property("nextUrl", codecForString())
+    .property("contractTerms", codecForContractTerms())
     .build("ConfirmPayResultDone");
 
 export const codecForConfirmPayResult = (): Codec<ConfirmPayResult> =>
@@ -368,14 +367,6 @@ export interface BenchmarkResult {
   repetitions: number;
 }
 
-/**
- * Cached next URL for a particular session id.
- */
-export interface NextUrlResult {
-  nextUrl: string;
-  lastSessionId: string | undefined;
-}
-
 export const enum PreparePayResultType {
   PaymentPossible = "payment-possible",
   InsufficientBalance = "insufficient-balance",
@@ -388,7 +379,7 @@ export const codecForPreparePayResultPaymentPossible = (): 
Codec<
   buildCodecForObject<PreparePayResultPaymentPossible>()
     .property("amountEffective", codecForAmountString())
     .property("amountRaw", codecForAmountString())
-    .property("contractTerms", codecForAny())
+    .property("contractTerms", codecForContractTerms())
     .property("proposalId", codecForString())
     .property(
       "status",
@@ -419,7 +410,6 @@ export const codecForPreparePayResultAlreadyConfirmed = (): 
Codec<
     )
     .property("amountEffective", codecForAmountString())
     .property("amountRaw", codecForAmountString())
-    .property("nextUrl", codecForString())
     .property("paid", codecForBoolean)
     .property("contractTerms", codecForAny())
     .property("contractTermsHash", codecForString())
@@ -450,7 +440,7 @@ export type PreparePayResult =
 export interface PreparePayResultPaymentPossible {
   status: PreparePayResultType.PaymentPossible;
   proposalId: string;
-  contractTerms: Record<string, unknown>;
+  contractTerms: ContractTerms;
   amountRaw: string;
   amountEffective: string;
 }
@@ -458,19 +448,16 @@ export interface PreparePayResultPaymentPossible {
 export interface PreparePayResultInsufficientBalance {
   status: PreparePayResultType.InsufficientBalance;
   proposalId: string;
-  contractTerms: Record<string, unknown>;
+  contractTerms: ContractTerms;
   amountRaw: string;
 }
 
 export interface PreparePayResultAlreadyConfirmed {
   status: PreparePayResultType.AlreadyConfirmed;
-  contractTerms: Record<string, unknown>;
+  contractTerms: ContractTerms;
   paid: boolean;
   amountRaw: string;
   amountEffective: string;
-  // Only specified if paid.
-  nextUrl?: string;
-
   contractTermsHash: string;
 }
 
diff --git a/packages/taler-wallet-webextension/src/pages/pay.tsx 
b/packages/taler-wallet-webextension/src/pages/pay.tsx
index a7c5526e..fcf50cf3 100644
--- a/packages/taler-wallet-webextension/src/pages/pay.tsx
+++ b/packages/taler-wallet-webextension/src/pages/pay.tsx
@@ -37,10 +37,13 @@ import {
   ContractTerms,
   codecForContractTerms,
   ConfirmPayResultType,
+  ConfirmPayResult,
+  getJsonI18n,
 } from "taler-wallet-core";
 
 function TalerPayDialog({ talerPayUri }: { talerPayUri: string }): JSX.Element 
{
   const [payStatus, setPayStatus] = useState<PreparePayResult | undefined>();
+  const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>();
   const [payErrMsg, setPayErrMsg] = useState<string | undefined>("");
   const [numTries, setNumTries] = useState(0);
   const [loading, setLoading] = useState(false);
@@ -71,25 +74,25 @@ function TalerPayDialog({ talerPayUri }: { talerPayUri: 
string }): JSX.Element {
     payStatus.status === PreparePayResultType.AlreadyConfirmed &&
     numTries === 0
   ) {
-    return (
+    const fulfillmentUrl = payStatus.contractTerms.fulfillment_url;
+    if (fulfillmentUrl) {
+      return (
+        <span>
+          You have already paid for this article. Click{" "}
+          <a href={fulfillmentUrl}>here</a> to view it again.
+        </span>
+      );
+    } else {
       <span>
-        You have already paid for this article. Click{" "}
-        <a href={payStatus.nextUrl}>here</a> to view it again.
-      </span>
-    );
+        You have already paid for this article:{" "}
+        <em>
+          {payStatus.contractTerms.fulfillment_message ?? "no message given"}
+        </em>
+      </span>;
+    }
   }
 
-  let contractTerms: ContractTerms;
-
-  try {
-    contractTerms = codecForContractTerms().decode(payStatus.contractTerms);
-  } catch (e) {
-    // This should never happen, as the wallet is supposed to check the 
contract terms
-    // before storing them.
-    console.error(e);
-    console.log("raw contract terms were", payStatus.contractTerms);
-    return <span>Invalid contract terms.</span>;
-  }
+  let contractTerms: ContractTerms = payStatus.contractTerms;
 
   if (!contractTerms) {
     return (
@@ -122,13 +125,33 @@ function TalerPayDialog({ talerPayUri }: { talerPayUri: 
string }): JSX.Element {
       if (res.type !== ConfirmPayResultType.Done) {
         throw Error("payment pending");
       }
-      document.location.href = res.nextUrl;
+      const fu = res.contractTerms.fulfillment_url;
+      if (fu) {
+        document.location.href = fu;
+      }
+      setPayResult(res);
     } catch (e) {
       console.error(e);
       setPayErrMsg(e.message);
     }
   };
 
+  if (payResult && payResult.type === ConfirmPayResultType.Done) {
+    if (payResult.contractTerms.fulfillment_message) {
+      const obj = {
+        fulfillment_message: payResult.contractTerms.fulfillment_message,
+        fulfillment_message_i18n: 
payResult.contractTerms.fulfillment_message_i18n,
+      };
+      const msg = getJsonI18n(obj, "fulfillment_message")
+      return <div>
+        <p>Payment succeeded.</p>
+        <p>{msg}</p>
+      </div>;
+    } else {
+      return <span>Redirecting ...</span>;
+    }
+  }
+
   return (
     <div>
       <p>

-- 
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]