gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (599c8380 -> e3850158)


From: gnunet
Subject: [taler-wallet-core] branch master updated (599c8380 -> e3850158)
Date: Fri, 14 Aug 2020 09:36:47 +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 599c8380 make withdrawal requests sequentially, clean up withdrawal 
logic a bit
     new cbe325cb web extension WIP
     new d5f89469 error codes
     new e3850158 re-implement integration test functionalty that will be used 
by the exchange for testing

The 3 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:
 packages/taler-wallet-android/src/index.ts         |   3 +-
 packages/taler-wallet-cli/package.json             |   1 +
 packages/taler-wallet-cli/src/index.ts             |   8 +-
 packages/taler-wallet-core/src/TalerErrorCode.ts   |   7 +
 packages/taler-wallet-core/src/index.ts            |   4 +-
 .../taler-wallet-core/src/operations/testing.ts    | 281 +++++++++++++++++-
 .../taler-wallet-core/src/types/walletTypes.ts     | 187 +++++++++++-
 packages/taler-wallet-core/src/wallet.ts           | 194 ++++++++++++-
 .../taler-wallet-core/src/walletCoreApiHandler.ts  | 318 ---------------------
 .../src/pageEntryPoint.ts                          |   3 +
 .../taler-wallet-webextension/src/pages/popup.tsx  |   8 +-
 .../src/pages/withdraw.tsx                         | 102 +------
 packages/taler-wallet-webextension/src/wxApi.ts    |  61 ++--
 .../taler-wallet-webextension/src/wxBackend.ts     | 275 ++----------------
 .../static/benchmark.html                          |   6 +-
 packages/taler-wallet-webextension/static/pay.html |   8 +-
 .../taler-wallet-webextension/static/popup.html    |  10 +-
 .../taler-wallet-webextension/static/withdraw.html |   8 +-
 pnpm-lock.yaml                                     |   2 +
 19 files changed, 763 insertions(+), 723 deletions(-)
 delete mode 100644 packages/taler-wallet-core/src/walletCoreApiHandler.ts

diff --git a/packages/taler-wallet-android/src/index.ts 
b/packages/taler-wallet-android/src/index.ts
index 4430848c..07d15d58 100644
--- a/packages/taler-wallet-android/src/index.ts
+++ b/packages/taler-wallet-android/src/index.ts
@@ -38,7 +38,6 @@ import {
   WalletNotification,
   WALLET_EXCHANGE_PROTOCOL_VERSION,
   WALLET_MERCHANT_PROTOCOL_VERSION,
-  handleCoreApiRequest,
 } from "taler-wallet-core";
 
 import fs from "fs";
@@ -229,7 +228,7 @@ class AndroidWalletMessageHandler {
       }
       default: {
         const wallet = await this.wp.promise;
-        return await handleCoreApiRequest(wallet, operation, id, args);
+        return await wallet.handleCoreApiRequest(operation, id, args);
       }
     }
   }
diff --git a/packages/taler-wallet-cli/package.json 
b/packages/taler-wallet-cli/package.json
index 1870aa29..72a737d4 100644
--- a/packages/taler-wallet-cli/package.json
+++ b/packages/taler-wallet-cli/package.json
@@ -43,6 +43,7 @@
     "typescript": "^3.9.7"
   },
   "dependencies": {
+    "axios": "^0.19.2",
     "source-map-support": "^0.5.19",
     "taler-wallet-core": "workspace:*",
     "tslib": "^2.0.0"
diff --git a/packages/taler-wallet-cli/src/index.ts 
b/packages/taler-wallet-cli/src/index.ts
index a32ed226..94f01ba8 100644
--- a/packages/taler-wallet-cli/src/index.ts
+++ b/packages/taler-wallet-cli/src/index.ts
@@ -26,7 +26,6 @@ import {
   NodeHttpLib,
   PreparePayResultType,
   setDangerousTimetravel,
-  handleCoreApiRequest,
   classifyTalerUri,
   TalerUriType,
   decodeCrock,
@@ -34,10 +33,10 @@ import {
   codecForList,
   codecForString,
   printTestVectors,
+  NodeThreadCryptoWorkerFactory,
+  CryptoApi,
 } from "taler-wallet-core";
 import * as clk from "./clk";
-import { NodeThreadCryptoWorkerFactory } from 
"taler-wallet-core/lib/crypto/workers/nodeThreadWorker";
-import { CryptoApi } from "taler-wallet-core/lib/crypto/workers/cryptoApi";
 
 // This module also serves as the entry point for the crypto
 // thread worker, and thus must expose these two handlers.
@@ -210,8 +209,7 @@ walletCli
         console.error("Invalid JSON");
         process.exit(1);
       }
-      const resp = await handleCoreApiRequest(
-        wallet,
+      const resp = await wallet.handleCoreApiRequest(
         args.api.operation,
         "reqid-1",
         requestJson,
diff --git a/packages/taler-wallet-core/src/TalerErrorCode.ts 
b/packages/taler-wallet-core/src/TalerErrorCode.ts
index 1557007f..fd3ca1fc 100644
--- a/packages/taler-wallet-core/src/TalerErrorCode.ts
+++ b/packages/taler-wallet-core/src/TalerErrorCode.ts
@@ -3146,6 +3146,13 @@ export enum TalerErrorCode {
    */
   WALLET_WITHDRAW_RESERVE_UNKNOWN_AT_EXCHANGE = 7010,
 
+  /**
+   * The wallet core service is not available.
+   * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  WALLET_CORE_NOT_AVAILABLE = 7011,
+
   /**
    * End of error code range.
    * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
diff --git a/packages/taler-wallet-core/src/index.ts 
b/packages/taler-wallet-core/src/index.ts
index 784c9d63..1d5dc493 100644
--- a/packages/taler-wallet-core/src/index.ts
+++ b/packages/taler-wallet-core/src/index.ts
@@ -37,8 +37,6 @@ export * from "./types/walletTypes";
 
 export * from "./types/talerTypes";
 
-export * from "./walletCoreApiHandler";
-
 export * from "./util/taleruri";
 
 export * from "./util/time";
@@ -54,7 +52,7 @@ export * from "./util/testvectors";
 export * from "./operations/versions";
 
 export type { CryptoWorker } from "./crypto/workers/cryptoWorker";
-export type { CryptoWorkerFactory } from "./crypto/workers/cryptoApi";
+export { CryptoWorkerFactory, CryptoApi } from "./crypto/workers/cryptoApi";
 
 export * from "./util/http";
 
diff --git a/packages/taler-wallet-core/src/operations/testing.ts 
b/packages/taler-wallet-core/src/operations/testing.ts
index 3ccfafc9..629cd92f 100644
--- a/packages/taler-wallet-core/src/operations/testing.ts
+++ b/packages/taler-wallet-core/src/operations/testing.ts
@@ -21,10 +21,19 @@ import {
   checkSuccessResponseOrThrow,
 } from "../util/http";
 import { codecForAny } from "../util/codec";
-import { AmountString } from "../types/talerTypes";
+import {
+  AmountString,
+  CheckPaymentResponse,
+  codecForCheckPaymentResponse,
+} from "../types/talerTypes";
 import { InternalWalletState } from "./state";
 import { createTalerWithdrawReserve } from "./reserves";
 import { URL } from "../util/url";
+import { Wallet } from "../wallet";
+import { Amounts } from "../util/amounts";
+import { NodeHttpLib } from "../headless/NodeHttpLib";
+import { getDefaultNodeWallet } from "../headless/helpers";
+import { TestPayArgs, PreparePayResultType, IntegrationTestArgs } from 
"../types/walletTypes";
 
 const logger = new Logger("operations/testing.ts");
 
@@ -38,6 +47,11 @@ interface BankWithdrawalResponse {
   withdrawal_id: string;
 }
 
+interface MerchantBackendInfo {
+  baseUrl: string;
+  apikey: string;
+}
+
 /**
  * Generate a random alphanumeric ID.  Does *not* use cryptographically
  * secure randomness.
@@ -154,3 +168,268 @@ async function registerRandomBankUser(
   await checkSuccessResponseOrThrow(resp);
   return bankUser;
 }
+
+async function refund(
+  http: HttpRequestLibrary,
+  merchantBackend: MerchantBackendInfo,
+  orderId: string,
+  reason: string,
+  refundAmount: string,
+): Promise<string> {
+  const reqUrl = new URL(
+    `private/orders/${orderId}/refund`,
+    merchantBackend.baseUrl,
+  );
+  const refundReq = {
+    order_id: orderId,
+    reason,
+    refund: refundAmount,
+  };
+  const resp = await http.postJson(reqUrl.href, refundReq, {
+    headers: {
+      Authorization: `ApiKey ${merchantBackend.apikey}`,
+    },
+  });
+  const r = await readSuccessResponseJsonOrThrow(resp, codecForAny());
+  const refundUri = r.taler_refund_uri;
+  if (!refundUri) {
+    throw Error("no refund URI in response");
+  }
+  return refundUri;
+}
+
+async function createOrder(
+  http: HttpRequestLibrary,
+  merchantBackend: MerchantBackendInfo,
+  amount: string,
+  summary: string,
+  fulfillmentUrl: string,
+): Promise<{ orderId: string }> {
+  const t = Math.floor(new Date().getTime() / 1000) + 15 * 60;
+  const reqUrl = new URL("private/orders", merchantBackend.baseUrl).href;
+  const orderReq = {
+    order: {
+      amount,
+      summary,
+      fulfillment_url: fulfillmentUrl,
+      refund_deadline: { t_ms: t * 1000 },
+      wire_transfer_deadline: { t_ms: t * 1000 },
+    },
+  };
+  const resp = await http.postJson(reqUrl, orderReq, {
+    headers: {
+      Authorization: `ApiKey ${merchantBackend.apikey}`,
+    },
+  });
+  const r = await readSuccessResponseJsonOrThrow(resp, codecForAny());
+  const orderId = r.order_id;
+  if (!orderId) {
+    throw Error("no order id in response");
+  }
+  return { orderId };
+}
+
+async function checkPayment(
+  http: HttpRequestLibrary,
+  merchantBackend: MerchantBackendInfo,
+  orderId: string,
+): Promise<CheckPaymentResponse> {
+  const reqUrl = new URL(`/private/orders/${orderId}`, 
merchantBackend.baseUrl);
+  reqUrl.searchParams.set("order_id", orderId);
+  const resp = await http.get(reqUrl.href, {
+    headers: {
+      Authorization: `ApiKey ${merchantBackend.apikey}`,
+    },
+  });
+  return readSuccessResponseJsonOrThrow(resp, codecForCheckPaymentResponse());
+}
+
+interface BankUser {
+  username: string;
+  password: string;
+}
+
+interface BankWithdrawalResponse {
+  taler_withdraw_uri: string;
+  withdrawal_id: string;
+}
+
+async function makePayment(
+  http: HttpRequestLibrary,
+  wallet: Wallet,
+  merchant: MerchantBackendInfo,
+  amount: string,
+  summary: string,
+): Promise<{ orderId: string }> {
+  const orderResp = await createOrder(
+    http,
+    merchant,
+    amount,
+    summary,
+    "taler://fulfillment-success/thx",
+  );
+
+  console.log("created order with orderId", orderResp.orderId);
+
+  let paymentStatus = await checkPayment(http, merchant, orderResp.orderId);
+
+  console.log("payment status", paymentStatus);
+
+  const talerPayUri = paymentStatus.taler_pay_uri;
+  if (!talerPayUri) {
+    throw Error("no taler://pay/ URI in payment response");
+  }
+
+  const preparePayResult = await wallet.preparePayForUri(talerPayUri);
+
+  console.log("prepare pay result", preparePayResult);
+
+  if (preparePayResult.status != "payment-possible") {
+    throw Error("payment not possible");
+  }
+
+  const confirmPayResult = await wallet.confirmPay(
+    preparePayResult.proposalId,
+    undefined,
+  );
+
+  console.log("confirmPayResult", confirmPayResult);
+
+  paymentStatus = await checkPayment(http, merchant, orderResp.orderId);
+
+  console.log("payment status after wallet payment:", paymentStatus);
+
+  if (paymentStatus.order_status !== "paid") {
+    throw Error("payment did not succeed");
+  }
+
+  return {
+    orderId: orderResp.orderId,
+  };
+}
+
+export async function runIntegrationTest(
+  http: HttpRequestLibrary,
+  wallet: Wallet,
+  args: IntegrationTestArgs,
+): Promise<void> {
+  logger.info("running test with arguments", args);
+
+  const parsedSpendAmount = Amounts.parseOrThrow(args.amountToSpend);
+  const currency = parsedSpendAmount.currency;
+
+  const myHttpLib = new NodeHttpLib();
+  myHttpLib.setThrottling(false);
+
+  const myWallet = await getDefaultNodeWallet({ httpLib: myHttpLib });
+
+  myWallet.runRetryLoop().catch((e) => {
+    console.error("exception during retry loop:", e);
+  });
+
+  logger.info("withdrawing test balance");
+  await wallet.withdrawTestBalance(
+    args.amountToWithdraw,
+    args.bankBaseUrl,
+    args.exchangeBaseUrl,
+  );
+  logger.info("done withdrawing test balance");
+
+  const balance = await myWallet.getBalances();
+
+  console.log(JSON.stringify(balance, null, 2));
+
+  const myMerchant: MerchantBackendInfo = {
+    baseUrl: args.merchantBaseUrl,
+    apikey: args.merchantApiKey,
+  };
+
+  await makePayment(
+    http,
+    wallet,
+    myMerchant,
+    args.amountToSpend,
+    "hello world",
+  );
+
+  // Wait until the refresh is done
+  await myWallet.runUntilDone();
+
+  console.log("withdrawing test balance for refund");
+  const withdrawAmountTwo = Amounts.parseOrThrow(`${currency}:18`);
+  const spendAmountTwo = Amounts.parseOrThrow(`${currency}:7`);
+  const refundAmount = Amounts.parseOrThrow(`${currency}:6`);
+  const spendAmountThree = Amounts.parseOrThrow(`${currency}:3`);
+
+  await myWallet.withdrawTestBalance(
+    Amounts.stringify(withdrawAmountTwo),
+    args.bankBaseUrl,
+    args.exchangeBaseUrl,
+  );
+
+  // Wait until the withdraw is done
+  await myWallet.runUntilDone();
+
+  const { orderId: refundOrderId } = await makePayment(
+    http,
+    myWallet,
+    myMerchant,
+    Amounts.stringify(spendAmountTwo),
+    "order that will be refunded",
+  );
+
+  const refundUri = await refund(
+    http,
+    myMerchant,
+    refundOrderId,
+    "test refund",
+    Amounts.stringify(refundAmount),
+  );
+
+  console.log("refund URI", refundUri);
+
+  await myWallet.applyRefund(refundUri);
+
+  // Wait until the refund is done
+  await myWallet.runUntilDone();
+
+  await makePayment(
+    http,
+    myWallet,
+    myMerchant,
+    Amounts.stringify(spendAmountThree),
+    "payment after refund",
+  );
+
+  await myWallet.runUntilDone();
+}
+
+export async function testPay(
+  http: HttpRequestLibrary,
+  wallet: Wallet,
+  args: TestPayArgs,
+) {
+  console.log("creating order");
+  const merchant = { apikey: args.apikey, baseUrl: args.merchant };
+  const orderResp = await createOrder(
+    http,
+    merchant,
+    args.amount,
+    args.summary,
+    "taler://fulfillment-success/thank+you",
+  );
+  console.log("created new order with order ID", orderResp.orderId);
+  const checkPayResp = await checkPayment(http, merchant, orderResp.orderId);
+  const talerPayUri = checkPayResp.taler_pay_uri;
+  if (!talerPayUri) {
+    console.error("fatal: no taler pay URI received from backend");
+    process.exit(1);
+    return;
+  }
+  console.log("taler pay URI:", talerPayUri);
+  const result = await wallet.preparePayForUri(talerPayUri);
+  if (result.status !== PreparePayResultType.PaymentPossible) {
+    throw Error(`unexpected prepare pay status: ${result.status}`);
+  }
+  await wallet.confirmPay(result.proposalId, undefined);
+}
diff --git a/packages/taler-wallet-core/src/types/walletTypes.ts 
b/packages/taler-wallet-core/src/types/walletTypes.ts
index 8521af3f..e64187e7 100644
--- a/packages/taler-wallet-core/src/types/walletTypes.ts
+++ b/packages/taler-wallet-core/src/types/walletTypes.ts
@@ -229,9 +229,7 @@ export const codecForConfirmPayResultPending = (): Codec<
     .property("type", codecForConstString(ConfirmPayResultType.Pending))
     .build("ConfirmPayResultPending");
 
-export const codecForConfirmPayResultDone = (): Codec<
-  ConfirmPayResultDone
-> =>
+export const codecForConfirmPayResultDone = (): Codec<ConfirmPayResultDone> =>
   buildCodecForObject<ConfirmPayResultDone>()
     .property("type", codecForConstString(ConfirmPayResultType.Done))
     .property("nextUrl", codecForString())
@@ -240,7 +238,10 @@ export const codecForConfirmPayResultDone = (): Codec<
 export const codecForConfirmPayResult = (): Codec<ConfirmPayResult> =>
   buildCodecForUnion<ConfirmPayResult>()
     .discriminateOn("type")
-    .alternative(ConfirmPayResultType.Pending, 
codecForConfirmPayResultPending())
+    .alternative(
+      ConfirmPayResultType.Pending,
+      codecForConfirmPayResultPending(),
+    )
     .alternative(ConfirmPayResultType.Done, codecForConfirmPayResultDone())
     .build("ConfirmPayResult");
 
@@ -650,3 +651,181 @@ export interface GetExchangeTosResult {
    */
   acceptedEtag: string | undefined;
 }
+
+export interface TestPayArgs {
+  merchant: string;
+  apikey: string;
+  amount: string;
+  summary: string;
+}
+
+export const codecForTestPayArgs = (): Codec<TestPayArgs> =>
+  buildCodecForObject<TestPayArgs>()
+    .property("merchant", codecForString())
+    .property("apikey", codecForString())
+    .property("amount", codecForString())
+    .property("summary", codecForString())
+    .build("TestPayArgs");
+
+export interface IntegrationTestArgs {
+  exchangeBaseUrl: string;
+  bankBaseUrl: string;
+  merchantBaseUrl: string;
+  merchantApiKey: string;
+  amountToWithdraw: string;
+  amountToSpend: string;
+}
+
+export const codecForIntegrationTestArgs = (): Codec<IntegrationTestArgs> =>
+  buildCodecForObject<IntegrationTestArgs>()
+    .property("exchangeBaseUrl", codecForString())
+    .property("bankBaseUrl", codecForString())
+    .property("merchantBaseUrl", codecForString())
+    .property("merchantApiKey", codecForString())
+    .property("amountToSpend", codecForAmountString())
+    .property("amountToWithdraw", codecForAmountString())
+    .build("IntegrationTestArgs");
+
+export interface AddExchangeRequest {
+  exchangeBaseUrl: string;
+}
+
+export const codecForAddExchangeRequest = (): Codec<AddExchangeRequest> =>
+  buildCodecForObject<AddExchangeRequest>()
+    .property("exchangeBaseUrl", codecForString())
+    .build("AddExchangeRequest");
+
+export interface GetExchangeTosRequest {
+  exchangeBaseUrl: string;
+}
+
+export const codecForGetExchangeTosRequest = (): Codec<GetExchangeTosRequest> 
=>
+  buildCodecForObject<GetExchangeTosRequest>()
+    .property("exchangeBaseUrl", codecForString())
+    .build("GetExchangeTosRequest");
+
+export interface AcceptManualWithdrawalRequest {
+  exchangeBaseUrl: string;
+  amount: string;
+}
+
+export const codecForAcceptManualWithdrawalRequet = (): Codec<
+  AcceptManualWithdrawalRequest
+> =>
+  buildCodecForObject<AcceptManualWithdrawalRequest>()
+    .property("exchangeBaseUrl", codecForString())
+    .property("amount", codecForString())
+    .build("AcceptManualWithdrawalRequest");
+
+export interface GetWithdrawalDetailsForAmountRequest {
+  exchangeBaseUrl: string;
+  amount: string;
+}
+
+export interface AcceptBankIntegratedWithdrawalRequest {
+  talerWithdrawUri: string;
+  exchangeBaseUrl: string;
+}
+
+export const codecForAcceptBankIntegratedWithdrawalRequest = (): Codec<
+  AcceptBankIntegratedWithdrawalRequest
+> =>
+  buildCodecForObject<AcceptBankIntegratedWithdrawalRequest>()
+    .property("exchangeBaseUrl", codecForString())
+    .property("talerWithdrawUri", codecForString())
+    .build("AcceptBankIntegratedWithdrawalRequest");
+
+export const codecForGetWithdrawalDetailsForAmountRequest = (): Codec<
+  GetWithdrawalDetailsForAmountRequest
+> =>
+  buildCodecForObject<GetWithdrawalDetailsForAmountRequest>()
+    .property("exchangeBaseUrl", codecForString())
+    .property("amount", codecForString())
+    .build("GetWithdrawalDetailsForAmountRequest");
+
+export interface AcceptExchangeTosRequest {
+  exchangeBaseUrl: string;
+  etag: string;
+}
+
+export const codecForAcceptExchangeTosRequest = (): Codec<
+  AcceptExchangeTosRequest
+> =>
+  buildCodecForObject<AcceptExchangeTosRequest>()
+    .property("exchangeBaseUrl", codecForString())
+    .property("etag", codecForString())
+    .build("AcceptExchangeTosRequest");
+
+export interface ApplyRefundRequest {
+  talerRefundUri: string;
+}
+
+export const codecForApplyRefundRequest = (): Codec<ApplyRefundRequest> =>
+  buildCodecForObject<ApplyRefundRequest>()
+    .property("talerRefundUri", codecForString())
+    .build("ApplyRefundRequest");
+
+export interface GetWithdrawalDetailsForUriRequest {
+  talerWithdrawUri: string;
+}
+
+export const codecForGetWithdrawalDetailsForUri = (): Codec<
+  GetWithdrawalDetailsForUriRequest
+> =>
+  buildCodecForObject<GetWithdrawalDetailsForUriRequest>()
+    .property("talerWithdrawUri", codecForString())
+    .build("GetWithdrawalDetailsForUriRequest");
+
+export interface AbortProposalRequest {
+  proposalId: string;
+}
+
+export const codecForAbortProposalRequest = (): Codec<AbortProposalRequest> =>
+  buildCodecForObject<AbortProposalRequest>()
+    .property("proposalId", codecForString())
+    .build("AbortProposalRequest");
+
+export interface PreparePayRequest {
+  talerPayUri: string;
+}
+
+export const codecForPreparePayRequest = (): Codec<PreparePayRequest> =>
+  buildCodecForObject<PreparePayRequest>()
+    .property("talerPayUri", codecForString())
+    .build("PreparePay");
+
+export interface ConfirmPayRequest {
+  proposalId: string;
+  sessionId?: string;
+}
+
+export const codecForConfirmPayRequest = (): Codec<ConfirmPayRequest> =>
+  buildCodecForObject<ConfirmPayRequest>()
+    .property("proposalId", codecForString())
+    .property("sessionId", codecOptional(codecForString()))
+    .build("ConfirmPay");
+
+export type CoreApiResponse = CoreApiResponseSuccess | CoreApiResponseError;
+
+export type CoreApiEnvelope = CoreApiResponse | CoreApiNotification;
+
+export interface CoreApiNotification {
+  type: "notification";
+  payload: unknown;
+}
+
+export interface CoreApiResponseSuccess {
+  // To distinguish the message from notifications
+  type: "response";
+  operation: string;
+  id: string;
+  result: unknown;
+}
+
+export interface CoreApiResponseError {
+  // To distinguish the message from notifications
+  type: "error";
+  operation: string;
+  id: string;
+  error: OperationErrorDetails;
+}
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 4a409f58..0b3e2ed6 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -70,6 +70,22 @@ import {
   GetExchangeTosResult,
   AcceptManualWithdrawalResult,
   BalancesResponse,
+  TestPayArgs,
+  PreparePayResultType,
+  IntegrationTestArgs,
+  codecForAddExchangeRequest,
+  codecForGetWithdrawalDetailsForUri,
+  codecForAcceptManualWithdrawalRequet,
+  codecForGetWithdrawalDetailsForAmountRequest,
+  codecForAcceptExchangeTosRequest,
+  codecForApplyRefundRequest,
+  codecForAcceptBankIntegratedWithdrawalRequest,
+  codecForGetExchangeTosRequest,
+  codecForAbortProposalRequest,
+  codecForConfirmPayRequest,
+  CoreApiResponse,
+  codecForPreparePayRequest,
+  codecForIntegrationTestArgs,
 } from "./types/walletTypes";
 import { Logger } from "./util/logging";
 
@@ -107,13 +123,23 @@ import { WalletNotification, NotificationType } from 
"./types/notifications";
 import { processPurchaseQueryRefund, applyRefund } from "./operations/refund";
 import { durationMin, Duration } from "./util/time";
 import { processRecoupGroup } from "./operations/recoup";
-import { OperationFailedAndReportedError } from "./operations/errors";
+import {
+  OperationFailedAndReportedError,
+  OperationFailedError,
+  makeErrorDetails,
+} from "./operations/errors";
 import {
   TransactionsRequest,
   TransactionsResponse,
+  codecForTransactionsRequest,
 } from "./types/transactions";
 import { getTransactions } from "./operations/transactions";
-import { withdrawTestBalance } from "./operations/testing";
+import {
+  withdrawTestBalance,
+  runIntegrationTest,
+  testPay,
+} from "./operations/testing";
+import { TalerErrorCode } from ".";
 
 const builtinCurrencies: CurrencyRecord[] = [
   {
@@ -879,4 +905,168 @@ export class Wallet {
   ): Promise<void> {
     await withdrawTestBalance(this.ws, amount, bankBaseUrl, exchangeBaseUrl);
   }
+
+  async runIntegrationtest(args: IntegrationTestArgs): Promise<void> {
+    return runIntegrationTest(this.ws.http, this, args);
+  }
+
+  async testPay(args: TestPayArgs) {
+    return testPay(this.ws.http, this, args);
+  }
+
+  /**
+   * Implementation of the "wallet-core" API.
+   */
+
+  private async dispatchRequestInternal(
+    operation: string,
+    payload: unknown,
+  ): Promise<Record<string, any>> {
+    switch (operation) {
+      case "withdrawTestkudos": {
+        await this.withdrawTestBalance();
+        return {};
+      }
+      case "runIntegrationtest": {
+        const req = codecForIntegrationTestArgs().decode(payload);
+        await this.runIntegrationtest(req);
+        return {}
+      }
+      case "testPay": {
+        const req = codecForIntegrationTestArgs().decode(payload);
+        await this.runIntegrationtest(req);
+        return {}
+      }
+      case "getTransactions": {
+        const req = codecForTransactionsRequest().decode(payload);
+        return await this.getTransactions(req);
+      }
+      case "addExchange": {
+        const req = codecForAddExchangeRequest().decode(payload);
+        await this.updateExchangeFromUrl(req.exchangeBaseUrl);
+        return {};
+      }
+      case "listExchanges": {
+        return await this.getExchanges();
+      }
+      case "getWithdrawalDetailsForUri": {
+        const req = codecForGetWithdrawalDetailsForUri().decode(payload);
+        return await this.getWithdrawalDetailsForUri(req.talerWithdrawUri);
+      }
+      case "acceptManualWithdrawal": {
+        const req = codecForAcceptManualWithdrawalRequet().decode(payload);
+        const res = await this.acceptManualWithdrawal(
+          req.exchangeBaseUrl,
+          Amounts.parseOrThrow(req.amount),
+        );
+        return res;
+      }
+      case "getWithdrawalDetailsForAmount": {
+        const req = codecForGetWithdrawalDetailsForAmountRequest().decode(
+          payload,
+        );
+        return await this.getWithdrawalDetailsForAmount(
+          req.exchangeBaseUrl,
+          Amounts.parseOrThrow(req.amount),
+        );
+      }
+      case "getBalances": {
+        return await this.getBalances();
+      }
+      case "getPendingOperations": {
+        return await this.getPendingOperations();
+      }
+      case "setExchangeTosAccepted": {
+        const req = codecForAcceptExchangeTosRequest().decode(payload);
+        await this.acceptExchangeTermsOfService(
+          req.exchangeBaseUrl,
+          req.etag,
+        );
+        return {};
+      }
+      case "applyRefund": {
+        const req = codecForApplyRefundRequest().decode(payload);
+        return await this.applyRefund(req.talerRefundUri);
+      }
+      case "acceptBankIntegratedWithdrawal": {
+        const req = codecForAcceptBankIntegratedWithdrawalRequest().decode(
+          payload,
+        );
+        return await this.acceptWithdrawal(
+          req.talerWithdrawUri,
+          req.exchangeBaseUrl,
+        );
+      }
+      case "getExchangeTos": {
+        const req = codecForGetExchangeTosRequest().decode(payload);
+        return this.getExchangeTos(req.exchangeBaseUrl);
+      }
+      case "abortProposal": {
+        const req = codecForAbortProposalRequest().decode(payload);
+        await this.refuseProposal(req.proposalId);
+        return {};
+      }
+      case "retryPendingNow": {
+        await this.runPending(true);
+        return {};
+      }
+      case "preparePay": {
+        const req = codecForPreparePayRequest().decode(payload);
+        return await this.preparePayForUri(req.talerPayUri);
+      }
+      case "confirmPay": {
+        const req = codecForConfirmPayRequest().decode(payload);
+        return await this.confirmPay(req.proposalId, req.sessionId);
+      }
+    }
+    throw OperationFailedError.fromCode(
+      TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN,
+      "unknown operation",
+      {
+        operation,
+      },
+    );
+  }
+
+  /**
+   * Handle a request to the wallet-core API.
+   */
+  async handleCoreApiRequest(
+    operation: string,
+    id: string,
+    payload: unknown,
+  ): Promise<CoreApiResponse> {
+    try {
+      const result = await this.dispatchRequestInternal(operation, payload);
+      return {
+        type: "response",
+        operation,
+        id,
+        result,
+      };
+    } catch (e) {
+      if (
+        e instanceof OperationFailedError ||
+        e instanceof OperationFailedAndReportedError
+      ) {
+        return {
+          type: "error",
+          operation,
+          id,
+          error: e.operationError,
+        };
+      } else {
+        return {
+          type: "error",
+          operation,
+          id,
+          error: makeErrorDetails(
+            TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
+            `unexpected exception: ${e}`,
+            {},
+          ),
+        };
+      }
+    }
+  }
 }
diff --git a/packages/taler-wallet-core/src/walletCoreApiHandler.ts 
b/packages/taler-wallet-core/src/walletCoreApiHandler.ts
deleted file mode 100644
index 5bc9005a..00000000
--- a/packages/taler-wallet-core/src/walletCoreApiHandler.ts
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2020 Taler Systems S.A.
-
- 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-import { Wallet } from "./wallet";
-import {
-  OperationFailedError,
-  OperationFailedAndReportedError,
-  makeErrorDetails,
-} from "./operations/errors";
-import { TalerErrorCode } from "./TalerErrorCode";
-import { codecForTransactionsRequest } from "./types/transactions";
-import {
-  buildCodecForObject,
-  codecForString,
-  Codec,
-  codecOptional,
-} from "./util/codec";
-import { Amounts } from "./util/amounts";
-import { OperationErrorDetails } from "./types/walletTypes";
-
-export interface AddExchangeRequest {
-  exchangeBaseUrl: string;
-}
-
-export const codecForAddExchangeRequest = (): Codec<AddExchangeRequest> =>
-  buildCodecForObject<AddExchangeRequest>()
-    .property("exchangeBaseUrl", codecForString())
-    .build("AddExchangeRequest");
-
-export interface GetExchangeTosRequest {
-  exchangeBaseUrl: string;
-}
-
-export const codecForGetExchangeTosRequest = (): Codec<GetExchangeTosRequest> 
=>
-  buildCodecForObject<GetExchangeTosRequest>()
-    .property("exchangeBaseUrl", codecForString())
-    .build("GetExchangeTosRequest");
-
-export interface AcceptManualWithdrawalRequest {
-  exchangeBaseUrl: string;
-  amount: string;
-}
-
-export const codecForAcceptManualWithdrawalRequet = (): Codec<
-  AcceptManualWithdrawalRequest
-> =>
-  buildCodecForObject<AcceptManualWithdrawalRequest>()
-    .property("exchangeBaseUrl", codecForString())
-    .property("amount", codecForString())
-    .build("AcceptManualWithdrawalRequest");
-
-export interface GetWithdrawalDetailsForAmountRequest {
-  exchangeBaseUrl: string;
-  amount: string;
-}
-
-export interface AcceptBankIntegratedWithdrawalRequest {
-  talerWithdrawUri: string;
-  exchangeBaseUrl: string;
-}
-
-export const codecForAcceptBankIntegratedWithdrawalRequest = (): Codec<
-  AcceptBankIntegratedWithdrawalRequest
-> =>
-  buildCodecForObject<AcceptBankIntegratedWithdrawalRequest>()
-    .property("exchangeBaseUrl", codecForString())
-    .property("talerWithdrawUri", codecForString())
-    .build("AcceptBankIntegratedWithdrawalRequest");
-
-export const codecForGetWithdrawalDetailsForAmountRequest = (): Codec<
-  GetWithdrawalDetailsForAmountRequest
-> =>
-  buildCodecForObject<GetWithdrawalDetailsForAmountRequest>()
-    .property("exchangeBaseUrl", codecForString())
-    .property("amount", codecForString())
-    .build("GetWithdrawalDetailsForAmountRequest");
-
-export interface AcceptExchangeTosRequest {
-  exchangeBaseUrl: string;
-  etag: string;
-}
-
-export const codecForAcceptExchangeTosRequest = (): 
Codec<AcceptExchangeTosRequest> =>
-  buildCodecForObject<AcceptExchangeTosRequest>()
-    .property("exchangeBaseUrl", codecForString())
-    .property("etag", codecForString())
-    .build("AcceptExchangeTosRequest");
-
-export interface ApplyRefundRequest {
-  talerRefundUri: string;
-}
-
-export const codecForApplyRefundRequest = (): Codec<ApplyRefundRequest> =>
-  buildCodecForObject<ApplyRefundRequest>()
-    .property("talerRefundUri", codecForString())
-    .build("ApplyRefundRequest");
-
-export interface GetWithdrawalDetailsForUriRequest {
-  talerWithdrawUri: string;
-}
-
-export const codecForGetWithdrawalDetailsForUri = (): Codec<
-  GetWithdrawalDetailsForUriRequest
-> =>
-  buildCodecForObject<GetWithdrawalDetailsForUriRequest>()
-    .property("talerWithdrawUri", codecForString())
-    .build("GetWithdrawalDetailsForUriRequest");
-
-export interface AbortProposalRequest {
-  proposalId: string;
-}
-
-export const codecForAbortProposalRequest = (): Codec<AbortProposalRequest> =>
-  buildCodecForObject<AbortProposalRequest>()
-    .property("proposalId", codecForString())
-    .build("AbortProposalRequest");
-
-export interface PreparePayRequest {
-  talerPayUri: string;
-}
-
-const codecForPreparePayRequest = (): Codec<PreparePayRequest> =>
-  buildCodecForObject<PreparePayRequest>()
-    .property("talerPayUri", codecForString())
-    .build("PreparePay");
-
-export interface ConfirmPayRequest {
-  proposalId: string;
-  sessionId?: string;
-}
-
-export const codecForConfirmPayRequest = (): Codec<ConfirmPayRequest> =>
-  buildCodecForObject<ConfirmPayRequest>()
-    .property("proposalId", codecForString())
-    .property("sessionId", codecOptional(codecForString()))
-    .build("ConfirmPay");
-
-/**
- * Implementation of the "wallet-core" API.
- */
-
-async function dispatchRequestInternal(
-  wallet: Wallet,
-  operation: string,
-  payload: unknown,
-): Promise<Record<string, any>> {
-  switch (operation) {
-    case "withdrawTestkudos":
-      await wallet.withdrawTestBalance();
-      return {};
-    case "getTransactions": {
-      const req = codecForTransactionsRequest().decode(payload);
-      return await wallet.getTransactions(req);
-    }
-    case "addExchange": {
-      const req = codecForAddExchangeRequest().decode(payload);
-      await wallet.updateExchangeFromUrl(req.exchangeBaseUrl);
-      return {};
-    }
-    case "listExchanges": {
-      return await wallet.getExchanges();
-    }
-    case "getWithdrawalDetailsForUri": {
-      const req = codecForGetWithdrawalDetailsForUri().decode(payload);
-      return await wallet.getWithdrawalDetailsForUri(req.talerWithdrawUri);
-    }
-    case "acceptManualWithdrawal": {
-      const req = codecForAcceptManualWithdrawalRequet().decode(payload);
-      const res = await wallet.acceptManualWithdrawal(
-        req.exchangeBaseUrl,
-        Amounts.parseOrThrow(req.amount),
-      );
-      return res;
-    }
-    case "getWithdrawalDetailsForAmount": {
-      const req = codecForGetWithdrawalDetailsForAmountRequest().decode(
-        payload,
-      );
-      return await wallet.getWithdrawalDetailsForAmount(
-        req.exchangeBaseUrl,
-        Amounts.parseOrThrow(req.amount),
-      );
-    }
-    case "getBalances": {
-      return await wallet.getBalances();
-    }
-    case "getPendingOperations": {
-      return await wallet.getPendingOperations();
-    }
-    case "setExchangeTosAccepted": {
-      const req = codecForAcceptExchangeTosRequest().decode(payload);
-      await wallet.acceptExchangeTermsOfService(req.exchangeBaseUrl, req.etag);
-      return {};
-    }
-    case "applyRefund": {
-      const req = codecForApplyRefundRequest().decode(payload);
-      return await wallet.applyRefund(req.talerRefundUri);
-    }
-    case "acceptBankIntegratedWithdrawal": {
-      const req = codecForAcceptBankIntegratedWithdrawalRequest().decode(
-        payload,
-      );
-      return await wallet.acceptWithdrawal(
-        req.talerWithdrawUri,
-        req.exchangeBaseUrl,
-      );
-    }
-    case "getExchangeTos": {
-      const req = codecForGetExchangeTosRequest().decode(payload);
-      return wallet.getExchangeTos(req.exchangeBaseUrl);
-    }
-    case "abortProposal": {
-      const req = codecForAbortProposalRequest().decode(payload);
-      await wallet.refuseProposal(req.proposalId);
-      return {};
-    }
-    case "retryPendingNow": {
-      await wallet.runPending(true);
-      return {};
-    }
-    case "preparePay": {
-      const req = codecForPreparePayRequest().decode(payload);
-      return await wallet.preparePayForUri(req.talerPayUri);
-    }
-    case "confirmPay": {
-      const req = codecForConfirmPayRequest().decode(payload);
-      return await wallet.confirmPay(req.proposalId, req.sessionId);
-    }
-  }
-  throw OperationFailedError.fromCode(
-    TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN,
-    "unknown operation",
-    {
-      operation,
-    },
-  );
-}
-
-export type CoreApiResponse = CoreApiResponseSuccess | CoreApiResponseError;
-
-export type CoreApiEnvelope = CoreApiResponse | CoreApiNotification;
-
-export interface CoreApiNotification {
-  type: "notification";
-  payload: unknown;
-}
-
-export interface CoreApiResponseSuccess {
-  // To distinguish the message from notifications
-  type: "response";
-  operation: string;
-  id: string;
-  result: unknown;
-}
-
-export interface CoreApiResponseError {
-  // To distinguish the message from notifications
-  type: "error";
-  operation: string;
-  id: string;
-  error: OperationErrorDetails;
-}
-
-/**
- * Handle a request to the wallet-core API.
- */
-export async function handleCoreApiRequest(
-  w: Wallet,
-  operation: string,
-  id: string,
-  payload: unknown,
-): Promise<CoreApiResponse> {
-  try {
-    const result = await dispatchRequestInternal(w, operation, payload);
-    return {
-      type: "response",
-      operation,
-      id,
-      result,
-    };
-  } catch (e) {
-    if (
-      e instanceof OperationFailedError ||
-      e instanceof OperationFailedAndReportedError
-    ) {
-      return {
-        type: "error",
-        operation,
-        id,
-        error: e.operationError,
-      };
-    } else {
-      return {
-        type: "error",
-        operation,
-        id,
-        error: makeErrorDetails(
-          TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
-          `unexpected exception: ${e}`,
-          {},
-        ),
-      };
-    }
-  }
-}
diff --git a/packages/taler-wallet-webextension/src/pageEntryPoint.ts 
b/packages/taler-wallet-webextension/src/pageEntryPoint.ts
index 9fd1d36f..216cb83c 100644
--- a/packages/taler-wallet-webextension/src/pageEntryPoint.ts
+++ b/packages/taler-wallet-webextension/src/pageEntryPoint.ts
@@ -26,6 +26,7 @@ import { createWithdrawPage } from "./pages/withdraw";
 import { createWelcomePage } from "./pages/welcome";
 import { createPayPage } from "./pages/pay";
 import { createRefundPage } from "./pages/refund";
+import { setupI18n } from "taler-wallet-core";
 
 function main(): void {
   try {
@@ -65,6 +66,8 @@ function main(): void {
   }
 }
 
+setupI18n("en-US");
+
 if (document.readyState === "loading") {
   document.addEventListener("DOMContentLoaded", main);
 } else {
diff --git a/packages/taler-wallet-webextension/src/pages/popup.tsx 
b/packages/taler-wallet-webextension/src/pages/popup.tsx
index 61edcfe5..e833f595 100644
--- a/packages/taler-wallet-webextension/src/pages/popup.tsx
+++ b/packages/taler-wallet-webextension/src/pages/popup.tsx
@@ -397,19 +397,19 @@ function actionForTalerUri(talerUri: string): string | 
undefined {
   const uriType = classifyTalerUri(talerUri);
   switch (uriType) {
     case TalerUriType.TalerWithdraw:
-      return makeExtensionUrlWithParams("withdraw.html", {
+      return makeExtensionUrlWithParams("static/withdraw.html", {
         talerWithdrawUri: talerUri,
       });
     case TalerUriType.TalerPay:
-      return makeExtensionUrlWithParams("pay.html", {
+      return makeExtensionUrlWithParams("static/pay.html", {
         talerPayUri: talerUri,
       });
     case TalerUriType.TalerTip:
-      return makeExtensionUrlWithParams("tip.html", {
+      return makeExtensionUrlWithParams("static/tip.html", {
         talerTipUri: talerUri,
       });
     case TalerUriType.TalerRefund:
-      return makeExtensionUrlWithParams("refund.html", {
+      return makeExtensionUrlWithParams("static/refund.html", {
         talerRefundUri: talerUri,
       });
     case TalerUriType.TalerNotifyReserve:
diff --git a/packages/taler-wallet-webextension/src/pages/withdraw.tsx 
b/packages/taler-wallet-webextension/src/pages/withdraw.tsx
index 4a92704b..1637176a 100644
--- a/packages/taler-wallet-webextension/src/pages/withdraw.tsx
+++ b/packages/taler-wallet-webextension/src/pages/withdraw.tsx
@@ -29,11 +29,13 @@ import React, { useState, useEffect } from "react";
 import {
   acceptWithdrawal,
   onUpdateNotification,
+  getWithdrawalDetailsForUri,
 } from "../wxApi";
+import { WithdrawUriInfoResponse } from "taler-wallet-core";
 
 function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element {
   const [details, setDetails] = useState<
-    any | undefined
+    WithdrawUriInfoResponse | undefined
   >();
   const [selectedExchange, setSelectedExchange] = useState<
     string | undefined
@@ -54,55 +56,12 @@ function WithdrawalDialog(props: { talerWithdrawUri: string 
}): JSX.Element {
 
   useEffect(() => {
     const fetchData = async (): Promise<void> => {
-    // FIXME: re-implement with new API
-    //   console.log("getting from", talerWithdrawUri);
-    //   let d: WithdrawalDetailsResponse | undefined = undefined;
-    //   try {
-    //     d = await getWithdrawDetails(talerWithdrawUri, selectedExchange);
-    //   } catch (e) {
-    //     console.error(
-    //       `error getting withdraw details for uri ${talerWithdrawUri}, 
exchange ${selectedExchange}`,
-    //       e,
-    //     );
-    //     setErrMsg(e.message);
-    //     return;
-    //   }
-    //   console.log("got withdrawDetails", d);
-    //   if (!selectedExchange && d.bankWithdrawDetails.suggestedExchange) {
-    //     console.log("setting selected exchange");
-    //     setSelectedExchange(d.bankWithdrawDetails.suggestedExchange);
-    //   }
-    //   setDetails(d);
+      const res = await getWithdrawalDetailsForUri({talerWithdrawUri: 
props.talerWithdrawUri});
+      setDetails(res);
     };
     fetchData();
   }, [selectedExchange, errMsg, selecting, talerWithdrawUri, updateCounter]);
 
-  if (errMsg) {
-    return (
-      <div>
-        <i18n.Translate wrap="p">
-          Could not get details for withdraw operation:
-        </i18n.Translate>
-        <p style={{ color: "red" }}>{errMsg}</p>
-        <p>
-          <span
-            role="button"
-            tabIndex={0}
-            style={{ textDecoration: "underline", cursor: "pointer" }}
-            onClick={() => {
-              setSelecting(true);
-              setErrMsg(undefined);
-              setSelectedExchange(undefined);
-              setDetails(undefined);
-            }}
-          >
-            {i18n.str`Chose different exchange provider`}
-          </span>
-        </p>
-      </div>
-    );
-  }
-
   if (!details) {
     return <span>Loading...</span>;
   }
@@ -111,51 +70,6 @@ function WithdrawalDialog(props: { talerWithdrawUri: string 
}): JSX.Element {
     return <span>Withdraw operation has been cancelled.</span>;
   }
 
-  if (selecting) {
-    const bankSuggestion =
-      details && details.bankWithdrawDetails.suggestedExchange;
-    return (
-      <div>
-        {i18n.str`Please select an exchange.  You can review the details 
before after your selection.`}
-        {bankSuggestion && (
-          <div>
-            <h2>Bank Suggestion</h2>
-            <button
-              className="pure-button button-success"
-              onClick={() => {
-                setDetails(undefined);
-                setSelectedExchange(bankSuggestion);
-                setSelecting(false);
-              }}
-            >
-              <i18n.Translate wrap="span">
-                Select <strong>{bankSuggestion}</strong>
-              </i18n.Translate>
-            </button>
-          </div>
-        )}
-        <h2>Custom Selection</h2>
-        <p>
-          <input
-            type="text"
-            onChange={(e) => setCustomUrl(e.target.value)}
-            value={customUrl}
-          />
-        </p>
-        <button
-          className="pure-button button-success"
-          onClick={() => {
-            setDetails(undefined);
-            setSelectedExchange(customUrl);
-            setSelecting(false);
-          }}
-        >
-          <i18n.Translate wrap="span">Select custom exchange</i18n.Translate>
-        </button>
-      </div>
-    );
-  }
-
   const accept = async (): Promise<void> => {
     if (!selectedExchange) {
       throw Error("can't accept, no exchange selected");
@@ -173,7 +87,7 @@ function WithdrawalDialog(props: { talerWithdrawUri: string 
}): JSX.Element {
       <h1>Digital Cash Withdrawal</h1>
       <i18n.Translate wrap="p">
         You are about to withdraw{" "}
-        <strong>{renderAmount(details.bankWithdrawDetails.amount)}</strong> 
from
+        <strong>{renderAmount(details.amount)}</strong> from
         your bank account into your wallet.
       </i18n.Translate>
       {selectedExchange ? (
@@ -211,9 +125,9 @@ function WithdrawalDialog(props: { talerWithdrawUri: string 
}): JSX.Element {
           </span>
         </p>
 
-        {details.exchangeWithdrawDetails ? (
+        {/* {details.exchangeWithdrawDetails ? (
           <WithdrawDetailView rci={details.exchangeWithdrawDetails} />
-        ) : null}
+        ) : null} */}
       </div>
     </div>
   );
diff --git a/packages/taler-wallet-webextension/src/wxApi.ts 
b/packages/taler-wallet-webextension/src/wxApi.ts
index 1bcee128..836466ff 100644
--- a/packages/taler-wallet-webextension/src/wxApi.ts
+++ b/packages/taler-wallet-webextension/src/wxApi.ts
@@ -21,14 +21,26 @@
 /**
  * Imports.
  */
-import { AmountJson, ConfirmPayResult, BalancesResponse, PurchaseDetails, 
TipStatus, BenchmarkResult, PreparePayResult, AcceptWithdrawalResponse, 
WalletDiagnostics } from "taler-wallet-core";
-
+import {
+  AmountJson,
+  ConfirmPayResult,
+  BalancesResponse,
+  PurchaseDetails,
+  TipStatus,
+  BenchmarkResult,
+  PreparePayResult,
+  AcceptWithdrawalResponse,
+  WalletDiagnostics,
+  CoreApiResponse,
+  OperationFailedError,
+  GetWithdrawalDetailsForUriRequest,
+  WithdrawUriInfoResponse,
+} from "taler-wallet-core";
 
 export interface ExtendedPermissionsResponse {
   newValue: boolean;
 }
 
-
 /**
  * Response with information about available version upgrades.
  */
@@ -50,23 +62,9 @@ export interface UpgradeResponse {
   oldDbVersion: string;
 }
 
-/**
- * Error thrown when the function from the backend (via RPC) threw an error.
- */
-export class WalletApiError extends Error {
-  constructor(message: string, public detail: any) {
-    super(message);
-    // restore prototype chain
-    Object.setPrototypeOf(this, new.target.prototype);
-  }
-}
-
-async function callBackend(
-  type: string,
-  detail: any,
-): Promise<any> {
+async function callBackend(operation: string, payload: any): Promise<any> {
   return new Promise<any>((resolve, reject) => {
-    chrome.runtime.sendMessage({ type, detail }, (resp) => {
+    chrome.runtime.sendMessage({ operation, payload, id: "(none)" }, (resp) => 
{
       if (chrome.runtime.lastError) {
         console.log("Error calling backend");
         reject(
@@ -75,19 +73,17 @@ async function callBackend(
           ),
         );
       }
-      if (typeof resp === "object" && resp && resp.error) {
-        console.warn("response error:", resp);
-        const e = new WalletApiError(resp.error.message, resp.error);
-        reject(e);
-      } else {
-        resolve(resp);
+      console.log("got response", resp);
+      const r = resp as CoreApiResponse;
+      if (r.type === "error") {
+        reject(new OperationFailedError(r.error));
+        return;
       }
+      resolve(r.result);
     });
   });
 }
 
-
-
 /**
  * Start refreshing a coin.
  */
@@ -123,7 +119,7 @@ export function resetDb(): Promise<void> {
  * Get balances for all currencies/exchanges.
  */
 export function getBalance(): Promise<BalancesResponse> {
-  return callBackend("balances", {});
+  return callBackend("getBalances", {});
 }
 
 /**
@@ -227,6 +223,15 @@ export function getExtendedPermissions(): 
Promise<ExtendedPermissionsResponse> {
   return callBackend("get-extended-permissions", {});
 }
 
+/**
+ * Get diagnostics information
+ */
+export function getWithdrawalDetailsForUri(
+  req: GetWithdrawalDetailsForUriRequest,
+): Promise<WithdrawUriInfoResponse> {
+  return callBackend("getWithdrawalDetailsForUri", req);
+}
+
 export function onUpdateNotification(f: () => void): () => void {
   const port = chrome.runtime.connect({ name: "notifications" });
   const listener = (): void => {
diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts 
b/packages/taler-wallet-webextension/src/wxBackend.ts
index ce024e09..60d0b6d4 100644
--- a/packages/taler-wallet-webextension/src/wxBackend.ts
+++ b/packages/taler-wallet-webextension/src/wxBackend.ts
@@ -24,11 +24,20 @@
  * Imports.
  */
 import { isFirefox, getPermissionsApi } from "./compat";
-import * as wxApi from "./wxApi";
 import MessageSender = chrome.runtime.MessageSender;
 import { extendedPermissions } from "./permissions";
 
-import { Wallet, OpenedPromise, openPromise, deleteTalerDatabase, 
WALLET_DB_MINOR_VERSION, WalletDiagnostics, openTalerDatabase, Database, 
classifyTalerUri, TalerUriType } from "taler-wallet-core";
+import {
+  Wallet,
+  OpenedPromise,
+  openPromise,
+  openTalerDatabase,
+  Database,
+  classifyTalerUri,
+  TalerUriType,
+  makeErrorDetails,
+  TalerErrorCode,
+} from "taler-wallet-core";
 import { BrowserHttpLib } from "./browserHttpLib";
 import { BrowserCryptoWorkerFactory } from "./browserCryptoWorkerFactory";
 
@@ -51,254 +60,28 @@ const walletInit: OpenedPromise<void> = 
openPromise<void>();
 
 const notificationPorts: chrome.runtime.Port[] = [];
 
-async function handleMessage(
-  sender: MessageSender,
-  type: string,
-  detail: any,
-): Promise<any> {
-  function needsWallet(): Wallet {
-    if (!currentWallet) {
-      throw NeedsWallet;
-    }
-    return currentWallet;
-  }
-  switch (type) {
-    case "balances": {
-      return needsWallet().getBalances();
-    }
-    case "dump-db": {
-      const db = needsWallet().db;
-      return db.exportDatabase();
-    }
-    case "import-db": {
-      const db = needsWallet().db;
-      return db.importDatabase(detail.dump);
-    }
-    case "ping": {
-      return Promise.resolve();
-    }
-    case "reset-db": {
-      deleteTalerDatabase(indexedDB);
-      setBadgeText({ text: "" });
-      console.log("reset done");
-      if (!currentWallet) {
-        reinitWallet();
-      }
-      return Promise.resolve({});
-    }
-    case "confirm-pay": {
-      if (typeof detail.proposalId !== "string") {
-        throw Error("proposalId must be string");
-      }
-      return needsWallet().confirmPay(detail.proposalId, detail.sessionId);
-    }
-    case "exchange-info": {
-      if (!detail.baseUrl) {
-        return Promise.resolve({ error: "bad url" });
-      }
-      return needsWallet().updateExchangeFromUrl(detail.baseUrl);
-    }
-    case "get-exchanges": {
-      return needsWallet().getExchangeRecords();
-    }
-    case "get-currencies": {
-      return needsWallet().getCurrencies();
-    }
-    case "update-currency": {
-      return needsWallet().updateCurrency(detail.currencyRecord);
-    }
-    case "get-reserves": {
-      if (typeof detail.exchangeBaseUrl !== "string") {
-        return Promise.reject(Error("exchangeBaseUrl missing"));
-      }
-      return needsWallet().getReserves(detail.exchangeBaseUrl);
-    }
-    case "get-coins": {
-      if (typeof detail.exchangeBaseUrl !== "string") {
-        return Promise.reject(Error("exchangBaseUrl missing"));
-      }
-      return needsWallet().getCoinsForExchange(detail.exchangeBaseUrl);
-    }
-    case "get-denoms": {
-      if (typeof detail.exchangeBaseUrl !== "string") {
-        return Promise.reject(Error("exchangBaseUrl missing"));
-      }
-      return needsWallet().getDenoms(detail.exchangeBaseUrl);
-    }
-    case "refresh-coin": {
-      if (typeof detail.coinPub !== "string") {
-        return Promise.reject(Error("coinPub missing"));
-      }
-      return needsWallet().refresh(detail.coinPub);
-    }
-    case "get-sender-wire-infos": {
-      return needsWallet().getSenderWireInfos();
-    }
-    case "return-coins": {
-      const d = {
-        amount: detail.amount,
-        exchange: detail.exchange,
-        senderWire: detail.senderWire,
-      };
-      return needsWallet().returnCoins(d);
-    }
-    case "check-upgrade": {
-      let dbResetRequired = false;
-      if (!currentWallet) {
-        dbResetRequired = true;
-      }
-      const resp: wxApi.UpgradeResponse = {
-        currentDbVersion: WALLET_DB_MINOR_VERSION.toString(),
-        dbResetRequired,
-        oldDbVersion: (outdatedDbVersion || "unknown").toString(),
-      };
-      return resp;
-    }
-    case "get-purchase-details": {
-      const proposalId = detail.proposalId;
-      if (!proposalId) {
-        throw Error("proposalId missing");
-      }
-      if (typeof proposalId !== "string") {
-        throw Error("proposalId must be a string");
-      }
-      return needsWallet().getPurchaseDetails(proposalId);
-    }
-    case "accept-refund":
-      return needsWallet().applyRefund(detail.refundUrl);
-    case "get-tip-status": {
-      return needsWallet().getTipStatus(detail.talerTipUri);
-    }
-    case "accept-tip": {
-      return needsWallet().acceptTip(detail.talerTipUri);
-    }
-    case "abort-failed-payment": {
-      if (!detail.contractTermsHash) {
-        throw Error("contracTermsHash not given");
-      }
-      return needsWallet().abortFailedPayment(detail.contractTermsHash);
-    }
-    case "benchmark-crypto": {
-      if (!detail.repetitions) {
-        throw Error("repetitions not given");
-      }
-      return needsWallet().benchmarkCrypto(detail.repetitions);
-    }
-    case "accept-withdrawal": {
-      return needsWallet().acceptWithdrawal(
-        detail.talerWithdrawUri,
-        detail.selectedExchange,
-      );
-    }
-    case "get-diagnostics": {
-      const manifestData = chrome.runtime.getManifest();
-      const errors: string[] = [];
-      let firefoxIdbProblem = false;
-      let dbOutdated = false;
-      try {
-        await walletInit.promise;
-      } catch (e) {
-        errors.push("Error during wallet initialization: " + e);
-        if (
-          currentDatabase === undefined &&
-          outdatedDbVersion === undefined &&
-          isFirefox()
-        ) {
-          firefoxIdbProblem = true;
-        }
-      }
-      if (!currentWallet) {
-        errors.push("Could not create wallet backend.");
-      }
-      if (!currentDatabase) {
-        errors.push("Could not open database");
-      }
-      if (outdatedDbVersion !== undefined) {
-        errors.push(`Outdated DB version: ${outdatedDbVersion}`);
-        dbOutdated = true;
-      }
-      const diagnostics: WalletDiagnostics = {
-        walletManifestDisplayVersion:
-          manifestData.version_name || "(undefined)",
-        walletManifestVersion: manifestData.version,
-        errors,
-        firefoxIdbProblem,
-        dbOutdated,
-      };
-      return diagnostics;
-    }
-    case "prepare-pay":
-      return needsWallet().preparePayForUri(detail.talerPayUri);
-    case "set-extended-permissions": {
-      const newVal = detail.value;
-      console.log("new extended permissions value", newVal);
-      if (newVal) {
-        setupHeaderListener();
-        return { newValue: true };
-      } else {
-        await new Promise((resolve, reject) => {
-          getPermissionsApi().remove(extendedPermissions, (rem) => {
-            console.log("permissions removed:", rem);
-            resolve();
-          });
-        });
-        return { newVal: false };
-      }
-    }
-    case "get-extended-permissions": {
-      const res = await new Promise((resolve, reject) => {
-        getPermissionsApi().contains(extendedPermissions, (result: boolean) => 
{
-          resolve(result);
-        });
-      });
-      return { newValue: res };
-    }
-    default:
-      console.error(`Request type ${type} unknown`);
-      console.error(`Request detail was ${detail}`);
-      return {
-        error: {
-          message: `request type ${type} unknown`,
-          requestType: type,
-        },
-      };
-  }
-}
-
 async function dispatch(
   req: any,
   sender: any,
   sendResponse: any,
 ): Promise<void> {
+  const w = currentWallet;
+  if (!w) {
+    sendResponse(
+      makeErrorDetails(
+        TalerErrorCode.WALLET_CORE_NOT_AVAILABLE,
+        "wallet core not available",
+        {},
+      ),
+    );
+    return;
+  }
+
+  const r = await w.handleCoreApiRequest(req.operation, req.id, req.payload);
   try {
-    const p = handleMessage(sender, req.type, req.detail);
-    const r = await p;
-    try {
-      sendResponse(r);
-    } catch (e) {
-      // might fail if tab disconnected
-    }
+    sendResponse(r);
   } catch (e) {
-    console.log(`exception during wallet handler for '${req.type}'`);
-    console.log("request", req);
-    console.error(e);
-    let stack;
-    try {
-      stack = e.stack.toString();
-    } catch (e) {
-      // might fail
-    }
-    try {
-      sendResponse({
-        error: {
-          message: e.message,
-          stack,
-        },
-      });
-    } catch (e) {
-      console.log(e);
-      // might fail if tab disconnected
-    }
+    // might fail if tab disconnected
   }
 }
 
@@ -436,7 +219,7 @@ function headerListener(
         switch (uriType) {
           case TalerUriType.TalerWithdraw:
             return makeSyncWalletRedirect(
-              "withdraw.html",
+              "/static/withdraw.html",
               details.tabId,
               details.url,
               {
@@ -445,7 +228,7 @@ function headerListener(
             );
           case TalerUriType.TalerPay:
             return makeSyncWalletRedirect(
-              "pay.html",
+              "/static/pay.html",
               details.tabId,
               details.url,
               {
@@ -454,7 +237,7 @@ function headerListener(
             );
           case TalerUriType.TalerTip:
             return makeSyncWalletRedirect(
-              "tip.html",
+              "/static/tip.html",
               details.tabId,
               details.url,
               {
diff --git a/packages/taler-wallet-webextension/static/benchmark.html 
b/packages/taler-wallet-webextension/static/benchmark.html
index a29fe072..d0ca32ae 100644
--- a/packages/taler-wallet-webextension/static/benchmark.html
+++ b/packages/taler-wallet-webextension/static/benchmark.html
@@ -3,9 +3,9 @@
   <head>
     <meta charset="UTF-8" />
     <title>Taler Wallet: Benchmarks</title>
-    <link rel="stylesheet" type="text/css" href="/style/wallet.css" />
-    <link rel="icon" href="/img/icon.png" />
-    <script src="/pageEntryPoint.js"></script>
+    <link rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
+    <link rel="icon" href="/static/img/icon.png" />
+    <script src="/dist/pageEntryPoint.js"></script>
   </head>
   <body>
     <section id="main">
diff --git a/packages/taler-wallet-webextension/static/pay.html 
b/packages/taler-wallet-webextension/static/pay.html
index 452c56df..12976581 100644
--- a/packages/taler-wallet-webextension/static/pay.html
+++ b/packages/taler-wallet-webextension/static/pay.html
@@ -4,10 +4,10 @@
     <meta charset="UTF-8" />
     <title>Taler Wallet: Confirm Contract</title>
 
-    <link rel="stylesheet" type="text/css" href="/style/pure.css" />
-    <link rel="stylesheet" type="text/css" href="/style/wallet.css" />
-    <link rel="icon" href="/img/icon.png" />
-    <script src="/pageEntryPoint.js"></script>
+    <link rel="stylesheet" type="text/css" href="/static/style/pure.css" />
+    <link rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
+    <link rel="icon" href="/static/img/icon.png" />
+    <script src="/dist/pageEntryPoint.js"></script>
 
     <style>
       button.accept {
diff --git a/packages/taler-wallet-webextension/static/popup.html 
b/packages/taler-wallet-webextension/static/popup.html
index 83f2f286..024a0d18 100644
--- a/packages/taler-wallet-webextension/static/popup.html
+++ b/packages/taler-wallet-webextension/static/popup.html
@@ -2,11 +2,11 @@
 <html>
   <head>
     <meta charset="utf-8" />
-    <link rel="stylesheet" type="text/css" href="/style/pure.css" />
-    <link rel="stylesheet" type="text/css" href="/style/wallet.css" />
-    <link rel="stylesheet" type="text/css" href="/style/popup.css" />
-    <link rel="icon" href="/img/icon.png" />
-    <script src="/pageEntryPoint.js"></script>
+    <link rel="stylesheet" type="text/css" href="/static/style/pure.css" />
+    <link rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
+    <link rel="stylesheet" type="text/css" href="/static/style/popup.css" />
+    <link rel="icon" href="/static/img/icon.png" />
+    <script src="/dist/pageEntryPoint.js"></script>
   </head>
 
   <body>
diff --git a/packages/taler-wallet-webextension/static/withdraw.html 
b/packages/taler-wallet-webextension/static/withdraw.html
index 5137204b..b2b83765 100644
--- a/packages/taler-wallet-webextension/static/withdraw.html
+++ b/packages/taler-wallet-webextension/static/withdraw.html
@@ -3,10 +3,10 @@
   <head>
     <meta charset="UTF-8" />
     <title>Taler Wallet: Withdraw</title>
-    <link rel="icon" href="/img/icon.png" />
-    <link rel="stylesheet" type="text/css" href="/style/pure.css" />
-    <link rel="stylesheet" type="text/css" href="/style/wallet.css" />
-    <script src="/pageEntryPoint.js"></script>
+    <link rel="icon" href="/static/img/icon.png" />
+    <link rel="stylesheet" type="text/css" href="/static/style/pure.css" />
+    <link rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
+    <script src="/dist/pageEntryPoint.js"></script>
   </head>
 
   <body>
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 78011a19..9bdba271 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -83,6 +83,7 @@ importers:
       typescript: ^3.9.7
   packages/taler-wallet-cli:
     dependencies:
+      axios: 0.19.2
       source-map-support: 0.5.19
       taler-wallet-core: 'link:../taler-wallet-core'
       tslib: 2.0.1
@@ -105,6 +106,7 @@ importers:
       '@rollup/plugin-node-resolve': ^8.4.0
       '@rollup/plugin-replace': ^2.3.3
       '@types/node': ^14.0.27
+      axios: ^0.19.2
       prettier: ^2.0.5
       rimraf: ^3.0.2
       rollup: ^2.23.0

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