taler-typescript-core

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

commit e256276effc7df369add3ca463c4d36921a5820e
parent 1c51dc52b0b4e9cecd208f2f596fa2eb6dc8e702
Author: Sebastian <sebasjm@gmail.com>
Date:   Tue, 11 Nov 2025 16:54:39 -0300

merchant protocol v23

Diffstat:
Mpackages/aml-backoffice-ui/src/pages/AccountList.tsx | 9++++-----
Mpackages/merchant-backoffice-ui/src/hooks/instance.ts | 2+-
Mpackages/merchant-backoffice-ui/src/hooks/order.test.ts | 75++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mpackages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx | 3+++
Mpackages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx | 9++++++---
Mpackages/merchant-backoffice-ui/src/paths/instance/orders/details/Detail.stories.tsx | 2++
Mpackages/merchant-backoffice-ui/src/paths/instance/update/Update.stories.tsx | 4+++-
Mpackages/taler-util/src/http-client/merchant.ts | 26++++++++++++++++++++++----
Mpackages/taler-util/src/types-taler-merchant.ts | 316+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
9 files changed, 301 insertions(+), 145 deletions(-)

diff --git a/packages/aml-backoffice-ui/src/pages/AccountList.tsx b/packages/aml-backoffice-ui/src/pages/AccountList.tsx @@ -20,9 +20,8 @@ import { HttpStatusCode, Paytos, TalerError, - TalerProtocolTimestamp, assertUnreachable, - opFixedSuccess, + opFixedSuccess } from "@gnu-taler/taler-util"; import { Attention, @@ -39,12 +38,12 @@ import { import { Fragment, VNode, h } from "preact"; import { useAmlAccounts } from "../hooks/decisions.js"; +import { format } from "date-fns"; import { useEffect, useState } from "preact/hooks"; -import { useOfficer } from "../hooks/officer.js"; -import { Profile } from "./Profile.js"; import csvIcon from "../assets/csv-icon.png"; import xlsIcon from "../assets/excel-icon.png"; -import { format } from "date-fns"; +import { useOfficer } from "../hooks/officer.js"; +import { Profile } from "./Profile.js"; export function AccountList({ routeToAccountById: caseByIdRoute, diff --git a/packages/merchant-backoffice-ui/src/hooks/instance.ts b/packages/merchant-backoffice-ui/src/hooks/instance.ts @@ -164,7 +164,7 @@ export function useInstanceKYCDetailsLongPolling() { } } latestRequestTime = new Date().getTime(); - return lib.instance.getCurrentInstanceKycStatus(state.token, { + return lib.instance.getCurrentInstanceKycStatus(token, { timeout: DEFAULT_WAIT, reason: latestReason, }); diff --git a/packages/merchant-backoffice-ui/src/hooks/order.test.ts b/packages/merchant-backoffice-ui/src/hooks/order.test.ts @@ -19,7 +19,11 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { AbsoluteTime, AmountString, TalerMerchantApi } from "@gnu-taler/taler-util"; +import { + AbsoluteTime, + AmountString, + TalerMerchantApi, +} from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { expect } from "chai"; import { useInstanceOrders, useOrderDetails } from "./order.js"; @@ -41,7 +45,10 @@ describe("order api interaction with listing", () => { env.addRequestExpectation(API_LIST_ORDERS, { qparam: { delta: -20, paid: "yes" }, response: { - orders: [{ order_id: "1" }, { order_id: "2" } as TalerMerchantApi.OrderHistoryEntry], + orders: [ + { order_id: "1" }, + { order_id: "2" } as TalerMerchantApi.OrderHistoryEntry, + ], }, }); @@ -52,7 +59,7 @@ describe("order api interaction with listing", () => { const hookBehavior = await tests.hookBehaveLikeThis( () => { const query = useInstanceOrders({ paid: true }, newDate); - const { lib: api } = useMerchantApiContext() + const { lib: api } = useMerchantApiContext(); return { query, api }; }, {}, @@ -74,21 +81,25 @@ describe("order api interaction with listing", () => { env.addRequestExpectation(API_CREATE_ORDER, { request: { order: { amount: "ARS:12" as AmountString, summary: "pay me" }, - lock_uuids: [] + lock_uuids: [], }, - response: { order_id: "3" }, + response: { order_id: "3", pay_deadline: { t_s: "never" } }, }); env.addRequestExpectation(API_LIST_ORDERS, { qparam: { delta: -20, paid: "yes" }, response: { - orders: [{ order_id: "1" }, { order_id: "2" } as any, { order_id: "3" } as any], + orders: [ + { order_id: "1" }, + { order_id: "2" } as any, + { order_id: "3" } as any, + ], }, }); api.instance.createOrder(undefined, { order: { amount: "ARS:12" as AmountString, summary: "pay me" }, - }) + }); }, ({ query, api }) => { expect(env.assertJustExpectedRequestWereMade()).deep.eq({ @@ -115,11 +126,13 @@ describe("order api interaction with listing", () => { env.addRequestExpectation(API_LIST_ORDERS, { qparam: { delta: -20, paid: "yes" }, response: { - orders: [{ - order_id: "1", - amount: "EUR:12", - refundable: true, - } as TalerMerchantApi.OrderHistoryEntry] + orders: [ + { + order_id: "1", + amount: "EUR:12", + refundable: true, + } as TalerMerchantApi.OrderHistoryEntry, + ], }, }); @@ -130,7 +143,7 @@ describe("order api interaction with listing", () => { const hookBehavior = await tests.hookBehaveLikeThis( () => { const query = useInstanceOrders({ paid: true }, newDate); - const { lib: api } = useMerchantApiContext() + const { lib: api } = useMerchantApiContext(); return { query, api }; }, {}, @@ -167,14 +180,14 @@ describe("order api interaction with listing", () => { response: { orders: [ { order_id: "1", amount: "EUR:12", refundable: false } as any, - ] + ], }, }); api.instance.addRefund(undefined, "1", { reason: "double pay", refund: "EUR:1" as AmountString, - }) + }); }, ({ query, api }) => { expect(env.assertJustExpectedRequestWereMade()).deep.eq({ @@ -208,7 +221,10 @@ describe("order api interaction with listing", () => { env.addRequestExpectation(API_LIST_ORDERS, { qparam: { delta: -20, paid: "yes" }, response: { - orders: [{ order_id: "1" }, { order_id: "2" } as TalerMerchantApi.OrderHistoryEntry], + orders: [ + { order_id: "1" }, + { order_id: "2" } as TalerMerchantApi.OrderHistoryEntry, + ], }, }); @@ -219,7 +235,7 @@ describe("order api interaction with listing", () => { const hookBehavior = await tests.hookBehaveLikeThis( () => { const query = useInstanceOrders({ paid: true }, newDate); - const { lib: api } = useMerchantApiContext() + const { lib: api } = useMerchantApiContext(); return { query, api }; }, {}, @@ -247,7 +263,7 @@ describe("order api interaction with listing", () => { }, }); - api.instance.deleteOrder(undefined, "1") + api.instance.deleteOrder(undefined, "1"); }, ({ query, api }) => { expect(env.assertJustExpectedRequestWereMade()).deep.eq({ @@ -283,7 +299,7 @@ describe("order api interaction with details", () => { const hookBehavior = await tests.hookBehaveLikeThis( () => { const query = useOrderDetails("1"); - const { lib: api } = useMerchantApiContext() + const { lib: api } = useMerchantApiContext(); return { query, api }; }, {}, @@ -319,7 +335,7 @@ describe("order api interaction with details", () => { api.instance.addRefund(undefined, "1", { reason: "double pay", refund: "EUR:1" as AmountString, - }) + }); }, ({ query, api }) => { expect(env.assertJustExpectedRequestWereMade()).deep.eq({ @@ -355,7 +371,7 @@ describe("order api interaction with details", () => { const hookBehavior = await tests.hookBehaveLikeThis( () => { const query = useOrderDetails("1"); - const { lib: api } = useMerchantApiContext() + const { lib: api } = useMerchantApiContext(); return { query, api }; }, {}, @@ -388,7 +404,7 @@ describe("order api interaction with details", () => { api.instance.forgetOrder(undefined, "1", { fields: ["$.summary"], - }) + }); }, ({ query, api }) => { expect(env.assertJustExpectedRequestWereMade()).deep.eq({ @@ -433,8 +449,11 @@ describe("order listing pagination", () => { const hookBehavior = await tests.hookBehaveLikeThis( () => { const date = new Date(12000); - const query = useInstanceOrders({ wired: true, date: AbsoluteTime.fromMilliseconds(date.getTime()) }, newDate); - const { lib: api } = useMerchantApiContext() + const query = useInstanceOrders( + { wired: true, date: AbsoluteTime.fromMilliseconds(date.getTime()) }, + newDate, + ); + const { lib: api } = useMerchantApiContext(); return { query, api }; }, {}, @@ -454,7 +473,6 @@ describe("order listing pagination", () => { // }); // expect(query.isReachingEnd).true; // expect(query.isReachingStart).true; - }, ], env.buildTestingContext(), @@ -495,8 +513,11 @@ describe("order listing pagination", () => { const hookBehavior = await tests.hookBehaveLikeThis( () => { const date = new Date(12000); - const query = useInstanceOrders({ wired: true, date: AbsoluteTime.fromMilliseconds(date.getTime()) }, newDate); - const { lib: api } = useMerchantApiContext() + const query = useInstanceOrders( + { wired: true, date: AbsoluteTime.fromMilliseconds(date.getTime()) }, + newDate, + ); + const { lib: api } = useMerchantApiContext(); return { query, api }; }, {}, diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx @@ -22,6 +22,7 @@ import { MerchantApiProviderTesting } from "@gnu-taler/web-util/browser"; import { FunctionalComponent, h } from "preact"; import { CreatePage as TestedComponent } from "./CreatePage.js"; +import { RoundingInterval } from "@gnu-taler/taler-util"; export default { title: "Pages/Instance/Create", @@ -53,6 +54,8 @@ function createExample<Props>( num_fractional_trailing_zero_digits: 1, } }, + default_wire_transfer_rounding_interval: RoundingInterval.NONE, + default_pay_delay: {d_us:"forever"}, have_donau: false, mandatory_tan_channels: [], payment_target_regex: "*", diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx @@ -22,6 +22,7 @@ import { FunctionalComponent, h } from "preact"; import { CreatePage as TestedComponent } from "./CreatePage.js"; import { MerchantApiProviderTesting } from "@gnu-taler/web-util/browser"; +import { RoundingInterval } from "@gnu-taler/taler-util"; export default { title: "Pages/Instance/Create", @@ -45,20 +46,22 @@ function createExample<Props>( currency: "ARS", version: "1", currencies: { - "ASD": { + ASD: { name: "testkudos", alt_unit_names: {}, num_fractional_input_digits: 1, num_fractional_normal_digits: 1, num_fractional_trailing_zero_digits: 1, - } + }, }, + default_wire_transfer_rounding_interval: RoundingInterval.NONE, + default_pay_delay: { d_us: "forever" }, have_donau: false, mandatory_tan_channels: [], payment_target_regex: "*", payment_target_types: "*", exchanges: [], - name: "taler-merchant" + name: "taler-merchant", }, hints: [], lib: {} as any, diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Detail.stories.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Detail.stories.tsx @@ -92,6 +92,7 @@ export const PaidNotRefundable = createExample(TestedComponent, { refund_pending: false, wire_details: [], wired: false, + last_payment: { t_s: "never" }, wire_reports: [], }, }); @@ -115,6 +116,7 @@ export const PaidRefundable = createExample(TestedComponent, { refund_details: [], wire_reports: [], refund_pending: false, + last_payment: { t_s: "never" }, wire_details: [], wired: false, }, diff --git a/packages/merchant-backoffice-ui/src/paths/instance/update/Update.stories.tsx b/packages/merchant-backoffice-ui/src/paths/instance/update/Update.stories.tsx @@ -21,7 +21,7 @@ import { FunctionalComponent, h } from "preact"; import { UpdatePage as TestedComponent } from "./UpdatePage.js"; -import { MerchantAuthMethod } from "@gnu-taler/taler-util"; +import { MerchantAuthMethod, RoundingInterval } from "@gnu-taler/taler-util"; export default { title: "Pages/Instance/Update", @@ -54,6 +54,8 @@ export const Example = createExample(TestedComponent, { default_wire_transfer_delay: { d_us: 1000 * 1000, //one second }, + default_wire_transfer_rounding_interval: RoundingInterval.NONE, + default_refund_delay: { d_us: "forever" }, merchant_pub: "ASDWQEKASJDKSADJ", }, }); diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts @@ -15,6 +15,7 @@ */ import { + AbsoluteTime, AccessToken, CancellationToken, ChallengeRequestResponse, @@ -155,7 +156,7 @@ export enum TalerMerchantManagementCacheEviction { * Uses libtool's current:revision:age versioning. */ export class TalerMerchantInstanceHttpClient { - public static readonly PROTOCOL_VERSION = "22:0:4"; + public static readonly PROTOCOL_VERSION = "23:0:0"; readonly httpLib: HttpRequestLibrary; readonly cacheEvictor: CacheEvictor<TalerMerchantInstanceCacheEviction>; @@ -1242,11 +1243,24 @@ export class TalerMerchantInstanceHttpClient { */ async listProducts( token: AccessToken | undefined, - params?: PaginationParams, + params: PaginationParams & { + category?: string; + name?: string; + description?: string; + } = {}, ) { const url = new URL(`private/products`, this.baseUrl); addPaginationParams(url, params); + if (params.category) { + url.searchParams.set("category_filter", params.category); + } + if (params.name) { + url.searchParams.set("name_filter", params.name); + } + if (params.description) { + url.searchParams.set("description_filter", params.description); + } const headers: Record<string, string> = {}; if (token) { @@ -1457,8 +1471,9 @@ export class TalerMerchantInstanceHttpClient { ) { const url = new URL(`private/orders`, this.baseUrl); - if (params.date) { - url.searchParams.set("date_s", String(params.date)); + if (params.date && !AbsoluteTime.isNever(params.date)) { + const time = AbsoluteTime.toProtocolTimestamp(params.date) + url.searchParams.set("date_s", String(time.t_s)); } if (params.fulfillmentUrl) { url.searchParams.set("fulfillment_url", params.fulfillmentUrl); @@ -1478,6 +1493,9 @@ export class TalerMerchantInstanceHttpClient { if (params.wired !== undefined) { url.searchParams.set("wired", params.wired ? "YES" : "NO"); } + if (params.summary) { + url.searchParams.set("summary_filter", params.summary); + } addPaginationParams(url, params); const headers: Record<string, string> = {}; diff --git a/packages/taler-util/src/types-taler-merchant.ts b/packages/taler-util/src/types-taler-merchant.ts @@ -30,7 +30,9 @@ import { import { AccessToken, AccountLimit, + BlindedDonationReceiptKeyPair, DenomKeyType, + DonationRequestData, ExchangeWireAccount, ObjectCodec, PaytoString, @@ -85,6 +87,7 @@ import { codecForInternationalizedString, codecForURLString, } from "./types-taler-common.js"; +import { PayWalletData } from "./types-taler-wallet.js"; /** * Proposal returned from the contract URL. @@ -144,11 +147,26 @@ export interface TokenUseSig { } export interface MerchantPayResponse { - sig: string; + // Signature on TALER_PaymentResponsePS with the public + // key of the merchant instance. + sig: EddsaSignatureString; + + // Text to be shown to the point-of-sale staff as a proof of + // payment. pos_confirmation?: string; + + // Signed tokens. Returned in the same order as the + // token envelopes were provided in the request. Specifically, + // the order will follow the order of the outputs from the + // contract terms, and then within each output follow the + // order in which the wallet_data contained the respective + // blinded envelopes. The donation tokens will be present + // at the offset matching the place where a donation receipt + // was indicated in the outputs array, and of course be skipped + // if the PayWalletData did not have a donau field. + // @since protocol **v21** token_sigs?: SignedTokenEnvelope[]; } - interface MerchantOrderStatusPaid { // Was the payment refunded (even partially, via refund or abort)? refunded: boolean; @@ -1242,7 +1260,11 @@ export interface MerchantVersionResponse { // Optional, no restrictions are imposed if the field is // absent. // @since **v22** - payment_target_regex: string; + // CAUTION: Likely to be removed/deprecated, + // as we'll want an array of restrictions with the + // same format as the exchange uses, as this allows + // proper i18n and spec/code reuse. + payment_target_regex?: string; // Default wire transfer delay for new instances. // This is the default to use for new instances, see the instance value for @@ -1262,6 +1284,24 @@ export interface MerchantVersionResponse { // the instance-specific default. // @since **v22** default_refund_delay?: RelativeTime; + + // Default interval to which wire deadlines computed by + // adding the wire_transfer_delay on top of the refund + // deadline should be rounded up to. + // @since **v23** + default_wire_transfer_rounding_interval: RoundingInterval; +} + +export enum RoundingInterval { + NONE = "none", + SECOND = "second", + MINUTE = "minute", + HOUR = "hour", + DAY = "day", + WEEK = "week", + MONTH = "month", + QUARTER = "quarter", + YEAR = "year", } export interface ExchangeConfigInfo { @@ -1351,8 +1391,17 @@ export interface PaymentStatusRequestParams { } export enum KycStatusLongPollingReason { + /** + * Waiting for an account to receive the wire transfer + */ AUTH_TRANSFER = 1, + /** + * Waiting for an account on aml investigation to be completed + */ AML_INVESTIGATION = 2, + /** + * Waiting for an account to be ready to be used + */ TO_BE_OK = 3, } export interface GetKycStatusRequestParams { @@ -1418,40 +1467,61 @@ export interface ListWireTransferRequestParams { order?: "asc" | "dec"; } export interface ListOrdersRequestParams { - // If set to yes, only return paid orders, if no only - // unpaid orders. Do not give (or use “all”) to see all - // orders regardless of payment status. + /** + * If set to yes, only return paid orders, if no only + * unpaid orders. Do not give (or use “all”) to see all + * orders regardless of payment status. + */ paid?: boolean; - // If set to yes, only return refunded orders, if no only - // unrefunded orders. Do not give (or use “all”) to see - // all orders regardless of refund status. + /** + * If set to yes, only return refunded orders, if no only + * unrefunded orders. Do not give (or use “all”) to see + * all orders regardless of refund status. + */ refunded?: boolean; - // If set to yes, only return wired orders, if no only - // orders with missing wire transfers. Do not give (or - // use “all”) to see all orders regardless of wire transfer - // status. + /** + * If set to yes, only return wired orders, if no only + * orders with missing wire transfers. Do not give (or + * use “all”) to see all orders regardless of wire transfer + * status. + */ wired?: boolean; - // At most return the given number of results. Negative - // for descending by row ID, positive for ascending by - // row ID. Default is 20. Since protocol v12. + /** + * At most return the given number of results. Negative + * for descending by row ID, positive for ascending by + * row ID. Default is 20. Since protocol v12. + */ limit?: number; - // Non-negative date in seconds after the UNIX Epoc, see delta - // for its interpretation. If not specified, we default to the - // oldest or most recent entry, depending on delta. + /** + * Non-negative date in seconds after the UNIX Epoc, see delta + * for its interpretation. If not specified, we default to the + * oldest or most recent entry, depending on delta. + */ date?: AbsoluteTime; - // Starting product_serial_id for an iteration. - // Since protocol v12. + /** + * Starting product_serial_id for an iteration. + * Since protocol v12. + */ offset?: string; - // Timeout in milliseconds to wait for additional orders if the - // answer would otherwise be negative (long polling). Only useful - // if delta is positive. Note that the merchant MAY still return - // a response that contains fewer than delta orders. + /** + * Timeout in milliseconds to wait for additional orders if the + * answer would otherwise be negative (long polling). Only useful + * if delta is positive. Note that the merchant MAY still return + * a response that contains fewer than delta orders. + */ timeout?: number; - // Since protocol v6. Filters by session ID. + /** + * Filters by session ID. + */ sessionId?: string; - // Since protocol v6. Filters by fulfillment URL. + /** + * Filters by fulfillment URL. + */ fulfillmentUrl?: string; - + /** + * Only returns orders where the summary contains the given text as a substring. Matching is case-insensitive + */ + summary?: string; order?: "asc" | "dec"; } @@ -1459,8 +1529,12 @@ export interface PayRequest { // The coins used to make the payment. coins: CoinPaySig[]; + // Input tokens required by choice indicated by choice_index. + // @since protocol **v21** + tokens?: TokenUseSig[]; + // Custom inputs from the wallet for the contract. - wallet_data?: Object; + wallet_data?: PayWalletData; // The session for which the payment is made (or replayed). // Only set for session-based payments. @@ -1659,15 +1733,32 @@ export interface InstanceConfigurationMessage { // Can always be overridden by the frontend on a per-order basis. use_stefan: boolean; + // If the frontend does NOT specify a payment deadline, how long should + // offers we make be valid by default? + // Optional @since **v22** (before the setting was mandatory). + // If not provided, the global merchant default will be used. + default_pay_delay?: RelativeTime; + + // If the frontend does NOT specify a refund deadline, how long should + // refunds be allowed by default? Added on top of the + // payment deadline. + // @since **v22** + default_refund_delay?: RelativeTime; + // If the frontend does NOT specify an execution date, how long should // we tell the exchange to wait to aggregate transactions before - // executing the wire transfer? This delay is added to the current - // time when we generate the advisory execution time for the exchange. - default_wire_transfer_delay: RelativeTime; + // executing the wire transfer? This delay is added on top of + // the refund deadline and afterwards subject to rounding + // via the default_wire_transfer_rounding_interval. + // Optional @since **v22** (before the setting was mandatory). + // If not provided, the global merchant default will be used. + default_wire_transfer_delay?: RelativeTime; - // If the frontend does NOT specify a payment deadline, how long should - // offers we make be valid by default? - default_pay_delay: RelativeTime; + // How far should the wire deadline (if computed with the help of + // the default_wire_transfer_delay) be rounded up to compute + // the ultimate wire deadline? + // @since **v22**, defaults to no rounding if not given. + default_wire_transfer_rounding_interval?: RoundingInterval; } export interface InstanceAuthConfigurationMessage { @@ -1782,15 +1873,31 @@ export interface InstanceReconfigurationMessage { // Can always be overridden by the frontend on a per-order basis. use_stefan: boolean; + // If the frontend does NOT specify a payment deadline, how long should + // offers we make be valid by default? + // Optional @since **v22** (before the setting was mandatory). + // If not provided, the previous setting will now simply be preserved. + default_pay_delay?: RelativeTime; + + // If the frontend does NOT specify a refund deadline, how long should + // refunds be allowed by default? Added on top of the payment deadline. + // @since **v22** + default_refund_delay?: RelativeTime; + // If the frontend does NOT specify an execution date, how long should // we tell the exchange to wait to aggregate transactions before - // executing the wire transfer? This delay is added to the current - // time when we generate the advisory execution time for the exchange. - default_wire_transfer_delay: RelativeTime; + // executing the wire transfer? This delay is added on top of + // the refund deadline and afterwards subject to rounding + // via the default_wire_transfer_rounding_interval. + // Optional @since **v22** (before the setting was mandatory). + // If not provided, the previous setting will now simply be preserved. + default_wire_transfer_delay?: RelativeTime; - // If the frontend does NOT specify a payment deadline, how long should - // offers we make be valid by default? - default_pay_delay: RelativeTime; + // How far should the wire deadline (if computed with the help of + // the default_wire_transfer_delay) be rounded up to compute + // the ultimate wire deadline? + // @since **v22**, defaults to no rounding if not given. + default_wire_transfer_rounding_interval?: RoundingInterval; } export interface InstancesResponse { @@ -1863,15 +1970,28 @@ export interface QueryInstancesResponse { // Can always be overridden by the frontend on a per-order basis. use_stefan: boolean; + // If the frontend does NOT specify a payment deadline, how long should + // offers we make be valid by default? Added to the order creation + // time. + default_pay_delay: RelativeTime; + + // If the frontend does NOT specify a refund deadline, how long should + // refunds be allowed by default? Added to the payment deadline. + // @since **v22** + default_refund_delay: RelativeTime; + // If the frontend does NOT specify an execution date, how long should // we tell the exchange to wait to aggregate transactions before - // executing the wire transfer? This delay is added to the current - // time when we generate the advisory execution time for the exchange. + // executing the wire transfer? This delay is added to the + // refund deadline and subject to rounding to the + // default_wire_transfer_rounding_interval. default_wire_transfer_delay: RelativeTime; - // If the frontend does NOT specify a payment deadline, how long should - // offers we make be valid by default? - default_pay_delay: RelativeTime; + // Default interval to which wire deadlines computed by + // adding the wire_transfer_delay on top of the refund + // deadline should be rounded up to. + // @since **v23** + default_wire_transfer_rounding_interval: RoundingInterval; // Authentication configuration. // Does not contain the token when token auth is configured. @@ -2527,7 +2647,7 @@ export interface PostOrderResponse { // Deadline when the offer expires; the customer must pay before. // @since protocol **v21**. - pay_deadline?: Timestamp; + pay_deadline: Timestamp; // Token that authorizes the wallet to claim the order. // Provided only if "create_token" was set to 'true' @@ -2622,6 +2742,15 @@ export interface CheckPaymentPaidResponse { // FIXME: support for contract v1 contract_terms: MerchantContractTerms; + // Index of the selected choice within the choices array of + // contract terms. + // @since protocol **v21** + choice_index?: Integer; + + // If the order is paid, set to the last time when a payment + // was made to pay for this order. @since **v14**. + last_payment: Timestamp; + // The wire transfer status from the exchange for this order if // available, otherwise empty array. wire_details: TransactionWireTransfer[]; @@ -3182,6 +3311,14 @@ export interface TokenFamilySummary { // Human-readable name for the token family. name: string; + // Human-readable description for the token family. + // @since protocol **v23**. + description: string; + + // Optional map from IETF BCP 47 language tags to localized descriptions. + // @since protocol **v23**. + description_i18n?: { [lang_tag: string]: string }; + // Start time of the token family's validity period. valid_after: Timestamp; @@ -3427,11 +3564,11 @@ export interface OrderV0 extends OrderCommon { export interface OrderV1 extends OrderCommon { // Version 1 order support discounts and subscriptions. // https://docs.taler.net/design-documents/046-mumimo-contracts.html - // @since protocol **vSUBSCRIBE** + // @since protocol **v21** version: OrderVersion.V1; // List of contract choices that the customer can select from. - // @since protocol **vSUBSCRIBE** + // @since protocol **v21** choices?: OrderChoice[]; } @@ -3629,60 +3766,6 @@ const codecForExchangeConfigInfo = (): Codec<ExchangeConfigInfo> => .property("master_pub", codecForEddsaPublicKey()) .build("TalerMerchantApi.ExchangeConfigInfo"); -// // Set when the merchant supports -// // self-provisioning instances. -// // Since protocol **v21** -// have_self_provisioning: boolean; - -// // True if this merchant backend supports the Donau -// // extension and can thus issue donation receipts. -// // Should primarily be used to control the SPA's CRUD -// // functionality for Donau. -// // @since **v21** -// have_donau: boolean; - -// // Tan channels that are required -// // to be confirmed for an instance to -// // be useable. -// // @since **v21** -// mandatory_tan_channels?: TanChannel[]; - -// // Space-separated list of enabled payment target types. -// // Useful if the SPA should not show allow adding other -// // types of bank accounts. "*" is used to represent no -// // restriction. -// // @since **v22** -// payment_target_types: string; - -// // Regular expression representing further restrictions -// // on allowed payment targets. Any "payto://"-URI supplied -// // for a bank account must match the given regular expression. -// // For example, "payto://iban/CH.*" would restrict the system -// // to only Swiss bank accounts. -// // Optional, no restrictions are imposed if the field is -// // absent. -// // @since **v22** -// payment_target_regex? string; - -// // Default wire transfer delay for new instances. -// // This is the default to use for new instances, see the instance value for -// // the instance-specific default. -// // @since **v22** -// default_wire_transfer_delay: RelativeTime; - -// // Default payment delay for new instances. -// // This is the default to use for new instances, see the instance value for -// // the instance-specific default. -// // @since **v22** -// default_pay_delay: RelativeTime; - -// // If the frontend does NOT specify a refund deadline, how long should -// // refunds be allowed by default? -// // This is the default to use for new instances, see the instance value for -// // the instance-specific default. -// // @since **v22** -// default_refund_delay: RelativeTime; - export const codecForTalerMerchantConfigResponse = (): Codec<MerchantVersionResponse> => buildCodecForObject<MerchantVersionResponse>() @@ -3720,8 +3803,24 @@ export const codecForTalerMerchantConfigResponse = "payment_target_types", codecOptionalDefault(codecForString(), "*"), ) + .property( + "default_wire_transfer_rounding_interval", + codecForRoundingInterval, + ) .build("TalerMerchantApi.VersionResponse"); +export const codecForRoundingInterval = codecForEither( + codecForConstString(RoundingInterval.NONE), + codecForConstString(RoundingInterval.SECOND), + codecForConstString(RoundingInterval.MINUTE), + codecForConstString(RoundingInterval.HOUR), + codecForConstString(RoundingInterval.DAY), + codecForConstString(RoundingInterval.WEEK), + codecForConstString(RoundingInterval.MONTH), + codecForConstString(RoundingInterval.QUARTER), + codecForConstString(RoundingInterval.YEAR), +); + export const codecForClaimResponse = (): Codec<ClaimResponse> => buildCodecForObject<ClaimResponse>() // Must be 'any', otherwise, contract terms won't match. @@ -3873,6 +3972,11 @@ export const codecForQueryInstancesResponse = .property("use_stefan", codecForBoolean()) .property("default_wire_transfer_delay", codecForDuration) .property("default_pay_delay", codecForDuration) + .property("default_refund_delay", codecForDuration) + .property( + "default_wire_transfer_rounding_interval", + codecForRoundingInterval, + ) .property( "auth", buildCodecForObject<{ @@ -4078,7 +4182,7 @@ export const codecForTax = (): Codec<Tax> => export const codecForPostOrderResponse = (): Codec<PostOrderResponse> => buildCodecForObject<PostOrderResponse>() .property("order_id", codecForString()) - .property("pay_deadline", codecOptional(codecForTimestamp)) + .property("pay_deadline", codecForTimestamp) .property("token", codecOptional(codecForString())) .build("TalerMerchantApi.PostOrderResponse"); @@ -4248,6 +4352,8 @@ export const codecForCheckPaymentPaidResponse = .property("exchange_http_status", codecForNumber()) .property("refund_amount", codecForAmountString()) .property("contract_terms", codecForMerchantContractTerms()) + .property("choice_index", codecOptional(codecForNumber())) + .property("last_payment", codecForTimestamp) .property("wire_reports", codecForList(codecForTransactionWireReport())) .property("wire_details", codecForList(codecForTransactionWireTransfer())) .property("refund_details", codecForList(codecForRefundDetails())) @@ -4459,6 +4565,8 @@ export const codecForTokenFamilySummary = (): Codec<TokenFamilySummary> => buildCodecForObject<TokenFamilySummary>() .property("slug", codecForString()) .property("name", codecForString()) + .property("description", codecForString()) + .property("description_i18n", codecForInternationalizedString()) .property("valid_after", codecForTimestamp) .property("valid_before", codecForTimestamp) .property("kind", codecForTokenFamilyKind)