gnunet-svn
[Top][All Lists]
Advanced

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

[GNUnet-SVN] [taler-wallet-webex] branch master updated: refactor tippin


From: gnunet
Subject: [GNUnet-SVN] [taler-wallet-webex] branch master updated: refactor tipping, adjust to new redirect-based API
Date: Fri, 19 Jan 2018 01:27:35 +0100

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

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

The following commit(s) were added to refs/heads/master by this push:
     new 1671d9a5 refactor tipping, adjust to new redirect-based API
1671d9a5 is described below

commit 1671d9a508b803af31762bcd9508e70eb40e7b48
Author: Florian Dold <address@hidden>
AuthorDate: Fri Jan 19 01:27:27 2018 +0100

    refactor tipping, adjust to new redirect-based API
---
 src/dbTypes.ts                       |   5 +
 src/i18n/de.po                       |   8 +-
 src/i18n/en-US.po                    |   8 +-
 src/i18n/fr.po                       |   8 +-
 src/i18n/it.po                       |   8 +-
 src/i18n/taler-wallet-webex.pot      |   8 +-
 src/wallet.ts                        | 184 +++++++++++++----------------------
 src/walletTypes.ts                   | 141 +--------------------------
 src/webex/messages.ts                |  16 ++-
 src/webex/pages/confirm-contract.tsx |  73 +++++++++-----
 src/webex/pages/tip.tsx              |  16 ++-
 src/webex/wxApi.ts                   |  39 +++-----
 src/webex/wxBackend.ts               |  67 +++++--------
 13 files changed, 191 insertions(+), 390 deletions(-)

diff --git a/src/dbTypes.ts b/src/dbTypes.ts
index 609c8526..035c100a 100644
--- a/src/dbTypes.ts
+++ b/src/dbTypes.ts
@@ -575,6 +575,11 @@ export interface TipRecord {
   accepted: boolean;
 
   /**
+   * Have we picked up the tip record from the merchant already?
+   */
+  pickedUp: boolean;
+
+  /**
    * The tipped amount.
    */
   amount: AmountJson;
diff --git a/src/i18n/de.po b/src/i18n/de.po
index 39f1f56e..1a003c17 100644
--- a/src/i18n/de.po
+++ b/src/i18n/de.po
@@ -42,13 +42,13 @@ msgstr ""
 msgid "Exchanges in the wallet:"
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:188
+#: src/webex/pages/confirm-contract.tsx:200
 #, c-format
 msgid "You have insufficient funds of the requested currency in your wallet."
 msgstr ""
 
 #. tslint:disable-next-line:max-line-length
-#: src/webex/pages/confirm-contract.tsx:190
+#: src/webex/pages/confirm-contract.tsx:202
 #, c-format
 msgid ""
 "You do not have any funds from an exchange that is accepted by this "
@@ -56,12 +56,12 @@ msgid ""
 "wallet."
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:251
+#: src/webex/pages/confirm-contract.tsx:280
 #, c-format
 msgid "The merchant%1$s offers you to purchase:\n"
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:272
+#: src/webex/pages/confirm-contract.tsx:301
 #, fuzzy, c-format
 msgid "Confirm payment"
 msgstr "Bezahlung bestätigen"
diff --git a/src/i18n/en-US.po b/src/i18n/en-US.po
index 2fdb451d..3d3fd433 100644
--- a/src/i18n/en-US.po
+++ b/src/i18n/en-US.po
@@ -42,13 +42,13 @@ msgstr ""
 msgid "Exchanges in the wallet:"
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:188
+#: src/webex/pages/confirm-contract.tsx:200
 #, c-format
 msgid "You have insufficient funds of the requested currency in your wallet."
 msgstr ""
 
 #. tslint:disable-next-line:max-line-length
-#: src/webex/pages/confirm-contract.tsx:190
+#: src/webex/pages/confirm-contract.tsx:202
 #, c-format
 msgid ""
 "You do not have any funds from an exchange that is accepted by this "
@@ -56,12 +56,12 @@ msgid ""
 "wallet."
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:251
+#: src/webex/pages/confirm-contract.tsx:280
 #, c-format
 msgid "The merchant%1$s offers you to purchase:\n"
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:272
+#: src/webex/pages/confirm-contract.tsx:301
 #, c-format
 msgid "Confirm payment"
 msgstr ""
diff --git a/src/i18n/fr.po b/src/i18n/fr.po
index 5d47a1f7..08f4a9d0 100644
--- a/src/i18n/fr.po
+++ b/src/i18n/fr.po
@@ -42,13 +42,13 @@ msgstr ""
 msgid "Exchanges in the wallet:"
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:188
+#: src/webex/pages/confirm-contract.tsx:200
 #, c-format
 msgid "You have insufficient funds of the requested currency in your wallet."
 msgstr ""
 
 #. tslint:disable-next-line:max-line-length
-#: src/webex/pages/confirm-contract.tsx:190
+#: src/webex/pages/confirm-contract.tsx:202
 #, c-format
 msgid ""
 "You do not have any funds from an exchange that is accepted by this "
@@ -56,12 +56,12 @@ msgid ""
 "wallet."
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:251
+#: src/webex/pages/confirm-contract.tsx:280
 #, c-format
 msgid "The merchant%1$s offers you to purchase:\n"
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:272
+#: src/webex/pages/confirm-contract.tsx:301
 #, c-format
 msgid "Confirm payment"
 msgstr ""
diff --git a/src/i18n/it.po b/src/i18n/it.po
index 5d47a1f7..08f4a9d0 100644
--- a/src/i18n/it.po
+++ b/src/i18n/it.po
@@ -42,13 +42,13 @@ msgstr ""
 msgid "Exchanges in the wallet:"
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:188
+#: src/webex/pages/confirm-contract.tsx:200
 #, c-format
 msgid "You have insufficient funds of the requested currency in your wallet."
 msgstr ""
 
 #. tslint:disable-next-line:max-line-length
-#: src/webex/pages/confirm-contract.tsx:190
+#: src/webex/pages/confirm-contract.tsx:202
 #, c-format
 msgid ""
 "You do not have any funds from an exchange that is accepted by this "
@@ -56,12 +56,12 @@ msgid ""
 "wallet."
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:251
+#: src/webex/pages/confirm-contract.tsx:280
 #, c-format
 msgid "The merchant%1$s offers you to purchase:\n"
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:272
+#: src/webex/pages/confirm-contract.tsx:301
 #, c-format
 msgid "Confirm payment"
 msgstr ""
diff --git a/src/i18n/taler-wallet-webex.pot b/src/i18n/taler-wallet-webex.pot
index 5d47a1f7..08f4a9d0 100644
--- a/src/i18n/taler-wallet-webex.pot
+++ b/src/i18n/taler-wallet-webex.pot
@@ -42,13 +42,13 @@ msgstr ""
 msgid "Exchanges in the wallet:"
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:188
+#: src/webex/pages/confirm-contract.tsx:200
 #, c-format
 msgid "You have insufficient funds of the requested currency in your wallet."
 msgstr ""
 
 #. tslint:disable-next-line:max-line-length
-#: src/webex/pages/confirm-contract.tsx:190
+#: src/webex/pages/confirm-contract.tsx:202
 #, c-format
 msgid ""
 "You do not have any funds from an exchange that is accepted by this "
@@ -56,12 +56,12 @@ msgid ""
 "wallet."
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:251
+#: src/webex/pages/confirm-contract.tsx:280
 #, c-format
 msgid "The merchant%1$s offers you to purchase:\n"
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:272
+#: src/webex/pages/confirm-contract.tsx:301
 #, c-format
 msgid "Confirm payment"
 msgstr ""
diff --git a/src/wallet.ts b/src/wallet.ts
index 7c291492..9498fe82 100644
--- a/src/wallet.ts
+++ b/src/wallet.ts
@@ -99,7 +99,6 @@ import {
   NextUrlResult,
   Notifier,
   PayCoinInfo,
-  QueryPaymentResult,
   ReserveCreationInfo,
   ReturnCoinsRequest,
   SenderWireInfos,
@@ -652,8 +651,8 @@ export class Wallet {
       contractTerms: proposal.contractTerms,
       contractTermsHash: proposal.contractTermsHash,
       finished: false,
-      lastSessionSig: undefined,
       lastSessionId: undefined,
+      lastSessionSig: undefined,
       merchantSig: proposal.merchantSig,
       payReq,
       refundsDone: {},
@@ -717,7 +716,11 @@ export class Wallet {
     return id;
   }
 
-  private async submitPay(purchase: PurchaseRecord, sessionId: string | 
undefined): Promise<ConfirmPayResult> {
+  async submitPay(contractTermsHash: string, sessionId: string | undefined): 
Promise<ConfirmPayResult> {
+    const purchase = await this.q().get(Stores.purchases, contractTermsHash);
+    if (!purchase) {
+      throw Error("Purchase not found: " + contractTermsHash);
+    }
     let resp;
     const payReq = { ...purchase.payReq, session_id: sessionId };
     try {
@@ -764,7 +767,7 @@ export class Wallet {
     let purchase = await this.q().get(Stores.purchases, 
proposal.contractTermsHash);
 
     if (purchase) {
-      return this.submitPay(purchase, sessionId);
+      return this.submitPay(purchase.contractTermsHash, sessionId);
     }
 
     const res = await this.getCoinsForPayment({
@@ -796,7 +799,7 @@ export class Wallet {
       purchase = await this.recordConfirmPay(sd.proposal, sd.payCoinInfo, 
sd.exchangeUrl);
     }
 
-    return this.submitPay(purchase, sessionId);
+    return this.submitPay(purchase.contractTermsHash, sessionId);
   }
 
 
@@ -885,52 +888,17 @@ export class Wallet {
    * Retrieve information required to pay for a contract, where the
    * contract is identified via the fulfillment url.
    */
-  async queryPaymentByFulfillmentUrl(url: string): Promise<QueryPaymentResult> 
{
+  async queryPaymentByFulfillmentUrl(url: string): Promise<PurchaseRecord | 
undefined> {
     console.log("query for payment", url);
 
     const t = await this.q().getIndexed(Stores.purchases.fulfillmentUrlIndex, 
url);
 
     if (!t) {
       console.log("query for payment failed");
-      return {
-        found: false,
-      };
-    }
-    console.log("query for payment succeeded:", t);
-    return {
-      contractTerms: t.contractTerms,
-      contractTermsHash: t.contractTermsHash,
-      found: true,
-      lastSessionId: t.lastSessionId,
-      lastSessionSig: t.lastSessionSig,
-      payReq: t.payReq,
-    };
-  }
-
-  /**
-   * Retrieve information required to pay for a contract, where the
-   * contract is identified via the contract terms hash.
-   */
-  async queryPaymentByContractTermsHash(contractTermsHash: string): 
Promise<QueryPaymentResult> {
-    console.log("query for payment", contractTermsHash);
-
-    const t = await this.q().get(Stores.purchases, contractTermsHash);
-
-    if (!t) {
-      console.log("query for payment failed");
-      return {
-        found: false,
-      };
+      return undefined;
     }
     console.log("query for payment succeeded:", t);
-    return {
-      contractTerms: t.contractTerms,
-      contractTermsHash: t.contractTermsHash,
-      found: true,
-      lastSessionSig: t.lastSessionSig,
-      lastSessionId: t.lastSessionId,
-      payReq: t.payReq,
-    };
+    return t;
   }
 
 
@@ -2723,46 +2691,11 @@ export class Wallet {
   }
 
   /**
-   * Get planchets for a tip.  Creates new planchets if they don't exist 
already
-   * for this tip.  The tip is uniquely identified by the merchant's domain 
and the tip id.
+   * Workaround for merchant bug (#5258)
    */
-  async getTipPlanchets(merchantDomain: string,
-                        tipId: string,
-                        amount: AmountJson,
-                        deadline: number,
-                        exchangeUrl: string,
-                        nextUrl: string): Promise<TipPlanchetDetail[]> {
-    let tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]);
-    if (!tipRecord) {
-      await this.updateExchangeFromUrl(exchangeUrl);
-      const denomsForWithdraw = await 
this.getVerifiedWithdrawDenomList(exchangeUrl, amount);
-      const planchets = await Promise.all(denomsForWithdraw.map(d => 
this.cryptoApi.createTipPlanchet(d)));
-      const coinPubs: string[] = planchets.map(x => x.coinPub);
-      const now = (new Date()).getTime();
-      tipRecord = {
-        accepted: false,
-        amount,
-        coinPubs,
-        deadline,
-        exchangeUrl,
-        merchantDomain,
-        nextUrl,
-        planchets,
-        timestamp: now,
-        tipId,
-      };
-      await this.q().put(Stores.tips, tipRecord).finish();
-    }
-    // Planchets in the form that the merchant expects
-    const planchetDetail: TipPlanchetDetail[] = tipRecord.planchets.map((p) => 
({
-      coin_ev: p.coinEv,
-      denom_pub_hash: p.denomPubHash,
-    }));
-    return planchetDetail;
-  }
+  private tipPickupWorkaround: { [tipId: string]: boolean } = {};
 
-
-  async processTip(tipToken: TipToken): Promise<void> {
+  async processTip(tipToken: TipToken): Promise<TipRecord> {
     console.log("got tip token", tipToken);
 
     const deadlineSec = getTalerStampSec(tipToken.expiration);
@@ -2770,55 +2703,61 @@ export class Wallet {
       throw Error("tipping failed (invalid expiration)");
     }
 
-    const merchantDomain = new URI(document.location.href).origin();
-    let walletResp;
-    walletResp = await this.getTipPlanchets(merchantDomain,
-                                              tipToken.tip_id,
-                                              tipToken.amount,
-                                              deadlineSec,
-                                              tipToken.exchange_url,
-                                              tipToken.next_url);
-
-    const planchets = walletResp;
+    const merchantDomain = new URI(tipToken.pickup_url).origin();
+    let tipRecord = await this.q().get(Stores.tips, [tipToken.tip_id, 
merchantDomain]);
 
-    if (!planchets) {
-      console.log("failed tip", walletResp);
-      throw Error("processing tip failed");
+    if (tipRecord && tipRecord.pickedUp) {
+      return tipRecord;
     }
+    await this.updateExchangeFromUrl(tipToken.exchange_url);
+    const denomsForWithdraw = await 
this.getVerifiedWithdrawDenomList(tipToken.exchange_url, tipToken.amount);
+    const planchets = await Promise.all(denomsForWithdraw.map(d => 
this.cryptoApi.createTipPlanchet(d)));
+    const coinPubs: string[] = planchets.map(x => x.coinPub);
+    const now = (new Date()).getTime();
+    tipRecord = {
+      accepted: false,
+      amount: tipToken.amount,
+      coinPubs,
+      deadline: deadlineSec,
+      exchangeUrl: tipToken.exchange_url,
+      merchantDomain,
+      nextUrl: tipToken.next_url,
+      pickedUp: false,
+      planchets,
+      timestamp: now,
+      tipId: tipToken.tip_id,
+    };
+
+    // Planchets in the form that the merchant expects
+    const planchetsDetail: TipPlanchetDetail[] = tipRecord.planchets.map((p) 
=> ({
+      coin_ev: p.coinEv,
+      denom_pub_hash: p.denomPubHash,
+    }));
 
     let merchantResp;
 
+    await this.q().put(Stores.tips, tipRecord).finish();
+
+    if (this.tipPickupWorkaround[tipRecord.tipId]) {
+      // Be careful to not accidentally download twice (#5258)
+      return tipRecord;
+    }
+
     try {
       const config = {
         validateStatus: (s: number) => s === 200,
       };
-      const req = { planchets, tip_id: tipToken.tip_id };
+      const req = { planchets: planchetsDetail, tip_id: tipToken.tip_id };
       merchantResp = await axios.post(tipToken.pickup_url, req, config);
     } catch (e) {
       console.log("tipping failed", e);
       throw e;
     }
 
-    try {
-      this.processTipResponse(merchantDomain, tipToken.tip_id, 
merchantResp.data);
-    } catch (e) {
-      console.log("processTipResponse failed", e);
-      throw e;
-    }
+    this.tipPickupWorkaround[tipToken.tip_id] = true;
 
-    return;
-  }
+    const response = TipResponse.checked(merchantResp.data);
 
-  /**
-   * Accept a merchant's response to a tip pickup and start withdrawing the 
coins.
-   * These coins will not appear in the wallet yet.
-   */
-  async processTipResponse(merchantDomain: string, tipId: string, response: 
TipResponse): Promise<void> {
-    const tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]);
-    if (!tipRecord) {
-      throw Error("tip not found");
-    }
-    console.log("processing tip response", response);
     if (response.reserve_sigs.length !== tipRecord.planchets.length) {
       throw Error("number of tip responses does not match requested 
planchets");
     }
@@ -2840,12 +2779,21 @@ export class Wallet {
       await this.q().put(Stores.precoins, preCoin);
       this.processPreCoin(preCoin);
     }
+
+    tipRecord.pickedUp = true;
+
+    await this.q().put(Stores.tips, tipRecord).finish();
+
+    return tipRecord;
   }
 
+
   /**
    * Start using the coins from a tip.
    */
-  async acceptTip(merchantDomain: string, tipId: string): Promise<void> {
+  async acceptTip(tipToken: TipToken): Promise<void> {
+    const tipId = tipToken.tip_id;
+    const merchantDomain = new URI(tipToken.pickup_url).origin();
     const tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]);
     if (!tipRecord) {
       throw Error("tip not found");
@@ -2875,11 +2823,9 @@ export class Wallet {
     this.notifier.notify();
   }
 
-  async getTipStatus(merchantDomain: string, tipId: string): 
Promise<TipStatus> {
-    const tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]);
-    if (!tipRecord) {
-      throw Error("tip not found");
-    }
+
+  async getTipStatus(tipToken: TipToken): Promise<TipStatus> {
+    const tipRecord = await this.processTip(tipToken);
     const rci = await this.getReserveCreationInfo(tipRecord.exchangeUrl, 
tipRecord.amount);
     const tipStatus: TipStatus = {
       rci,
diff --git a/src/walletTypes.ts b/src/walletTypes.ts
index c98717ac..aba7dbfb 100644
--- a/src/walletTypes.ts
+++ b/src/walletTypes.ts
@@ -41,7 +41,6 @@ import {
   CoinPaySig,
   ContractTerms,
   PayReq,
-  TipResponse,
 } from "./talerTypes";
 
 
@@ -281,12 +280,6 @@ export interface HistoryRecord {
 
 
 /**
- * Response to a query payment request.  Tagged union over the 'found' field.
- */
-export type QueryPaymentResult = QueryPaymentNotFound | QueryPaymentFound;
-
-
-/**
  * Query payment response when the payment was found.
  */
 export interface QueryPaymentNotFound {
@@ -304,6 +297,7 @@ export interface QueryPaymentFound {
   lastSessionSig?: string;
   lastSessionId?: string;
   payReq: PayReq;
+  proposalId: number;
 }
 
 
@@ -438,7 +432,6 @@ export interface CoinWithDenom {
   denom: DenominationRecord;
 }
 
-
 /**
  * Status of processing a tip.
  */
@@ -449,138 +442,6 @@ export interface TipStatus {
 
 
 /**
- * Request to the wallet for the status of processing a tip.
- */
address@hidden()
-export class TipStatusRequest {
-  /**
-   * Identifier of the tip.
-   */
-  @Checkable.String
-  tipId: string;
-
-  /**
-   * Merchant domain.  Within each merchant domain, the tip identifier
-   * uniquely identifies a tip.
-   */
-  @Checkable.String
-  merchantDomain: string;
-
-  /**
-   * Create a TipStatusRequest from untyped JSON.
-   */
-  static checked: (obj: any) => TipStatusRequest;
-}
-
-/**
- * Request to the wallet to accept a tip.
- */
address@hidden()
-export class AcceptTipRequest {
-  /**
-   * Identifier of the tip.
-   */
-  @Checkable.String
-  tipId: string;
-
-  /**
-   * Merchant domain.  Within each merchant domain, the tip identifier
-   * uniquely identifies a tip.
-   */
-  @Checkable.String
-  merchantDomain: string;
-
-  /**
-   * Create an AcceptTipRequest from untyped JSON.
-   * Validates the schema and throws on error.
-   */
-  static checked: (obj: any) => AcceptTipRequest;
-}
-
-
-/**
- * Request for the wallet to process a tip response from a merchant.
- */
address@hidden()
-export class ProcessTipResponseRequest {
-  /**
-   * Identifier of the tip.
-   */
-  @Checkable.String
-  tipId: string;
-
-  /**
-   * Merchant domain.  Within each merchant domain, the tip identifier
-   * uniquely identifies a tip.
-   */
-  @Checkable.String
-  merchantDomain: string;
-
-  /**
-   * Tip response from the merchant.
-   */
-  @Checkable.Value(() => TipResponse)
-  tipResponse: TipResponse;
-
-  /**
-   * Create an AcceptTipRequest from untyped JSON.
-   * Validates the schema and throws on error.
-   */
-  static checked: (obj: any) => ProcessTipResponseRequest;
-}
-
-
-/**
- * Request for the wallet to generate tip planchets.
- */
address@hidden()
-export class GetTipPlanchetsRequest {
-  /**
-   * Identifier of the tip.
-   */
-  @Checkable.String
-  tipId: string;
-
-  /**
-   * Merchant domain.  Within each merchant domain, the tip identifier
-   * uniquely identifies a tip.
-   */
-  @Checkable.String
-  merchantDomain: string;
-
-  /**
-   * Amount of the tip.
-   */
-  @Checkable.Optional(Checkable.Value(() => AmountJson))
-  amount: AmountJson;
-
-  /**
-   * Deadline for picking up the tip.
-   */
-  @Checkable.Number
-  deadline: number;
-
-  /**
-   * Exchange URL that must be used to pick up the tip.
-   */
-  @Checkable.String
-  exchangeUrl: string;
-
-  /**
-   * URL to nagivate to after processing the tip.
-   */
-  @Checkable.String
-  nextUrl: string;
-
-  /**
-   * Create an AcceptTipRequest from untyped JSON.
-   * Validates the schema and throws on error.
-   */
-  static checked: (obj: any) => GetTipPlanchetsRequest;
-}
-
-
-/**
  * Badge that shows activity for the wallet.
  */
 export interface Badge {
diff --git a/src/webex/messages.ts b/src/webex/messages.ts
index 0fcd6047..e1bd6f12 100644
--- a/src/webex/messages.ts
+++ b/src/webex/messages.ts
@@ -171,20 +171,12 @@ export interface MessageMap {
     request: { refundPermissions: talerTypes.RefundPermission[] };
     response: void;
   };
-  "get-tip-planchets": {
-    request: walletTypes.GetTipPlanchetsRequest;
-    response: void;
-  };
-  "process-tip-response": {
-    request: walletTypes.ProcessTipResponseRequest;
-    response: void;
-  };
   "accept-tip": {
-    request: walletTypes.AcceptTipRequest;
+    request: { tipToken: talerTypes.TipToken };
     response: void;
   };
   "get-tip-status": {
-    request: walletTypes.TipStatusRequest;
+    request: { tipToken: talerTypes.TipToken };
     response: void;
   };
   "clear-notification": {
@@ -199,6 +191,10 @@ export interface MessageMap {
     request: any;
     response: void;
   };
+  "submit-pay": {
+    request: { contractTermsHash: string, sessionId: string | undefined };
+    response: void;
+  };
 }
 
 /**
diff --git a/src/webex/pages/confirm-contract.tsx 
b/src/webex/pages/confirm-contract.tsx
index cd58d712..2ec13105 100644
--- a/src/webex/pages/confirm-contract.tsx
+++ b/src/webex/pages/confirm-contract.tsx
@@ -122,6 +122,7 @@ interface ContractPromptState {
    */
   holdCheck: boolean;
   payStatus?: CheckPayResult;
+  replaying: boolean;
 }
 
 class ContractPrompt extends React.Component<ContractPromptProps, 
ContractPromptState> {
@@ -135,6 +136,7 @@ class ContractPrompt extends 
React.Component<ContractPromptProps, ContractPrompt
       payDisabled: true,
       proposal: null,
       proposalId: props.proposalId,
+      replaying: false,
     };
   }
 
@@ -150,13 +152,23 @@ class ContractPrompt extends 
React.Component<ContractPromptProps, ContractPrompt
     if (this.props.resourceUrl) {
       const p = await 
wxApi.queryPaymentByFulfillmentUrl(this.props.resourceUrl);
       console.log("query for resource url", this.props.resourceUrl, "result", 
p);
-      if (p.found && (p.lastSessionSig === undefined || p.lastSessionSig === 
this.props.sessionId)) {
-        const nextUrl = new URI(p.contractTerms.fulfillment_url);
-        nextUrl.addSearch("order_id", p.contractTerms.order_id);
-        if (p.lastSessionSig) {
-          nextUrl.addSearch("session_sig", p.lastSessionSig);
+      if (p) {
+        if (p.lastSessionSig === undefined || p.lastSessionSig === 
this.props.sessionId) {
+          const nextUrl = new URI(p.contractTerms.fulfillment_url);
+          nextUrl.addSearch("order_id", p.contractTerms.order_id);
+          if (p.lastSessionSig) {
+            nextUrl.addSearch("session_sig", p.lastSessionSig);
+          }
+          location.replace(nextUrl.href());
+          return;
+        } else {
+          // We're in a new session
+          this.setState({ replaying: true });
+          const payResult = await wxApi.submitPay(p.contractTermsHash, 
this.props.sessionId);
+          console.log("payResult", payResult);
+          location.replace(payResult.nextUrl);
+          return;
         }
-        location.href = nextUrl.href();
       }
     }
     let proposalId = this.props.proposalId;
@@ -230,6 +242,9 @@ class ContractPrompt extends 
React.Component<ContractPromptProps, ContractPrompt
     if (this.props.contractUrl === undefined && this.props.proposalId === 
undefined) {
       return <span>Error: either contractUrl or proposalId must be 
given</span>;
     }
+    if (this.state.replaying) {
+      return <span>Re-submitting existing payment</span>;
+    }
     if (this.state.proposalId === undefined) {
       return <span>Downloading contract terms</span>;
     }
@@ -245,26 +260,40 @@ class ContractPrompt extends 
React.Component<ContractPromptProps, ContractPrompt
     }
     const amount = <strong>{renderAmount(c.amount)}</strong>;
     console.log("payStatus", this.state.payStatus);
+
+    let products = null;
+    if (c.products.length) {
+      products = (
+        <>
+          <span>The following items are included:</span>
+          <ul>
+            {c.products.map(
+              (p: any, i: number) => (<li key={i}>{p.description}: 
{renderAmount(p.price)}</li>))
+            }
+          </ul>
+      </>
+      );
+    }
     return (
-      <div>
+      <>
         <div>
           <i18n.Translate wrap="p">
             The merchant <span>{merchantName}</span> {" "}
             offers you to purchase:
           </i18n.Translate>
-          <ul>
-            {c.products.map(
-              (p: any, i: number) => (<li key={i}>{p.description}: 
{renderAmount(p.price)}</li>))
-            }
-          </ul>
-            {(this.state.payStatus && this.state.payStatus.coinSelection)
-              ? <p>
-                  The total price is <span>{amount}</span>{" "}
-                  (plus 
<span>{renderAmount(this.state.payStatus.coinSelection.totalFees)}</span> fees).
-                </p>
-              :
-              <p>The total price is <span>{amount}</span>.</p>
-            }
+          <div style={{"text-align": "center"}}>
+            <strong>{c.summary}</strong>
+          </div>
+          <strong></strong>
+          {products}
+          {(this.state.payStatus && this.state.payStatus.coinSelection)
+            ? <p>
+                The total price is <span>{amount}</span>{" "}
+                (plus 
<span>{renderAmount(this.state.payStatus.coinSelection.totalFees)}</span> fees).
+              </p>
+            :
+            <p>The total price is <span>{amount}</span>.</p>
+          }
         </div>
         <button className="pure-button button-success"
                 disabled={this.state.payDisabled}
@@ -280,7 +309,7 @@ class ContractPrompt extends 
React.Component<ContractPromptProps, ContractPrompt
           {(this.state.error ? <p className="errorbox">{this.state.error}</p> 
: <p />)}
         </div>
         <Details exchanges={this.state.exchanges} contractTerms={c} 
collapsed={!this.state.error}/>
-      </div>
+      </>
     );
   }
 }
@@ -296,10 +325,8 @@ document.addEventListener("DOMContentLoaded", () => {
   } catch  {
     // ignore error
   }
-
   const sessionId = query.sessionId;
   const contractUrl = query.contractUrl;
-
   const resourceUrl = query.resourceUrl;
 
   ReactDOM.render(
diff --git a/src/webex/pages/tip.tsx b/src/webex/pages/tip.tsx
index 7f96401c..578ae6aa 100644
--- a/src/webex/pages/tip.tsx
+++ b/src/webex/pages/tip.tsx
@@ -39,11 +39,11 @@ import {
 } from "../renderHtml";
 
 import * as Amounts from "../../amounts";
+import { TipToken } from "../../talerTypes";
 import { TipStatus } from "../../walletTypes";
 
 interface TipDisplayProps {
-  merchantDomain: string;
-  tipId: string;
+  tipToken: TipToken;
 }
 
 interface TipDisplayState {
@@ -58,7 +58,7 @@ class TipDisplay extends React.Component<TipDisplayProps, 
TipDisplayState> {
   }
 
   async update() {
-    const tipStatus = await getTipStatus(this.props.merchantDomain, 
this.props.tipId);
+    const tipStatus = await getTipStatus(this.props.tipToken);
     this.setState({ tipStatus });
   }
 
@@ -96,7 +96,7 @@ class TipDisplay extends React.Component<TipDisplayProps, 
TipDisplayState> {
 
   accept() {
     this.setState({ working: true});
-    acceptTip(this.props.merchantDomain, this.props.tipId);
+    acceptTip(this.props.tipToken);
   }
 
   renderButtons() {
@@ -126,7 +126,7 @@ class TipDisplay extends React.Component<TipDisplayProps, 
TipDisplayState> {
       <div>
         <h2>Tip Received!</h2>
         <p>You received a tip of 
<strong>{renderAmount(ts.tip.amount)}</strong> from <span> </span>
-        <strong>{this.props.merchantDomain}</strong>.</p>
+        <strong>{ts.tip.merchantDomain}</strong>.</p>
         {ts.tip.accepted
           ? <p>You've accepted this tip! <a href={ts.tip.nextUrl}>Go back to 
merchant</a></p>
           : this.renderButtons()
@@ -142,11 +142,9 @@ async function main() {
     const url = new URI(document.location.href);
     const query: any = URI.parseQuery(url.query());
 
-    const merchantDomain = query.merchant_domain;
-    const tipId = query.tip_id;
-    const props: TipDisplayProps = { tipId, merchantDomain };
+    const tipToken = TipToken.checked(JSON.parse(query.tip_token));
 
-    ReactDOM.render(<TipDisplay {...props} />,
+    ReactDOM.render(<TipDisplay tipToken={tipToken} />,
                     document.getElementById("container")!);
 
   } catch (e) {
diff --git a/src/webex/wxApi.ts b/src/webex/wxApi.ts
index 84c44dba..566f4526 100644
--- a/src/webex/wxApi.ts
+++ b/src/webex/wxApi.ts
@@ -35,7 +35,6 @@ import {
 import {
   CheckPayResult,
   ConfirmPayResult,
-  QueryPaymentResult,
   ReserveCreationInfo,
   SenderWireInfos,
   TipStatus,
@@ -44,8 +43,7 @@ import {
 
 import {
   RefundPermission,
-  TipPlanchetDetail,
-  TipResponse,
+  TipToken,
 } from "../talerTypes";
 
 import { MessageMap, MessageType } from "./messages";
@@ -222,6 +220,13 @@ export function confirmPay(proposalId: number, sessionId: 
string | undefined): P
 }
 
 /**
+ * Replay paying for a purchase.
+ */
+export function submitPay(contractTermsHash: string, sessionId: string | 
undefined): Promise<ConfirmPayResult> {
+  return callBackend("submit-pay", { contractTermsHash, sessionId });
+}
+
+/**
  * Hash a contract.  Throws if its not a valid contract.
  */
 export function hashContract(contract: object): Promise<string> {
@@ -238,7 +243,7 @@ export function confirmReserve(reservePub: string): 
Promise<void> {
 /**
  * Query for a payment by fulfillment URL.
  */
-export function queryPaymentByFulfillmentUrl(url: string): 
Promise<QueryPaymentResult> {
+export function queryPaymentByFulfillmentUrl(url: string): 
Promise<PurchaseRecord> {
   return callBackend("query-payment", { url });
 }
 
@@ -324,37 +329,19 @@ export function getFullRefundFees(args: { 
refundPermissions: RefundPermission[]
 
 
 /**
- * Get or generate planchets to give the merchant that wants to tip us.
- */
-export function getTipPlanchets(merchantDomain: string,
-                                tipId: string,
-                                amount: AmountJson,
-                                deadline: number,
-                                exchangeUrl: string,
-                                nextUrl: string): Promise<TipPlanchetDetail[]> 
{
-  return callBackend("get-tip-planchets", { merchantDomain, tipId, amount, 
deadline, exchangeUrl, nextUrl });
-}
-
-/**
  * Get the status of processing a tip.
  */
-export function getTipStatus(merchantDomain: string, tipId: string): 
Promise<TipStatus> {
-  return callBackend("get-tip-status", { merchantDomain, tipId });
+export function getTipStatus(tipToken: TipToken): Promise<TipStatus> {
+  return callBackend("get-tip-status", { tipToken });
 }
 
 /**
  * Mark a tip as accepted by the user.
  */
-export function acceptTip(merchantDomain: string, tipId: string): 
Promise<TipStatus> {
-  return callBackend("accept-tip", { merchantDomain, tipId });
+export function acceptTip(tipToken: TipToken): Promise<TipStatus> {
+  return callBackend("accept-tip", { tipToken });
 }
 
-/**
- * Process a response from the merchant for a tip request.
- */
-export function processTipResponse(merchantDomain: string, tipId: string, 
tipResponse: TipResponse): Promise<void> {
-  return callBackend("process-tip-response", { merchantDomain, tipId, 
tipResponse });
-}
 
 /**
  * Clear notifications that the wallet shows to the user.
diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts
index a4f534af..26b8ff2c 100644
--- a/src/webex/wxBackend.ts
+++ b/src/webex/wxBackend.ts
@@ -34,15 +34,10 @@ import {
 import { AmountJson } from "../amounts";
 
 import {
-  AcceptTipRequest,
   ConfirmReserveRequest,
   CreateReserveRequest,
-  GetTipPlanchetsRequest,
   Notifier,
-  ProcessTipResponseRequest,
-  QueryPaymentFound,
   ReturnCoinsRequest,
-  TipStatusRequest,
 } from "../walletTypes";
 
 import {
@@ -50,6 +45,7 @@ import {
 } from "../wallet";
 
 import {
+  PurchaseRecord,
   Stores,
   WALLET_DB_VERSION,
 } from "../dbTypes";
@@ -136,6 +132,12 @@ function handleMessage(sender: MessageSender,
       }
       return needsWallet().confirmPay(detail.proposalId, detail.sessionId);
     }
+    case "submit-pay": {
+      if (typeof detail.contractTermsHash !== "string") {
+        throw Error("contractTermsHash must be a string");
+      }
+      return needsWallet().submitPay(detail.contractTermsHash, 
detail.sessionId);
+    }
     case "check-pay": {
       if (typeof detail.proposalId !== "number") {
         throw Error("proposalId must be number");
@@ -291,25 +293,12 @@ function handleMessage(sender: MessageSender,
     case "get-full-refund-fees":
       return needsWallet().getFullRefundFees(detail.refundPermissions);
     case "get-tip-status": {
-      const req = TipStatusRequest.checked(detail);
-      return needsWallet().getTipStatus(req.merchantDomain, req.tipId);
+      const tipToken = TipToken.checked(detail.tipToken);
+      return needsWallet().getTipStatus(tipToken);
     }
     case "accept-tip": {
-      const req = AcceptTipRequest.checked(detail);
-      return needsWallet().acceptTip(req.merchantDomain, req.tipId);
-    }
-    case "process-tip-response": {
-      const req = ProcessTipResponseRequest.checked(detail);
-      return needsWallet().processTipResponse(req.merchantDomain, req.tipId, 
req.tipResponse);
-    }
-    case "get-tip-planchets": {
-      const req = GetTipPlanchetsRequest.checked(detail);
-      return needsWallet().getTipPlanchets(req.merchantDomain,
-                                           req.tipId,
-                                           req.amount,
-                                           req.deadline,
-                                           req.exchangeUrl,
-                                           req.nextUrl);
+      const tipToken = TipToken.checked(detail.tipToken);
+      return needsWallet().acceptTip(tipToken);
     }
     case "clear-notification": {
       return needsWallet().clearNotification();
@@ -410,7 +399,7 @@ async function talerPay(fields: any, url: string, tabId: 
number): Promise<string
 
   const w = currentWallet;
 
-  const goToPayment = (p: QueryPaymentFound): string => {
+  const goToPayment = (p: PurchaseRecord): string => {
     const nextUrl = new URI(p.contractTerms.fulfillment_url);
     nextUrl.addSearch("order_id", p.contractTerms.order_id);
     if (p.lastSessionSig) {
@@ -422,14 +411,7 @@ async function talerPay(fields: any, url: string, tabId: 
number): Promise<string
   if (fields.resource_url) {
     const p = await w.queryPaymentByFulfillmentUrl(fields.resource_url);
     console.log("query for resource url", fields.resource_url, "result", p);
-    if (p.found && (fields.session_id === undefined || fields.session_id === 
p.lastSessionId)) {
-      return goToPayment(p);
-    }
-  }
-  if (fields.contract_hash) {
-    const p = await w.queryPaymentByContractTermsHash(fields.contract_hash);
-    if (p.found) {
-      goToPayment(p);
+    if (p && (fields.session_id === undefined || fields.session_id === 
p.lastSessionId)) {
       return goToPayment(p);
     }
   }
@@ -452,15 +434,8 @@ async function talerPay(fields: any, url: string, tabId: 
number): Promise<string
     return 
chrome.extension.getURL(`/src/webex/pages/refund.html?contractTermsHash=${hc}`);
   }
   if (fields.tip) {
-    const tipToken = TipToken.checked(fields.tip);
-    w.processTip(tipToken);
-    // Go to tip dialog page, where the user can confirm the tip or
-    // decline if they are not happy with the exchange.
-    const merchantDomain = new URI(url).origin();
     const uri = new URI(chrome.extension.getURL("/src/webex/pages/tip.html"));
-    const params = { tip_id: tipToken.tip_id, merchant_domain: merchantDomain 
};
-    const redirectUrl = uri.query(params).href();
-    return redirectUrl;
+    return uri.query({ tip_token: fields.tip }).href();
   }
   return undefined;
 }
@@ -486,7 +461,6 @@ function handleHttpPayment(headerList: 
chrome.webRequest.HttpHeader[], url: stri
   }
 
   const fields = {
-    contract_hash: headers["x-taler-contract-hash"],
     contract_url: headers["x-taler-contract-url"],
     offer_url: headers["x-taler-offer-url"],
     refund_url: headers["x-taler-refund-url"],
@@ -506,15 +480,15 @@ function handleHttpPayment(headerList: 
chrome.webRequest.HttpHeader[], url: stri
 
   console.log("got pay detail", fields);
 
-  // Fast path for existing payment
+  // Synchronous fast path for existing payment
   if (fields.resource_url) {
     const result = 
currentWallet.getNextUrlFromResourceUrl(fields.resource_url);
     if (result && (fields.session_id === undefined || fields.session_id === 
result.lastSessionId)) {
       return { redirectUrl: result.nextUrl };
     }
   }
-  // Fast path for new contract
-  if (!fields.contract_hash && fields.contract_url) {
+  // Synchronous fast path for new contract
+  if (fields.contract_url) {
     const uri = new 
URI(chrome.extension.getURL("/src/webex/pages/confirm-contract.html"));
     uri.addSearch("contractUrl", fields.contract_url);
     if (fields.session_id) {
@@ -526,6 +500,13 @@ function handleHttpPayment(headerList: 
chrome.webRequest.HttpHeader[], url: stri
     return { redirectUrl: uri.href() };
   }
 
+  // Synchronous fast path for tip
+  if (fields.tip) {
+    const uri = new URI(chrome.extension.getURL("/src/webex/pages/tip.html"));
+    uri.query({ tip_token: fields.tip });
+    return { redirectUrl: uri.href() };
+  }
+
   // We need to do some asynchronous operation, we can't directly redirect
   talerPay(fields, url, tabId).then((nextUrl) => {
     if (nextUrl) {

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



reply via email to

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