From 5c742afbdf9aaa767c3e4617c48a98439e400fa2 Mon Sep 17 00:00:00 2001
From: Sebastian
Date: Tue, 8 Nov 2022 13:00:34 -0300
Subject: feature: 7440 add expiration to p2p
---
.../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 +-
9 files changed, 291 insertions(+), 62 deletions(-)
(limited to 'packages/taler-wallet-webextension')
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();
+ const [timestamp, setTimestamp] = useState()
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 {
+ 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
*/
+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 (
@@ -75,16 +98,6 @@ export function ReadyView({
/>
)}
-
-
+
+
+
+
+
+
+
+
+
+
+
+
Details}
@@ -114,19 +173,14 @@ export function ReadyView({
}
/>
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
*/
-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();
+ const [timestamp, setTimestamp] = useState()
+
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 {
+ 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
*/
+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 (
@@ -65,34 +89,65 @@ export function ReadyView({
/>
)}
-
+
+
+
+
+
+
+
+
+
+
+
Details}
text={
}
/>
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 (