diff options
author | Sebastian <sebasjm@gmail.com> | 2023-03-10 01:27:31 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2023-03-10 01:27:31 -0300 |
commit | 867d2ca76b2ca8903b2263a68243899749de7011 (patch) | |
tree | 5abb027f7976a8694d29e5be4f8d9e8528bf1a0c /packages/taler-wallet-webextension/src/cta | |
parent | f40487806304dbaafa74544d5a8f74ab56569044 (diff) | |
download | wallet-core-867d2ca76b2ca8903b2263a68243899749de7011.tar.xz |
fix encoded uri, add pay template cta
Diffstat (limited to 'packages/taler-wallet-webextension/src/cta')
7 files changed, 210 insertions, 24 deletions
diff --git a/packages/taler-wallet-webextension/src/cta/Payment/index.ts b/packages/taler-wallet-webextension/src/cta/Payment/index.ts index e844c1706..c9bead89c 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Payment/index.ts @@ -30,7 +30,7 @@ import { useComponentState } from "./state.js"; import { BaseView } from "./views.js"; export interface Props { - talerPayUri?: string; + talerPayUri: string; goToWalletManualWithdraw: (amount?: string) => Promise<void>; cancel: () => Promise<void>; onSuccess: (tx: string) => Promise<void>; diff --git a/packages/taler-wallet-webextension/src/cta/Payment/test.ts b/packages/taler-wallet-webextension/src/cta/Payment/test.ts index e92eb78c0..f4b63955d 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Payment/test.ts @@ -41,7 +41,7 @@ describe("Payment CTA states", () => { it("should tell the user that the URI is missing", async () => { const { handler, TestingContext } = createWalletApiMock(); const props = { - talerPayUri: undefined, + talerPayUri: "", cancel: nullFunction, goToWalletManualWithdraw: nullFunction, onSuccess: nullFunction, diff --git a/packages/taler-wallet-webextension/src/cta/PaymentTemplate/index.ts b/packages/taler-wallet-webextension/src/cta/PaymentTemplate/index.ts index 2cdc8d2e1..f5a8c8814 100644 --- a/packages/taler-wallet-webextension/src/cta/PaymentTemplate/index.ts +++ b/packages/taler-wallet-webextension/src/cta/PaymentTemplate/index.ts @@ -20,12 +20,25 @@ import { ErrorAlert } from "../../context/alert.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { useComponentState } from "./state.js"; import { ReadyView } from "./views.js"; +import { PaymentPage } from "../Payment/index.js"; +import { + AmountFieldHandler, + ButtonHandler, + TextFieldHandler, +} from "../../mui/handlers.js"; export interface Props { - talerTemplateUri?: string; + talerTemplateUri: string; + goToWalletManualWithdraw: (amount?: string) => Promise<void>; + cancel: () => Promise<void>; + onSuccess: (tx: string) => Promise<void>; } -export type State = State.Loading | State.LoadingUriError | State.Ready; +export type State = + | State.Loading + | State.LoadingUriError + | State.OrderReady + | State.FillTemplate; export namespace State { export interface Loading { @@ -37,16 +50,30 @@ export namespace State { error: ErrorAlert; } - export interface Ready { - status: "ready"; + export interface FillTemplate { + status: "fill-template"; error: undefined; + currency: string; + amount?: AmountFieldHandler; + summary?: TextFieldHandler; + onCreate: ButtonHandler; + } + + export interface OrderReady { + status: "order-ready"; + error: undefined; + talerPayUri: string; + onSuccess: (tx: string) => Promise<void>; + cancel: () => Promise<void>; + goToWalletManualWithdraw: () => Promise<void>; } } const viewMapping: StateViewMap<State> = { loading: Loading, error: ErrorAlertView, - ready: ReadyView, + "fill-template": ReadyView, + "order-ready": PaymentPage, }; export const PaymentTemplatePage = compose( diff --git a/packages/taler-wallet-webextension/src/cta/PaymentTemplate/state.ts b/packages/taler-wallet-webextension/src/cta/PaymentTemplate/state.ts index f5e6dee61..abcf040b7 100644 --- a/packages/taler-wallet-webextension/src/cta/PaymentTemplate/state.ts +++ b/packages/taler-wallet-webextension/src/cta/PaymentTemplate/state.ts @@ -14,27 +14,56 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { Amounts } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { alertFromError } from "../../context/alert.js"; +import { useState } from "preact/hooks"; +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 { AmountFieldHandler, TextFieldHandler } from "../../mui/handlers.js"; import { Props, State } from "./index.js"; -export function useComponentState({ talerTemplateUri }: Props): State { - // const { pushAlertOnError } = useAlertContext(); +export function useComponentState({ + talerTemplateUri, + cancel, + goToWalletManualWithdraw, + onSuccess, +}: Props): State { const api = useBackendContext(); const { i18n } = useTranslationContext(); + const { safely } = useAlertContext(); + + const url = talerTemplateUri ? new URL(talerTemplateUri) : undefined; + + const amountParam = !url + ? undefined + : url.searchParams.get("amount") ?? undefined; + const summaryParam = !url + ? undefined + : url.searchParams.get("summary") ?? undefined; + + const parsedAmount = !amountParam ? undefined : Amounts.parse(amountParam); + const currency = parsedAmount ? parsedAmount.currency : amountParam; + + const initialAmount = + parsedAmount ?? (currency ? Amounts.zeroOfCurrency(currency) : undefined); + const [amount, setAmount] = useState(initialAmount); + const [summary, setSummary] = useState(summaryParam); + const [newOrder, setNewOrder] = useState(""); const hook = useAsyncAsHook(async () => { if (!talerTemplateUri) throw Error("ERROR_NO-URI-FOR-PAYMENT-TEMPLATE"); - const payStatus = await api.wallet.call( - WalletApiOperation.PreparePayForTemplate, - { - talerPayTemplateUri: talerTemplateUri, - templateParams: {}, - }, - ); + let payStatus; + if (!amountParam && !summaryParam) { + payStatus = await api.wallet.call( + WalletApiOperation.PreparePayForTemplate, + { + talerPayTemplateUri: talerTemplateUri, + templateParams: {}, + }, + ); + } const balance = await api.wallet.call(WalletApiOperation.GetBalances, {}); return { payStatus, balance, uri: talerTemplateUri }; }, []); @@ -56,8 +85,85 @@ export function useComponentState({ talerTemplateUri }: Props): State { }; } + if (hook.response.payStatus) { + return { + status: "order-ready", + error: undefined, + cancel, + goToWalletManualWithdraw, + onSuccess, + talerPayUri: hook.response.payStatus.talerUri!, + }; + } + + if (newOrder) { + return { + status: "order-ready", + error: undefined, + cancel, + goToWalletManualWithdraw, + onSuccess, + talerPayUri: newOrder, + }; + } + + async function createOrder() { + try { + const templateParams: Record<string, string> = {}; + if (amount) { + templateParams["amount"] = Amounts.stringify(amount); + } + if (summary) { + templateParams["summary"] = summary; + } + const payStatus = await api.wallet.call( + WalletApiOperation.PreparePayForTemplate, + { + talerPayTemplateUri: talerTemplateUri, + templateParams, + }, + ); + setNewOrder(payStatus.talerUri!); + } catch (e) {} + } + const errors = undefinedIfEmpty({ + amount: amount && Amounts.isZero(amount) ? i18n.str`required` : undefined, + summary: !summary ? i18n.str`required` : undefined, + }); return { - status: "ready", + status: "fill-template", error: undefined, + currency: currency!, //currency is always not null + amount: + amount !== undefined + ? ({ + onInput: (a) => { + setAmount(a); + }, + value: amount, + error: errors?.amount, + } as AmountFieldHandler) + : undefined, + summary: + summary !== undefined + ? ({ + onInput: (t) => { + setSummary(t); + }, + value: summary, + error: errors?.summary, + } as TextFieldHandler) + : undefined, + onCreate: { + onClick: errors + ? undefined + : safely(createOrder, i18n.str`Could not create order`), + }, }; } + +function undefinedIfEmpty<T extends object>(obj: T): T | undefined { + return Object.keys(obj).some((k) => (obj as any)[k] !== undefined) + ? obj + : undefined; +} diff --git a/packages/taler-wallet-webextension/src/cta/PaymentTemplate/stories.tsx b/packages/taler-wallet-webextension/src/cta/PaymentTemplate/stories.tsx index 32a080959..93421eaa3 100644 --- a/packages/taler-wallet-webextension/src/cta/PaymentTemplate/stories.tsx +++ b/packages/taler-wallet-webextension/src/cta/PaymentTemplate/stories.tsx @@ -29,6 +29,6 @@ export default { }; export const PaymentPossible = tests.createExample(ReadyView, { - status: "ready", + status: "fill-template", error: undefined, }); diff --git a/packages/taler-wallet-webextension/src/cta/PaymentTemplate/test.ts b/packages/taler-wallet-webextension/src/cta/PaymentTemplate/test.ts index d4c65e008..72fbb6853 100644 --- a/packages/taler-wallet-webextension/src/cta/PaymentTemplate/test.ts +++ b/packages/taler-wallet-webextension/src/cta/PaymentTemplate/test.ts @@ -21,6 +21,7 @@ import { expect } from "chai"; import { tests } from "../../../../web-util/src/index.browser.js"; +import { nullFunction } from "../../mui/handlers.js"; import { createWalletApiMock } from "../../test-utils.js"; import { useComponentState } from "./state.js"; @@ -28,7 +29,10 @@ describe("Order template CTA states", () => { it("should tell the user that the URI is missing", async () => { const { handler, TestingContext } = createWalletApiMock(); const props = { - talerTemplateUri: undefined, + talerTemplateUri: "", + cancel: nullFunction, + goToWalletManualWithdraw: nullFunction, + onSuccess: nullFunction, }; const hookBehavior = await tests.hookBehaveLikeThis( diff --git a/packages/taler-wallet-webextension/src/cta/PaymentTemplate/views.tsx b/packages/taler-wallet-webextension/src/cta/PaymentTemplate/views.tsx index d3f893c7e..9f4c0f28c 100644 --- a/packages/taler-wallet-webextension/src/cta/PaymentTemplate/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/PaymentTemplate/views.tsx @@ -15,15 +15,64 @@ */ import { Fragment, h, VNode } from "preact"; +import { AmountField } from "../../components/AmountField.js"; +import { Part } from "../../components/Part.js"; import { useTranslationContext } from "../../context/translation.js"; +import { Button } from "../../mui/Button.js"; +import { TextField } from "../../mui/TextField.js"; import { State } from "./index.js"; -export function ReadyView({ status }: State.Ready): VNode { +export function ReadyView({ + currency, + amount, + summary, + onCreate, +}: State.FillTemplate): VNode { const { i18n } = useTranslationContext(); + console.log("is summary", !!summary); return ( - <div> - <i18n.Translate>Not yet implemented</i18n.Translate> - </div> + <Fragment> + <section style={{ textAlign: "left" }}> + {/* <Part + title={ + <div + style={{ + display: "flex", + alignItems: "center", + }} + > + <i18n.Translate>Merchant</i18n.Translate> + </div> + } + text={<ExchangeDetails exchange={exchangeUrl} />} + kind="neutral" + big + /> */} + {!amount ? undefined : ( + <p> + <AmountField label={i18n.str`Amount`} handler={amount} /> + </p> + )} + {!summary ? undefined : ( + <p> + <TextField + label="Summary" + variant="filled" + required + fullWidth + error={summary.error} + value={summary.value} + onChange={summary.onInput} + /> + </p> + )} + </section> + <section> + <Button onClick={onCreate.onClick} variant="contained" color="success"> + <i18n.Translate>Review order</i18n.Translate> + </Button> + </section> + </Fragment> ); } |