aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/merchant-backoffice-ui/src/declaration.d.ts75
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/index.stories.ts1
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatedSuccessfully.stories.tsx85
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatedSuccessfully.tsx125
4 files changed, 227 insertions, 59 deletions
diff --git a/packages/merchant-backoffice-ui/src/declaration.d.ts b/packages/merchant-backoffice-ui/src/declaration.d.ts
index 58e14a114..2c5730666 100644
--- a/packages/merchant-backoffice-ui/src/declaration.d.ts
+++ b/packages/merchant-backoffice-ui/src/declaration.d.ts
@@ -48,6 +48,64 @@ type Amount = string;
type UUID = string;
type Integer = number;
+interface WireAccount {
+ // payto:// URI identifying the account and wire method
+ payto_uri: string;
+
+ // URI to convert amounts from or to the currency used by
+ // this wire account of the exchange. Missing if no
+ // conversion is applicable.
+ conversion_url?: string;
+
+ // Restrictions that apply to bank accounts that would send
+ // funds to the exchange (crediting this exchange bank account).
+ // Optional, empty array for unrestricted.
+ credit_restrictions: AccountRestriction[];
+
+ // Restrictions that apply to bank accounts that would receive
+ // funds from the exchange (debiting this exchange bank account).
+ // Optional, empty array for unrestricted.
+ debit_restrictions: AccountRestriction[];
+
+ // Signature using the exchange's offline key over
+ // a TALER_MasterWireDetailsPS
+ // with purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS.
+ master_sig: EddsaSignature;
+}
+
+type AccountRestriction = RegexAccountRestriction | DenyAllAccountRestriction;
+
+// Account restriction that disables this type of
+// account for the indicated operation categorically.
+interface DenyAllAccountRestriction {
+ type: "deny";
+}
+
+// Accounts interacting with this type of account
+// restriction must have a payto://-URI matching
+// the given regex.
+interface RegexAccountRestriction {
+ type: "regex";
+
+ // Regular expression that the payto://-URI of the
+ // partner account must follow. The regular expression
+ // should follow posix-egrep, but without support for character
+ // classes, GNU extensions, back-references or intervals. See
+ // https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html
+ // for a description of the posix-egrep syntax. Applications
+ // may support regexes with additional features, but exchanges
+ // must not use such regexes.
+ payto_regex: string;
+
+ // Hint for a human to understand the restriction
+ // (that is hopefully easier to comprehend than the regex itself).
+ human_hint: string;
+
+ // Map from IETF BCP 47 language tags to localized
+ // human hints.
+ human_hint_i18n?: { [lang_tag: string]: string };
+}
+
export namespace ExchangeBackend {
interface WireResponse {
// Master public key of the exchange, must match the key returned in /keys.
@@ -61,14 +119,6 @@ export namespace ExchangeBackend {
// to wire fees.
fees: { method: AggregateTransferFee };
}
- interface WireAccount {
- // payto:// URI identifying the account and wire method
- payto_uri: string;
-
- // Signature using the exchange's offline key
- // with purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS.
- master_sig: EddsaSignature;
- }
interface AggregateTransferFee {
// Per transfer wire transfer fee.
wire_fee: Amount;
@@ -1028,8 +1078,8 @@ export namespace MerchantBackend {
// Public key identifying the reserve
reserve_pub: EddsaPublicKey;
- // Wire account of the exchange where to transfer the funds
- payto_uri: string;
+ // Wire accounts of the exchange where to transfer the funds.
+ accounts: WireAccount[];
}
interface TipCreateRequest {
// Amount that the customer should be tipped
@@ -1084,9 +1134,10 @@ export namespace MerchantBackend {
// Is this reserve active (false if it was deleted but not purged)?
active: boolean;
- // URI to use to fill the reserve, can be NULL
+ // Array of wire accounts of the exchange that could
+ // be used to fill the reserve, can be NULL
// if the reserve is inactive or was already filled
- payto_uri: string;
+ accounts?: WireAccount[];
// URL of the exchange hosting the reserve,
// NULL if the reserve is inactive
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/index.stories.ts b/packages/merchant-backoffice-ui/src/paths/instance/index.stories.ts
index 50918e131..1d8c76ff9 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/index.stories.ts
+++ b/packages/merchant-backoffice-ui/src/paths/instance/index.stories.ts
@@ -16,3 +16,4 @@
export * as details from "./details/stories.js";
export * as kycList from "./kyc/list/ListPage.stories.js";
+export * as reserve from "./reserves/create/CreatedSuccessfully.stories.js";
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatedSuccessfully.stories.tsx b/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatedSuccessfully.stories.tsx
index 1d848a033..4b634c6c0 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatedSuccessfully.stories.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatedSuccessfully.stories.tsx
@@ -21,6 +21,7 @@
import { h, VNode, FunctionalComponent } from "preact";
import { CreatedSuccessfully as TestedComponent } from "./CreatedSuccessfully.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
export default {
title: "Pages/Reserve/CreatedSuccessfully",
@@ -31,16 +32,65 @@ export default {
},
};
-function createExample<Props>(
- Component: FunctionalComponent<Props>,
- props: Partial<Props>,
-) {
- const r = (args: any) => <Component {...args} />;
- r.args = props;
- return r;
-}
+export const OneBankAccount = tests.createExample(TestedComponent, {
+ entity: {
+ request: {
+ exchange_url: "http://exchange.taler/",
+ initial_balance: "TESTKUDOS:1",
+ wire_method: "x-taler-bank",
+ },
+ response: {
+ accounts: [
+ {
+ payto_uri: "payto://x-taler-bank/bank.taler:8080/exchange_account",
+ credit_restrictions: [],
+ debit_restrictions: [],
+ master_sig: "asd",
+ conversion_url: "",
+ },
+ ],
+ reserve_pub: "WEQWDASDQWEASDADASDQWEQWEASDAS",
+ },
+ },
+});
+
+export const ThreeBankAccount = tests.createExample(TestedComponent, {
+ entity: {
+ request: {
+ exchange_url: "http://exchange.taler/",
+ initial_balance: "TESTKUDOS:1",
+ wire_method: "x-taler-bank",
+ },
+ response: {
+ accounts: [
+ {
+ payto_uri: "payto://x-taler-bank/bank.taler:8080/exchange_account",
+ credit_restrictions: [],
+ debit_restrictions: [],
+ master_sig: "asd",
+ conversion_url: "",
+ },
+ {
+ payto_uri: "payto://x-taler-bank/bank1.taler:8080/asd",
+ credit_restrictions: [],
+ debit_restrictions: [],
+ master_sig: "asd",
+ conversion_url: "",
+ },
+ {
+ payto_uri: "payto://x-taler-bank/bank2.taler:8080/qwe",
+ credit_restrictions: [],
+ debit_restrictions: [],
+ master_sig: "asd",
+ conversion_url: "",
+ },
+ ],
+ reserve_pub: "WEQWDASDQWEASDADASDQWEQWEASDAS",
+ },
+ },
+});
-export const Example = createExample(TestedComponent, {
+export const NoBankAccount = tests.createExample(TestedComponent, {
entity: {
request: {
exchange_url: "http://exchange.taler/",
@@ -48,7 +98,22 @@ export const Example = createExample(TestedComponent, {
wire_method: "x-taler-bank",
},
response: {
- payto_uri: "payto://x-taler-bank/bank.taler:8080/exchange_account",
+ accounts: [
+ {
+ payto_uri: "payo://x-talr-bank/bank.taler:8080/exchange_account",
+ credit_restrictions: [],
+ debit_restrictions: [],
+ master_sig: "asd",
+ conversion_url: "",
+ },
+ {
+ payto_uri: "payto://x-taler-bank",
+ credit_restrictions: [],
+ debit_restrictions: [],
+ master_sig: "asd",
+ conversion_url: "",
+ },
+ ],
reserve_pub: "WEQWDASDQWEASDADASDQWEQWEASDAS",
},
},
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatedSuccessfully.tsx b/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatedSuccessfully.tsx
index b82907122..7ba531a94 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatedSuccessfully.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatedSuccessfully.tsx
@@ -16,7 +16,7 @@
import { parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
-import { h, VNode } from "preact";
+import { Fragment, h, VNode } from "preact";
import { QR } from "../../../../components/exception/QR.js";
import { CreatedSuccessfully as Template } from "../../../../components/notifications/CreatedSuccessfully.js";
import { MerchantBackend } from "../../../../declaration.js";
@@ -32,18 +32,29 @@ interface Props {
onCreateAnother?: () => void;
}
+function isNotUndefined<X>(x: X | undefined): x is X {
+ return !!x;
+}
+
export function CreatedSuccessfully({
entity,
onConfirm,
onCreateAnother,
}: Props): VNode {
- const p = parsePaytoUri(entity.response.payto_uri);
- if (p) {
- p.params["message"] = entity.response.reserve_pub;
- p.params["amount"] = entity.request.initial_balance;
- }
+ const accountsInfo = !entity.response.accounts
+ ? []
+ : entity.response.accounts
+ .map((acc) => {
+ const p = parsePaytoUri(acc.payto_uri);
+ if (p) {
+ p.params["message"] = entity.response.reserve_pub;
+ p.params["amount"] = entity.request.initial_balance;
+ }
+ return p;
+ })
+ .filter(isNotUndefined);
- const link = !p ? entity.response.payto_uri : stringifyPaytoUri(p);
+ const links = accountsInfo.map((a) => stringifyPaytoUri(a));
const { i18n } = useTranslationContext();
return (
<Template onConfirm={onConfirm} onCreateAnother={onCreateAnother}>
@@ -65,18 +76,6 @@ export function CreatedSuccessfully({
</div>
<div class="field is-horizontal">
<div class="field-label is-normal">
- <label class="label">Exchange bank account</label>
- </div>
- <div class="field-body is-flex-grow-3">
- <div class="field">
- <p class="control">
- <input readonly class="input" value={entity.response.payto_uri} />
- </p>
- </div>
- </div>
- </div>
- <div class="field is-horizontal">
- <div class="field-label is-normal">
<label class="label">Wire transfer subject</label>
</div>
<div class="field-body is-flex-grow-3">
@@ -91,24 +90,76 @@ export function CreatedSuccessfully({
</div>
</div>
</div>
- <p class="is-size-5">
- <i18n.Translate>
- To complete the setup of the reserve, you must now initiate a wire
- transfer using the given wire transfer subject and crediting the
- specified amount to the indicated account of the exchange.
- </i18n.Translate>
- </p>
- <p class="is-size-5">
- <i18n.Translate>
- If your system supports RFC 8905, you can do this by opening this URI:
- </i18n.Translate>
- </p>
- <pre>
- <a target="_blank" rel="noreferrer" href={link}>
- {link}
- </a>
- </pre>
- <QR text={link} />
+ {links.length === 0 ? (
+ <Fragment>
+ <p class="is-size-5">
+ The response of the reserve creation have invalid accounts. List of
+ invalid payto URIs below:
+ </p>
+ <ul>
+ {entity.response.accounts.map((a, idx) => {
+ return <li key={idx}>{a.payto_uri}</li>;
+ })}
+ </ul>
+ </Fragment>
+ ) : links.length === 1 ? (
+ <Fragment>
+ <p class="is-size-5">
+ <i18n.Translate>
+ To complete the setup of the reserve, you must now initiate a wire
+ transfer using the given wire transfer subject and crediting the
+ specified amount to the indicated account of the exchange.
+ </i18n.Translate>
+ </p>
+ <p style={{ margin: 10 }}>
+ <b>Exchange bank account</b>
+ </p>
+ <QR text={links[0]} />
+ <p class="is-size-5">
+ <i18n.Translate>
+ If your system supports RFC 8905, you can do this by opening this
+ URI:
+ </i18n.Translate>
+ </p>
+ <pre>
+ <a target="_blank" rel="noreferrer" href={links[0]}>
+ {links[0]}
+ </a>
+ </pre>
+ </Fragment>
+ ) : (
+ <div>
+ <p class="is-size-5">
+ <i18n.Translate>
+ To complete the setup of the reserve, you must now initiate a wire
+ transfer using the given wire transfer subject and crediting the
+ specified amount to one of the indicated account of the exchange.
+ </i18n.Translate>
+ </p>
+
+ <p style={{ margin: 10 }}>
+ <b>Exchange bank accounts</b>
+ </p>
+ <p class="is-size-5">
+ <i18n.Translate>
+ If your system supports RFC 8905, you can do this by clicking on
+ the URI below the QR code:
+ </i18n.Translate>
+ </p>
+ {links.map((link) => {
+ return (
+ <Fragment>
+ <QR text={link} />
+ <pre>
+ <a target="_blank" rel="noreferrer" href={link}>
+ {link}
+ </a>
+ </pre>
+ </Fragment>
+ );
+ })}
+ </div>
+ )}
</Template>
);
}