taler-typescript-core

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

commit 8c1ace014422fb8e0abae1921b015646dc633019
parent 68f2a74022ae484e93e3db7fa2c067cad543773f
Author: Sebastian <sebasjm@gmail.com>
Date:   Fri, 11 Apr 2025 12:06:09 -0300

new PostOrder api

Diffstat:
Mpackages/merchant-backoffice-ui/src/paths/instance/kyc/list/ListPage.tsx | 21+++++++++------------
Mpackages/merchant-backoffice-ui/src/paths/instance/kyc/list/index.tsx | 32++++++++++++++++++--------------
Mpackages/merchant-backoffice-ui/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx | 75+++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mpackages/taler-harness/src/index.ts | 8++++----
Mpackages/taler-util/src/types-taler-merchant.ts | 187++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
5 files changed, 262 insertions(+), 61 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/ListPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/ListPage.tsx @@ -143,25 +143,22 @@ function PendingTable({ switch (e.status) { case TalerMerchantApi.MerchantAccountKycStatus .KYC_WIRE_REQUIRED: - // action required return ( <i18n.Translate> - KYC wire transfer required, click for details. + Bank account verification required. </i18n.Translate> ); case TalerMerchantApi.MerchantAccountKycStatus .KYC_REQUIRED: - // action required return ( <i18n.Translate> - KYC required, click here to proceed + More information required. </i18n.Translate> ); case TalerMerchantApi.MerchantAccountKycStatus .AWAITING_AML_REVIEW: - // FIXME: can the account be used? return ( - <i18n.Translate>Awaiting AML review</i18n.Translate> + <i18n.Translate>Awaiting for account review.</i18n.Translate> ); case TalerMerchantApi.MerchantAccountKycStatus.READY: return <i18n.Translate>Ready</i18n.Translate>; @@ -170,14 +167,14 @@ function PendingTable({ .NO_EXCHANGE_KEY: return ( <i18n.Translate> - Updating exchange keys... + Syncing... </i18n.Translate> ); case TalerMerchantApi.MerchantAccountKycStatus .EXCHANGE_INTERNAL_ERROR: return ( <i18n.Translate> - Exchange internal error. Contact administrator or + Payment service internal error. Contact administrator or check again later. </i18n.Translate> ); @@ -185,7 +182,7 @@ function PendingTable({ .EXCHANGE_GATEWAY_TIMEOUT: return ( <i18n.Translate> - Exchange timeout. Contact administrator or check + Payment service timeout. Contact administrator or check again later. </i18n.Translate> ); @@ -193,7 +190,7 @@ function PendingTable({ .EXCHANGE_UNREACHABLE: return ( <i18n.Translate> - Exchange unreachable. Contact administrator or check + Payment service unreachable. Contact administrator or check again later. </i18n.Translate> ); @@ -207,7 +204,7 @@ function PendingTable({ case TalerMerchantApi.MerchantAccountKycStatus.LOGIC_BUG: return ( <i18n.Translate> - Merchant internal error. Contact administrator or + Backend internal error. Contact administrator or check again later. </i18n.Translate> ); @@ -216,7 +213,7 @@ function PendingTable({ .EXCHANGE_STATUS_INVALID: return ( <i18n.Translate> - Exchange response is invalid. Contact administrator + Payment service response is invalid. Contact administrator or check again later. </i18n.Translate> ); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/index.tsx @@ -22,23 +22,21 @@ import { HttpStatusCode, TalerError, - TalerExchangeHttpClient, TalerMerchantApi, assertUnreachable, parsePaytoUri, } from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; +import { useState } from "preact/hooks"; import { ErrorLoadingMerchant } from "../../../../components/ErrorLoadingMerchant.js"; import { Loading } from "../../../../components/exception/loading.js"; -import { useInstanceKYCDetails } from "../../../../hooks/instance.js"; -import { ListPage } from "./ListPage.js"; -import { useState } from "preact/hooks"; import { ConfirmModal, ValidBankAccount, } from "../../../../components/modal/index.js"; -import { useTransition } from "preact/compat"; -import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { useInstanceKYCDetails } from "../../../../hooks/instance.js"; +import { ListPage } from "./ListPage.js"; interface Props { // onGetInfo: (id: string) => void; @@ -47,6 +45,7 @@ interface Props { export default function ListKYC(_p: Props): VNode { const result = useInstanceKYCDetails(); + const [showingInstructions, setShowingInstructions] = useState< TalerMerchantApi.MerchantAccountKycRedirect | undefined >(undefined); @@ -171,18 +170,23 @@ function ShowInstructionForKycRedirect({ case TalerMerchantApi.MerchantAccountKycStatus.KYC_REQUIRED: { return ( <ConfirmModal - label={i18n.str`Ok`} + label={i18n.str`Test`} description={i18n.str`KYC Required`} active onCancel={onCancel} > - <a href="#"> - <p style={{ paddingTop: 0 }}> - <i18n.Translate> - The payment provider requires information to enable the account. - </i18n.Translate> - </p> - </a> + <p style={{ paddingTop: 0 }}> + <i18n.Translate> + The payment provider requires more information. + </i18n.Translate>{" "} + <a + href={`${e.exchange_url}kyc-spa/${e.access_token}`} + target="_blank" + rel="noreferrer" + > + <i18n.Translate>Click here to complete this step.</i18n.Translate> + </a> + </p> </ConfirmModal> ); } diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx @@ -13,7 +13,11 @@ You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { HttpStatusCode, TalerError, assertUnreachable } from "@gnu-taler/taler-util"; +import { + HttpStatusCode, + TalerError, + assertUnreachable, +} from "@gnu-taler/taler-util"; import { Loading, useTranslationContext } from "@gnu-taler/web-util/browser"; import { VNode, h } from "preact"; import { ErrorLoadingMerchant } from "../../../../components/ErrorLoadingMerchant.js"; @@ -34,17 +38,17 @@ export function OrderCreatedSuccessfully({ onConfirm, onCreateAnother, }: Props): VNode { - const result = useOrderDetails(entity.response.order_id) + const result = useOrderDetails(entity.response.order_id); const { i18n } = useTranslationContext(); - if (!result) return <Loading /> + if (!result) return <Loading />; if (result instanceof TalerError) { - return <ErrorLoadingMerchant error={result} /> + return <ErrorLoadingMerchant error={result} />; } if (result.type === "fail") { - switch(result.case) { + switch (result.case) { case HttpStatusCode.NotFound: { - return <NotFoundPageOrAdminCreate /> + return <NotFoundPageOrAdminCreate />; } case HttpStatusCode.BadGateway: { return <div>Failed to obtain a response from the exchange</div>; @@ -55,41 +59,60 @@ export function OrderCreatedSuccessfully({ ); } case HttpStatusCode.Unauthorized: { - return <LoginPage /> + return <LoginPage />; } default: { - assertUnreachable(result) + assertUnreachable(result); } } } - const url = result.body.order_status === "unpaid" ? - result.body.taler_pay_uri : - result.body.contract_terms.fulfillment_url + const url = + result.body.order_status === "unpaid" + ? result.body.taler_pay_uri + : result.body.contract_terms.fulfillment_url; return ( <CreatedSuccessfully onConfirm={onConfirm} onCreateAnother={onCreateAnother} > - <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - <i18n.Translate>Amount</i18n.Translate> - </label> + {!entity.request.order.version ? ( + <div class="field is-horizontal"> + <div class="field-label is-normal"> + <label class="label"> + <i18n.Translate>Amount</i18n.Translate> + </label> + </div> + <div class="field-body is-flex-grow-3"> + <div class="field"> + <p class="control"> + <input + class="input" + readonly + value={entity.request.order.amount} + /> + </p> + </div> + </div> </div> - <div class="field-body is-flex-grow-3"> - <div class="field"> - <p class="control"> - <input - class="input" - readonly - value={entity.request.order.amount} - /> - </p> + ) : ( + <div class="field is-horizontal"> + <div class="field-label is-normal"> + <label class="label"> + <i18n.Translate>Choices</i18n.Translate> + </label> + </div> + <div class="field-body is-flex-grow-3"> + <div class="field"> + <p class="control"> + <i18n.Translate>This feature is not yet supported</i18n.Translate> + </p> + </div> </div> </div> - </div> + )} + <div class="field is-horizontal"> <div class="field-label is-normal"> <label class="label"> diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts @@ -222,9 +222,9 @@ testingCli order: { amount: "TESTKUDOS:42", summary: "Hello", - // Intentional, test.taler.net merchant does not support it. - wire_method: "bitcoin", }, + // Intentional, test.taler.net merchant does not support it. + payment_target: "bitcoin", }), ); const orderId = createResp.order_id; @@ -1716,10 +1716,10 @@ export const merchantCli = talerHarnessCli.subcommand("merchant", "merchant", { merchantCli .subcommand("checkKyc", "check-kyc", { - help: "gets updated information about the kyc state" + help: "gets updated information about the kyc state", }) .requiredArgument("config", clk.STRING, { - help: "configuration file" + help: "configuration file", }) .maybeOption("id", ["--id"], clk.STRING, { help: "selected instance id of the merchant backend, default to 'admin'", diff --git a/packages/taler-util/src/types-taler-merchant.ts b/packages/taler-util/src/types-taler-merchant.ts @@ -2190,16 +2190,139 @@ export interface PostOrderRequest { otp_id?: string; } -export type Order = MinimalOrderDetail & Partial<ContractTerms>; +export type Order = (OrderV1 | OrderV0) & OrderCommon; + +// export interface MinimalOrderDetail { +// // Amount to be paid by the customer. +// amount: AmountString; + +// // Short summary of the order. +// summary: string; + +// // See documentation of fulfillment_url in ContractTerms. +// // Either fulfillment_url or fulfillment_message must be specified. +// // When creating an order, the fulfillment URL can +// // contain ${ORDER_ID} which will be substituted with the +// // order ID of the newly created order. +// fulfillment_url?: string; + +// // See documentation of fulfillment_message in ContractTerms. +// // Either fulfillment_url or fulfillment_message must be specified. +// fulfillment_message?: string; +// } + +export interface OrderV1 { + // Version 1 order support discounts and subscriptions. + // https://docs.taler.net/design-documents/046-mumimo-contracts.html + // @since protocol **vSUBSCRIBE** + version: 1; + + // List of contract choices that the customer can select from. + // @since protocol **vSUBSCRIBE** + choices?: OrderChoice[]; +} -export interface MinimalOrderDetail { - // Amount to be paid by the customer. +export interface OrderV0 { + // Optional, defaults to 0 if not set. + version?: 0; + + // Total price for the transaction. The exchange will subtract deposit + // fees from that amount before transferring it to the merchant. amount: AmountString; - // Short summary of the order. + // Maximum total deposit fee accepted by the merchant for this contract. + // Overrides defaults of the merchant instance. + max_fee?: AmountString; +} + +export interface OrderChoice { + // Total price for the choice. The exchange will subtract deposit + // fees from that amount before transferring it to the merchant. + amount: AmountString; + + // Inputs that must be provided by the customer, if this choice is selected. + // Defaults to empty array if not specified. + inputs?: OrderInput[]; + + // Outputs provided by the merchant, if this choice is selected. + // Defaults to empty array if not specified. + outputs?: OrderOutput[]; + + // Maximum total deposit fee accepted by the merchant for this contract. + // Overrides defaults of the merchant instance. + max_fee?: AmountString; +} + +// For now, only token inputs are supported. +export type OrderInput = OrderInputToken; + +export interface OrderInputToken { + // Token input. + type: "token"; + + // Token family slug as configured in the merchant backend. Slug is unique + // across all configured tokens of a merchant. + token_family_slug: string; + + // How many units of the input are required. + // Defaults to 1 if not specified. Output with count == 0 are ignored by + // the merchant backend. + count?: Integer; +} + +export type OrderOutput = OrderOutputToken | OrderOutputTaxReceipt; + +export interface OrderOutputToken { + // Token output. + type: "token"; + + // Token family slug as configured in the merchant backend. Slug is unique + // across all configured tokens of a merchant. + token_family_slug: string; + + // How many units of the output are issued by the merchant. + // Defaults to 1 if not specified. Output with count == 0 are ignored by + // the merchant backend. + count?: Integer; + + // When should the output token be valid. Can be specified if the + // desired validity period should be in the future (like selling + // a subscription for the next month). Optional. If not given, + // the validity is supposed to be "now" (time of order creation). + valid_at?: Timestamp; +} + +export interface OrderOutputTaxReceipt { + // Tax receipt output. + type: "tax-receipt"; + + // Total amount that will be on the tax receipt. + // Optional, if missing the full amount will be on the receipt. + amount?: AmountString; +} +export interface OrderCommon { + // Human-readable description of the whole purchase. summary: string; - // See documentation of fulfillment_url in ContractTerms. + // Map from IETF BCP 47 language tags to localized summaries. + summary_i18n?: { [lang_tag: string]: string }; + + // Unique identifier for the order. Only characters + // allowed are "A-Za-z0-9" and ".:_-". + // Must be unique within a merchant instance. + // For merchants that do not store proposals in their DB + // before the customer paid for them, the order_id can be used + // by the frontend to restore a proposal from the information + // encoded in it (such as a short product identifier and timestamp). + order_id?: string; + + // URL where the same contract could be ordered again (if + // available). Returned also at the public order endpoint + // for people other than the actual buyer (hence public, + // in case order IDs are guessable). + public_reorder_url?: string; + + // See documentation of fulfillment_url field in ContractTerms. // Either fulfillment_url or fulfillment_message must be specified. // When creating an order, the fulfillment URL can // contain ${ORDER_ID} which will be substituted with the @@ -2209,6 +2332,60 @@ export interface MinimalOrderDetail { // See documentation of fulfillment_message in ContractTerms. // Either fulfillment_url or fulfillment_message must be specified. fulfillment_message?: string; + + // Map from IETF BCP 47 language tags to localized fulfillment + // messages. + fulfillment_message_i18n?: { [lang_tag: string]: string }; + + // Minimum age the buyer must have to buy. + minimum_age?: Integer; + + // List of products that are part of the purchase. + products?: Product[]; + + // Time when this contract was generated. If null, defaults to current + // time of merchant backend. + timestamp?: Timestamp; + + // After this deadline has passed, no refunds will be accepted. + // Overrides deadline calculated from refund_delay in + // PostOrderRequest. + refund_deadline?: Timestamp; + + // After this deadline, the merchant won't accept payments for the contract. + // Overrides deadline calculated from default pay delay configured in + // merchant backend. + pay_deadline?: Timestamp; + + // Transfer deadline for the exchange. Must be in the deposit permissions + // of coins used to pay for this order. + // Overrides deadline calculated from default wire transfer delay + // configured in merchant backend. Must be after refund deadline. + wire_transfer_deadline?: Timestamp; + + // Base URL of the (public!) merchant backend API. + // Must be an absolute URL that ends with a slash. + // Defaults to the base URL this request was made to. + merchant_base_url?: string; + + // Delivery location for (all!) products. + delivery_location?: Location; + + // Time indicating when the order should be delivered. + // May be overwritten by individual products. + // Must be in the future. + delivery_date?: Timestamp; + + // See documentation of auto_refund in ContractTerms. + // Specifies for how long the wallet should try to get an + // automatic refund for the purchase. + auto_refund?: RelativeTime; + + // Extra data that is only interpreted by the merchant frontend. + // Useful when the merchant needs to store extra information on a + // contract without storing it separately in their database. + // Must really be an Object (not a string, integer, float or array). + extra?: Object; } export interface MinimalInventoryProduct {