commit 8c1ace014422fb8e0abae1921b015646dc633019
parent 68f2a74022ae484e93e3db7fa2c067cad543773f
Author: Sebastian <sebasjm@gmail.com>
Date: Fri, 11 Apr 2025 12:06:09 -0300
new PostOrder api
Diffstat:
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 {