gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: feature: 7440 add expiration


From: gnunet
Subject: [taler-wallet-core] branch master updated: feature: 7440 add expiration to p2p
Date: Tue, 08 Nov 2022 17:00:42 +0100

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

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

The following commit(s) were added to refs/heads/master by this push:
     new 5c742afbd feature: 7440 add expiration to p2p
5c742afbd is described below

commit 5c742afbdf9aaa767c3e4617c48a98439e400fa2
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Tue Nov 8 13:00:34 2022 -0300

    feature: 7440 add expiration to p2p
---
 packages/taler-util/src/payto.ts                   | 11 +--
 packages/taler-util/src/taler-types.ts             | 11 ++-
 packages/taler-util/src/types-test.ts              |  2 +-
 packages/taler-util/src/wallet-types.ts            | 59 ++++++++++----
 .../src/crypto/workers/synchronousWorkerFactory.ts |  1 -
 .../src/operations/backup/import.ts                |  4 +-
 .../src/operations/pay-merchant.ts                 |  6 +-
 .../taler-wallet-core/src/operations/pay-peer.ts   | 69 +++++++++-------
 packages/taler-wallet-core/src/wallet-api-types.ts | 26 ++++++
 packages/taler-wallet-core/src/wallet.ts           | 22 +++++-
 .../src/cta/InvoiceCreate/index.ts                 |  4 +-
 .../src/cta/InvoiceCreate/state.ts                 | 73 ++++++++++++++---
 .../src/cta/InvoiceCreate/stories.tsx              |  5 +-
 .../src/cta/InvoiceCreate/views.tsx                | 92 +++++++++++++++++-----
 .../src/cta/TransferCreate/index.ts                |  4 +-
 .../src/cta/TransferCreate/state.ts                | 77 +++++++++++++++---
 .../src/cta/TransferCreate/stories.tsx             |  5 +-
 .../src/cta/TransferCreate/views.tsx               | 91 ++++++++++++++++-----
 .../taler-wallet-webextension/src/mui/Button.tsx   |  2 +-
 19 files changed, 440 insertions(+), 124 deletions(-)

diff --git a/packages/taler-util/src/payto.ts b/packages/taler-util/src/payto.ts
index 3073b991c..8eb0b88a8 100644
--- a/packages/taler-util/src/payto.ts
+++ b/packages/taler-util/src/payto.ts
@@ -139,12 +139,13 @@ export function parsePaytoUri(s: string): PaytoUri | 
undefined {
     let iban: string | undefined = undefined;
     let bic: string | undefined = undefined;
     if (parts.length === 1) {
-      iban = parts[0]
-    } if (parts.length === 2) {
-      bic = parts[0]
-      iban = parts[1]
+      iban = parts[0];
+    }
+    if (parts.length === 2) {
+      bic = parts[0];
+      iban = parts[1];
     } else {
-      iban = targetPath
+      iban = targetPath;
     }
     return {
       isKnown: true,
diff --git a/packages/taler-util/src/taler-types.ts 
b/packages/taler-util/src/taler-types.ts
index d4f96f5cd..292ace94b 100644
--- a/packages/taler-util/src/taler-types.ts
+++ b/packages/taler-util/src/taler-types.ts
@@ -1297,7 +1297,7 @@ export const codecForProduct = (): Codec<Product> =>
     .property("price", codecOptional(codecForString()))
     .build("Tax");
 
-export const codecForContractTerms = (): Codec<MerchantContractTerms> =>
+export const codecForMerchantContractTerms = (): Codec<MerchantContractTerms> 
=>
   buildCodecForObject<MerchantContractTerms>()
     .property("order_id", codecForString())
     .property("fulfillment_url", codecOptional(codecForString()))
@@ -1329,7 +1329,14 @@ export const codecForContractTerms = (): 
Codec<MerchantContractTerms> =>
     .property("products", codecOptional(codecForList(codecForProduct())))
     .property("extra", codecForAny())
     .property("minimum_age", codecOptional(codecForNumber()))
-    .build("ContractTerms");
+    .build("MerchantContractTerms");
+
+export const codecForPeerContractTerms = (): Codec<PeerContractTerms> =>
+  buildCodecForObject<PeerContractTerms>()
+    .property("summary", codecForString())
+    .property("amount", codecForString())
+    .property("purse_expiration", codecForTimestamp)
+    .build("PeerContractTerms");
 
 export const codecForMerchantRefundPermission =
   (): Codec<MerchantAbortPayRefundDetails> =>
diff --git a/packages/taler-util/src/types-test.ts 
b/packages/taler-util/src/types-test.ts
index 2915106c2..6acd2c26e 100644
--- a/packages/taler-util/src/types-test.ts
+++ b/packages/taler-util/src/types-test.ts
@@ -15,7 +15,7 @@
  */
 
 import test from "ava";
-import { codecForContractTerms } from "./taler-types.js";
+import { codecForMerchantContractTerms as codecForContractTerms } from 
"./taler-types.js";
 
 test("contract terms validation", (t) => {
   const c = {
diff --git a/packages/taler-util/src/wallet-types.ts 
b/packages/taler-util/src/wallet-types.ts
index daeac73fd..4e1563e27 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -53,13 +53,15 @@ import { TalerErrorCode } from "./taler-error-codes.js";
 import {
   AmountString,
   AuditorDenomSig,
-  codecForContractTerms,
+  codecForMerchantContractTerms,
   CoinEnvelope,
   MerchantContractTerms,
+  PeerContractTerms,
   DenominationPubKey,
   DenomKeyType,
   ExchangeAuditor,
   UnblindedSignature,
+  codecForPeerContractTerms,
 } from "./taler-types.js";
 import {
   AbsoluteTime,
@@ -253,7 +255,7 @@ export const codecForConfirmPayResultDone = (): 
Codec<ConfirmPayResultDone> =>
   buildCodecForObject<ConfirmPayResultDone>()
     .property("type", codecForConstString(ConfirmPayResultType.Done))
     .property("transactionId", codecForString())
-    .property("contractTerms", codecForContractTerms())
+    .property("contractTerms", codecForMerchantContractTerms())
     .build("ConfirmPayResultDone");
 
 export const codecForConfirmPayResult = (): Codec<ConfirmPayResult> =>
@@ -383,7 +385,7 @@ export const codecForPreparePayResultPaymentPossible =
     buildCodecForObject<PreparePayResultPaymentPossible>()
       .property("amountEffective", codecForAmountString())
       .property("amountRaw", codecForAmountString())
-      .property("contractTerms", codecForContractTerms())
+      .property("contractTerms", codecForMerchantContractTerms())
       .property("proposalId", codecForString())
       .property("contractTermsHash", codecForString())
       .property("noncePriv", codecForString())
@@ -1738,9 +1740,26 @@ export interface PayCoinSelection {
   customerDepositFees: AmountString;
 }
 
-export interface InitiatePeerPushPaymentRequest {
+export interface PreparePeerPushPaymentRequest {
+  exchangeBaseUrl?: string;
   amount: AmountString;
-  partialContractTerms: any;
+}
+
+export const codecForPreparePeerPushPaymentRequest =
+  (): Codec<PreparePeerPushPaymentRequest> =>
+    buildCodecForObject<PreparePeerPushPaymentRequest>()
+      .property("exchangeBaseUrl", codecOptional(codecForString()))
+      .property("amount", codecForAmountString())
+      .build("InitiatePeerPushPaymentRequest");
+
+export interface PreparePeerPushPaymentResponse {
+  amountRaw: AmountString;
+  amountEffective: AmountString;
+}
+
+export interface InitiatePeerPushPaymentRequest {
+  exchangeBaseUrl?: string;
+  partialContractTerms: PeerContractTerms;
 }
 
 export interface InitiatePeerPushPaymentResponse {
@@ -1755,8 +1774,7 @@ export interface InitiatePeerPushPaymentResponse {
 export const codecForInitiatePeerPushPaymentRequest =
   (): Codec<InitiatePeerPushPaymentRequest> =>
     buildCodecForObject<InitiatePeerPushPaymentRequest>()
-      .property("amount", codecForAmountString())
-      .property("partialContractTerms", codecForAny())
+      .property("partialContractTerms", codecForPeerContractTerms())
       .build("InitiatePeerPushPaymentRequest");
 
 export interface CheckPeerPushPaymentRequest {
@@ -1768,13 +1786,13 @@ export interface CheckPeerPullPaymentRequest {
 }
 
 export interface CheckPeerPushPaymentResponse {
-  contractTerms: any;
+  contractTerms: PeerContractTerms;
   amount: AmountString;
   peerPushPaymentIncomingId: string;
 }
 
 export interface CheckPeerPullPaymentResponse {
-  contractTerms: any;
+  contractTerms: PeerContractTerms;
   amount: AmountString;
   peerPullPaymentIncomingId: string;
 }
@@ -1843,21 +1861,34 @@ export const codecForAcceptPeerPullPaymentRequest =
       .property("peerPullPaymentIncomingId", codecForString())
       .build("AcceptPeerPllPaymentRequest");
 
+export interface PreparePeerPullPaymentRequest {
+  exchangeBaseUrl: string;
+  amount: AmountString;
+}
+export const codecForPreparePeerPullPaymentRequest =
+  (): Codec<PreparePeerPullPaymentRequest> =>
+    buildCodecForObject<PreparePeerPullPaymentRequest>()
+      .property("amount", codecForAmountString())
+      .property("exchangeBaseUrl", codecForString())
+      .build("PreparePeerPullPaymentRequest");
+
+export interface PreparePeerPullPaymentResponse {
+  amountRaw: AmountString;
+  amountEffective: AmountString;
+}
 export interface InitiatePeerPullPaymentRequest {
   /**
    * FIXME: Make this optional?
    */
   exchangeBaseUrl: string;
-  amount: AmountString;
-  partialContractTerms: any;
+  partialContractTerms: PeerContractTerms;
 }
 
 export const codecForInitiatePeerPullPaymentRequest =
   (): Codec<InitiatePeerPullPaymentRequest> =>
     buildCodecForObject<InitiatePeerPullPaymentRequest>()
-      .property("partialContractTerms", codecForAny())
-      .property("amount", codecForAmountString())
-      .property("exchangeBaseUrl", codecForAmountString())
+      .property("partialContractTerms", codecForPeerContractTerms())
+      .property("exchangeBaseUrl", codecForString())
       .build("InitiatePeerPullPaymentRequest");
 
 export interface InitiatePeerPullPaymentResponse {
diff --git 
a/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactory.ts 
b/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactory.ts
index 6ce21572e..e9d67eec6 100644
--- a/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactory.ts
+++ b/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactory.ts
@@ -27,7 +27,6 @@ import { SynchronousCryptoWorker } from 
"./synchronousWorkerNode.js";
  */
 export class SynchronousCryptoWorkerFactory implements CryptoWorkerFactory {
   startWorker(): CryptoWorker {
-
     return new SynchronousCryptoWorker();
   }
 
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts 
b/packages/taler-wallet-core/src/operations/backup/import.ts
index 3159c60af..5fd220113 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -26,7 +26,7 @@ import {
   BackupRefreshReason,
   BackupRefundState,
   BackupWgType,
-  codecForContractTerms,
+  codecForMerchantContractTerms,
   CoinStatus,
   DenomKeyType,
   DenomSelectionState,
@@ -638,7 +638,7 @@ export async function importBackup(
                 break;
             }
           }
-          const parsedContractTerms = codecForContractTerms().decode(
+          const parsedContractTerms = codecForMerchantContractTerms().decode(
             backupPurchase.contract_terms_raw,
           );
           const amount = Amounts.parseOrThrow(parsedContractTerms.amount);
diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts 
b/packages/taler-wallet-core/src/operations/pay-merchant.ts
index 4483a57c0..bb391d468 100644
--- a/packages/taler-wallet-core/src/operations/pay-merchant.ts
+++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts
@@ -34,7 +34,7 @@ import {
   Amounts,
   ApplyRefundResponse,
   codecForAbortResponse,
-  codecForContractTerms,
+  codecForMerchantContractTerms,
   codecForMerchantOrderRefundPickupResponse,
   codecForMerchantOrderStatusPaid,
   codecForMerchantPayResponse,
@@ -456,7 +456,7 @@ export async function processDownloadProposal(
   let parsedContractTerms: MerchantContractTerms;
 
   try {
-    parsedContractTerms = codecForContractTerms().decode(
+    parsedContractTerms = codecForMerchantContractTerms().decode(
       proposalResp.contract_terms,
     );
   } catch (e) {
@@ -1584,7 +1584,7 @@ export async function runPayForConfirmPay(
       const numRetry = opRetry?.retryInfo.retryCounter ?? 0;
       if (
         res.errorDetail.code ===
-          TalerErrorCode.WALLET_PAY_MERCHANT_SERVER_ERROR &&
+        TalerErrorCode.WALLET_PAY_MERCHANT_SERVER_ERROR &&
         numRetry < maxRetry
       ) {
         // Pretend the operation is pending instead of reporting
diff --git a/packages/taler-wallet-core/src/operations/pay-peer.ts 
b/packages/taler-wallet-core/src/operations/pay-peer.ts
index b6acef2dc..f31a7f37c 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer.ts
@@ -57,6 +57,10 @@ import {
   parsePayPullUri,
   parsePayPushUri,
   PeerContractTerms,
+  PreparePeerPullPaymentRequest,
+  PreparePeerPullPaymentResponse,
+  PreparePeerPushPaymentRequest,
+  PreparePeerPushPaymentResponse,
   RefreshReason,
   strcmp,
   TalerProtocolTimestamp,
@@ -218,28 +222,30 @@ export async function selectPeerCoins(
   return undefined;
 }
 
+export async function preparePeerPushPayment(
+  ws: InternalWalletState,
+  req: PreparePeerPushPaymentRequest,
+): Promise<PreparePeerPushPaymentResponse> {
+  // FIXME: look up for the exchange and calculate fee
+  return {
+    amountEffective: req.amount,
+    amountRaw: req.amount,
+  };
+}
+
 export async function initiatePeerToPeerPush(
   ws: InternalWalletState,
   req: InitiatePeerPushPaymentRequest,
 ): Promise<InitiatePeerPushPaymentResponse> {
-  const instructedAmount = Amounts.parseOrThrow(req.amount);
+  const instructedAmount = Amounts.parseOrThrow(
+    req.partialContractTerms.amount,
+  );
+  const purseExpiration = req.partialContractTerms.purse_expiration;
+  const contractTerms = req.partialContractTerms;
 
   const pursePair = await ws.cryptoApi.createEddsaKeypair({});
   const mergePair = await ws.cryptoApi.createEddsaKeypair({});
 
-  const purseExpiration: TalerProtocolTimestamp = AbsoluteTime.toTimestamp(
-    AbsoluteTime.addDuration(
-      AbsoluteTime.now(),
-      Duration.fromSpec({ days: 2 }),
-    ),
-  );
-
-  const contractTerms = {
-    ...req.partialContractTerms,
-    purse_expiration: purseExpiration,
-    amount: req.amount,
-  };
-
   const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);
 
   const econtractResp = await ws.cryptoApi.encryptContractForMerge({
@@ -751,6 +757,16 @@ export async function checkPeerPullPayment(
   };
 }
 
+export async function preparePeerPullPayment(
+  ws: InternalWalletState,
+  req: PreparePeerPullPaymentRequest,
+): Promise<PreparePeerPullPaymentResponse> {
+  //FIXME: look up for exchange details and use purse fee
+  return {
+    amountEffective: req.amount,
+    amountRaw: req.amount,
+  };
+}
 /**
  * Initiate a peer pull payment.
  */
@@ -769,24 +785,17 @@ export async function initiatePeerPullPayment(
   const pursePair = await ws.cryptoApi.createEddsaKeypair({});
   const mergePair = await ws.cryptoApi.createEddsaKeypair({});
 
-  const purseExpiration: TalerProtocolTimestamp = AbsoluteTime.toTimestamp(
-    AbsoluteTime.addDuration(
-      AbsoluteTime.now(),
-      Duration.fromSpec({ days: 2 }),
-    ),
+  const instructedAmount = Amounts.parseOrThrow(
+    req.partialContractTerms.amount,
   );
+  const purseExpiration = req.partialContractTerms.purse_expiration;
+  const contractTerms = req.partialContractTerms;
 
   const reservePayto = talerPaytoFromExchangeReserve(
     req.exchangeBaseUrl,
     mergeReserveInfo.reservePub,
   );
 
-  const contractTerms = {
-    ...req.partialContractTerms,
-    amount: req.amount,
-    purse_expiration: purseExpiration,
-  };
-
   const econtractResp = await ws.cryptoApi.encryptContractForDeposit({
     contractTerms,
     pursePriv: pursePair.priv,
@@ -796,7 +805,7 @@ export async function initiatePeerPullPayment(
   const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);
 
   const purseFee = Amounts.stringify(
-    Amounts.zeroOfCurrency(Amounts.parseOrThrow(req.amount).currency),
+    Amounts.zeroOfCurrency(instructedAmount.currency),
   );
 
   const sigRes = await ws.cryptoApi.signReservePurseCreate({
@@ -804,7 +813,7 @@ export async function initiatePeerPullPayment(
     flags: WalletAccountMergeFlags.CreateWithPurseFee,
     mergePriv: mergePair.priv,
     mergeTimestamp: mergeTimestamp,
-    purseAmount: req.amount,
+    purseAmount: req.partialContractTerms.amount,
     purseExpiration: purseExpiration,
     purseFee: purseFee,
     pursePriv: pursePair.priv,
@@ -817,7 +826,7 @@ export async function initiatePeerPullPayment(
     .mktx((x) => [x.peerPullPaymentInitiations, x.contractTerms])
     .runReadWrite(async (tx) => {
       await tx.peerPullPaymentInitiations.put({
-        amount: req.amount,
+        amount: req.partialContractTerms.amount,
         contractTermsHash: hContractTerms,
         exchangeBaseUrl: req.exchangeBaseUrl,
         pursePriv: pursePair.priv,
@@ -840,7 +849,7 @@ export async function initiatePeerPullPayment(
     purse_fee: purseFee,
     purse_pub: pursePair.pub,
     purse_sig: sigRes.purseSig,
-    purse_value: req.amount,
+    purse_value: req.partialContractTerms.amount,
     reserve_sig: sigRes.accountSig,
     econtract: econtractResp.econtract,
   };
@@ -862,7 +871,7 @@ export async function initiatePeerPullPayment(
   logger.info(`reserve merge response: ${j2s(resp)}`);
 
   const wg = await internalCreateWithdrawalGroup(ws, {
-    amount: Amounts.parseOrThrow(req.amount),
+    amount: instructedAmount,
     wgInfo: {
       withdrawalType: WithdrawalRecordType.PeerPullCredit,
       contractTerms,
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts 
b/packages/taler-wallet-core/src/wallet-api-types.ts
index e36e630f4..b7d0ada3d 100644
--- a/packages/taler-wallet-core/src/wallet-api-types.ts
+++ b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -75,6 +75,10 @@ import {
   PrepareDepositResponse,
   PreparePayRequest,
   PreparePayResult,
+  PreparePeerPullPaymentRequest,
+  PreparePeerPullPaymentResponse,
+  PreparePeerPushPaymentRequest,
+  PreparePeerPushPaymentResponse,
   PrepareRefundRequest,
   PrepareRefundResult,
   PrepareTipRequest,
@@ -164,9 +168,11 @@ export enum WalletApiOperation {
   WithdrawFakebank = "withdrawFakebank",
   ImportDb = "importDb",
   ExportDb = "exportDb",
+  PreparePeerPushPayment = "preparePeerPushPayment",
   InitiatePeerPushPayment = "initiatePeerPushPayment",
   CheckPeerPushPayment = "checkPeerPushPayment",
   AcceptPeerPushPayment = "acceptPeerPushPayment",
+  PreparePeerPullPayment = "preparePeerPullPayment",
   InitiatePeerPullPayment = "initiatePeerPullPayment",
   CheckPeerPullPayment = "checkPeerPullPayment",
   AcceptPeerPullPayment = "acceptPeerPullPayment",
@@ -553,6 +559,15 @@ export type ExportBackupPlainOp = {
 
 // group: Peer Payments
 
+/**
+ * Initiate an outgoing peer push payment.
+ */
+export type PreparePeerPushPaymentOp = {
+  op: WalletApiOperation.PreparePeerPushPayment;
+  request: PreparePeerPushPaymentRequest;
+  response: PreparePeerPushPaymentResponse;
+};
+
 /**
  * Initiate an outgoing peer push payment.
  */
@@ -580,6 +595,15 @@ export type AcceptPeerPushPaymentOp = {
   response: EmptyObject;
 };
 
+/**
+ * Initiate an outgoing peer pull payment.
+ */
+export type PreparePeerPullPaymentOp = {
+  op: WalletApiOperation.PreparePeerPullPayment;
+  request: PreparePeerPullPaymentRequest;
+  response: PreparePeerPullPaymentResponse;
+};
+
 /**
  * Initiate an outgoing peer pull payment.
  */
@@ -815,9 +839,11 @@ export type WalletOperations = {
   [WalletApiOperation.TestPay]: TestPayOp;
   [WalletApiOperation.ExportDb]: ExportDbOp;
   [WalletApiOperation.ImportDb]: ImportDbOp;
+  [WalletApiOperation.PreparePeerPushPayment]: PreparePeerPushPaymentOp;
   [WalletApiOperation.InitiatePeerPushPayment]: InitiatePeerPushPaymentOp;
   [WalletApiOperation.CheckPeerPushPayment]: CheckPeerPushPaymentOp;
   [WalletApiOperation.AcceptPeerPushPayment]: AcceptPeerPushPaymentOp;
+  [WalletApiOperation.PreparePeerPullPayment]: PreparePeerPullPaymentOp;
   [WalletApiOperation.InitiatePeerPullPayment]: InitiatePeerPullPaymentOp;
   [WalletApiOperation.CheckPeerPullPayment]: CheckPeerPullPaymentOp;
   [WalletApiOperation.AcceptPeerPullPayment]: AcceptPeerPullPaymentOp;
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 9339b2f8e..caaf6d410 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -57,6 +57,8 @@ import {
   codecForListKnownBankAccounts,
   codecForPrepareDepositRequest,
   codecForPreparePayRequest,
+  codecForPreparePeerPullPaymentRequest,
+  codecForPreparePeerPushPaymentRequest,
   codecForPrepareRefundRequest,
   codecForPrepareTipRequest,
   codecForRetryTransactionRequest,
@@ -186,6 +188,8 @@ import {
   checkPeerPushPayment,
   initiatePeerPullPayment,
   initiatePeerToPeerPush,
+  preparePeerPullPayment,
+  preparePeerPushPayment,
 } from "./operations/pay-peer.js";
 import { getPendingOperations } from "./operations/pending.js";
 import {
@@ -659,7 +663,9 @@ async function getExchanges(
         const opRetryRecord = await tx.operationRetries.get(
           RetryTags.forExchangeUpdate(r),
         );
-        exchanges.push(makeExchangeListItem(r, exchangeDetails, 
opRetryRecord?.lastError));
+        exchanges.push(
+          makeExchangeListItem(r, exchangeDetails, opRetryRecord?.lastError),
+        );
       }
     });
   return { exchanges };
@@ -927,9 +933,9 @@ async function dumpCoins(ws: InternalWalletState): 
Promise<CoinDumpJson> {
           ageCommitmentProof: c.ageCommitmentProof,
           spend_allocation: c.spendAllocation
             ? {
-                amount: c.spendAllocation.amount,
-                id: c.spendAllocation.id,
-              }
+              amount: c.spendAllocation.amount,
+              id: c.spendAllocation.id,
+            }
             : undefined,
         });
       }
@@ -1340,6 +1346,10 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
       await importDb(ws.db.idbHandle(), req.dump);
       return [];
     }
+    case WalletApiOperation.PreparePeerPushPayment: {
+      const req = codecForPreparePeerPushPaymentRequest().decode(payload);
+      return await preparePeerPushPayment(ws, req);
+    }
     case WalletApiOperation.InitiatePeerPushPayment: {
       const req = codecForInitiatePeerPushPaymentRequest().decode(payload);
       return await initiatePeerToPeerPush(ws, req);
@@ -1352,6 +1362,10 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
       const req = codecForAcceptPeerPushPaymentRequest().decode(payload);
       return await acceptPeerPushPayment(ws, req);
     }
+    case WalletApiOperation.PreparePeerPullPayment: {
+      const req = codecForPreparePeerPullPaymentRequest().decode(payload);
+      return await preparePeerPullPayment(ws, req);
+    }
     case WalletApiOperation.InitiatePeerPullPayment: {
       const req = codecForInitiatePeerPullPaymentRequest().decode(payload);
       return await initiatePeerPullPayment(ws, req);
diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts 
b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts
index 0389a17fb..01dbb6d6d 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts
+++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts
@@ -59,10 +59,10 @@ export namespace State {
     doSelectExchange: ButtonHandler;
     create: ButtonHandler;
     subject: TextFieldHandler;
+    expiration: TextFieldHandler;
     toBeReceived: AmountJson;
-    chosenAmount: AmountJson;
+    requestAmount: AmountJson;
     exchangeUrl: string;
-    invalid: boolean;
     error: undefined;
     operationError?: TalerErrorDetail;
   }
diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts 
b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts
index d845e121a..27f05ce03 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts
+++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts
@@ -15,8 +15,9 @@
  */
 
 /* eslint-disable react-hooks/rules-of-hooks */
-import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util";
+import { Amounts, TalerErrorDetail, TalerProtocolTimestamp } from 
"@gnu-taler/taler-util";
 import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { isFuture, parse } from "date-fns";
 import { useState } from "preact/hooks";
 import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
 import { useSelectedExchange } from "../../hooks/useSelectedExchange.js";
@@ -49,7 +50,8 @@ export function useComponentState(
   const exchangeList = hook.response.exchanges;
 
   return () => {
-    const [subject, setSubject] = useState("");
+    const [subject, setSubject] = useState<string | undefined>();
+    const [timestamp, setTimestamp] = useState<string | undefined>()
 
     const [operationError, setOperationError] = useState<
       TalerErrorDetail | undefined
@@ -67,13 +69,59 @@ export function useComponentState(
 
     const exchange = selectedExchange.selected;
 
+    const hook = useAsyncAsHook(async () => {
+      const resp = await 
api.wallet.call(WalletApiOperation.PreparePeerPullPayment, {
+        amount: amountStr,
+        exchangeBaseUrl: exchange.exchangeBaseUrl,
+      })
+      return resp
+    })
+
+    if (!hook) {
+      return {
+        status: "loading",
+        error: undefined
+      }
+    }
+    if (hook.hasError) {
+      return {
+        status: "loading-uri",
+        error: hook
+      }
+    }
+
+    const { amountEffective, amountRaw } = hook.response
+    const requestAmount = Amounts.parseOrThrow(amountRaw)
+    const toBeReceived = Amounts.parseOrThrow(amountEffective)
+
+    let purse_expiration: TalerProtocolTimestamp | undefined = undefined
+    let timestampError: string | undefined = undefined;
+
+    const t = timestamp === undefined ? undefined : parse(timestamp, 
"dd/MM/yyyy", new Date())
+
+    if (t !== undefined) {
+      if (Number.isNaN(t.getTime())) {
+        timestampError = 'Should have the format "dd/MM/yyyy"'
+      } else {
+        if (!isFuture(t)) {
+          timestampError = 'Should be in the future'
+        } else {
+          purse_expiration = {
+            t_s: t.getTime() / 1000
+          }
+        }
+      }
+    }
+
     async function accept(): Promise<void> {
+      if (!subject || !purse_expiration) return;
       try {
         const resp = await 
api.wallet.call(WalletApiOperation.InitiatePeerPullPayment, {
-          amount: Amounts.stringify(amount),
           exchangeBaseUrl: exchange.exchangeBaseUrl,
           partialContractTerms: {
+            amount: Amounts.stringify(amount),
             summary: subject,
+            purse_expiration
           },
         });
 
@@ -86,25 +134,32 @@ export function useComponentState(
         throw Error("error trying to accept");
       }
     }
+    const unableToCreate = !subject || Amounts.isZero(amount) || 
!purse_expiration
 
     return {
       status: "ready",
       subject: {
-        error: !subject ? "cant be empty" : undefined,
-        value: subject,
+        error: subject === undefined ? undefined : !subject ? "Can't be empty" 
: undefined,
+        value: subject ?? "",
         onInput: async (e) => setSubject(e),
       },
+      expiration: {
+        error: timestampError,
+        value: timestamp === undefined ? "" : timestamp,
+        onInput: async (e) => {
+          setTimestamp(e)
+        }
+      },
       doSelectExchange: selectedExchange.doSelect,
-      invalid: !subject || Amounts.isZero(amount),
       exchangeUrl: exchange.exchangeBaseUrl,
       create: {
-        onClick: accept,
+        onClick: unableToCreate ? undefined : accept,
       },
       cancel: {
         onClick: onClose,
       },
-      chosenAmount: amount,
-      toBeReceived: amount,
+      requestAmount,
+      toBeReceived,
       error: undefined,
       operationError,
     };
diff --git 
a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/stories.tsx 
b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/stories.tsx
index 77885b0c1..8d4473d8f 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/stories.tsx
+++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/stories.tsx
@@ -27,11 +27,14 @@ export default {
 };
 
 export const Ready = createExample(ReadyView, {
-  chosenAmount: {
+  requestAmount: {
     currency: "ARS",
     value: 1,
     fraction: 0,
   },
+  expiration: {
+    value: "2/12/12",
+  },
   cancel: {},
   toBeReceived: {
     currency: "ARS",
diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx 
b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx
index 4970f590f..f15482953 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx
+++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx
@@ -14,6 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
+import { format } from "date-fns";
 import { h, VNode } from "preact";
 import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
 import { LoadingError } from "../../components/LoadingError.js";
@@ -46,18 +47,40 @@ export function LoadingUriView({ error }: 
State.LoadingUriError): VNode {
 }
 
 export function ReadyView({
-  invalid,
   exchangeUrl,
   subject,
+  expiration,
   cancel,
   operationError,
   create,
   toBeReceived,
-  chosenAmount,
+  requestAmount,
   doSelectExchange,
 }: State.Ready): VNode {
   const { i18n } = useTranslationContext();
 
+  async function oneDayExpiration() {
+    if (expiration.onInput) {
+      expiration.onInput(
+        format(new Date().getTime() + 1000 * 60 * 60 * 24, "dd/MM/yyyy"),
+      );
+    }
+  }
+
+  async function oneWeekExpiration() {
+    if (expiration.onInput) {
+      expiration.onInput(
+        format(new Date().getTime() + 1000 * 60 * 60 * 24 * 7, "dd/MM/yyyy"),
+      );
+    }
+  }
+  async function _20DaysExpiration() {
+    if (expiration.onInput) {
+      expiration.onInput(
+        format(new Date().getTime() + 1000 * 60 * 60 * 24 * 20, "dd/MM/yyyy"),
+      );
+    }
+  }
   return (
     <WalletAction>
       <LogoHeader />
@@ -75,16 +98,6 @@ export function ReadyView({
         />
       )}
       <section style={{ textAlign: "left" }}>
-        <TextField
-          label="Subject"
-          variant="filled"
-          error={subject.error}
-          required
-          fullWidth
-          value={subject.value}
-          onChange={subject.onInput}
-        />
-
         <Part
           title={
             <div
@@ -107,6 +120,52 @@ export function ReadyView({
           kind="neutral"
           big
         />
+        <p>
+          <TextField
+            label="Subject"
+            variant="filled"
+            error={subject.error}
+            required
+            fullWidth
+            value={subject.value}
+            onChange={subject.onInput}
+          />
+        </p>
+
+        <p>
+          <TextField
+            label="Expiration"
+            variant="filled"
+            error={expiration.error}
+            required
+            fullWidth
+            value={expiration.value}
+            onChange={expiration.onInput}
+          />
+          <p>
+            <Button
+              variant="outlined"
+              disabled={!expiration.onInput}
+              onClick={oneDayExpiration}
+            >
+              1 day
+            </Button>
+            <Button
+              variant="outlined"
+              disabled={!expiration.onInput}
+              onClick={oneWeekExpiration}
+            >
+              1 week
+            </Button>
+            <Button
+              variant="outlined"
+              disabled={!expiration.onInput}
+              onClick={_20DaysExpiration}
+            >
+              20 days
+            </Button>
+          </p>
+        </p>
 
         <Part
           title={<i18n.Translate>Details</i18n.Translate>}
@@ -114,19 +173,14 @@ export function ReadyView({
             <InvoiceDetails
               amount={{
                 effective: toBeReceived,
-                raw: chosenAmount,
+                raw: requestAmount,
               }}
             />
           }
         />
       </section>
       <section>
-        <Button
-          disabled={invalid}
-          onClick={create.onClick}
-          variant="contained"
-          color="success"
-        >
+        <Button onClick={create.onClick} variant="contained" color="success">
           <i18n.Translate>Create</i18n.Translate>
         </Button>
       </section>
diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts 
b/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts
index 83293438f..8d51ff3e0 100644
--- a/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts
+++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts
@@ -48,11 +48,11 @@ export namespace State {
   }
   export interface Ready extends BaseInfo {
     status: "ready";
-    invalid: boolean;
     create: ButtonHandler;
     toBeReceived: AmountJson;
-    chosenAmount: AmountJson;
+    debitAmount: AmountJson;
     subject: TextFieldHandler;
+    expiration: TextFieldHandler;
     error: undefined;
     operationError?: TalerErrorDetail;
   }
diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts 
b/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts
index b229924b2..089f46047 100644
--- a/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts
+++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts
@@ -14,9 +14,11 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util";
+import { Amounts, TalerErrorDetail, TalerProtocolTimestamp } from 
"@gnu-taler/taler-util";
 import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { format, isFuture, parse } from "date-fns";
 import { useState } from "preact/hooks";
+import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
 import { wxApi } from "../../wxApi.js";
 import { Props, State } from "./index.js";
 
@@ -26,17 +28,65 @@ export function useComponentState(
 ): State {
   const amount = Amounts.parseOrThrow(amountStr);
 
-  const [subject, setSubject] = useState("");
+  const [subject, setSubject] = useState<string | undefined>();
+  const [timestamp, setTimestamp] = useState<string | undefined>()
+
   const [operationError, setOperationError] = useState<
     TalerErrorDetail | undefined
   >(undefined);
 
+
+  const hook = useAsyncAsHook(async () => {
+    const resp = await 
api.wallet.call(WalletApiOperation.PreparePeerPushPayment, {
+      amount: amountStr
+    })
+    return resp
+  })
+
+  if (!hook) {
+    return {
+      status: "loading",
+      error: undefined
+    }
+  }
+  if (hook.hasError) {
+    return {
+      status: "loading-uri",
+      error: hook
+    }
+  }
+
+  const { amountEffective, amountRaw } = hook.response
+  const debitAmount = Amounts.parseOrThrow(amountRaw)
+  const toBeReceived = Amounts.parseOrThrow(amountEffective)
+
+  let purse_expiration: TalerProtocolTimestamp | undefined = undefined
+  let timestampError: string | undefined = undefined;
+
+  const t = timestamp === undefined ? undefined : parse(timestamp, 
"dd/MM/yyyy", new Date())
+
+  if (t !== undefined) {
+    if (Number.isNaN(t.getTime())) {
+      timestampError = 'Should have the format "dd/MM/yyyy"'
+    } else {
+      if (!isFuture(t)) {
+        timestampError = 'Should be in the future'
+      } else {
+        purse_expiration = {
+          t_s: t.getTime() / 1000
+        }
+      }
+    }
+  }
+
   async function accept(): Promise<void> {
+    if (!subject || !purse_expiration) return;
     try {
       const resp = await 
api.wallet.call(WalletApiOperation.InitiatePeerPushPayment, {
-        amount: Amounts.stringify(amount),
         partialContractTerms: {
           summary: subject,
+          amount: amountStr,
+          purse_expiration
         },
       });
       onSuccess(resp.transactionId);
@@ -48,22 +98,31 @@ export function useComponentState(
       throw Error("error trying to accept");
     }
   }
+
+  const unableToCreate = !subject || Amounts.isZero(amount) || 
!purse_expiration
+
   return {
     status: "ready",
-    invalid: !subject || Amounts.isZero(amount),
     cancel: {
       onClick: onClose,
     },
     subject: {
-      error: !subject ? "cant be empty" : undefined,
-      value: subject,
+      error: subject === undefined ? undefined : !subject ? "Can't be empty" : 
undefined,
+      value: subject ?? "",
       onInput: async (e) => setSubject(e),
     },
+    expiration: {
+      error: timestampError,
+      value: timestamp === undefined ? "" : timestamp,
+      onInput: async (e) => {
+        setTimestamp(e)
+      }
+    },
     create: {
-      onClick: accept,
+      onClick: unableToCreate ? undefined : accept,
     },
-    chosenAmount: amount,
-    toBeReceived: amount,
+    debitAmount,
+    toBeReceived,
     error: undefined,
     operationError,
   };
diff --git 
a/packages/taler-wallet-webextension/src/cta/TransferCreate/stories.tsx 
b/packages/taler-wallet-webextension/src/cta/TransferCreate/stories.tsx
index 2746cc153..de781f008 100644
--- a/packages/taler-wallet-webextension/src/cta/TransferCreate/stories.tsx
+++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/stories.tsx
@@ -27,11 +27,14 @@ export default {
 };
 
 export const Ready = createExample(ReadyView, {
-  chosenAmount: {
+  debitAmount: {
     currency: "ARS",
     value: 1,
     fraction: 0,
   },
+  expiration: {
+    value: "20/1/2022",
+  },
   create: {},
   cancel: {},
   toBeReceived: {
diff --git 
a/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx 
b/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx
index bca806c5d..7b1c208b9 100644
--- a/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx
+++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx
@@ -14,6 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
+import { format } from "date-fns";
 import { h, VNode } from "preact";
 import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
 import { LoadingError } from "../../components/LoadingError.js";
@@ -40,14 +41,37 @@ export function LoadingUriView({ error }: 
State.LoadingUriError): VNode {
 
 export function ReadyView({
   subject,
+  expiration,
   toBeReceived,
-  chosenAmount,
+  debitAmount,
   create,
   operationError,
   cancel,
-  invalid,
 }: State.Ready): VNode {
   const { i18n } = useTranslationContext();
+
+  async function oneDayExpiration() {
+    if (expiration.onInput) {
+      expiration.onInput(
+        format(new Date().getTime() + 1000 * 60 * 60 * 24, "dd/MM/yyyy"),
+      );
+    }
+  }
+
+  async function oneWeekExpiration() {
+    if (expiration.onInput) {
+      expiration.onInput(
+        format(new Date().getTime() + 1000 * 60 * 60 * 24 * 7, "dd/MM/yyyy"),
+      );
+    }
+  }
+  async function _20DaysExpiration() {
+    if (expiration.onInput) {
+      expiration.onInput(
+        format(new Date().getTime() + 1000 * 60 * 60 * 24 * 20, "dd/MM/yyyy"),
+      );
+    }
+  }
   return (
     <WalletAction>
       <LogoHeader />
@@ -65,34 +89,65 @@ export function ReadyView({
         />
       )}
       <section style={{ textAlign: "left" }}>
-        <TextField
-          label="Subject"
-          variant="filled"
-          error={subject.error}
-          required
-          fullWidth
-          value={subject.value}
-          onChange={subject.onInput}
-        />
+        <p>
+          <TextField
+            label="Subject"
+            variant="filled"
+            error={subject.error}
+            required
+            fullWidth
+            value={subject.value}
+            onChange={subject.onInput}
+          />
+        </p>
+        <p>
+          <TextField
+            label="Expiration"
+            variant="filled"
+            error={expiration.error}
+            required
+            fullWidth
+            value={expiration.value}
+            onChange={expiration.onInput}
+          />
+          <p>
+            <Button
+              variant="outlined"
+              disabled={!expiration.onInput}
+              onClick={oneDayExpiration}
+            >
+              1 day
+            </Button>
+            <Button
+              variant="outlined"
+              disabled={!expiration.onInput}
+              onClick={oneWeekExpiration}
+            >
+              1 week
+            </Button>
+            <Button
+              variant="outlined"
+              disabled={!expiration.onInput}
+              onClick={_20DaysExpiration}
+            >
+              20 days
+            </Button>
+          </p>
+        </p>
         <Part
           title={<i18n.Translate>Details</i18n.Translate>}
           text={
             <TransferDetails
               amount={{
                 effective: toBeReceived,
-                raw: chosenAmount,
+                raw: debitAmount,
               }}
             />
           }
         />
       </section>
       <section>
-        <Button
-          disabled={invalid}
-          onClick={create.onClick}
-          variant="contained"
-          color="success"
-        >
+        <Button onClick={create.onClick} variant="contained" color="success">
           <i18n.Translate>Create</i18n.Translate>
         </Button>
       </section>
diff --git a/packages/taler-wallet-webextension/src/mui/Button.tsx 
b/packages/taler-wallet-webextension/src/mui/Button.tsx
index 0aaa5ee97..bca0d6231 100644
--- a/packages/taler-wallet-webextension/src/mui/Button.tsx
+++ b/packages/taler-wallet-webextension/src/mui/Button.tsx
@@ -290,7 +290,7 @@ export function Button({
 
   return (
     <ButtonBase
-      disabled={disabled || running}
+      disabled={disabled || running || !doClick}
       class={[
         theme.typography.button,
         theme.shape.roundBorder,

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