gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (8e8bada64 -> bd57fa46a)


From: gnunet
Subject: [taler-wallet-core] branch master updated (8e8bada64 -> bd57fa46a)
Date: Sun, 15 Jan 2023 21:50:11 +0100

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

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

    from 8e8bada64 fix deposit navigation
     new bfc7b2010 TrackTransaction interface
     new e034f1045 removing merchantPub from signature
     new fc38d0da9 query transaction status for deposit
     new bd57fa46a show deposit transaction info

The 4 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-util/src/taler-types.ts             |  63 ++++++
 packages/taler-util/src/transactions-types.ts      |   8 +-
 packages/taler-util/src/wallet-types.ts            |  17 +-
 .../src/crypto/cryptoImplementation.ts             |   1 -
 packages/taler-wallet-core/src/db.ts               |  23 +++
 .../taler-wallet-core/src/operations/deposits.ts   | 200 ++++++++++++-------
 .../src/operations/transactions.ts                 |   8 +
 .../src/wallet/DepositPage/state.ts                | 222 ++++++++++-----------
 .../src/wallet/DepositPage/test.ts                 |  23 ++-
 .../src/wallet/Transaction.tsx                     |  52 +++--
 10 files changed, 402 insertions(+), 215 deletions(-)

diff --git a/packages/taler-util/src/taler-types.ts 
b/packages/taler-util/src/taler-types.ts
index 9251868e6..8b680bdd9 100644
--- a/packages/taler-util/src/taler-types.ts
+++ b/packages/taler-util/src/taler-types.ts
@@ -1807,6 +1807,69 @@ export const codecForDepositSuccess = (): 
Codec<DepositSuccess> =>
     .property("transaction_base_url", codecOptional(codecForString()))
     .build("DepositSuccess");
 
+export interface TrackTransactionWired {
+  // Raw wire transfer identifier of the deposit.
+  wtid: Base32String;
+
+  // When was the wire transfer given to the bank.
+  execution_time: TalerProtocolTimestamp;
+
+  // The contribution of this coin to the total (without fees)
+  coin_contribution: AmountString;
+
+  // Binary-only Signature_ with purpose TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE
+  // over a TALER_ConfirmWirePS
+  // whereby the exchange affirms the successful wire transfer.
+  exchange_sig: EddsaSignatureString;
+
+  // Public EdDSA key of the exchange that was used to generate the signature.
+  // Should match one of the exchange's signing keys from /keys.  Again given
+  // explicitly as the client might otherwise be confused by clock skew as to
+  // which signing key was used.
+  exchange_pub: EddsaPublicKeyString;
+}
+
+export const codecForTackTransactionWired = (): Codec<TrackTransactionWired> =>
+  buildCodecForObject<TrackTransactionWired>()
+    .property("wtid", codecForString())
+    .property("execution_time", codecForTimestamp)
+    .property("coin_contribution", codecForAmountString())
+    .property("exchange_sig", codecForString())
+    .property("exchange_pub", codecForString())
+    .build("TackTransactionWired");
+
+interface TrackTransactionAccepted {
+  // Legitimization target that the merchant should
+  // use to check for its KYC status using
+  // the /kyc-check/$REQUIREMENT_ROW/... endpoint.
+  // Optional, not present if the deposit has not
+  // yet been aggregated to the point that a KYC
+  // need has been evaluated.
+  requirement_row?: number;
+
+  // True if the KYC check for the merchant has been
+  // satisfied.  False does not mean that KYC
+  // is strictly needed, unless also a
+  // legitimization_uuid is provided.
+  kyc_ok: boolean;
+
+  // Time by which the exchange currently thinks the deposit will be executed.
+  // Actual execution may be later if the KYC check is not satisfied by then.
+  execution_time: TalerProtocolTimestamp;
+}
+
+export const codecForTackTransactionAccepted =
+  (): Codec<TrackTransactionAccepted> =>
+    buildCodecForObject<TrackTransactionAccepted>()
+      .property("requirement_row", codecOptional(codecForNumber()))
+      .property("kyc_ok", codecForBoolean())
+      .property("execution_time", codecForTimestamp)
+      .build("TackTransactionAccepted");
+
+export type TrackTransaction =
+  | ({ type: "accepted" } & TrackTransactionAccepted)
+  | ({ type: "wired" } & TrackTransactionWired);
+
 export interface PurseDeposit {
   /**
    * Amount to be deposited, can be a fraction of the
diff --git a/packages/taler-util/src/transactions-types.ts 
b/packages/taler-util/src/transactions-types.ts
index 3678dfa86..e81625a5a 100644
--- a/packages/taler-util/src/transactions-types.ts
+++ b/packages/taler-util/src/transactions-types.ts
@@ -95,7 +95,7 @@ export interface TransactionCommon {
    * true if the transaction is still pending, false otherwise
    * If a transaction is not longer pending, its timestamp will be updated,
    * but its transactionId will remain unchanged
-   * 
+   *
    * @deprecated show extendedStatus
    */
   pending: boolean;
@@ -103,7 +103,7 @@ export interface TransactionCommon {
   /**
    * True if the transaction encountered a problem that might be
    * permanent.  A frozen transaction won't be automatically retried.
-   * 
+   *
    * @deprecated show extendedStatus
    */
   frozen: boolean;
@@ -351,7 +351,7 @@ export interface TransactionPayment extends 
TransactionCommon {
 
   /**
    * How far did the wallet get with processing the payment?
-   * 
+   *
    * @deprecated use extendedStatus
    */
   status: PaymentStatus;
@@ -548,6 +548,8 @@ export interface TransactionDeposit extends 
TransactionCommon {
   amountEffective: AmountString;
 
   wireTransferDeadline: TalerProtocolTimestamp;
+
+  wireTransferProgress: number;
 }
 
 export interface TransactionByIdRequest {
diff --git a/packages/taler-util/src/wallet-types.ts 
b/packages/taler-util/src/wallet-types.ts
index c32bb94e5..af775a54e 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -63,6 +63,7 @@ import {
   ExchangeAuditor,
   UnblindedSignature,
   codecForPeerContractTerms,
+  TrackTransaction,
 } from "./taler-types.js";
 import {
   AbsoluteTime,
@@ -1624,12 +1625,11 @@ export interface AbortTransactionRequest {
   forceImmediateAbort?: boolean;
 }
 
-export const codecForAbortTransaction =
-  (): Codec<AbortTransactionRequest> =>
-    buildCodecForObject<AbortTransactionRequest>()
-      .property("transactionId", codecForString())
-      .property("forceImmediateAbort", codecOptional(codecForBoolean()))
-      .build("AbortTransactionRequest");
+export const codecForAbortTransaction = (): Codec<AbortTransactionRequest> =>
+  buildCodecForObject<AbortTransactionRequest>()
+    .property("transactionId", codecForString())
+    .property("forceImmediateAbort", codecOptional(codecForBoolean()))
+    .build("AbortTransactionRequest");
 
 export interface GetFeeForDepositRequest {
   depositPaytoUri: string;
@@ -1685,10 +1685,7 @@ export interface TrackDepositGroupRequest {
 }
 
 export interface TrackDepositGroupResponse {
-  responses: {
-    status: number;
-    body: any;
-  }[];
+  responses: TrackTransaction[];
 }
 
 export const codecForTrackDepositGroupRequest =
diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts 
b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
index 316755840..52d2dd24e 100644
--- a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
+++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
@@ -803,7 +803,6 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
     const p = buildSigPS(TalerSignaturePurpose.MERCHANT_TRACK_TRANSACTION)
       .put(decodeCrock(req.contractTermsHash))
       .put(decodeCrock(req.wireHash))
-      .put(decodeCrock(req.merchantPub))
       .put(decodeCrock(req.coinPub))
       .build();
     return { sig: encodeCrock(eddsaSign(p, decodeCrock(req.merchantPriv))) };
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index adf704bc4..e6131334c 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -850,6 +850,13 @@ export enum RefreshOperationStatus {
   FinishedWithError = 51 /* DORMANT_START + 1 */,
 }
 
+export enum TransactionStatus {
+  Unknown = 10,
+  Accepted = 20,
+  KycRequired = 30,
+  Wired = 40,
+}
+
 /**
  * Additional information about the reason of a refresh.
  */
@@ -1652,6 +1659,8 @@ export interface DepositGroupRecord {
   timestampFinished: TalerProtocolTimestamp | undefined;
 
   operationStatus: OperationStatus;
+
+  transactionPerCoin: TransactionStatus[];
 }
 
 /**
@@ -2416,6 +2425,20 @@ export const walletDbFixups: FixupDescription[] = [
       });
     },
   },
+  {
+    name: "DepositGroupRecord_transactionPerCoin",
+    async fn(tx): Promise<void> {
+      await tx.depositGroups.iter().forEachAsync(async (dg) => {
+        if (dg.transactionPerCoin) {
+          return;
+        }
+        dg.transactionPerCoin = dg.depositedPerCoin.map(
+          (c) => TransactionStatus.Unknown,
+        );
+        await tx.depositGroups.put(dg);
+      });
+    },
+  },
 ];
 
 const logger = new Logger("db.ts");
diff --git a/packages/taler-wallet-core/src/operations/deposits.ts 
b/packages/taler-wallet-core/src/operations/deposits.ts
index 649621948..b529e5ead 100644
--- a/packages/taler-wallet-core/src/operations/deposits.ts
+++ b/packages/taler-wallet-core/src/operations/deposits.ts
@@ -24,7 +24,9 @@ import {
   CancellationToken,
   canonicalJson,
   codecForDepositSuccess,
-  MerchantContractTerms,
+  codecForTackTransactionAccepted,
+  codecForTackTransactionWired,
+  CoinDepositPermission,
   CreateDepositGroupRequest,
   CreateDepositGroupResponse,
   DepositGroupFees,
@@ -34,23 +36,27 @@ import {
   GetFeeForDepositRequest,
   getRandomBytes,
   hashWire,
+  HttpStatusCode,
   Logger,
+  MerchantContractTerms,
   parsePaytoUri,
   PayCoinSelection,
   PrepareDepositRequest,
   PrepareDepositResponse,
   RefreshReason,
+  TalerErrorCode,
   TalerProtocolTimestamp,
   TrackDepositGroupRequest,
   TrackDepositGroupResponse,
+  TrackTransaction,
   TransactionType,
   URL,
-  TalerErrorCode,
 } from "@gnu-taler/taler-util";
 import {
   DenominationRecord,
   DepositGroupRecord,
   OperationStatus,
+  TransactionStatus,
 } from "../db.js";
 import { TalerError } from "../errors.js";
 import { InternalWalletState } from "../internal-wallet-state.js";
@@ -111,43 +117,60 @@ export async function processDepositGroup(
   );
 
   for (let i = 0; i < depositPermissions.length; i++) {
-    if (depositGroup.depositedPerCoin[i]) {
-      continue;
-    }
     const perm = depositPermissions[i];
-    const requestBody: ExchangeDepositRequest = {
-      contribution: Amounts.stringify(perm.contribution),
-      merchant_payto_uri: depositGroup.wire.payto_uri,
-      wire_salt: depositGroup.wire.salt,
-      h_contract_terms: depositGroup.contractTermsHash,
-      ub_sig: perm.ub_sig,
-      timestamp: depositGroup.contractTermsRaw.timestamp,
-      wire_transfer_deadline:
-        depositGroup.contractTermsRaw.wire_transfer_deadline,
-      refund_deadline: depositGroup.contractTermsRaw.refund_deadline,
-      coin_sig: perm.coin_sig,
-      denom_pub_hash: perm.h_denom,
-      merchant_pub: depositGroup.merchantPub,
-      h_age_commitment: perm.h_age_commitment,
-    };
-    // Check for cancellation before making network request.
-    options.cancellationToken?.throwIfCancelled();
-    const url = new URL(`coins/${perm.coin_pub}/deposit`, perm.exchange_url);
-    logger.info(`depositing to ${url}`);
-    const httpResp = await ws.http.postJson(url.href, requestBody, {
-      cancellationToken: options.cancellationToken,
-    });
-    await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess());
-    await ws.db
-      .mktx((x) => [x.depositGroups])
-      .runReadWrite(async (tx) => {
-        const dg = await tx.depositGroups.get(depositGroupId);
-        if (!dg) {
-          return;
-        }
-        dg.depositedPerCoin[i] = true;
-        await tx.depositGroups.put(dg);
+
+    let updatedDeposit: boolean | undefined = undefined;
+    let updatedTxStatus: TransactionStatus | undefined = undefined;
+
+    if (!depositGroup.depositedPerCoin[i]) {
+      const requestBody: ExchangeDepositRequest = {
+        contribution: Amounts.stringify(perm.contribution),
+        merchant_payto_uri: depositGroup.wire.payto_uri,
+        wire_salt: depositGroup.wire.salt,
+        h_contract_terms: depositGroup.contractTermsHash,
+        ub_sig: perm.ub_sig,
+        timestamp: depositGroup.contractTermsRaw.timestamp,
+        wire_transfer_deadline:
+          depositGroup.contractTermsRaw.wire_transfer_deadline,
+        refund_deadline: depositGroup.contractTermsRaw.refund_deadline,
+        coin_sig: perm.coin_sig,
+        denom_pub_hash: perm.h_denom,
+        merchant_pub: depositGroup.merchantPub,
+        h_age_commitment: perm.h_age_commitment,
+      };
+      // Check for cancellation before making network request.
+      options.cancellationToken?.throwIfCancelled();
+      const url = new URL(`coins/${perm.coin_pub}/deposit`, perm.exchange_url);
+      logger.info(`depositing to ${url}`);
+      const httpResp = await ws.http.postJson(url.href, requestBody, {
+        cancellationToken: options.cancellationToken,
       });
+      await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess());
+      updatedDeposit = true;
+    }
+
+    if (depositGroup.transactionPerCoin[i] !== TransactionStatus.Wired) {
+      const track = await trackDepositPermission(ws, depositGroup, perm);
+      updatedTxStatus = txStatusFromTrack(track);
+    }
+
+    if (updatedTxStatus !== undefined || updatedDeposit !== undefined) {
+      await ws.db
+        .mktx((x) => [x.depositGroups])
+        .runReadWrite(async (tx) => {
+          const dg = await tx.depositGroups.get(depositGroupId);
+          if (!dg) {
+            return;
+          }
+          if (updatedDeposit !== undefined) {
+            dg.depositedPerCoin[i] = updatedDeposit;
+          }
+          if (updatedTxStatus !== undefined) {
+            dg.transactionPerCoin[i] = updatedTxStatus;
+          }
+          await tx.depositGroups.put(dg);
+        });
+    }
   }
 
   await ws.db
@@ -157,13 +180,17 @@ export async function processDepositGroup(
       if (!dg) {
         return;
       }
-      let allDeposited = true;
-      for (const d of depositGroup.depositedPerCoin) {
-        if (!d) {
-          allDeposited = false;
+      let allDepositedAndWired = true;
+      for (let i = 0; i < depositGroup.depositedPerCoin.length; i++) {
+        if (
+          !depositGroup.depositedPerCoin[i] ||
+          depositGroup.transactionPerCoin[i] !== TransactionStatus.Wired
+        ) {
+          allDepositedAndWired = false;
+          break;
         }
       }
-      if (allDeposited) {
+      if (allDepositedAndWired) {
         dg.timestampFinished = TalerProtocolTimestamp.now();
         dg.operationStatus = OperationStatus.Finished;
         await tx.depositGroups.put(dg);
@@ -172,14 +199,24 @@ export async function processDepositGroup(
   return OperationAttemptResult.finishedEmpty();
 }
 
+function txStatusFromTrack(t: TrackTransaction): TransactionStatus {
+  if (t.type === "accepted") {
+    if (!t.kyc_ok && t.requirement_row !== undefined) {
+      return TransactionStatus.KycRequired;
+    }
+    return TransactionStatus.Accepted;
+  }
+  if (t.type === "wired") {
+    return TransactionStatus.Wired;
+  }
+  return TransactionStatus.Unknown;
+}
+
 export async function trackDepositGroup(
   ws: InternalWalletState,
   req: TrackDepositGroupRequest,
 ): Promise<TrackDepositGroupResponse> {
-  const responses: {
-    status: number;
-    body: any;
-  }[] = [];
+  const responses: TrackTransaction[] = [];
   const depositGroup = await ws.db
     .mktx((x) => [x.depositGroups])
     .runReadOnly(async (tx) => {
@@ -200,31 +237,55 @@ export async function trackDepositGroup(
     contractData,
   );
 
+  for (const dp of depositPermissions) {
+    const track = await trackDepositPermission(ws, depositGroup, dp);
+    responses.push(track);
+  }
+
+  return { responses };
+}
+
+async function trackDepositPermission(
+  ws: InternalWalletState,
+  depositGroup: DepositGroupRecord,
+  dp: CoinDepositPermission,
+): Promise<TrackTransaction> {
   const wireHash = depositGroup.contractTermsRaw.h_wire;
 
-  for (const dp of depositPermissions) {
-    const url = new URL(
-      
`deposits/${wireHash}/${depositGroup.merchantPub}/${depositGroup.contractTermsHash}/${dp.coin_pub}`,
-      dp.exchange_url,
-    );
-    const sigResp = await ws.cryptoApi.signTrackTransaction({
-      coinPub: dp.coin_pub,
-      contractTermsHash: depositGroup.contractTermsHash,
-      merchantPriv: depositGroup.merchantPriv,
-      merchantPub: depositGroup.merchantPub,
-      wireHash,
-    });
-    url.searchParams.set("merchant_sig", sigResp.sig);
-    const httpResp = await ws.http.get(url.href);
-    const body = await httpResp.json();
-    responses.push({
-      body,
-      status: httpResp.status,
-    });
+  const url = new URL(
+    
`deposits/${wireHash}/${depositGroup.merchantPub}/${depositGroup.contractTermsHash}/${dp.coin_pub}`,
+    dp.exchange_url,
+  );
+  const sigResp = await ws.cryptoApi.signTrackTransaction({
+    coinPub: dp.coin_pub,
+    contractTermsHash: depositGroup.contractTermsHash,
+    merchantPriv: depositGroup.merchantPriv,
+    merchantPub: depositGroup.merchantPub,
+    wireHash,
+  });
+  url.searchParams.set("merchant_sig", sigResp.sig);
+  const httpResp = await ws.http.get(url.href);
+  switch (httpResp.status) {
+    case HttpStatusCode.Accepted: {
+      const accepted = await readSuccessResponseJsonOrThrow(
+        httpResp,
+        codecForTackTransactionAccepted(),
+      );
+      return { type: "accepted", ...accepted };
+    }
+    case HttpStatusCode.Ok: {
+      const wired = await readSuccessResponseJsonOrThrow(
+        httpResp,
+        codecForTackTransactionWired(),
+      );
+      return { type: "wired", ...wired };
+    }
+    default: {
+      throw Error(
+        `unexpected response from track-transaction (${httpResp.status})`,
+      );
+    }
   }
-  return {
-    responses,
-  };
 }
 
 export async function getFeeForDeposit(
@@ -491,6 +552,9 @@ export async function createDepositGroup(
     noncePub: noncePair.pub,
     timestampCreated: AbsoluteTime.toTimestamp(now),
     timestampFinished: undefined,
+    transactionPerCoin: payCoinSel.coinSel.coinPubs.map(
+      () => TransactionStatus.Unknown,
+    ),
     payCoinSelection: payCoinSel.coinSel,
     payCoinSelectionUid: encodeCrock(getRandomBytes(32)),
     depositedPerCoin: payCoinSel.coinSel.coinPubs.map(() => false),
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts 
b/packages/taler-wallet-core/src/operations/transactions.ts
index a702fab2f..0e86c77ed 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -53,6 +53,7 @@ import {
   WalletContractData,
   PeerPushPaymentInitiationStatus,
   PeerPullPaymentIncomingStatus,
+  TransactionStatus,
 } from "../db.js";
 import { InternalWalletState } from "../internal-wallet-state.js";
 import { assertUnreachable } from "../util/assertUnreachable.js";
@@ -552,6 +553,13 @@ function buildTransactionForDeposit(
       TransactionType.Deposit,
       dg.depositGroupId,
     ),
+    wireTransferProgress:
+      (100 *
+        dg.transactionPerCoin.reduce(
+          (prev, cur) => prev + (cur === TransactionStatus.Wired ? 1 : 0),
+          0,
+        )) /
+      dg.transactionPerCoin.length,
     depositGroupId: dg.depositGroupId,
     ...(ort?.lastError ? { error: ort.lastError } : {}),
   };
diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts 
b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts
index 1b628047a..d7d9f2da7 100644
--- a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts
+++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts
@@ -29,13 +29,14 @@ import { alertFromError, useAlertContext } from 
"../../context/alert.js";
 import { useBackendContext } from "../../context/backend.js";
 import { useTranslationContext } from "../../context/translation.js";
 import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
+import { RecursiveState } from "../../utils/index.js";
 import { Props, State } from "./index.js";
 
 export function useComponentState({
   amount: amountStr,
   onCancel,
   onSuccess,
-}: Props): State {
+}: Props): RecursiveState<State> {
   const api = useBackendContext();
   const { i18n } = useTranslationContext();
   const { pushAlertOnError } = useAlertContext();
@@ -49,9 +50,7 @@ export function useComponentState({
     );
     const { accounts } = await api.wallet.call(
       WalletApiOperation.ListKnownBankAccounts,
-      {
-        currency,
-      },
+      { currency },
     );
 
     return { accounts, balances };
@@ -61,13 +60,11 @@ export function useComponentState({
     parsed !== undefined
       ? parsed
       : currency !== undefined
-      ? Amounts.zeroOfCurrency(currency)
-      : undefined;
+        ? Amounts.zeroOfCurrency(currency)
+        : undefined;
   // const [accountIdx, setAccountIdx] = useState<number>(0);
-  const [amount, setAmount] = useState<AmountJson>(initialValue ?? ({} as 
any));
   const [selectedAccount, setSelectedAccount] = useState<PaytoUri>();
 
-  const [fee, setFee] = useState<DepositGroupFees | undefined>(undefined);
   const [addingAccount, setAddingAccount] = useState(false);
 
   if (!currency) {
@@ -91,7 +88,12 @@ export function useComponentState({
   }
   const { accounts, balances } = hook.response;
 
-  // const parsedAmount = Amounts.parse(`${currency}:${amount}`);
+  async function updateAccountFromList(accountStr: string): Promise<void> {
+    const uri = !accountStr ? undefined : parsePaytoUri(accountStr);
+    if (uri) {
+      setSelectedAccount(uri);
+    }
+  }
 
   if (addingAccount) {
     return {
@@ -139,128 +141,110 @@ export function useComponentState({
   const firstAccount = accounts[0].uri;
   const currentAccount = !selectedAccount ? firstAccount : selectedAccount;
 
-  if (fee === undefined) {
-    getFeeForAmount(currentAccount, amount, api.wallet).then((initialFee) => {
-      setFee(initialFee);
-    });
-    return {
-      status: "loading",
-      error: undefined,
-    };
-  }
+  return () => {
+    // eslint-disable-next-line react-hooks/rules-of-hooks
+    const [amount, setAmount] = useState<AmountJson>(
+      initialValue ?? ({} as any),
+    );
+    const amountStr = Amounts.stringify(amount);
+    const depositPaytoUri = 
`payto://${currentAccount.targetType}/${currentAccount.targetPath}`;
 
-  const accountMap = createLabelsForBankAccount(accounts);
+    // eslint-disable-next-line react-hooks/rules-of-hooks
+    const hook = useAsyncAsHook(async () => {
+      const fee = await api.wallet.call(WalletApiOperation.GetFeeForDeposit, {
+        amount: amountStr,
+        depositPaytoUri,
+      });
 
-  async function updateAccountFromList(accountStr: string): Promise<void> {
-    const uri = !accountStr ? undefined : parsePaytoUri(accountStr);
-    if (uri) {
-      try {
-        const result = await getFeeForAmount(uri, amount, api.wallet);
-        setSelectedAccount(uri);
-        setFee(result);
-      } catch (e) {
-        setSelectedAccount(uri);
-        setFee(undefined);
-      }
-    }
-  }
+      return { fee };
+    }, [amountStr, depositPaytoUri]);
 
-  async function updateAmount(newAmount: AmountJson): Promise<void> {
-    // const parsed = Amounts.parse(`${currency}:${numStr}`);
-    try {
-      const result = await getFeeForAmount(
-        currentAccount,
-        newAmount,
-        api.wallet,
-      );
-      setAmount(newAmount);
-      setFee(result);
-    } catch (e) {
-      setAmount(newAmount);
-      setFee(undefined);
+    if (!hook) {
+      return {
+        status: "loading",
+        error: undefined,
+      };
+    }
+    if (hook.hasError) {
+      return {
+        status: "error",
+        error: alertFromError(
+          i18n.str`Could not load fee for amount ${amountStr}`,
+          hook,
+        ),
+      };
     }
-  }
 
-  const totalFee =
-    fee !== undefined
-      ? Amounts.sum([fee.wire, fee.coin, fee.refresh]).amount
-      : Amounts.zeroOfCurrency(currency);
+    const { fee } = hook.response;
 
-  const totalToDeposit =
-    fee !== undefined
-      ? Amounts.sub(amount, totalFee).amount
-      : Amounts.zeroOfCurrency(currency);
+    const accountMap = createLabelsForBankAccount(accounts);
 
-  const isDirty = amount !== initialValue;
-  const amountError = !isDirty
-    ? undefined
-    : Amounts.cmp(balance, amount) === -1
-    ? `Too much, your current balance is ${Amounts.stringifyValue(balance)}`
-    : undefined;
+    const totalFee =
+      fee !== undefined
+        ? Amounts.sum([fee.wire, fee.coin, fee.refresh]).amount
+        : Amounts.zeroOfCurrency(currency);
 
-  const unableToDeposit =
-    Amounts.isZero(totalToDeposit) || //deposit may be zero because of fee
-    fee === undefined || //no fee calculated yet
-    amountError !== undefined; //amount field may be invalid
+    const totalToDeposit =
+      fee !== undefined
+        ? Amounts.sub(amount, totalFee).amount
+        : Amounts.zeroOfCurrency(currency);
 
-  async function doSend(): Promise<void> {
-    if (!currency) return;
+    const isDirty = amount !== initialValue;
+    const amountError = !isDirty
+      ? undefined
+      : Amounts.cmp(balance, amount) === -1
+        ? `Too much, your current balance is 
${Amounts.stringifyValue(balance)}`
+        : undefined;
 
-    const depositPaytoUri = stringifyPaytoUri(currentAccount);
-    const amountStr = Amounts.stringify(amount);
-    await api.wallet.call(WalletApiOperation.CreateDepositGroup, {
-      amount: amountStr,
-      depositPaytoUri,
-    });
-    onSuccess(currency);
-  }
+    const unableToDeposit =
+      Amounts.isZero(totalToDeposit) || //deposit may be zero because of fee
+      fee === undefined || //no fee calculated yet
+      amountError !== undefined; //amount field may be invalid
 
-  return {
-    status: "ready",
-    error: undefined,
-    currency,
-    amount: {
-      value: amount,
-      onInput: pushAlertOnError(updateAmount),
-      error: amountError,
-    },
-    onAddAccount: {
-      onClick: pushAlertOnError(async () => {
-        setAddingAccount(true);
-      }),
-    },
-    account: {
-      list: accountMap,
-      value: stringifyPaytoUri(currentAccount),
-      onChange: pushAlertOnError(updateAccountFromList),
-    },
-    currentAccount,
-    cancelHandler: {
-      onClick: pushAlertOnError(async () => {
-        onCancel(currency);
-      }),
-    },
-    depositHandler: {
-      onClick: unableToDeposit ? undefined : pushAlertOnError(doSend),
-    },
-    totalFee,
-    totalToDeposit,
-    // currentAccount,
-    // parsedAmount,
-  };
-}
+    async function doSend(): Promise<void> {
+      if (!currency) return;
 
-async function getFeeForAmount(
-  p: PaytoUri,
-  a: AmountJson,
-  wallet: ReturnType<typeof useBackendContext>["wallet"],
-): Promise<DepositGroupFees> {
-  const depositPaytoUri = `payto://${p.targetType}/${p.targetPath}`;
-  const amount = Amounts.stringify(a);
-  return await wallet.call(WalletApiOperation.GetFeeForDeposit, {
-    amount,
-    depositPaytoUri,
-  });
+      const depositPaytoUri = stringifyPaytoUri(currentAccount);
+      const amountStr = Amounts.stringify(amount);
+      await api.wallet.call(WalletApiOperation.CreateDepositGroup, {
+        amount: amountStr,
+        depositPaytoUri,
+      });
+      onSuccess(currency);
+    }
+
+    return {
+      status: "ready",
+      error: undefined,
+      currency,
+      amount: {
+        value: amount,
+        onInput: pushAlertOnError(async (a) => setAmount(a)),
+        error: amountError,
+      },
+      onAddAccount: {
+        onClick: pushAlertOnError(async () => {
+          setAddingAccount(true);
+        }),
+      },
+      account: {
+        list: accountMap,
+        value: stringifyPaytoUri(currentAccount),
+        onChange: pushAlertOnError(updateAccountFromList),
+      },
+      currentAccount,
+      cancelHandler: {
+        onClick: pushAlertOnError(async () => {
+          onCancel(currency);
+        }),
+      },
+      depositHandler: {
+        onClick: unableToDeposit ? undefined : pushAlertOnError(doSend),
+      },
+      totalFee,
+      totalToDeposit,
+    };
+  };
 }
 
 export function labelForAccountType(id: string): string {
diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts 
b/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts
index 42b76cf50..bfd69f945 100644
--- a/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts
+++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts
@@ -264,6 +264,15 @@ describe("DepositPage states", () => {
           
expect(state.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
           expect(state.depositHandler.onClick).undefined;
         },
+        (state) => {
+          if (state.status !== "ready") expect.fail();
+          expect(state.cancelHandler.onClick).not.undefined;
+          expect(state.currency).eq(currency);
+          expect(state.account.value).eq(accountSelected);
+          expect(state.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0"));
+          
expect(state.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
+          expect(state.depositHandler.onClick).undefined;
+        },
       ],
       TestingContext,
     );
@@ -341,7 +350,7 @@ describe("DepositPage states", () => {
           expect(state.account.value).eq(accountSelected);
           expect(state.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0"));
           expect(state.depositHandler.onClick).undefined;
-          
expect(state.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
+          
expect(state.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
 
           expect(state.amount.onInput).not.undefined;
           if (!state.amount.onInput) return;
@@ -359,6 +368,18 @@ describe("DepositPage states", () => {
           );
           expect(state.depositHandler.onClick).not.undefined;
         },
+        (state) => {
+          if (state.status !== "ready") expect.fail();
+          expect(state.cancelHandler.onClick).not.undefined;
+          expect(state.currency).eq(currency);
+          expect(state.account.value).eq(accountSelected);
+          expect(state.amount.value).deep.eq(Amounts.parseOrThrow("EUR:10"));
+          
expect(state.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
+          expect(state.totalToDeposit).deep.eq(
+            Amounts.parseOrThrow(`${currency}:7`),
+          );
+          expect(state.depositHandler.onClick).not.undefined;
+        },
       ],
       TestingContext,
     );
diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx 
b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
index 5ed05f87f..cc3a65f2d 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
@@ -38,7 +38,7 @@ import {
 } from "@gnu-taler/taler-util";
 import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
 import { styled } from "@linaria/react";
-import { differenceInSeconds } from "date-fns";
+import { differenceInSeconds, isAfter, isFuture, isPast } from "date-fns";
 import { ComponentChildren, Fragment, h, VNode } from "preact";
 import { useEffect, useState } from "preact/hooks";
 import emptyImg from "../../static/img/empty.png";
@@ -641,6 +641,11 @@ export function TransactionView({
   if (transaction.type === TransactionType.Deposit) {
     const total = Amounts.parseOrThrow(transaction.amountRaw);
     const payto = parsePaytoUri(transaction.targetPaytoUri);
+
+    const wireTime = AbsoluteTime.fromTimestamp(
+      transaction.wireTransferDeadline,
+    );
+    const shouldBeWired = wireTime.t_ms !== "never" && isPast(wireTime.t_ms);
     return (
       <TransactionTemplate
         transaction={transaction}
@@ -663,18 +668,39 @@ export function TransactionView({
           text={<DepositDetails transaction={transaction} />}
           kind="neutral"
         />
-        <Part
-          title={i18n.str`Wire transfer deadline`}
-          text={
-            <Time
-              timestamp={AbsoluteTime.fromTimestamp(
-                transaction.wireTransferDeadline,
-              )}
-              format="dd MMMM yyyy 'at' HH:mm"
-            />
-          }
-          kind="neutral"
-        />
+        {!shouldBeWired ? (
+          <Part
+            title={i18n.str`Wire transfer deadline`}
+            text={
+              <Time timestamp={wireTime} format="dd MMMM yyyy 'at' HH:mm" />
+            }
+            kind="neutral"
+          />
+        ) : transaction.wireTransferProgress === 0 ? (
+          <AlertView
+            alert={{
+              type: "warning",
+              message: i18n.str`Wire transfer is not initiated`,
+              description: i18n.str` `,
+            }}
+          />
+        ) : transaction.wireTransferProgress === 100 ? (
+          <AlertView
+            alert={{
+              type: "success",
+              message: i18n.str`Wire transfer completed`,
+              description: i18n.str` `,
+            }}
+          />
+        ) : (
+          <AlertView
+            alert={{
+              type: "info",
+              message: i18n.str`Wire transfer in progress`,
+              description: i18n.str` `,
+            }}
+          />
+        )}
       </TransactionTemplate>
     );
   }

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