gnunet-svn
[Top][All Lists]
Advanced

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

[GNUnet-SVN] [taler-wallet-core] 02/03: rudimentary taler://withdraw sup


From: gnunet
Subject: [GNUnet-SVN] [taler-wallet-core] 02/03: rudimentary taler://withdraw support
Date: Thu, 29 Aug 2019 23:13: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.

commit 1390175a9afc53948dd1d6f8a2f88e51c1bf53cc
Author: Florian Dold <address@hidden>
AuthorDate: Wed Aug 28 02:49:27 2019 +0200

    rudimentary taler://withdraw support
---
 package.json                     |   4 +-
 src/crypto/cryptoApi-test.ts     |   2 +
 src/crypto/synchronousWorker.ts  |  10 +-
 src/dbTypes.ts                   |  18 ++++
 src/headless/bank.ts             |   4 +-
 src/headless/helpers.ts          |  74 ++++++--------
 src/headless/taler-wallet-cli.ts |  57 +++++++++--
 src/talerTypes.ts                |  36 +++++++
 src/taleruri-test.ts             |  12 ++-
 src/taleruri.ts                  |  33 ++++++-
 src/wallet.ts                    | 209 ++++++++++++++++++++++++++++++++++-----
 src/walletTypes.ts               |  26 ++++-
 tsconfig.json                    |   2 +
 yarn.lock                        |  16 +--
 14 files changed, 411 insertions(+), 92 deletions(-)

diff --git a/package.json b/package.json
index b0a22151..6bb1caf7 100644
--- a/package.json
+++ b/package.json
@@ -38,7 +38,7 @@
     "structured-clone": "^0.2.2",
     "terser-webpack-plugin": "^1.2.3",
     "through2": "3.0.1",
-    "tslint": "^5.14.0",
+    "tslint": "^5.19.0",
     "typedoc": "^0.15.0",
     "typescript": "^3.3.4000",
     "uglify-js": "^3.0.27",
@@ -54,7 +54,7 @@
     "@types/urijs": "^1.19.3",
     "axios": "^0.19.0",
     "commander": "^3.0.0",
-    "idb-bridge": "^0.0.7",
+    "idb-bridge": "^0.0.9",
     "qrcode-generator": "^1.4.3",
     "source-map-support": "^0.5.12",
     "urijs": "^1.18.10"
diff --git a/src/crypto/cryptoApi-test.ts b/src/crypto/cryptoApi-test.ts
index 48231e5f..39f46c5c 100644
--- a/src/crypto/cryptoApi-test.ts
+++ b/src/crypto/cryptoApi-test.ts
@@ -96,6 +96,8 @@ test("precoin creation", async t => {
     reserve_pub: pub,
     timestamp_confirmed: 0,
     timestamp_depleted: 0,
+    timestamp_reserve_info_posted: 0,
+    exchangeWire: "payto://foo"
   };
 
   const precoin = await crypto.createPreCoin(denomValid1, r);
diff --git a/src/crypto/synchronousWorker.ts b/src/crypto/synchronousWorker.ts
index b697c8e1..41ebee4f 100644
--- a/src/crypto/synchronousWorker.ts
+++ b/src/crypto/synchronousWorker.ts
@@ -93,13 +93,19 @@ export class SynchronousCryptoWorker {
       return;
     }
 
+    let result: any;
     try {
-      const result = (impl as any)[operation](...args);
-      this.dispatchMessage({ result, id });
+      result = (impl as any)[operation](...args);
     } catch (e) {
       console.log("error during operation", e);
       return;
     }
+
+    try {
+      setImmediate(() => this.dispatchMessage({ result, id }));
+    } catch (e) {
+      console.log("got error during dispatch", e);
+    }
   }
 
   /**
diff --git a/src/dbTypes.ts b/src/dbTypes.ts
index 55b2ddbe..d9fd2e9d 100644
--- a/src/dbTypes.ts
+++ b/src/dbTypes.ts
@@ -81,6 +81,16 @@ export interface ReserveRecord {
    */
   timestamp_depleted: number;
 
+
+  /**
+   * Time when the information about this reserve was posted to the bank.
+   * 
+   * Only applies if bankWithdrawStatusUrl is defined.
+   * 
+   * Set to 0 if that hasn't happened yet.
+   */
+  timestamp_reserve_info_posted: number;
+
   /**
    * Time when the reserve was confirmed.
    *
@@ -117,6 +127,14 @@ export interface ReserveRecord {
    * transfered funds for this reserve.
    */
   senderWire?: string;
+
+  /**
+   * Wire information (as payto URI) for the exchange, specifically
+   * the account that was transferred to when creating the reserve.
+   */
+  exchangeWire: string;
+
+  bankWithdrawStatusUrl?: string;
 }
 
 
diff --git a/src/headless/bank.ts b/src/headless/bank.ts
index 7d8db9fe..f3502100 100644
--- a/src/headless/bank.ts
+++ b/src/headless/bank.ts
@@ -51,7 +51,7 @@ export class Bank {
     reservePub: string,
     exchangePaytoUri: string,
   ) {
-    const reqUrl = new URI("taler/withdraw")
+    const reqUrl = new URI("api/withdraw-headless")
       .absoluteTo(this.bankBaseUrl)
       .href();
 
@@ -80,7 +80,7 @@ export class Bank {
   }
 
   async registerRandomUser(): Promise<BankUser> {
-    const reqUrl = new URI("register").absoluteTo(this.bankBaseUrl).href();
+    const reqUrl = new URI("api/register").absoluteTo(this.bankBaseUrl).href();
     const randId = makeId(8);
     const bankUser: BankUser = {
       username: `testuser-${randId}`,
diff --git a/src/headless/helpers.ts b/src/headless/helpers.ts
index 9652c630..a86b2673 100644
--- a/src/headless/helpers.ts
+++ b/src/headless/helpers.ts
@@ -54,17 +54,21 @@ class ConsoleBadge implements Badge {
 export class NodeHttpLib implements HttpRequestLibrary {
   async get(url: string): Promise<import("../http").HttpResponse> {
     enableTracing && console.log("making GET request to", url);
-    const resp = await Axios({
-      method: "get",
-      url: url,
-      responseType: "json",
-    });
-    enableTracing && console.log("got response", resp.data);
-    enableTracing && console.log("resp type", typeof resp.data);
-    return {
-      responseJson: resp.data,
-      status: resp.status,
-    };
+    try {
+      const resp = await Axios({
+        method: "get",
+        url: url,
+        responseType: "json",
+      });
+      enableTracing && console.log("got response", resp.data);
+      enableTracing && console.log("resp type", typeof resp.data);
+      return {
+        responseJson: resp.data,
+        status: resp.status,
+      };
+    } catch (e) {
+      throw e;
+    }
   }
 
   async postJson(
@@ -72,37 +76,22 @@ export class NodeHttpLib implements HttpRequestLibrary {
     body: any,
   ): Promise<import("../http").HttpResponse> {
     enableTracing && console.log("making POST request to", url);
-    const resp = await Axios({
-      method: "post",
-      url: url,
-      responseType: "json",
-      data: body,
-    });
-    enableTracing && console.log("got response", resp.data);
-    enableTracing && console.log("resp type", typeof resp.data);
-    return {
-      responseJson: resp.data,
-      status: resp.status,
-    };
-  }
-
-  async postForm(
-    url: string,
-    form: any,
-  ): Promise<import("../http").HttpResponse> {
-    enableTracing && console.log("making POST request to", url);
-    const resp = await Axios({
-      method: "post",
-      url: url,
-      data: querystring.stringify(form),
-      responseType: "json",
-    });
-    enableTracing && console.log("got response", resp.data);
-    enableTracing && console.log("resp type", typeof resp.data);
-    return {
-      responseJson: resp.data,
-      status: resp.status,
-    };
+    try {
+      const resp = await Axios({
+        method: "post",
+        url: url,
+        responseType: "json",
+        data: body,
+      });
+      enableTracing && console.log("got response", resp.data);
+      enableTracing && console.log("resp type", typeof resp.data);
+      return {
+        responseJson: resp.data,
+        status: resp.status,
+      };
+    } catch (e) {
+      throw e;
+    }
   }
 }
 
@@ -221,6 +210,7 @@ export async function withdrawTestBalance(
   const reserveResponse = await myWallet.createReserve({
     amount: amounts.parseOrThrow(amount),
     exchange: exchangeBaseUrl,
+    exchangeWire: "payto://unknown",
   });
 
   const bank = new Bank(bankBaseUrl);
diff --git a/src/headless/taler-wallet-cli.ts b/src/headless/taler-wallet-cli.ts
index 4a1f5d91..65b2a029 100644
--- a/src/headless/taler-wallet-cli.ts
+++ b/src/headless/taler-wallet-cli.ts
@@ -103,15 +103,14 @@ program
     console.log("created new order with order ID", orderResp.orderId);
     const checkPayResp = await merchantBackend.checkPayment(orderResp.orderId);
     const qrcode = qrcodeGenerator(0, "M");
-    const contractUrl = checkPayResp.contract_url;
-    if (typeof contractUrl !== "string") {
-      console.error("fata: no contract url received from backend");
+    const talerPayUri = checkPayResp.taler_pay_uri;
+    if (!talerPayUri) {
+      console.error("fatal: no taler pay URI received from backend");
       process.exit(1);
       return;
     }
-    const url = "talerpay:" + querystring.escape(contractUrl);
-    console.log("contract url:", url);
-    qrcode.addData(url);
+    console.log("taler pay URI:", talerPayUri);
+    qrcode.addData(talerPayUri);
     qrcode.make();
     console.log(qrcode.createASCII());
     console.log("waiting for payment ...");
@@ -128,6 +127,45 @@ program
   });
 
 program
+  .command("withdraw-url <withdraw-url>")
+  .action(async (withdrawUrl, cmdObj) => {
+    applyVerbose(program.verbose);
+    console.log("withdrawing", withdrawUrl);
+    const wallet = await getDefaultNodeWallet({
+      persistentStoragePath: walletDbPath,
+    });
+
+    const withdrawInfo = await wallet.downloadWithdrawInfo(withdrawUrl);
+
+    console.log("withdraw info", withdrawInfo);
+
+    const selectedExchange = withdrawInfo.suggestedExchange;
+    if (!selectedExchange) {
+      console.error("no suggested exchange!");
+      process.exit(1);
+      return;
+    }
+
+    const {
+      reservePub,
+      confirmTransferUrl,
+    } = await wallet.createReserveFromWithdrawUrl(
+      withdrawUrl,
+      selectedExchange,
+    );
+
+    if (confirmTransferUrl) {
+      console.log("please confirm the transfer at", confirmTransferUrl);
+    }
+
+    await wallet.processReserve(reservePub);
+
+    console.log("finished withdrawing");
+
+    wallet.stop();
+  });
+
+program
   .command("pay-url <pay-url>")
   .option("-y, --yes", "automatically answer yes to prompts")
   .action(async (payUrl, cmdObj) => {
@@ -153,6 +191,11 @@ program
       process.exit(0);
       return;
     }
+    if (result.status === "session-replayed") {
+      console.log("already paid! (replayed in different session)");
+      process.exit(0);
+      return;
+    }
     if (result.status === "payment-possible") {
       console.log("paying ...");
     } else {
@@ -179,7 +222,7 @@ program
 
     if (pay) {
       const payRes = await wallet.confirmPay(result.proposalId!, undefined);
-      console.log("paid!");      
+      console.log("paid!");
     } else {
       console.log("not paying");
     }
diff --git a/src/talerTypes.ts b/src/talerTypes.ts
index 9176daf7..360be333 100644
--- a/src/talerTypes.ts
+++ b/src/talerTypes.ts
@@ -924,6 +924,9 @@ export class CheckPaymentResponse {
   contract_terms: ContractTerms | undefined;
 
   @Checkable.Optional(Checkable.String())
+  taler_pay_uri: string | undefined;
+
+  @Checkable.Optional(Checkable.String())
   contract_url: string | undefined;
 
   /**
@@ -931,4 +934,37 @@ export class CheckPaymentResponse {
    * member.
    */
   static checked: (obj: any) => CheckPaymentResponse;
+}
+
+/**
+ * Response from the bank.
+ */
+@Checkable.Class({extra: true})
+export class WithdrawOperationStatusResponse {
+  @Checkable.Boolean()
+  selection_done: boolean;
+
+  @Checkable.Boolean()
+  transfer_done: boolean;
+
+  @Checkable.String()
+  amount: string;
+
+  @Checkable.Optional(Checkable.String())
+  sender_wire?: string;
+
+  @Checkable.Optional(Checkable.String())
+  suggested_exchange?: string;
+
+  @Checkable.Optional(Checkable.String())
+  confirm_transfer_url?: string;
+
+  @Checkable.List(Checkable.String())
+  wire_types: string[];
+
+  /**
+   * Verify that a value matches the schema of this class and convert it into a
+   * member.
+   */
+  static checked: (obj: any) => WithdrawOperationStatusResponse;
 }
\ No newline at end of file
diff --git a/src/taleruri-test.ts b/src/taleruri-test.ts
index a9fa0b1e..27cd7d18 100644
--- a/src/taleruri-test.ts
+++ b/src/taleruri-test.ts
@@ -15,7 +15,7 @@
  */
 
 import test from "ava";
-import { parsePayUri } from "./taleruri";
+import { parsePayUri, parseWithdrawUri } from "./taleruri";
 
 test("taler pay url parsing: http(s)", (t) => {
   const url1 = "https://example.com/bar?spam=eggs";;
@@ -77,3 +77,13 @@ test("taler pay url parsing: trailing parts", (t) => {
   t.is(r1.downloadUrl, 
"https://example.com/public/proposal?instance=default&order_id=myorder";);
   t.is(r1.sessionId, "mysession");
 });
+
+test("taler withdraw uri parsing", (t) => {
+  const url1 = "taler://withdraw/bank.example.com/-/12345";
+  const r1 = parseWithdrawUri(url1);
+  if (!r1) {
+    t.fail();
+    return;
+  }
+  t.is(r1.statusUrl, "https://bank.example.com/api/withdraw-operation/12345";);
+});
\ No newline at end of file
diff --git a/src/taleruri.ts b/src/taleruri.ts
index fa3b09c3..fa305d1d 100644
--- a/src/taleruri.ts
+++ b/src/taleruri.ts
@@ -15,12 +15,39 @@
  */
 
 import URI = require("urijs");
+import { string } from "prop-types";
 
 export interface PayUriResult {
   downloadUrl: string;
   sessionId?: string;
 }
 
+export interface WithdrawUriResult {
+  statusUrl: string;
+}
+
+export function parseWithdrawUri(s: string): WithdrawUriResult | undefined {
+  const parsedUri = new URI(s);
+  if (parsedUri.scheme() !== "taler") {
+    return undefined;
+  }
+  if (parsedUri.authority() != "withdraw") {
+    return undefined;
+  }
+
+  let [host, path, withdrawId] = parsedUri.segmentCoded();
+
+  if (path === "-") {
+    path = "/api/withdraw-operation";
+  }
+
+  return {
+    statusUrl: new URI({ protocol: "https", hostname: host, path: path })
+      .segmentCoded(withdrawId)
+      .href(),
+  };
+}
+
 export function parsePayUri(s: string): PayUriResult | undefined {
   const parsedUri = new URI(s);
   if (parsedUri.scheme() === "http" || parsedUri.scheme() === "https") {
@@ -68,10 +95,12 @@ export function parsePayUri(s: string): PayUriResult | 
undefined {
 
   const downloadUrl = new URI(
     "https://"; + host + "/" + decodeURIComponent(maybePath),
-  ).addQuery({ instance: maybeInstance, order_id: orderId }).href();
+  )
+    .addQuery({ instance: maybeInstance, order_id: orderId })
+    .href();
 
   return {
     downloadUrl,
     sessionId: maybeSessionid,
-  }
+  };
 }
diff --git a/src/wallet.ts b/src/wallet.ts
index faced994..b6a9361c 100644
--- a/src/wallet.ts
+++ b/src/wallet.ts
@@ -81,6 +81,7 @@ import {
   TipPlanchetDetail,
   TipResponse,
   TipToken,
+  WithdrawOperationStatusResponse,
 } from "./talerTypes";
 import {
   Badge,
@@ -103,9 +104,10 @@ import {
   WalletBalance,
   WalletBalanceEntry,
   PreparePayResult,
+  DownloadedWithdrawInfo,
 } from "./walletTypes";
 import { openPromise } from "./promiseUtils";
-import Axios from "axios";
+import { parsePayUri, parseWithdrawUri } from "./taleruri";
 
 interface SpeculativePayData {
   payCoinInfo: PayCoinInfo;
@@ -183,12 +185,13 @@ export function getTotalRefreshCost(
     ...withdrawDenoms.map(d => d.value),
   ).amount;
   const totalCost = Amounts.sub(amountLeft, resultingAmount).amount;
-  Wallet.enableTracing && console.log(
-    "total refresh cost for",
-    amountToPretty(amountLeft),
-    "is",
-    amountToPretty(totalCost),
-  );
+  Wallet.enableTracing &&
+    console.log(
+      "total refresh cost for",
+      amountToPretty(amountLeft),
+      "is",
+      amountToPretty(totalCost),
+    );
   return totalCost;
 }
 
@@ -255,7 +258,8 @@ export function selectPayCoins(
       const depositFeeToCover = Amounts.sub(accDepositFee, depositFeeLimit)
         .amount;
       leftAmount = Amounts.sub(leftAmount, depositFeeToCover).amount;
-      Wallet.enableTracing && console.log("deposit fee to cover", 
amountToPretty(depositFeeToCover));
+      Wallet.enableTracing &&
+        console.log("deposit fee to cover", amountToPretty(depositFeeToCover));
 
       let totalFees: AmountJson = Amounts.getZero(currency);
       if (coversAmountWithFee && !isBelowFee) {
@@ -714,17 +718,22 @@ export class Wallet {
   }
 
   async preparePay(url: string): Promise<PreparePayResult> {
-    const talerpayPrefix = "talerpay:";
-    let downloadSessionId: string | undefined;
-    if (url.startsWith(talerpayPrefix)) {
-      let [p1, p2] = url.substring(talerpayPrefix.length).split(";");
-      url = decodeURIComponent(p1);
-      downloadSessionId = p2;
+    const uriResult = parsePayUri(url);
+
+    if (!uriResult) {
+      return {
+        status: "error",
+        error: "URI not supported",
+      };
     }
+
     let proposalId: number;
     let checkResult: CheckPayResult;
     try {
-      proposalId = await this.downloadProposal(url, downloadSessionId);
+      proposalId = await this.downloadProposal(
+        uriResult.downloadUrl,
+        uriResult.sessionId,
+      );
       checkResult = await this.checkPay(proposalId);
     } catch (e) {
       return {
@@ -736,6 +745,27 @@ export class Wallet {
     if (!proposal) {
       throw Error("could not get proposal");
     }
+
+    console.log("proposal", proposal);
+
+    if (uriResult.sessionId) {
+      const existingPayment = await this.q().getIndexed(
+        Stores.purchases.fulfillmentUrlIndex,
+        proposal.contractTerms.fulfillment_url,
+      );
+      if (existingPayment) {
+        console.log("existing payment", existingPayment);
+        await this.submitPay(
+          existingPayment.contractTermsHash,
+          uriResult.sessionId,
+        );
+        return {
+          status: "session-replayed",
+          contractTerms: existingPayment.contractTerms,
+        };
+      }
+    }
+
     if (checkResult.status === "paid") {
       return {
         status: "paid",
@@ -1139,21 +1169,78 @@ export class Wallet {
     const op = openPromise<void>();
 
     const processReserveInternal = async (retryDelayMs: number = 250) => {
+      let isHardError = false;
+      // By default, do random, exponential backoff truncated at 3 minutes.
+      // Sometimes though, we want to try again faster.
+      let maxTimeout = 3000 * 60;
       try {
-        const reserve = await this.updateReserve(reservePub);
-        await this.depleteReserve(reserve);
+        const reserve = await this.q().get<ReserveRecord>(
+          Stores.reserves,
+          reservePub,
+        );
+        if (!reserve) {
+          isHardError = true;
+          throw Error("reserve not in db");
+        }
+
+        if (reserve.timestamp_confirmed === 0) {
+          const bankStatusUrl = reserve.bankWithdrawStatusUrl;
+          if (!bankStatusUrl) {
+            isHardError = true;
+            throw Error(
+              "reserve not confirmed yet, and no status URL available.",
+            );
+          }
+          maxTimeout = 2000;
+          const now = new Date().getTime();
+          let status;
+          try {
+            const statusResp = await this.http.get(bankStatusUrl);
+            status = WithdrawOperationStatusResponse.checked(
+              statusResp.responseJson,
+            );
+          } catch (e) {
+            console.log("bank error response", e);
+            throw e;
+          }
+
+          if (status.transfer_done) {
+            await this.q().mutate(Stores.reserves, reservePub, r => {
+              r.timestamp_confirmed = now;
+              return r;
+            });
+          } else if (reserve.timestamp_reserve_info_posted === 0) {
+            try {
+              if (!status.selection_done) {
+                const bankResp = await this.http.postJson(bankStatusUrl, {
+                  reserve_pub: reservePub,
+                  selected_exchange: reserve.exchangeWire,
+                });
+              }
+            } catch (e) {
+              console.log("bank error response", e);
+              throw e;
+            }
+            await this.q().mutate(Stores.reserves, reservePub, r => {
+              r.timestamp_reserve_info_posted = now;
+              return r;
+            });
+            throw Error("waiting for reserve to be confirmed");
+          }
+        }
+
+        const updatedReserve = await this.updateReserve(reservePub);
+        await this.depleteReserve(updatedReserve);
         op.resolve();
       } catch (e) {
-        // random, exponential backoff truncated at 3 minutes
+        if (isHardError) {
+          op.reject(e);
+        }
         const nextDelay = Math.min(
           2 * retryDelayMs + retryDelayMs * Math.random(),
-          3000 * 60,
+          maxTimeout,
         );
-        Wallet.enableTracing &&
-          console.warn(
-            `Failed to deplete reserve, trying again in ${retryDelayMs} ms`,
-          );
-        Wallet.enableTracing && console.info("Cause for retry was:", e);
+
         this.timerGroup.after(retryDelayMs, () =>
           processReserveInternal(nextDelay),
         );
@@ -1346,7 +1433,10 @@ export class Wallet {
       reserve_pub: keypair.pub,
       senderWire: req.senderWire,
       timestamp_confirmed: 0,
+      timestamp_reserve_info_posted: 0,
       timestamp_depleted: 0,
+      bankWithdrawStatusUrl: req.bankWithdrawStatusUrl,
+      exchangeWire: req.exchangeWire,
     };
 
     const senderWire = req.senderWire;
@@ -1387,6 +1477,10 @@ export class Wallet {
       .put(Stores.reserves, reserveRecord)
       .finish();
 
+    if (req.bankWithdrawStatusUrl) {
+      this.processReserve(keypair.pub);
+    }
+
     const r: CreateReserveResponse = {
       exchange: canonExchange,
       reservePub: keypair.pub,
@@ -1513,6 +1607,7 @@ export class Wallet {
       }
 
       const preCoin = await this.cryptoApi.createPreCoin(denom, reserve);
+
       // This will fail and throw an exception if the remaining amount in the
       // reserve is too low to create a pre-coin.
       try {
@@ -1520,6 +1615,7 @@ export class Wallet {
           .put(Stores.precoins, preCoin)
           .mutate(Stores.reserves, reserve.reserve_pub, mutateReserve)
           .finish();
+        console.log("created precoin", preCoin.coinPub);
       } catch (e) {
         console.log("can't create pre-coin:", e.name, e.message);
         return;
@@ -1542,6 +1638,11 @@ export class Wallet {
     if (!reserve) {
       throw Error("reserve not in db");
     }
+
+    if (reserve.timestamp_confirmed === 0) {
+      throw Error("");
+    }
+
     const reqUrl = new URI("reserve/status").absoluteTo(
       reserve.exchange_base_url,
     );
@@ -2462,7 +2563,14 @@ export class Wallet {
       refreshSession.exchangeBaseUrl,
     );
     Wallet.enableTracing && console.log("reveal request:", req);
-    const resp = await this.http.postJson(reqUrl.href(), req);
+
+    let resp;
+    try {
+      resp = await this.http.postJson(reqUrl.href(), req);
+    } catch (e) {
+      console.error("got error during /refresh/reveal request");
+      return;
+    }
 
     Wallet.enableTracing && console.log("session:", refreshSession);
     Wallet.enableTracing && console.log("reveal response:", resp);
@@ -3427,6 +3535,57 @@ export class Wallet {
     // strategy to test it.
   }
 
+  async downloadWithdrawInfo(
+    talerWithdrawUri: string,
+  ): Promise<DownloadedWithdrawInfo> {
+    const uriResult = parseWithdrawUri(talerWithdrawUri);
+    if (!uriResult) {
+      throw Error("can't parse URL");
+    }
+    const resp = await this.http.get(uriResult.statusUrl);
+    console.log("resp:", resp.responseJson);
+    const status = WithdrawOperationStatusResponse.checked(resp.responseJson);
+    return {
+      amount: Amounts.parseOrThrow(status.amount),
+      confirmTransferUrl: status.confirm_transfer_url,
+      extractedStatusUrl: uriResult.statusUrl,
+      selectionDone: status.selection_done,
+      senderWire: status.sender_wire,
+      suggestedExchange: status.suggested_exchange,
+      transferDone: status.transfer_done,
+      wireTypes: status.wire_types,
+    };
+  }
+
+  async createReserveFromWithdrawUrl(
+    talerWithdrawUri: string,
+    selectedExchange: string,
+  ): Promise<{ reservePub: string; confirmTransferUrl?: string }> {
+    const withdrawInfo = await this.downloadWithdrawInfo(talerWithdrawUri);
+    const exchangeWire = await this.getExchangePaytoUri(
+      selectedExchange,
+      withdrawInfo.wireTypes,
+    );
+    const reserve = await this.createReserve({
+      amount: withdrawInfo.amount,
+      bankWithdrawStatusUrl: withdrawInfo.extractedStatusUrl,
+      exchange: selectedExchange,
+      senderWire: withdrawInfo.senderWire,
+      exchangeWire: exchangeWire,
+    });
+    return {
+      reservePub: reserve.reservePub,
+      confirmTransferUrl: withdrawInfo.confirmTransferUrl,
+    };
+  }
+
+  /**
+   * Reset the retry timeouts for ongoing operations.
+   */
+  resetRetryTimeouts(): void {
+    // FIXME: implement
+  }
+
   clearNotification(): void {
     this.badge.clearNotification();
   }
diff --git a/src/walletTypes.ts b/src/walletTypes.ts
index a74f8113..abe9f271 100644
--- a/src/walletTypes.ts
+++ b/src/walletTypes.ts
@@ -325,6 +325,13 @@ export class CreateReserveRequest {
   exchange: string;
 
   /**
+   * Payto URI that identifies the exchange's account that the funds
+   * for this reserve go into.
+   */
+  @Checkable.String()
+  exchangeWire: string;
+
+  /**
    * Wire details (as a payto URI) for the bank account that sent the funds to
    * the exchange.
    */
@@ -332,6 +339,12 @@ export class CreateReserveRequest {
   senderWire?: string;
 
   /**
+   * URL to fetch the withdraw status from the bank.
+   */
+  @Checkable.Optional(Checkable.String())
+  bankWithdrawStatusUrl?: string;
+
+  /**
    * Verify that a value matches the schema of this class and convert it into a
    * member.
    */
@@ -474,9 +487,20 @@ export interface NextUrlResult {
 }
 
 export interface PreparePayResult {
-  status: "paid" | "insufficient-balance" | "payment-possible" | "error";
+  status: "paid" | "session-replayed" | "insufficient-balance" | 
"payment-possible" | "error";
   contractTerms?: ContractTerms;
   error?: string;
   proposalId?: number;
   totalFees?: AmountJson;
+}
+
+export interface DownloadedWithdrawInfo {
+  selectionDone: boolean;
+  transferDone: boolean;
+  amount: AmountJson;
+  senderWire?: string;
+  suggestedExchange?: string;
+  confirmTransferUrl?: string;
+  wireTypes: string[];
+  extractedStatusUrl: string;
 }
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index 7c28dc38..4074512a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -55,6 +55,8 @@
     "src/promiseUtils.ts",
     "src/query.ts",
     "src/talerTypes.ts",
+    "src/taleruri-test.ts",
+    "src/taleruri.ts",
     "src/timer.ts",
     "src/types-test.ts",
     "src/wallet-test.ts",
diff --git a/yarn.lock b/yarn.lock
index 54ad479d..50953c26 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3165,10 +3165,10 @@ iconv-lite@0.4.24, iconv-lite@^0.4.4, 
iconv-lite@~0.4.13:
   dependencies:
     safer-buffer ">= 2.1.2 < 3"
 
-idb-bridge@^0.0.7:
-  version "0.0.7"
-  resolved 
"https://registry.yarnpkg.com/idb-bridge/-/idb-bridge-0.0.7.tgz#34705d79ab992c4b5d5fa048c313ac5f803a972f";
-  integrity 
sha512-vGTYbX6ni8h/6B2POS6f1Nuzp47+Tna5MggQKDmQp1MLCPFbI8RXBzs1rvKWuMx+WKX3LtMWbxSm8hQBnI9DLA==
+idb-bridge@^0.0.9:
+  version "0.0.9"
+  resolved 
"https://registry.yarnpkg.com/idb-bridge/-/idb-bridge-0.0.9.tgz#28c9a9e50b275dc80316b29bdaec536ee96bc65b";
+  integrity 
sha512-MOoiDJvbhskEzyDtbOz9ecfGZ2Jx0CZ6L81h71fPjdERCkUUqvkt6p7RkChPr8GmteIfja2k9mIRNFkS7enmrQ==
 
 ieee754@^1.1.4:
   version "1.1.13"
@@ -6250,10 +6250,10 @@ tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0:
   resolved 
"https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a";
   integrity 
sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
 
-tslint@^5.14.0:
-  version "5.18.0"
-  resolved 
"https://registry.yarnpkg.com/tslint/-/tslint-5.18.0.tgz#f61a6ddcf372344ac5e41708095bbf043a147ac6";
-  integrity 
sha512-Q3kXkuDEijQ37nXZZLKErssQVnwCV/+23gFEMROi8IlbaBG6tXqLPQJ5Wjcyt/yHPKBC+hD5SzuGaMora+ZS6w==
+tslint@^5.19.0:
+  version "5.19.0"
+  resolved 
"https://registry.yarnpkg.com/tslint/-/tslint-5.19.0.tgz#a2cbd4a7699386da823f6b499b8394d6c47bb968";
+  integrity 
sha512-1LwwtBxfRJZnUvoS9c0uj8XQtAnyhWr9KlNvDIdB+oXyT+VpsOAaEhEgKi1HrZ8rq0ki/AAnbGSv4KM6/AfVZw==
   dependencies:
     "@babel/code-frame" "^7.0.0"
     builtin-modules "^1.1.1"

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



reply via email to

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