taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 88cd7dfa507d6b44db00a2afc72ae048cd81ad0c
parent ece7aabb541a58ed74b7579393572af67ef8b40c
Author: Sebastian <sebasjm@taler-systems.com>
Date:   Thu,  2 Jul 2026 16:12:18 -0300

fix #11460

Diffstat:
Mpackages/taler-merchant-webui/src/components/modal/index.tsx | 395++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mpackages/taler-merchant-webui/src/paths/instance/kyc/list/index.tsx | 2++
2 files changed, 242 insertions(+), 155 deletions(-)

diff --git a/packages/taler-merchant-webui/src/components/modal/index.tsx b/packages/taler-merchant-webui/src/components/modal/index.tsx @@ -25,21 +25,25 @@ import { getQrCodesForPayto, Paytos, PaytoType, + Result, TranslatedString, } from "@gnu-taler/taler-util"; import { Button, QR_Bank, QR_SwissBank, + RenderAmountBulma, SafeHandler, + simpleSafeHandler, useCommonPreferences, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { ComponentChildren, Fragment, h, VNode } from "preact"; import { useEffect, useRef, useState } from "preact/hooks"; import { DEFAULT_REQUEST_TIMEOUT } from "../../utils/constants.js"; -import { Spinner } from "../exception/loading.js"; +import { Loading, Spinner } from "../exception/loading.js"; import { doAutoFocus } from "../form/Input.js"; +import { useSessionContext } from "../../context/session.js"; const TALER_SCREEN_ID = 18; @@ -85,7 +89,7 @@ export function ConfirmModal({ }: ConfirmProps): VNode { const { i18n } = useTranslationContext(); return ( - <div class="modal is-active" style={zIndex ? {zIndex } : undefined}> + <div class="modal is-active" style={zIndex ? { zIndex } : undefined}> <div class="modal-background " onClick={preventBackgroundClose ? undefined : onCancel} @@ -358,16 +362,59 @@ interface ValidateBankAccountModalProps { origin: Paytos.URI; targets: Paytos.URI[]; currency: string | undefined; + exchangeBaseUrl: string; + wireAccountHash: string; } export function ValidBankAccount({ onCancel, origin, - targets, - currency: exchangeCurrency, + exchangeBaseUrl, + wireAccountHash, }: ValidateBankAccountModalProps): VNode { const { i18n } = useTranslationContext(); - const payto = targets[0]; - const subject = payto.params["message"]; + const { state, lib, config } = useSessionContext(); + const [targetIndex, setTargetIndex] = useState(0); + + const [kycAuth, setKeyAuth] = + useState< + Awaited<ReturnType<typeof lib.instance.getInstancePrivateAccountKycauth>> + >(); + useEffect(() => { + if (!state.token) return; + lib.instance + .getInstancePrivateAccountKycauth(state.token, { + exchangeBaseUrl, + wireAccountHash, + }) + .then((r) => { + setKeyAuth(r); + }); + }, []); + if (!kycAuth) { + return ( + <ConfirmModal + title={i18n.str`Getting bank transfer instructions`} + onCancel={onCancel} + > + <div style={{ height: 300, display: "flex" }}> + <Loading /> + </div> + </ConfirmModal> + ); + } + if (kycAuth.type === "fail") { + return ( + <ConfirmModal + title={i18n.str`Getting bank transfer instructions`} + onCancel={onCancel} + > + <div>failed {kycAuth.case}</div> + </ConfirmModal> + ); + } + const all_ins = kycAuth.body.wire_instructions; + const instructions = all_ins[targetIndex]; + const payto = Result.unpack(Paytos.fromString(instructions.target_payto)); const accountPart = payto.targetType === PaytoType.TalerBank ? ( @@ -383,7 +430,6 @@ export function ValidBankAccount({ <Row name={i18n.str`IBAN`} value={payto.iban} /> </Fragment> ) : undefined; - const receiverName = payto.params["receiver-name"] || payto.params["receiver"] || undefined; const receiverPostalCode = payto.params["receiver-postal-code"] || undefined; @@ -401,160 +447,199 @@ export function ValidBankAccount({ ? `${origin.address.substring(0, 8)}...` : origin.displayName; - // FIXME: define a minimum by currency somewhere https://bugs.gnunet.org/view.php?id=11169 - const sendAmount = !exchangeCurrency - ? undefined - : Amounts.parseOrThrow(`${exchangeCurrency}:0.01`); - - if (sendAmount) { - payto.params["amount"] = Amounts.stringify(sendAmount); - } const qrs = getQrCodesForPayto(Paytos.toFullString(payto)); const strPayto = Paytos.toFullString(payto); const [{ showDebugInfo }] = useCommonPreferences(); - return ( - <ConfirmModal - title={i18n.str`Validate bank account: ${from}`} - onCancel={onCancel} - > - {!showDebugInfo ? undefined : ( - <pre>{JSON.stringify({ strPayto, payto }, undefined, 2)}</pre> - )} - <p style={{ paddingTop: 0 }}> - <i18n.Translate> - In order to prove that you are the beneficial owner of the bank - account, you are required to wire a small amount to a specified bank - account with the subject below. - </i18n.Translate> - </p> - <tr> - <td colSpan={3}> - {!qrs.length ? undefined : ( - <div style={{ overflowY: "auto", height: "95%", padding: 5 }}> - <p> - <i18n.Translate> - If your bank application allows you make a wire transfer using - standard QR codes then try scanning the following: - </i18n.Translate> - </p> - <table style={{ width: "100%" }}> - {qrs.map((q, idx) => { - return ( - <tr key={idx}> - <td style={{}}> - {(function () { - switch (q.type) { - case "epc-qr": - return ( - <QR_Bank - text={q.qrContent} - label={i18n.str`Bank app`} - /> - ); - case "spc": - return <QR_SwissBank text={q.qrContent} />; - default: - assertUnreachable(q.type); - } - })()} - </td> - </tr> - ); - })} - </table> - </div> + switch (instructions.subject.type) { + case "URI": + return ( + <ConfirmModal + title={i18n.str`Failed to get bank transfer instructions`} + onCancel={onCancel} + > + <div>unsupported instructions URI</div> + </ConfirmModal> + ); + case "CH_QR_BILL": + case "SIMPLE": { + const sendAmount = Amounts.parseOrThrow( + instructions.subject.credit_amount, + ); + return ( + <ConfirmModal + title={i18n.str`Validate bank account: ${from}`} + onCancel={onCancel} + confirm={ + all_ins.length > 1 + ? { + label: i18n.str`See other option`, + handler: simpleSafeHandler(() => { + setTargetIndex((idx) => (idx + 1) % all_ins.length); + }), + } + : undefined + } + > + {!showDebugInfo ? undefined : ( + <pre> + {JSON.stringify({ strPayto, payto, all_ins }, undefined, 2)} + </pre> )} - </td> - </tr> - <div class=""> - <table> - <tbody> - <tr> - <td colSpan={3}> - <i18n.Translate>Step 1:</i18n.Translate> - &nbsp; - <i18n.Translate> - Copy and paste this IBAN and the legal name of the beneficial - owner into the respective fields in your preferred banking app - or online banking website. - </i18n.Translate> - </td> - </tr> - {accountPart} - {receiverName ? ( - <Row name={i18n.str`Beneficiary`} value={receiverName} /> - ) : undefined} - <tr> - <td colSpan={3}> - <i18n.Translate>Step 2:</i18n.Translate> - &nbsp; - <i18n.Translate> - Copy this string and paste it into the 'Subject' or 'Purpose' - field in your preferred banking app or online banking website. - </i18n.Translate> - </td> - </tr> - <Row name={i18n.str`Subject`} value={subject} literal /> - {receiverPostalCode ? ( - <Row - name={i18n.str`Receiver postal code`} - value={receiverPostalCode} - /> - ) : undefined} - {receiverTown ? ( - <Row name={i18n.str`Receiver town`} value={receiverTown} /> - ) : undefined} + <p style={{ paddingTop: 0 }}> + <i18n.Translate> + In order to prove that you are the beneficial owner of the bank + account, you are required to wire a small amount to a specified + bank account with the subject below. + </i18n.Translate> + </p> + <tr> + <td colSpan={3}> + {!qrs.length ? undefined : ( + <div style={{ overflowY: "auto", height: "95%", padding: 5 }}> + <p> + <i18n.Translate> + If your bank application allows you make a wire transfer + using standard QR codes then try scanning the following: + </i18n.Translate> + </p> + <table style={{ width: "100%" }}> + {qrs.map((q, idx) => { + return ( + <tr key={idx}> + <td style={{}}> + {(function () { + switch (q.type) { + case "epc-qr": + return ( + <QR_Bank + text={q.qrContent} + label={i18n.str`Bank app`} + /> + ); + case "spc": + return <QR_SwissBank text={q.qrContent} />; - <tr> - <td colSpan={3}> - <i18n.Translate>Step 3:</i18n.Translate> - &nbsp; - <i18n.Translate> - Select the smallest possible amount for a wire transfer in - your preferred banking app or online banking website. - </i18n.Translate> - </td> - </tr> - <tr> - <td colSpan={3}> - <b> - <i18n.Translate> - Make sure ALL data is correct, especially the subject, and - that you are choosing the bank account from which you - definitely want to make the wire transfer. You can use the - copy buttons (<CopyIcon />) to avoid typos or make use of - the PayTo URI below to copy just one value. - </i18n.Translate> - </b> - </td> - </tr> - <tr> - <td colSpan={2} width="100%"> - <i18n.Translate> - As an alternative, in case your bank already supports the{" "} - <a - target="_blank" - rel="noreferrer" - title="RFC 8905 for designating targets for payments" - href="https://tools.ietf.org/html/rfc8905" - > - PayTo URI standard - </a> - , you can use this{" "} - <a href={Paytos.toFullString(payto)}>PayTo URI</a> link - instead. - </i18n.Translate> - </td> - <td> - <CopyButton getContent={() => Paytos.toFullString(payto)} /> - </td> - </tr> - </tbody> - </table> - </div> - </ConfirmModal> - ); + default: + assertUnreachable(q.type); + } + })()} + </td> + </tr> + ); + })} + </table> + </div> + )} + </td> + </tr> + <div class=""> + <table> + <tbody> + <tr> + <td colSpan={3}> + <i18n.Translate>Step 1:</i18n.Translate> + &nbsp; + <i18n.Translate> + Copy and paste this IBAN and the legal name of the + beneficial owner into the respective fields in your + preferred banking app or online banking website. + </i18n.Translate> + </td> + </tr> + {accountPart} + {receiverName ? ( + <Row name={i18n.str`Beneficiary`} value={receiverName} /> + ) : undefined} + <Row + name={i18n.str`Amount`} + value={ + <RenderAmountBulma + value={sendAmount} + specMap={config.currencies} + /> + } + /> + <tr> + <td colSpan={3}> + <i18n.Translate>Step 2:</i18n.Translate> + &nbsp; + <i18n.Translate> + Copy this string and paste it into the 'Subject' or + 'Purpose' field in your preferred banking app or online + banking website. + </i18n.Translate> + </td> + </tr> + {instructions.subject.type === "CH_QR_BILL" ? ( + <Row name={i18n.str`Reference`} value={instructions.subject.qr_reference_number} literal /> + ) : undefined} + {instructions.subject.type === "SIMPLE" ? ( + <Row name={i18n.str`Subject`} value={instructions.subject.subject} literal /> + ) : undefined} + {receiverPostalCode ? ( + <Row + name={i18n.str`Receiver postal code`} + value={receiverPostalCode} + /> + ) : undefined} + {receiverTown ? ( + <Row name={i18n.str`Receiver town`} value={receiverTown} /> + ) : undefined} + + <tr> + <td colSpan={3}> + <i18n.Translate>Step 3:</i18n.Translate> + &nbsp; + <i18n.Translate> + Select the smallest possible amount for a wire transfer in + your preferred banking app or online banking website. + </i18n.Translate> + </td> + </tr> + <tr> + <td colSpan={3}> + <b> + <i18n.Translate> + Make sure ALL data is correct, especially the subject, + and that you are choosing the bank account from which + you definitely want to make the wire transfer. You can + use the copy buttons (<CopyIcon />) to avoid typos or + make use of the PayTo URI below to copy just one value. + </i18n.Translate> + </b> + </td> + </tr> + <tr> + <td colSpan={2} width="100%"> + <i18n.Translate> + As an alternative, in case your bank already supports the{" "} + <a + target="_blank" + rel="noreferrer" + title="RFC 8905 for designating targets for payments" + href="https://tools.ietf.org/html/rfc8905" + > + PayTo URI standard + </a> + , you can use this{" "} + <a href={Paytos.toFullString(payto)}>PayTo URI</a> link + instead. + </i18n.Translate> + </td> + <td> + <CopyButton getContent={() => Paytos.toFullString(payto)} /> + </td> + </tr> + </tbody> + </table> + </div> + </ConfirmModal> + ); + } + default: + assertUnreachable(instructions.subject); + } } function Accordion({ diff --git a/packages/taler-merchant-webui/src/paths/instance/kyc/list/index.tsx b/packages/taler-merchant-webui/src/paths/instance/kyc/list/index.tsx @@ -163,6 +163,8 @@ function ShowInstructionForKycRedirect({ targets={tgs} onCancel={onCancel} currency={e.exchange_currency} + exchangeBaseUrl={e.exchange_url} + wireAccountHash={e.h_wire} /> ); case TalerMerchantApi.MerchantAccountKycStatus.NO_EXCHANGE_KEY: {