gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (5695ae0a9 -> f407ab202)


From: gnunet
Subject: [taler-wallet-core] branch master updated (5695ae0a9 -> f407ab202)
Date: Mon, 03 Jul 2023 17:44:31 +0200

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

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

    from 5695ae0a9 wallet-core: use testingWaitTransactionsFinal to wait for 
transactions
     new 15a15942d #7717 do not show walletcore version
     new f47b5bd78 #7741 share payment add shared flag in db new state purchase 
dialogShared new error WALLET_ORDER_ALREADY_PAID removing nonce from normal 
operation new operation: sharePayment which returns payUri with nonce
     new 5d76573ac #7741 share payment save shared state in backup if purchase 
is shared check before making the payment of before claim the order already 
confirmed order can return without effective if coin selection was not made 
sharePayment operation
     new 545bf16cd fix #7741 show QR with nonce
     new f407ab202 fix #7741 add integration test

The 5 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-harness/src/harness/harness.ts      |  47 ++--
 packages/taler-harness/src/harness/helpers.ts      |  20 +-
 .../src/integrationtests/test-payment-share.ts     | 239 +++++++++++++++++++
 .../src/integrationtests/testrunner.ts             |   2 +
 packages/taler-util/src/backup-types.ts            |  13 +-
 packages/taler-util/src/errors.ts                  |   7 +-
 packages/taler-util/src/taler-error-codes.ts       |  24 ++
 packages/taler-util/src/wallet-types.ts            |  26 +-
 packages/taler-wallet-core/src/db.ts               |  11 +
 .../src/operations/backup/export.ts                |   4 +
 .../src/operations/backup/import.ts                |  10 +-
 .../src/operations/pay-merchant.ts                 | 262 +++++++++++++++++++--
 packages/taler-wallet-core/src/wallet-api-types.ts |  10 +
 packages/taler-wallet-core/src/wallet.ts           |   7 +
 .../src/components/PaymentButtons.tsx              |  36 ++-
 .../src/cta/InvoicePay/state.ts                    |   1 -
 .../src/cta/Payment/stories.tsx                    |  20 +-
 .../src/cta/Payment/views.tsx                      |   4 +-
 .../src/wallet/Settings.tsx                        |  35 +--
 19 files changed, 661 insertions(+), 117 deletions(-)
 create mode 100644 
packages/taler-harness/src/integrationtests/test-payment-share.ts

diff --git a/packages/taler-harness/src/harness/harness.ts 
b/packages/taler-harness/src/harness/harness.ts
index 1120eae84..940e4258a 100644
--- a/packages/taler-harness/src/harness/harness.ts
+++ b/packages/taler-harness/src/harness/harness.ts
@@ -28,6 +28,8 @@ import {
   AmountJson,
   Amounts,
   AmountString,
+  codecForMerchantOrderPrivateStatusResponse,
+  codecForMerchantPostOrderResponse,
   codecForMerchantReserveCreateConfirmation,
   Configuration,
   CoreApiResponse,
@@ -39,28 +41,42 @@ import {
   hash,
   j2s,
   Logger,
+  MerchantInstancesResponse,
+  MerchantOrderPrivateStatusResponse,
+  MerchantPostOrderRequest,
+  MerchantPostOrderResponse,
   MerchantReserveCreateConfirmation,
   MerchantTemplateAddDetails,
-  NotificationType,
   parsePaytoUri,
   stringToBytes,
   TalerError,
   TalerProtocolDuration,
-  TransactionMajorState,
+  TipCreateConfirmation,
+  TipCreateRequest,
+  TippingReserveStatus,
   WalletNotification,
 } from "@gnu-taler/taler-util";
+import {
+  createPlatformHttpLib,
+  readSuccessResponseJsonOrThrow,
+} from "@gnu-taler/taler-util/http";
 import {
   BankApi,
   BankServiceHandle,
   HarnessExchangeBankAccount,
-  OpenedPromise,
   openPromise,
-  WalletApiOperation,
   WalletCoreApiClient,
   WalletCoreRequestType,
   WalletCoreResponseType,
   WalletOperations,
 } from "@gnu-taler/taler-wallet-core";
+import {
+  createRemoteWallet,
+  getClientFromRemoteWallet,
+  makeNotificationWaiter,
+  RemoteWallet,
+  WalletNotificationWaiter,
+} from "@gnu-taler/taler-wallet-core/remote";
 import { deepStrictEqual } from "assert";
 import axiosImp, { AxiosError } from "axios";
 import { ChildProcess, spawn } from "child_process";
@@ -72,29 +88,6 @@ import * as readline from "readline";
 import { URL } from "url";
 import { CoinConfig } from "./denomStructures.js";
 import { LibeufinNexusApi, LibeufinSandboxApi } from "./libeufin-apis.js";
-import {
-  codecForMerchantOrderPrivateStatusResponse,
-  codecForMerchantPostOrderResponse,
-  MerchantInstancesResponse,
-  MerchantOrderPrivateStatusResponse,
-  MerchantPostOrderRequest,
-  MerchantPostOrderResponse,
-  TipCreateConfirmation,
-  TipCreateRequest,
-  TippingReserveStatus,
-} from "@gnu-taler/taler-util";
-import {
-  createRemoteWallet,
-  getClientFromRemoteWallet,
-  makeNotificationWaiter,
-  RemoteWallet,
-  WalletNotificationWaiter,
-} from "@gnu-taler/taler-wallet-core/remote";
-import {
-  createPlatformHttpLib,
-  readSuccessResponseJsonOrErrorCode,
-  readSuccessResponseJsonOrThrow,
-} from "@gnu-taler/taler-util/http";
 
 const logger = new Logger("harness.ts");
 
diff --git a/packages/taler-harness/src/harness/helpers.ts 
b/packages/taler-harness/src/harness/helpers.ts
index 8c62aef37..3e91c8bd9 100644
--- a/packages/taler-harness/src/harness/helpers.ts
+++ b/packages/taler-harness/src/harness/helpers.ts
@@ -298,22 +298,10 @@ export async function createSimpleTestkudosEnvironmentV2(
     ),
   });
 
-  const walletService = new WalletService(t, {
-    name: "wallet",
-  });
-  await walletService.start();
-  await walletService.pingUntilAvailable();
-
-  const walletClient = new WalletClient({
-    unixPath: walletService.socketPath,
-    onNotification(n) {
-      console.log("got notification", n);
-    },
-  });
-  await walletClient.connect();
-  await walletClient.client.call(WalletApiOperation.InitWallet, {
-    skipDefaults: true,
-  });
+  const { walletClient, walletService } = await createWalletDaemonWithClient(
+    t,
+    { name: "wallet" },
+  );
 
   console.log("setup done!");
 
diff --git a/packages/taler-harness/src/integrationtests/test-payment-share.ts 
b/packages/taler-harness/src/integrationtests/test-payment-share.ts
new file mode 100644
index 000000000..2f853b91b
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-payment-share.ts
@@ -0,0 +1,239 @@
+/*
+ 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/>
+ */
+
+/**
+ * Imports.
+ */
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js";
+import {
+  createSimpleTestkudosEnvironmentV2,
+  withdrawViaBankV2,
+  makeTestPaymentV2,
+  createWalletDaemonWithClient,
+} from "../harness/helpers.js";
+import {
+  ConfirmPayResultType,
+  PreparePayResultType,
+  j2s,
+  parsePayUri,
+  stringifyPayUri,
+} from "@gnu-taler/taler-util";
+
+/**
+ * Run test for basic, bank-integrated withdrawal and payment.
+ */
+export async function runPaymentShareTest(t: GlobalTestState) {
+  // Set up test environment
+  const {
+    walletClient: firstWallet,
+    bank,
+    exchange,
+    merchant,
+  } = await createSimpleTestkudosEnvironmentV2(t);
+
+  // Withdraw digital cash into the wallet.
+  await withdrawViaBankV2(t, {
+    walletClient: firstWallet,
+    bank,
+    exchange,
+    amount: "TESTKUDOS:20",
+  });
+  await firstWallet.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
+
+  const { walletClient: secondWallet } = await createWalletDaemonWithClient(t, 
{
+    name: "wallet2",
+  });
+
+  await withdrawViaBankV2(t, {
+    walletClient: secondWallet,
+    bank,
+    exchange,
+    amount: "TESTKUDOS:20",
+  });
+  await secondWallet.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
+
+  //create two orders to pay
+  async function createOrder(amount: string) {
+    const order = {
+      summary: "Buy me!",
+      amount,
+      fulfillment_url: "taler://fulfillment-success/thx",
+    };
+
+    const instance = "default";
+    const args = { order };
+    const auth = {};
+
+    const orderResp = await MerchantPrivateApi.createOrder(
+      merchant,
+      instance,
+      {
+        order: args.order,
+      },
+      auth,
+    );
+
+    const orderStatus = await MerchantPrivateApi.queryPrivateOrderStatus(
+      merchant,
+      {
+        orderId: orderResp.order_id,
+      },
+      auth,
+    );
+
+    t.assertTrue(orderStatus.order_status === "unpaid");
+    return { id: orderResp.order_id, uri: orderStatus.taler_pay_uri };
+  }
+
+  /**
+   * FIRST CASE, create in first wallet and pay in the second wallet
+   * first wallet should not be able to continue
+   */
+  {
+    const order = await createOrder("TESTKUDOS:5");
+    // Claim the order with the first wallet
+    const claimFirstWallet = await firstWallet.call(
+      WalletApiOperation.PreparePayForUri,
+      { talerPayUri: order.uri },
+    );
+
+    t.assertTrue(
+      claimFirstWallet.status === PreparePayResultType.PaymentPossible,
+    );
+
+    //share order from the first wallet
+    const { privatePayUri } = await firstWallet.call(
+      WalletApiOperation.SharePayment,
+      {
+        merchantBaseUrl: merchant.makeInstanceBaseUrl(),
+        orderId: order.id,
+      },
+    );
+
+    //claim from the second wallet
+    const claimSecondWallet = await secondWallet.call(
+      WalletApiOperation.PreparePayForUri,
+      { talerPayUri: privatePayUri },
+    );
+
+    t.assertTrue(
+      claimSecondWallet.status === PreparePayResultType.PaymentPossible,
+    );
+
+    //pay from the second wallet
+    const r2 = await secondWallet.call(WalletApiOperation.ConfirmPay, {
+      proposalId: claimSecondWallet.proposalId,
+    });
+
+    t.assertTrue(r2.type === ConfirmPayResultType.Done);
+    {
+      const first = await firstWallet.call(WalletApiOperation.GetBalances, {});
+      const second = await secondWallet.call(
+        WalletApiOperation.GetBalances,
+        {},
+      );
+      t.assertAmountEquals(first.balances[0].available, "TESTKUDOS:19.53");
+      t.assertAmountEquals(second.balances[0].available, "TESTKUDOS:14.23");
+    }
+
+    // Claim the order with the first wallet
+    const claimFirstWalletAgain = await firstWallet.call(
+      WalletApiOperation.PreparePayForUri,
+      { talerPayUri: order.uri },
+    );
+
+    t.assertTrue(
+      claimFirstWalletAgain.status === PreparePayResultType.AlreadyConfirmed,
+    );
+
+    const r1 = await firstWallet.call(WalletApiOperation.ConfirmPay, {
+      proposalId: claimFirstWallet.proposalId,
+    });
+
+    t.assertTrue(r1.type === ConfirmPayResultType.Done);
+    {
+      const first = await firstWallet.call(WalletApiOperation.GetBalances, {});
+      const second = await secondWallet.call(
+        WalletApiOperation.GetBalances,
+        {},
+      );
+      t.assertAmountEquals(first.balances[0].available, "TESTKUDOS:19.53");
+      t.assertAmountEquals(second.balances[0].available, "TESTKUDOS:14.23");
+    }
+  }
+
+  /**
+   * SECOND CASE, create in first wallet and share to the second wallet
+   * pay with the first wallet, second wallet should not be able to continue
+   */
+  {
+    const order = await createOrder("TESTKUDOS:3");
+    // Claim the order with the first wallet
+    const claimFirstWallet = await firstWallet.call(
+      WalletApiOperation.PreparePayForUri,
+      { talerPayUri: order.uri },
+    );
+
+    t.assertTrue(
+      claimFirstWallet.status === PreparePayResultType.PaymentPossible,
+    );
+
+    //share order from the first wallet
+    const { privatePayUri } = await firstWallet.call(
+      WalletApiOperation.SharePayment,
+      {
+        merchantBaseUrl: merchant.makeInstanceBaseUrl(),
+        orderId: order.id,
+      },
+    );
+
+    //claim from the second wallet
+    const claimSecondWallet = await secondWallet.call(
+      WalletApiOperation.PreparePayForUri,
+      { talerPayUri: privatePayUri },
+    );
+
+    t.assertTrue(
+      claimSecondWallet.status === PreparePayResultType.PaymentPossible,
+    );
+
+    //pay from the second wallet
+    const r2 = await firstWallet.call(WalletApiOperation.ConfirmPay, {
+      proposalId: claimFirstWallet.proposalId,
+    });
+
+    t.assertTrue(r2.type === ConfirmPayResultType.Done);
+
+    const bal1 = await firstWallet.call(WalletApiOperation.GetBalances, {});
+    t.assertAmountEquals(bal1.balances[0].available, "TESTKUDOS:16.18");
+
+    const bal2 = await secondWallet.call(WalletApiOperation.GetBalances, {});
+    t.assertAmountEquals(bal2.balances[0].available, "TESTKUDOS:14.23");
+
+    // Claim the order with the first wallet
+    const claimSecondWalletAgain = await secondWallet.call(
+      WalletApiOperation.PreparePayForUri,
+      { talerPayUri: order.uri },
+    );
+
+    t.assertTrue(
+      claimSecondWalletAgain.status === PreparePayResultType.AlreadyConfirmed,
+    );
+  }
+}
+
+runPaymentShareTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts 
b/packages/taler-harness/src/integrationtests/testrunner.ts
index 0f64ae94f..e35264d13 100644
--- a/packages/taler-harness/src/integrationtests/testrunner.ts
+++ b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -102,6 +102,7 @@ import { runWalletBalanceTest } from 
"./test-wallet-balance.js";
 import { runPaymentTemplateTest } from "./test-payment-template.js";
 import { runExchangeDepositTest } from "./test-exchange-deposit.js";
 import { runPeerRepairTest } from "./test-peer-repair.js";
+import { runPaymentShareTest } from "./test-payment-share.js";
 
 /**
  * Test runner.
@@ -166,6 +167,7 @@ const allTests: TestMainFunction[] = [
   runPaymentIdempotencyTest,
   runPaymentMultipleTest,
   runPaymentTest,
+  runPaymentShareTest,
   runPaymentTemplateTest,
   runPaymentAbortTest,
   runPaymentTransientTest,
diff --git a/packages/taler-util/src/backup-types.ts 
b/packages/taler-util/src/backup-types.ts
index 2bfe6b886..2920838dd 100644
--- a/packages/taler-util/src/backup-types.ts
+++ b/packages/taler-util/src/backup-types.ts
@@ -63,7 +63,11 @@
  * Imports.
  */
 import { DenominationPubKey, UnblindedSignature } from "./taler-types.js";
-import { TalerProtocolDuration, TalerProtocolTimestamp, TalerPreciseTimestamp 
} from "./time.js";
+import {
+  TalerProtocolDuration,
+  TalerProtocolTimestamp,
+  TalerPreciseTimestamp,
+} from "./time.js";
 
 export const BACKUP_TAG = "gnu-taler-wallet-backup-content" as const;
 /**
@@ -970,6 +974,8 @@ export interface BackupPurchase {
    * Continue querying the refund status until this deadline has expired.
    */
   auto_refund_deadline: TalerProtocolTimestamp | undefined;
+
+  shared: boolean;
 }
 
 /**
@@ -1268,6 +1274,11 @@ export enum BackupProposalStatus {
    * but the user needs to accept/reject it.
    */
   Proposed = "proposed",
+  /**
+   * Proposed, other wallet may also have
+   * the purchase
+   */
+  Shared = "shared",
   /**
    * The user has rejected the proposal.
    */
diff --git a/packages/taler-util/src/errors.ts 
b/packages/taler-util/src/errors.ts
index 8476b63fc..06e338a25 100644
--- a/packages/taler-util/src/errors.ts
+++ b/packages/taler-util/src/errors.ts
@@ -52,6 +52,9 @@ export interface DetailsMap {
     orderId: string;
     claimUrl: string;
   };
+  [TalerErrorCode.WALLET_ORDER_ALREADY_PAID]: {
+    orderId: string;
+  };
   [TalerErrorCode.WALLET_CONTRACT_TERMS_MALFORMED]: empty;
   [TalerErrorCode.WALLET_CONTRACT_TERMS_SIGNATURE_INVALID]: {
     merchantPub: string;
@@ -98,7 +101,7 @@ export interface DetailsMap {
   };
   [TalerErrorCode.WALLET_EXCHANGE_COIN_SIGNATURE_INVALID]: empty;
   [TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE]: {
-    numErrors: number,
+    numErrors: number;
     errorsPerCoin: Record<number, TalerErrorDetail>;
   };
   [TalerErrorCode.WALLET_CORE_NOT_AVAILABLE]: empty;
@@ -133,7 +136,7 @@ export interface DetailsMap {
   [TalerErrorCode.WALLET_EXCHANGE_BASE_URL_MISMATCH]: {
     urlWallet: string;
     urlExchange: string;
-  }
+  };
 }
 
 type ErrBody<Y> = Y extends keyof DetailsMap ? DetailsMap[Y] : empty;
diff --git a/packages/taler-util/src/taler-error-codes.ts 
b/packages/taler-util/src/taler-error-codes.ts
index 019129c20..fa1f4a86a 100644
--- a/packages/taler-util/src/taler-error-codes.ts
+++ b/packages/taler-util/src/taler-error-codes.ts
@@ -776,6 +776,14 @@ export enum TalerErrorCode {
   EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH = 1164,
 
 
+  /**
+   * The maximum age in the commitment is too large for the reserve
+   * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  EXCHANGE_AGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE = 1165,
+
+
   /**
    * The batch withdraw included a planchet that was already withdrawn. This 
is not allowed.
    * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
@@ -1328,6 +1336,14 @@ export enum TalerErrorCode {
   EXCHANGE_PURSE_DELETE_SIGNATURE_INVALID = 1680,
 
 
+  /**
+   * Withdrawal from the reserve requires age restriction to be set.
+   * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  EXCHANGE_RESERVES_AGE_RESTRICTION_REQUIRED = 1681,
+
+
   /**
    * The exchange failed to talk to the process responsible for its private 
denomination keys or the helpers had no denominations (properly) configured.
    * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
@@ -3568,6 +3584,14 @@ export enum TalerErrorCode {
   WALLET_EXCHANGE_BASE_URL_MISMATCH = 7030,
 
 
+  /**
+   * The order has already been paid by another wallet.
+   * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  WALLET_ORDER_ALREADY_PAID = 7031,
+
+
   /**
    * We encountered a timeout with our payment backend.
    * Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504).
diff --git a/packages/taler-util/src/wallet-types.ts 
b/packages/taler-util/src/wallet-types.ts
index 66b5e7262..7926cdd4f 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -684,7 +684,6 @@ export const codecForPreparePayResultPaymentPossible =
       .property("proposalId", codecForString())
       .property("contractTermsHash", codecForString())
       .property("talerUri", codecForString())
-      .property("noncePriv", codecForString())
       .property(
         "status",
         codecForConstString(PreparePayResultType.PaymentPossible),
@@ -757,7 +756,6 @@ export const codecForPreparePayResultInsufficientBalance =
       .property("talerUri", codecForString())
       .property("proposalId", codecForString())
       .property("transactionId", codecForTransactionIdStr())
-      .property("noncePriv", codecForString())
       .property(
         "status",
         codecForConstString(PreparePayResultType.InsufficientBalance),
@@ -775,7 +773,7 @@ export const codecForPreparePayResultAlreadyConfirmed =
         "status",
         codecForConstString(PreparePayResultType.AlreadyConfirmed),
       )
-      .property("amountEffective", codecForAmountString())
+      .property("amountEffective", codecOptional(codecForAmountString()))
       .property("amountRaw", codecForAmountString())
       .property("paid", codecForBoolean())
       .property("talerUri", codecOptional(codecForString()))
@@ -824,7 +822,6 @@ export interface PreparePayResultPaymentPossible {
   contractTermsHash: string;
   amountRaw: string;
   amountEffective: string;
-  noncePriv: string;
   talerUri: string;
 }
 
@@ -834,7 +831,6 @@ export interface PreparePayResultInsufficientBalance {
   proposalId: string;
   contractTerms: MerchantContractTerms;
   amountRaw: string;
-  noncePriv: string;
   talerUri: string;
   balanceDetails: PayMerchantInsufficientBalanceDetails;
 }
@@ -845,7 +841,7 @@ export interface PreparePayResultAlreadyConfirmed {
   contractTerms: MerchantContractTerms;
   paid: boolean;
   amountRaw: string;
-  amountEffective: string;
+  amountEffective: string | undefined;
   contractTermsHash: string;
   proposalId: string;
   talerUri?: string;
@@ -1724,6 +1720,24 @@ export const codecForPreparePayRequest = (): 
Codec<PreparePayRequest> =>
     .property("talerPayUri", codecForString())
     .build("PreparePay");
 
+export interface SharePaymentRequest {
+  merchantBaseUrl: string;
+  orderId: string;
+}
+export const codecForSharePaymentRequest = (): Codec<SharePaymentRequest> =>
+  buildCodecForObject<SharePaymentRequest>()
+    .property("merchantBaseUrl", codecForString())
+    .property("orderId", codecForString())
+    .build("SharePaymentRequest");
+
+export interface SharePaymentResult {
+  privatePayUri: string;
+}
+export const codecForSharePaymentResult = (): Codec<SharePaymentResult> =>
+  buildCodecForObject<SharePaymentResult>()
+    .property("privatePayUri", codecForString())
+    .build("SharePaymentResult");
+
 export interface PreparePayTemplateRequest {
   talerPayTemplateUri: string;
   templateParams: Record<string, string>;
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index c12d0f2f7..6a7a26f2f 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -1145,6 +1145,11 @@ export enum PurchaseStatus {
    * Proposal downloaded, but the user needs to accept/reject it.
    */
   DialogProposed = 30,
+  /**
+   * Proposal shared to other wallet or read from other wallet
+   * the user needs to accept/reject it.
+   */
+  DialogShared = 31,
 
   /**
    * The user has rejected the proposal.
@@ -1270,6 +1275,12 @@ export interface PurchaseRecord {
 
   posConfirmation: string | undefined;
 
+  /**
+   * This purchase was created by sharing nonce or
+   * did the wallet made the nonce public
+   */
+  shared: boolean;
+
   /**
    * When was the purchase record created?
    */
diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts 
b/packages/taler-wallet-core/src/operations/backup/export.ts
index 23c6e787a..21ba5dc37 100644
--- a/packages/taler-wallet-core/src/operations/backup/export.ts
+++ b/packages/taler-wallet-core/src/operations/backup/export.ts
@@ -422,6 +422,9 @@ export async function exportBackup(
           case PurchaseStatus.PendingPaying:
             propStatus = BackupProposalStatus.Proposed;
             break;
+          case PurchaseStatus.DialogShared:
+            propStatus = BackupProposalStatus.Shared;
+            break;
           case PurchaseStatus.FailedClaim:
           case PurchaseStatus.AbortedIncompletePayment:
             propStatus = BackupProposalStatus.PermanentlyFailed;
@@ -483,6 +486,7 @@ export async function exportBackup(
           repurchase_proposal_id: purch.repurchaseProposalId,
           download_session_id: purch.downloadSessionId,
           timestamp_proposed: purch.timestamp,
+          shared: purch.shared,
         });
       });
 
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts 
b/packages/taler-wallet-core/src/operations/backup/import.ts
index 7f73a14b0..b161aa8f2 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -62,7 +62,11 @@ import { InternalWalletState } from 
"../../internal-wallet-state.js";
 import { assertUnreachable } from "../../util/assertUnreachable.js";
 import { checkLogicInvariant } from "../../util/invariants.js";
 import { GetReadOnlyAccess, GetReadWriteAccess } from "../../util/query.js";
-import { constructTombstone, makeCoinAvailable, TombstoneTag } from 
"../common.js";
+import {
+  constructTombstone,
+  makeCoinAvailable,
+  TombstoneTag,
+} from "../common.js";
 import { getExchangeDetails } from "../exchanges.js";
 import { extractContractData } from "../pay-merchant.js";
 import { provideBackupState } from "./state.js";
@@ -576,6 +580,9 @@ export async function importBackup(
           case BackupProposalStatus.Paid:
             proposalStatus = PurchaseStatus.Done;
             break;
+          case BackupProposalStatus.Shared:
+            proposalStatus = PurchaseStatus.DialogShared;
+            break;
           case BackupProposalStatus.Proposed:
             proposalStatus = PurchaseStatus.DialogProposed;
             break;
@@ -702,6 +709,7 @@ export async function importBackup(
             repurchaseProposalId: backupPurchase.repurchase_proposal_id,
             purchaseStatus: proposalStatus,
             timestamp: backupPurchase.timestamp_proposed,
+            shared: backupPurchase.shared,
           });
         }
       }
diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts 
b/packages/taler-wallet-core/src/operations/pay-merchant.ts
index c74fcedcf..d53ee1b43 100644
--- a/packages/taler-wallet-core/src/operations/pay-merchant.ts
+++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts
@@ -61,7 +61,10 @@ import {
   PreparePayResultType,
   randomBytes,
   RefreshReason,
+  SharePaymentResult,
   StartRefundQueryForUriResponse,
+  stringifyPaytoUri,
+  stringifyPayUri,
   stringifyTalerUri,
   TalerError,
   TalerErrorCode,
@@ -542,7 +545,9 @@ async function processDownloadProposal(
         p.repurchaseProposalId = otherPurchase.proposalId;
         await tx.purchases.put(p);
       } else {
-        p.purchaseStatus = PurchaseStatus.DialogProposed;
+        p.purchaseStatus = p.shared
+          ? PurchaseStatus.DialogShared
+          : PurchaseStatus.DialogProposed;
         await tx.purchases.put(p);
       }
       const newTxState = computePayMerchantTransactionState(p);
@@ -570,15 +575,22 @@ async function createPurchase(
   claimToken: string | undefined,
   noncePriv: string | undefined,
 ): Promise<string> {
-  const oldProposal = await ws.db
+  const oldProposals = await ws.db
     .mktx((x) => [x.purchases])
     .runReadOnly(async (tx) => {
-      return tx.purchases.indexes.byUrlAndOrderId.get([
+      return tx.purchases.indexes.byUrlAndOrderId.getAll([
         merchantBaseUrl,
         orderId,
       ]);
     });
 
+  const oldProposal = oldProposals.find((p) => {
+    return (
+      p.downloadSessionId === sessionId &&
+      (!noncePriv || p.noncePriv === noncePriv) &&
+      p.claimToken === claimToken
+    );
+  });
   /* If we have already claimed this proposal with the same sessionId
    * nonce and claim token, reuse it. */
   if (
@@ -589,11 +601,42 @@ async function createPurchase(
   ) {
     // FIXME: This lacks proper error handling
     await processDownloadProposal(ws, oldProposal.proposalId);
+
+    if (oldProposal.purchaseStatus === PurchaseStatus.DialogShared) {
+      const download = await expectProposalDownload(ws, oldProposal);
+      const paid = await checkIfOrderIsAlreadyPaid(ws, download.contractData);
+      if (paid) {
+        //if this transaction was shared and the order is paid then it
+        //means that another wallet already paid the proposal
+        const transitionInfo = await ws.db
+          .mktx((x) => [x.purchases])
+          .runReadWrite(async (tx) => {
+            const p = await tx.purchases.get(oldProposal.proposalId);
+            if (!p) {
+              logger.warn("purchase does not exist anymore");
+              return;
+            }
+            const oldTxState = computePayMerchantTransactionState(p);
+            p.purchaseStatus = PurchaseStatus.FailedClaim;
+            const newTxState = computePayMerchantTransactionState(p);
+            await tx.purchases.put(p);
+            return { oldTxState, newTxState };
+          });
+
+        const transactionId = constructTransactionIdentifier({
+          tag: TransactionType.Payment,
+          proposalId: oldProposal.proposalId,
+        });
+        notifyTransition(ws, transactionId, transitionInfo);
+      }
+    }
     return oldProposal.proposalId;
   }
 
   let noncePair: EddsaKeypair;
+  let shared = false;
   if (noncePriv) {
+    shared = true;
     noncePair = {
       priv: noncePriv,
       pub: (await ws.cryptoApi.eddsaGetPublic({ priv: noncePriv })).pub,
@@ -627,19 +670,12 @@ async function createPurchase(
     timestampLastRefundStatus: undefined,
     pendingRemovedCoinPubs: undefined,
     posConfirmation: undefined,
+    shared: shared,
   };
 
   const transitionInfo = await ws.db
     .mktx((x) => [x.purchases])
     .runReadWrite(async (tx) => {
-      const existingRecord = await tx.purchases.indexes.byUrlAndOrderId.get([
-        merchantBaseUrl,
-        orderId,
-      ]);
-      if (existingRecord) {
-        // Created concurrently
-        return undefined;
-      }
       await tx.purchases.put(proposalRecord);
       const oldTxState: TransactionState = {
         major: TransactionMajorState.None,
@@ -983,7 +1019,11 @@ export async function checkPaymentByProposalId(
       return tx.purchases.get(proposalId);
     });
 
-  if (!purchase || purchase.purchaseStatus === PurchaseStatus.DialogProposed) {
+  if (
+    !purchase ||
+    purchase.purchaseStatus === PurchaseStatus.DialogProposed ||
+    purchase.purchaseStatus === PurchaseStatus.DialogShared
+  ) {
     // If not already paid, check if we could pay for it.
     const res = await selectPayCoinsNew(ws, {
       auditors: [],
@@ -1007,7 +1047,6 @@ export async function checkPaymentByProposalId(
         contractTerms: d.contractTermsRaw,
         proposalId: proposal.proposalId,
         transactionId,
-        noncePriv: proposal.noncePriv,
         amountRaw: Amounts.stringify(d.contractData.amount),
         talerUri,
         balanceDetails: res.insufficientBalanceDetails,
@@ -1023,7 +1062,6 @@ export async function checkPaymentByProposalId(
       contractTerms: d.contractTermsRaw,
       transactionId,
       proposalId: proposal.proposalId,
-      noncePriv: proposal.noncePriv,
       amountEffective: Amounts.stringify(totalCost),
       amountRaw: Amounts.stringify(res.coinSel.paymentAmount),
       contractTermsHash: d.contractData.contractTermsHash,
@@ -1067,7 +1105,9 @@ export async function checkPaymentByProposalId(
       contractTermsHash: download.contractData.contractTermsHash,
       paid: true,
       amountRaw: Amounts.stringify(download.contractData.amount),
-      amountEffective: Amounts.stringify(purchase.payInfo?.totalPayCost!),
+      amountEffective: purchase.payInfo
+        ? Amounts.stringify(purchase.payInfo.totalPayCost)
+        : undefined,
       transactionId,
       proposalId,
       talerUri,
@@ -1080,7 +1120,9 @@ export async function checkPaymentByProposalId(
       contractTermsHash: download.contractData.contractTermsHash,
       paid: false,
       amountRaw: Amounts.stringify(download.contractData.amount),
-      amountEffective: Amounts.stringify(purchase.payInfo?.totalPayCost!),
+      amountEffective: purchase.payInfo
+        ? Amounts.stringify(purchase.payInfo.totalPayCost)
+        : undefined,
       transactionId,
       proposalId,
       talerUri,
@@ -1097,7 +1139,9 @@ export async function checkPaymentByProposalId(
       contractTermsHash: download.contractData.contractTermsHash,
       paid,
       amountRaw: Amounts.stringify(download.contractData.amount),
-      amountEffective: Amounts.stringify(purchase.payInfo?.totalPayCost!),
+      amountEffective: purchase.payInfo
+        ? Amounts.stringify(purchase.payInfo.totalPayCost)
+        : undefined,
       ...(paid ? { nextUrl: download.contractData.orderId } : {}),
       transactionId,
       proposalId,
@@ -1406,6 +1450,7 @@ export async function confirmPay(
       }
       const oldTxState = computePayMerchantTransactionState(p);
       switch (p.purchaseStatus) {
+        case PurchaseStatus.DialogShared:
         case PurchaseStatus.DialogProposed:
           p.payInfo = {
             payCoinSelection: coinSelection,
@@ -1480,6 +1525,8 @@ export async function processPurchase(
       return processPurchaseAbortingRefund(ws, purchase);
     case PurchaseStatus.PendingAcceptRefund:
       return processPurchaseAcceptRefund(ws, purchase);
+    case PurchaseStatus.DialogShared:
+      return processPurchaseDialogShared(ws, purchase);
     case PurchaseStatus.FailedClaim:
     case PurchaseStatus.Done:
     case PurchaseStatus.RepurchaseDetected:
@@ -1540,6 +1587,41 @@ export async function processPurchasePay(
   checkDbInvariant(!!payInfo, "payInfo");
 
   const download = await expectProposalDownload(ws, purchase);
+
+  if (purchase.shared) {
+    const paid = await checkIfOrderIsAlreadyPaid(ws, download.contractData);
+
+    if (paid) {
+      const transitionInfo = await ws.db
+        .mktx((x) => [x.purchases])
+        .runReadWrite(async (tx) => {
+          const p = await tx.purchases.get(purchase.proposalId);
+          if (!p) {
+            logger.warn("purchase does not exist anymore");
+            return;
+          }
+          const oldTxState = computePayMerchantTransactionState(p);
+          p.purchaseStatus = PurchaseStatus.FailedClaim;
+          const newTxState = computePayMerchantTransactionState(p);
+          await tx.purchases.put(p);
+          return { oldTxState, newTxState };
+        });
+      const transactionId = constructTransactionIdentifier({
+        tag: TransactionType.Payment,
+        proposalId,
+      });
+
+      notifyTransition(ws, transactionId, transitionInfo);
+
+      return {
+        type: TaskRunResultType.Error,
+        errorDetail: makeErrorDetail(TalerErrorCode.WALLET_ORDER_ALREADY_PAID, 
{
+          orderId: purchase.orderId,
+        }),
+      };
+    }
+  }
+
   if (!purchase.merchantPaySig) {
     const payUrl = new URL(
       `orders/${download.contractData.orderId}/pay`,
@@ -1681,7 +1763,10 @@ export async function refuseProposal(
         logger.trace(`proposal ${proposalId} not found, won't refuse 
proposal`);
         return undefined;
       }
-      if (proposal.purchaseStatus !== PurchaseStatus.DialogProposed) {
+      if (
+        proposal.purchaseStatus !== PurchaseStatus.DialogProposed &&
+        proposal.purchaseStatus !== PurchaseStatus.DialogShared
+      ) {
         return undefined;
       }
       const oldTxState = computePayMerchantTransactionState(proposal);
@@ -1996,6 +2081,11 @@ export function computePayMerchantTransactionState(
         major: TransactionMajorState.Dialog,
         minor: TransactionMinorState.MerchantOrderProposed,
       };
+    case PurchaseStatus.DialogShared:
+      return {
+        major: TransactionMajorState.Dialog,
+        minor: TransactionMinorState.MerchantOrderProposed,
+      };
     // Final States
     case PurchaseStatus.AbortedProposalRefused:
       return {
@@ -2078,6 +2168,8 @@ export function computePayMerchantTransactionActions(
     // Dialog States
     case PurchaseStatus.DialogProposed:
       return [];
+    case PurchaseStatus.DialogShared:
+      return [];
     // Final States
     case PurchaseStatus.AbortedProposalRefused:
       return [TransactionAction.Delete];
@@ -2096,6 +2188,140 @@ export function computePayMerchantTransactionActions(
   }
 }
 
+export async function sharePayment(
+  ws: InternalWalletState,
+  merchantBaseUrl: string,
+  orderId: string,
+): Promise<SharePaymentResult> {
+  const result = await ws.db
+    .mktx((x) => [x.purchases])
+    .runReadWrite(async (tx) => {
+      const p = await tx.purchases.indexes.byUrlAndOrderId.get([
+        merchantBaseUrl,
+        orderId,
+      ]);
+      if (!p) {
+        logger.warn("purchase does not exist anymore");
+        return undefined;
+      }
+      if (
+        p.purchaseStatus !== PurchaseStatus.DialogProposed &&
+        p.purchaseStatus !== PurchaseStatus.DialogShared
+      ) {
+        //FIXME: purchase can be shared before being paid
+        return undefined;
+      }
+      if (p.purchaseStatus === PurchaseStatus.DialogProposed) {
+        p.purchaseStatus = PurchaseStatus.DialogShared;
+        p.shared = true;
+        tx.purchases.put(p);
+      }
+
+      return {
+        nonce: p.noncePriv,
+        session: p.lastSessionId,
+        token: p.claimToken,
+      };
+    });
+
+  if (result === undefined) {
+    throw Error("This purchase can't be shared");
+  }
+  const privatePayUri = stringifyPayUri({
+    merchantBaseUrl,
+    orderId,
+    sessionId: result.session ?? "",
+    noncePriv: result.nonce,
+    claimToken: result.token,
+  });
+  return { privatePayUri };
+}
+
+async function checkIfOrderIsAlreadyPaid(
+  ws: InternalWalletState,
+  contract: WalletContractData,
+) {
+  const requestUrl = new URL(
+    `orders/${contract.orderId}`,
+    contract.merchantBaseUrl,
+  );
+  requestUrl.searchParams.set("h_contract", contract.contractTermsHash);
+
+  requestUrl.searchParams.set("timeout_ms", "1000");
+
+  const resp = await ws.http.fetch(requestUrl.href);
+  if (
+    resp.status === HttpStatusCode.Ok ||
+    resp.status === HttpStatusCode.Accepted ||
+    resp.status === HttpStatusCode.Found
+  ) {
+    return true;
+  } else if (resp.status === HttpStatusCode.PaymentRequired) {
+    return false;
+  }
+  //forbidden, not found, not acceptable
+  throw Error(`this order cant be paid: ${resp.status}`);
+}
+
+async function processPurchaseDialogShared(
+  ws: InternalWalletState,
+  purchase: PurchaseRecord,
+): Promise<TaskRunResult> {
+  const proposalId = purchase.proposalId;
+  logger.trace(`processing dialog-shared for proposal ${proposalId}`);
+
+  const taskId = constructTaskIdentifier({
+    tag: PendingTaskType.Purchase,
+    proposalId,
+  });
+
+  // FIXME: Put this logic into runLongpollAsync?
+  if (ws.activeLongpoll[taskId]) {
+    return TaskRunResult.longpoll();
+  }
+  const download = await expectProposalDownload(ws, purchase);
+
+  if (purchase.purchaseStatus !== PurchaseStatus.DialogShared) {
+    return TaskRunResult.finished();
+  }
+
+  runLongpollAsync(ws, taskId, async (ct) => {
+    const paid = await checkIfOrderIsAlreadyPaid(ws, download.contractData);
+    if (paid) {
+      const transitionInfo = await ws.db
+        .mktx((x) => [x.purchases])
+        .runReadWrite(async (tx) => {
+          const p = await tx.purchases.get(purchase.proposalId);
+          if (!p) {
+            logger.warn("purchase does not exist anymore");
+            return;
+          }
+          const oldTxState = computePayMerchantTransactionState(p);
+          p.purchaseStatus = PurchaseStatus.FailedClaim;
+          const newTxState = computePayMerchantTransactionState(p);
+          await tx.purchases.put(p);
+          return { oldTxState, newTxState };
+        });
+      const transactionId = constructTransactionIdentifier({
+        tag: TransactionType.Payment,
+        proposalId,
+      });
+
+      notifyTransition(ws, transactionId, transitionInfo);
+
+      return {
+        ready: true,
+      };
+    }
+
+    return {
+      ready: false,
+    };
+  });
+
+  return TaskRunResult.longpoll();
+}
+
 async function processPurchaseAutoRefund(
   ws: InternalWalletState,
   purchase: PurchaseRecord,
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts 
b/packages/taler-wallet-core/src/wallet-api-types.ts
index cea548db6..e395237cf 100644
--- a/packages/taler-wallet-core/src/wallet-api-types.ts
+++ b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -112,6 +112,8 @@ import {
   WithdrawFakebankRequest,
   WithdrawTestBalanceRequest,
   WithdrawUriInfoResponse,
+  SharePaymentRequest,
+  SharePaymentResult,
 } from "@gnu-taler/taler-util";
 import { AuditorTrustRecord, WalletContractData } from "./db.js";
 import {
@@ -129,6 +131,7 @@ export enum WalletApiOperation {
   WithdrawTestkudos = "withdrawTestkudos",
   WithdrawTestBalance = "withdrawTestBalance",
   PreparePayForUri = "preparePayForUri",
+  SharePayment = "sharePayment",
   PreparePayForTemplate = "preparePayForTemplate",
   GetContractTermsDetails = "getContractTermsDetails",
   RunIntegrationTest = "runIntegrationTest",
@@ -458,6 +461,12 @@ export type PreparePayForUriOp = {
   response: PreparePayResult;
 };
 
+export type SharePaymentOp = {
+  op: WalletApiOperation.SharePayment;
+  request: SharePaymentRequest;
+  response: SharePaymentResult;
+};
+
 /**
  * Prepare to make a payment based on a taler://pay-template/ URI.
  */
@@ -984,6 +993,7 @@ export type WalletOperations = {
   [WalletApiOperation.GetVersion]: GetVersionOp;
   [WalletApiOperation.WithdrawFakebank]: WithdrawFakebankOp;
   [WalletApiOperation.PreparePayForUri]: PreparePayForUriOp;
+  [WalletApiOperation.SharePayment]: SharePaymentOp;
   [WalletApiOperation.PreparePayForTemplate]: PreparePayForTemplateOp;
   [WalletApiOperation.GetContractTermsDetails]: GetContractTermsDetailsOp;
   [WalletApiOperation.WithdrawTestkudos]: WithdrawTestkudosOp;
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 11030af2b..ca86cbb14 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -117,6 +117,7 @@ import {
   parsePaytoUri,
   sampleWalletCoreTransactions,
   validateIban,
+  codecForSharePaymentRequest,
 } from "@gnu-taler/taler-util";
 import {
   HttpRequestLibrary,
@@ -203,6 +204,7 @@ import {
   getContractTermsDetails,
   preparePayForUri,
   processPurchase,
+  sharePayment,
   startQueryRefund,
   startRefundQueryForUri,
 } from "./operations/pay-merchant.js";
@@ -1207,6 +1209,11 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
       await runPending(ws);
       return {};
     }
+    case WalletApiOperation.SharePayment: {
+      const req = codecForSharePaymentRequest().decode(payload);
+      return await sharePayment(ws, req.merchantBaseUrl, req.orderId);
+    }
+
     case WalletApiOperation.PreparePayForUri: {
       const req = codecForPreparePayRequest().decode(payload);
       return await preparePayForUri(ws, req.talerPayUri);
diff --git 
a/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx 
b/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx
index d85a2c78e..8cb1c49dd 100644
--- a/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx
+++ b/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx
@@ -21,6 +21,8 @@ import {
   PreparePayResult,
   PreparePayResultType,
   TranslatedString,
+  parsePayUri,
+  stringifyPayUri,
 } from "@gnu-taler/taler-util";
 import { Fragment, h, VNode } from "preact";
 import { useState } from "preact/hooks";
@@ -32,6 +34,8 @@ import { useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { Button } from "../mui/Button.js";
 import { ButtonHandler } from "../mui/handlers.js";
 import { assertUnreachable } from "../utils/index.js";
+import { useBackendContext } from "../context/backend.js";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
 
 interface Props {
   payStatus: PreparePayResult;
@@ -50,8 +54,6 @@ export function PaymentButtons({
 }: Props): VNode {
   const { i18n } = useTranslationContext();
   if (payStatus.status === PreparePayResultType.PaymentPossible) {
-    const privateUri = `${uri}&n=${payStatus.noncePriv}`;
-
     return (
       <Fragment>
         <section>
@@ -66,7 +68,7 @@ export function PaymentButtons({
             </i18n.Translate>
           </Button>
         </section>
-        <PayWithMobile uri={privateUri} />
+        <PayWithMobile uri={uri} />
       </Fragment>
     );
   }
@@ -125,7 +127,6 @@ export function PaymentButtons({
       default:
         assertUnreachable(reason);
     }
-    const uriPrivate = `${uri}&n=${payStatus.noncePriv}`;
 
     return (
       <Fragment>
@@ -141,7 +142,7 @@ export function PaymentButtons({
             <i18n.Translate>Get digital cash</i18n.Translate>
           </Button>
         </section>
-        <PayWithMobile uri={uriPrivate} />
+        <PayWithMobile uri={uri} />
       </Fragment>
     );
   }
@@ -159,7 +160,6 @@ export function PaymentButtons({
             />
           )}
         </section>
-        {!payStatus.paid && <PayWithMobile uri={uri} />}
       </Fragment>
     );
   }
@@ -169,20 +169,36 @@ export function PaymentButtons({
 
 function PayWithMobile({ uri }: { uri: string }): VNode {
   const { i18n } = useTranslationContext();
+  const api = useBackendContext();
 
-  const [showQR, setShowQR] = useState<boolean>(false);
+  const payUri = parsePayUri(uri);
 
+  const [showQR, setShowQR] = useState<string | undefined>(undefined);
+  async function sharePrivatePaymentURI() {
+    if (!payUri) {
+      return;
+    }
+    if (!showQR) {
+      const result = await api.wallet.call(WalletApiOperation.SharePayment, {
+        merchantBaseUrl: payUri.merchantBaseUrl,
+        orderId: payUri.orderId,
+      });
+      setShowQR(result.privatePayUri);
+    } else {
+      setShowQR(undefined);
+    }
+  }
   return (
     <section>
-      <LinkSuccess upperCased onClick={() => setShowQR((qr) => !qr)}>
+      <LinkSuccess upperCased onClick={sharePrivatePaymentURI}>
         {!showQR ? i18n.str`Pay with a mobile phone` : i18n.str`Hide QR`}
       </LinkSuccess>
       {showQR && (
         <div>
-          <QR text={uri} />
+          <QR text={showQR} />
           <i18n.Translate>
             Scan the QR code or &nbsp;
-            <a href={uri}>
+            <a href={showQR}>
               <i18n.Translate>click here</i18n.Translate>
             </a>
           </i18n.Translate>
diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts 
b/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts
index c6512cd12..098f18921 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts
+++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts
@@ -102,7 +102,6 @@ export function useComponentState({
     contractTermsHash: "asd",
     amountRaw: hook.response.p2p.amount,
     amountEffective: hook.response.p2p.amount,
-    noncePriv: "",
   } as PreparePayResult;
 
   const insufficientBalance: PreparePayResult = {
diff --git a/packages/taler-wallet-webextension/src/cta/Payment/stories.tsx 
b/packages/taler-wallet-webextension/src/cta/Payment/stories.tsx
index 6572d1fd3..f030fa219 100644
--- a/packages/taler-wallet-webextension/src/cta/Payment/stories.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Payment/stories.tsx
@@ -61,7 +61,7 @@ export const NoEnoughBalanceAvailable = 
tests.createExample(BaseView, {
       feeGapEstimate: "USD:1",
     },
     talerUri: "taler://pay/..",
-    noncePriv: "",
+
     proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0",
     contractTerms: {
       merchant: {
@@ -101,7 +101,7 @@ export const NoEnoughBalanceMaterial = 
tests.createExample(BaseView, {
       feeGapEstimate: "USD:1",
     },
     talerUri: "taler://pay/..",
-    noncePriv: "",
+
     proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0",
     contractTerms: {
       merchant: {
@@ -141,7 +141,7 @@ export const NoEnoughBalanceAgeAcceptable = 
tests.createExample(BaseView, {
       feeGapEstimate: "USD:1",
     },
     talerUri: "taler://pay/..",
-    noncePriv: "",
+
     proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0",
     contractTerms: {
       merchant: {
@@ -182,7 +182,7 @@ export const NoEnoughBalanceMerchantAcceptable = 
tests.createExample(BaseView, {
       feeGapEstimate: "USD:1",
     },
     talerUri: "taler://pay/..",
-    noncePriv: "",
+
     proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0",
     contractTerms: {
       merchant: {
@@ -224,7 +224,7 @@ export const NoEnoughBalanceMerchantDepositable = 
tests.createExample(
         feeGapEstimate: "USD:1",
       },
       talerUri: "taler://pay/..",
-      noncePriv: "",
+
       proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0",
       contractTerms: {
         merchant: {
@@ -265,7 +265,7 @@ export const NoEnoughBalanceFeeGap = 
tests.createExample(BaseView, {
       feeGapEstimate: "USD:1",
     },
     talerUri: "taler://pay/..",
-    noncePriv: "",
+
     proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0",
     contractTerms: {
       merchant: {
@@ -302,7 +302,7 @@ export const PaymentPossible = 
tests.createExample(BaseView, {
     talerUri: "taler://pay/..",
     amountEffective: "USD:10",
     amountRaw: "USD:10",
-    noncePriv: "",
+
     contractTerms: {
       nonce: "123213123",
       merchant: {
@@ -342,7 +342,7 @@ export const PaymentPossibleWithFee = 
tests.createExample(BaseView, {
     talerUri: "taler://pay/..",
     amountEffective: "USD:10.20",
     amountRaw: "USD:10",
-    noncePriv: "",
+
     contractTerms: {
       nonce: "123213123",
       merchant: {
@@ -379,7 +379,7 @@ export const TicketWithAProductList = 
tests.createExample(BaseView, {
     talerUri: "taler://pay/..",
     amountEffective: "USD:10.20",
     amountRaw: "USD:10",
-    noncePriv: "",
+
     contractTerms: {
       nonce: "123213123",
       merchant: {
@@ -435,7 +435,7 @@ export const TicketWithShipping = 
tests.createExample(BaseView, {
     talerUri: "taler://pay/..",
     amountEffective: "USD:10.20",
     amountRaw: "USD:10",
-    noncePriv: "",
+
     contractTerms: {
       nonce: "123213123",
       merchant: {
diff --git a/packages/taler-wallet-webextension/src/cta/Payment/views.tsx 
b/packages/taler-wallet-webextension/src/cta/Payment/views.tsx
index b0911cb2f..0bdadef0e 100644
--- a/packages/taler-wallet-webextension/src/cta/Payment/views.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Payment/views.tsx
@@ -47,7 +47,9 @@ export function BaseView(state: SupportedStates): VNode {
 
   const effective =
     "amountEffective" in state.payStatus
-      ? Amounts.parseOrThrow(state.payStatus.amountEffective)
+      ? state.payStatus.amountEffective
+        ? Amounts.parseOrThrow(state.payStatus.amountEffective)
+        : Amounts.zeroOfCurrency(state.amount.currency)
       : state.amount;
 
   return (
diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx 
b/packages/taler-wallet-webextension/src/wallet/Settings.tsx
index 10bcee314..4098fd55b 100644
--- a/packages/taler-wallet-webextension/src/wallet/Settings.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Settings.tsx
@@ -210,17 +210,6 @@ export function SettingsView({
           </LinkPrimary>
         </div>
 
-        <Part
-          title={i18n.str`Web Extension`}
-          text={
-            <span>
-              {webexVersion.version}{" "}
-              <EnabledBySettings name="advanceMode">
-                {webexVersion.hash}
-              </EnabledBySettings>
-            </span>
-          }
-        />
         {coreVersion && (
           <EnabledBySettings name="advanceMode">
             <Part
@@ -283,19 +272,17 @@ export function SettingsView({
         <SubTitle>
           <i18n.Translate>Version</i18n.Translate>
         </SubTitle>
-        {coreVersion && (
-          <Part
-            title={i18n.str`Wallet Core`}
-            text={
-              <span>
-                {coreVersion.version}{" "}
-                <EnabledBySettings name="advanceMode">
-                  {coreVersion.hash}
-                </EnabledBySettings>
-              </span>
-            }
-          />
-        )}
+        <Part
+          title={i18n.str`Web Extension`}
+          text={
+            <span>
+              {webexVersion.version}{" "}
+              <EnabledBySettings name="advanceMode">
+                {webexVersion.hash}
+              </EnabledBySettings>
+            </span>
+          }
+        />
       </section>
     </Fragment>
   );

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