diff options
7 files changed, 127 insertions, 98 deletions
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index 77c531f39..dd8eb29a2 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -48,6 +48,7 @@ import { } from "./codec.js"; import { CurrencySpecification, + TalerMerchantApi, TemplateParams, WithdrawalOperationStatus, canonicalizeBaseUrl, @@ -1851,9 +1852,9 @@ export interface PrepareBankIntegratedWithdrawalRequest { export const codecForPrepareBankIntegratedWithdrawalRequest = (): Codec<PrepareBankIntegratedWithdrawalRequest> => buildCodecForObject<PrepareBankIntegratedWithdrawalRequest>() - .property("talerWithdrawUri", codecForString()) - .property("selectedExchange", codecOptional(codecForString())) - .build("PrepareBankIntegratedWithdrawalRequest"); + .property("talerWithdrawUri", codecForString()) + .property("selectedExchange", codecOptional(codecForString())) + .build("PrepareBankIntegratedWithdrawalRequest"); export interface PrepareBankIntegratedWithdrawalResponse { transactionId?: string; @@ -2043,6 +2044,10 @@ export interface CheckPayTemplateRequest { talerPayTemplateUri: string; } +export type CheckPayTemplateReponse = TalerMerchantApi.WalletTemplateDetails & { + supportedCurrencies: string[]; +} + export const codecForCheckPayTemplateRequest = (): Codec<CheckPayTemplateRequest> => buildCodecForObject<CheckPayTemplateRequest>() diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts index f08db3a6a..8b6cb5e8d 100644 --- a/packages/taler-wallet-core/src/pay-merchant.ts +++ b/packages/taler-wallet-core/src/pay-merchant.ts @@ -34,6 +34,8 @@ import { assertUnreachable, AsyncFlag, checkDbInvariant, + CheckPaymentResponse, + CheckPayTemplateReponse, CheckPayTemplateRequest, codecForAbortResponse, codecForMerchantContractTerms, @@ -79,6 +81,7 @@ import { TalerErrorCode, TalerErrorDetail, TalerMerchantApi, + TalerMerchantInstanceHttpClient, TalerPreciseTimestamp, TalerProtocolViolationError, TalerUriAction, @@ -627,8 +630,7 @@ async function processDownloadProposal( if (proposal.purchaseStatus != PurchaseStatus.PendingDownloadingProposal) { logger.error( - `unexpected state ${proposal.purchaseStatus}/${ - PurchaseStatus[proposal.purchaseStatus] + `unexpected state ${proposal.purchaseStatus}/${PurchaseStatus[proposal.purchaseStatus] } for ${ctx.transactionId} in processDownloadProposal`, ); return TaskRunResult.finished(); @@ -887,8 +889,7 @@ async function createOrReusePurchase( oldProposal.claimToken === claimToken ) { logger.info( - `Found old proposal (status=${ - PurchaseStatus[oldProposal.purchaseStatus] + `Found old proposal (status=${PurchaseStatus[oldProposal.purchaseStatus] }) for order ${orderId} at ${merchantBaseUrl}`, ); if (oldProposal.purchaseStatus === PurchaseStatus.DialogShared) { @@ -1601,16 +1602,31 @@ async function downloadTemplate( export async function checkPayForTemplate( wex: WalletExecutionContext, req: CheckPayTemplateRequest, -): Promise<TalerMerchantApi.WalletTemplateDetails> { +): Promise<CheckPayTemplateReponse> { const parsedUri = parsePayTemplateUri(req.talerPayTemplateUri); if (!parsedUri) { throw Error("invalid taler-template URI"); } - return await downloadTemplate( + const asd = await downloadTemplate( wex, parsedUri.merchantBaseUrl, parsedUri.templateId, ); + + const merchantApi = new TalerMerchantInstanceHttpClient( + parsedUri.merchantBaseUrl, + wex.http, + ); + + const cfg = await merchantApi.getConfig() + if (cfg.type === "fail") { + throw TalerError.fromUncheckedDetail(cfg.detail); + } + + return { + ...asd, + supportedCurrencies: Object.keys(cfg.body.currencies) + } } export async function preparePayForTemplate( @@ -1630,28 +1646,26 @@ export async function preparePayForTemplate( parsedUri.templateId, ); - const amountFromUri = templateInfo.editable_defaults?.amount; - if (amountFromUri != null) { - const templateParamsAmount = req.templateParams?.amount; - if (templateParamsAmount != null) { - templateDetails.amount = templateParamsAmount as AmountString; - } else { - if (Amounts.isCurrency(amountFromUri)) { - throw Error( - "Amount from template URI only has a currency without value. The value must be provided in the templateParams.", - ); - } else { - templateDetails.amount = amountFromUri as AmountString; - } + const templateParamsAmount = req.templateParams?.amount as AmountString | undefined; + if (templateParamsAmount === null) { + const amountFromUri = templateInfo.editable_defaults?.amount; + if (amountFromUri != null) { + templateDetails.amount = amountFromUri as AmountString; } + } else { + templateDetails.amount = templateParamsAmount } - if ( - templateInfo.editable_defaults?.summary !== undefined && - typeof templateInfo.editable_defaults?.summary === "string" - ) { - templateDetails.summary = - req.templateParams?.summary ?? templateInfo.editable_defaults?.summary; + + const templateParamsSummary = req.templateParams?.summary; + if (templateParamsSummary === null) { + const summaryFromUri = templateInfo.editable_defaults?.summary; + if (summaryFromUri != null) { + templateDetails.summary = summaryFromUri; + } + } else { + templateDetails.summary = templateParamsSummary } + const reqUrl = new URL( `templates/${parsedUri.templateId}`, parsedUri.merchantBaseUrl, diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts index 2a1b7d170..1bcab801c 100644 --- a/packages/taler-wallet-core/src/wallet-api-types.ts +++ b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -40,6 +40,7 @@ import { BalancesResponse, CanonicalizeBaseUrlRequest, CanonicalizeBaseUrlResponse, + CheckPayTemplateReponse, CheckPayTemplateRequest, CheckPeerPullCreditRequest, CheckPeerPullCreditResponse, @@ -553,7 +554,7 @@ export type SharePaymentOp = { export type CheckPayForTemplateOp = { op: WalletApiOperation.CheckPayForTemplate; request: CheckPayTemplateRequest; - response: TalerMerchantApi.WalletTemplateDetails; + response: CheckPayTemplateReponse; }; /** diff --git a/packages/taler-wallet-webextension/src/cta/PaymentTemplate/index.ts b/packages/taler-wallet-webextension/src/cta/PaymentTemplate/index.ts index f5a8c8814..80d952217 100644 --- a/packages/taler-wallet-webextension/src/cta/PaymentTemplate/index.ts +++ b/packages/taler-wallet-webextension/src/cta/PaymentTemplate/index.ts @@ -53,7 +53,6 @@ export namespace State { export interface FillTemplate { status: "fill-template"; error: undefined; - currency: string; amount?: AmountFieldHandler; summary?: TextFieldHandler; onCreate: ButtonHandler; diff --git a/packages/taler-wallet-webextension/src/cta/PaymentTemplate/state.ts b/packages/taler-wallet-webextension/src/cta/PaymentTemplate/state.ts index 6b4584fea..4881f6e91 100644 --- a/packages/taler-wallet-webextension/src/cta/PaymentTemplate/state.ts +++ b/packages/taler-wallet-webextension/src/cta/PaymentTemplate/state.ts @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { Amounts, PreparePayResult } from "@gnu-taler/taler-util"; +import { AmountJson, Amounts, PreparePayResult } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; import { alertFromError, useAlertContext } from "../../context/alert.js"; @@ -23,49 +23,39 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { AmountFieldHandler, TextFieldHandler } from "../../mui/handlers.js"; import { Props, State } from "./index.js"; +import { RecursiveState } from "../../utils/index.js"; export function useComponentState({ talerTemplateUri, cancel, goToWalletManualWithdraw, onSuccess, -}: Props): State { +}: Props): RecursiveState<State> { const api = useBackendContext(); const { i18n } = useTranslationContext(); const { safely } = useAlertContext(); - const url = talerTemplateUri ? new URL(talerTemplateUri) : undefined; + // const url = talerTemplateUri ? new URL(talerTemplateUri) : undefined; + // const parsedAmount = !amountParam ? undefined : Amounts.parse(amountParam); + // const currency = parsedAmount ? parsedAmount.currency : amountParam; - const amountParam = !url - ? undefined - : url.searchParams.get("amount") ?? undefined; - const summaryParam = !url - ? undefined - : url.searchParams.get("summary") ?? undefined; + // const initialAmount = + // parsedAmount ?? (currency ? Amounts.zeroOfCurrency(currency) : 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 templateP = await api.wallet.call( + WalletApiOperation.CheckPayForTemplate, { talerPayTemplateUri: talerTemplateUri }, + ); + const requireMoreInfo = !templateP.template_contract.amount || !templateP.template_contract.summary; let payStatus: PreparePayResult | undefined = undefined; - if (!amountParam && !summaryParam) { - payStatus = await api.wallet.call( - WalletApiOperation.PreparePayForTemplate, - { - talerPayTemplateUri: talerTemplateUri, - templateParams: {}, - }, - ); + if (!requireMoreInfo) { + payStatus = await api.wallet.call(WalletApiOperation.PreparePayForTemplate, { talerPayTemplateUri: talerTemplateUri }); } const balance = await api.wallet.call(WalletApiOperation.GetBalances, {}); - return { payStatus, balance, uri: talerTemplateUri }; + return { payStatus, balance, uri: talerTemplateUri, templateP }; }, []); if (!hook) { @@ -108,61 +98,82 @@ export function useComponentState({ }; } - async function createOrder() { - try { - const templateParams: Record<string, string> = {}; - if (amount) { - templateParams["amount"] = Amounts.stringify(amount); - } - if (summary) { - templateParams["summary"] = summary; + return () => { + const cfg = hook.response.templateP.template_contract; + const def = hook.response.templateP.editable_defaults; + + const fixedAmount = cfg.amount !== undefined ? Amounts.parseOrThrow(cfg.amount) : undefined; + const fixedSummary = cfg.summary !== undefined ? cfg.summary : undefined; + + const defaultAmount = def?.amount !== undefined ? Amounts.parseOrThrow(def.amount) : undefined; + const defaultSummary = def?.summary !== undefined ? def.summary : undefined; + + const zero = fixedAmount ? Amounts.zeroOfAmount(fixedAmount) : + cfg.currency !== undefined ? Amounts.zeroOfCurrency(cfg.currency) : + defaultAmount !== undefined ? Amounts.zeroOfAmount(defaultAmount) : + def?.currency !== undefined ? Amounts.zeroOfCurrency(def.currency) : + Amounts.zeroOfCurrency(hook.response.templateP.supportedCurrencies[0]); + + const [amount, setAmount] = useState(defaultAmount ?? zero); + const [summary, setSummary] = useState(defaultSummary ?? ""); + + async function createOrder() { + try { + const templateParams: Record<string, string> = {}; + if (amount && !fixedAmount) { + templateParams["amount"] = Amounts.stringify(amount); + } + if (summary && !fixedSummary) { + templateParams["summary"] = summary; + } + const payStatus = await api.wallet.call( + WalletApiOperation.PreparePayForTemplate, + { + talerPayTemplateUri: talerTemplateUri, + templateParams, + }, + ); + setNewOrder(payStatus.talerUri!); + } catch (e) { + console.error(e); } - const payStatus = await api.wallet.call( - WalletApiOperation.PreparePayForTemplate, - { - talerPayTemplateUri: talerTemplateUri, - templateParams, - }, - ); - setNewOrder(payStatus.talerUri!); - } catch (e) { - console.error(e); } - } - const errors = undefinedIfEmpty({ - amount: amount && Amounts.isZero(amount) ? i18n.str`required` : undefined, - summary: summary !== undefined && !summary ? i18n.str`required` : undefined, - }); - return { - status: "fill-template", - error: undefined, - currency: currency!, //currency is always not null - amount: - amount !== undefined - ? ({ + + const errors = undefinedIfEmpty({ + amount: fixedAmount !== undefined ? undefined : amount && Amounts.isZero(amount) ? i18n.str`required` : undefined, + summary: fixedSummary !== undefined ? undefined : summary !== undefined && !summary ? i18n.str`required` : undefined, + }); + return { + status: "fill-template", + error: undefined, + amount: + fixedAmount === undefined + ? ({ onInput: (a) => { setAmount(a); }, value: amount, error: errors?.amount, } as AmountFieldHandler) - : undefined, - summary: - summary !== undefined - ? ({ + : undefined, + summary: + fixedSummary === undefined + ? ({ onInput: (t) => { setSummary(t); }, value: summary, error: errors?.summary, } as TextFieldHandler) - : undefined, - onCreate: { - onClick: errors - ? undefined - : safely("create order for pay template", createOrder), - }, - }; + : undefined, + onCreate: { + onClick: errors + ? undefined + : safely("create order for pay template", createOrder), + }, + }; + } + } function undefinedIfEmpty<T extends object>(obj: T): T | undefined { diff --git a/packages/taler-wallet-webextension/src/cta/PaymentTemplate/views.tsx b/packages/taler-wallet-webextension/src/cta/PaymentTemplate/views.tsx index 88658b5e1..7efbb32e9 100644 --- a/packages/taler-wallet-webextension/src/cta/PaymentTemplate/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/PaymentTemplate/views.tsx @@ -23,7 +23,6 @@ import { TextField } from "../../mui/TextField.js"; import { State } from "./index.js"; export function ReadyView({ - currency, amount, summary, onCreate, diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx index 884c2eab7..893122c0f 100644 --- a/packages/taler-wallet-webextension/src/wallet/Application.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Application.tsx @@ -157,7 +157,7 @@ export function Application(): VNode { )} /> -<Route + <Route path={Pages.balanceHistory.pattern} component={({ currency }: { currency?: string }) => ( <WalletTemplate path="balance" goToTransaction={redirectToTxInfo} goToURL={redirectToURL}> |