update SPA for #7810

This commit is contained in:
Sebastian 2023-05-02 12:56:09 -03:00
parent a957e61a9c
commit da519af01f
No known key found for this signature in database
GPG Key ID: 173909D1A5F66069
4 changed files with 228 additions and 60 deletions

View File

@ -48,6 +48,64 @@ type Amount = string;
type UUID = string; type UUID = string;
type Integer = number; 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 { export namespace ExchangeBackend {
interface WireResponse { interface WireResponse {
// Master public key of the exchange, must match the key returned in /keys. // Master public key of the exchange, must match the key returned in /keys.
@ -61,14 +119,6 @@ export namespace ExchangeBackend {
// to wire fees. // to wire fees.
fees: { method: AggregateTransferFee }; 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 { interface AggregateTransferFee {
// Per transfer wire transfer fee. // Per transfer wire transfer fee.
wire_fee: Amount; wire_fee: Amount;
@ -1028,8 +1078,8 @@ export namespace MerchantBackend {
// Public key identifying the reserve // Public key identifying the reserve
reserve_pub: EddsaPublicKey; reserve_pub: EddsaPublicKey;
// Wire account of the exchange where to transfer the funds // Wire accounts of the exchange where to transfer the funds.
payto_uri: string; accounts: WireAccount[];
} }
interface TipCreateRequest { interface TipCreateRequest {
// Amount that the customer should be tipped // 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)? // Is this reserve active (false if it was deleted but not purged)?
active: boolean; 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 // if the reserve is inactive or was already filled
payto_uri: string; accounts?: WireAccount[];
// URL of the exchange hosting the reserve, // URL of the exchange hosting the reserve,
// NULL if the reserve is inactive // NULL if the reserve is inactive

View File

@ -16,3 +16,4 @@
export * as details from "./details/stories.js"; export * as details from "./details/stories.js";
export * as kycList from "./kyc/list/ListPage.stories.js"; export * as kycList from "./kyc/list/ListPage.stories.js";
export * as reserve from "./reserves/create/CreatedSuccessfully.stories.js";

View File

@ -21,6 +21,7 @@
import { h, VNode, FunctionalComponent } from "preact"; import { h, VNode, FunctionalComponent } from "preact";
import { CreatedSuccessfully as TestedComponent } from "./CreatedSuccessfully.js"; import { CreatedSuccessfully as TestedComponent } from "./CreatedSuccessfully.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
export default { export default {
title: "Pages/Reserve/CreatedSuccessfully", title: "Pages/Reserve/CreatedSuccessfully",
@ -31,16 +32,7 @@ export default {
}, },
}; };
function createExample<Props>( export const OneBankAccount = tests.createExample(TestedComponent, {
Component: FunctionalComponent<Props>,
props: Partial<Props>,
) {
const r = (args: any) => <Component {...args} />;
r.args = props;
return r;
}
export const Example = createExample(TestedComponent, {
entity: { entity: {
request: { request: {
exchange_url: "http://exchange.taler/", exchange_url: "http://exchange.taler/",
@ -48,7 +40,80 @@ export const Example = createExample(TestedComponent, {
wire_method: "x-taler-bank", wire_method: "x-taler-bank",
}, },
response: { response: {
payto_uri: "payto://x-taler-bank/bank.taler:8080/exchange_account", 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 NoBankAccount = tests.createExample(TestedComponent, {
entity: {
request: {
exchange_url: "http://exchange.taler/",
initial_balance: "TESTKUDOS:1",
wire_method: "x-taler-bank",
},
response: {
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", reserve_pub: "WEQWDASDQWEASDADASDQWEQWEASDAS",
}, },
}, },

View File

@ -16,7 +16,7 @@
import { parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util"; import { parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; 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 { QR } from "../../../../components/exception/QR.js";
import { CreatedSuccessfully as Template } from "../../../../components/notifications/CreatedSuccessfully.js"; import { CreatedSuccessfully as Template } from "../../../../components/notifications/CreatedSuccessfully.js";
import { MerchantBackend } from "../../../../declaration.js"; import { MerchantBackend } from "../../../../declaration.js";
@ -32,18 +32,29 @@ interface Props {
onCreateAnother?: () => void; onCreateAnother?: () => void;
} }
function isNotUndefined<X>(x: X | undefined): x is X {
return !!x;
}
export function CreatedSuccessfully({ export function CreatedSuccessfully({
entity, entity,
onConfirm, onConfirm,
onCreateAnother, onCreateAnother,
}: Props): VNode { }: Props): VNode {
const p = parsePaytoUri(entity.response.payto_uri); const accountsInfo = !entity.response.accounts
if (p) { ? []
p.params["message"] = entity.response.reserve_pub; : entity.response.accounts
p.params["amount"] = entity.request.initial_balance; .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(); const { i18n } = useTranslationContext();
return ( return (
<Template onConfirm={onConfirm} onCreateAnother={onCreateAnother}> <Template onConfirm={onConfirm} onCreateAnother={onCreateAnother}>
@ -63,18 +74,6 @@ export function CreatedSuccessfully({
</div> </div>
</div> </div>
</div> </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 is-horizontal">
<div class="field-label is-normal"> <div class="field-label is-normal">
<label class="label">Wire transfer subject</label> <label class="label">Wire transfer subject</label>
@ -91,24 +90,76 @@ export function CreatedSuccessfully({
</div> </div>
</div> </div>
</div> </div>
<p class="is-size-5"> {links.length === 0 ? (
<i18n.Translate> <Fragment>
To complete the setup of the reserve, you must now initiate a wire <p class="is-size-5">
transfer using the given wire transfer subject and crediting the The response of the reserve creation have invalid accounts. List of
specified amount to the indicated account of the exchange. invalid payto URIs below:
</i18n.Translate> </p>
</p> <ul>
<p class="is-size-5"> {entity.response.accounts.map((a, idx) => {
<i18n.Translate> return <li key={idx}>{a.payto_uri}</li>;
If your system supports RFC 8905, you can do this by opening this URI: })}
</i18n.Translate> </ul>
</p> </Fragment>
<pre> ) : links.length === 1 ? (
<a target="_blank" rel="noreferrer" href={link}> <Fragment>
{link} <p class="is-size-5">
</a> <i18n.Translate>
</pre> To complete the setup of the reserve, you must now initiate a wire
<QR text={link} /> 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> </Template>
); );
} }