taler-docs

Documentation for GNU Taler components, APIs and protocols
Log | Files | Refs | README | LICENSE

commit 8d375620a6001120ac4f0e9dd924967900314619
parent d711e37bab9aba079931780c975f252cea71fcbc
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date:   Tue,  4 Nov 2025 14:56:51 +0100

adding 073

Diffstat:
Mconf.py | 1+
Mcore/api-merchant.rst | 2++
Adesign-documents/073-extended-merchant-template.rst | 384+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdesign-documents/index.rst | 1+
4 files changed, 388 insertions(+), 0 deletions(-)

diff --git a/conf.py b/conf.py @@ -108,6 +108,7 @@ release = "1.0.0" exclude_patterns = [ "_build", "_exts", + ".*", "cf", "prebuilt", "**/README.md", diff --git a/core/api-merchant.rst b/core/api-merchant.rst @@ -4623,6 +4623,8 @@ to validate that a customer made a payment. The backend does not know the instance or the OTP device. +.. _merchant-template-api: + --------- Templates --------- diff --git a/design-documents/073-extended-merchant-template.rst b/design-documents/073-extended-merchant-template.rst @@ -0,0 +1,384 @@ +DD 73: Extended Merchant Template +################################# + +Summary +======= + +`#0010234 <https://bugs.gnunet.org/view.php?id=10234>`_ targets a wallet-first shopping cart experience by extending the merchant +template feature set with a dedicated inventory-driven template type. The new +design keeps legacy fixed-order templates intact while enabling merchants to +publish a single ``taler://pay-template`` QR code that lets the customer pick one +or multiple inventory(product) entries directly inside the wallet. + +Motivation +========== + +The existing template API (see :ref:`Section 1.4.15 <merchant-template-api>` of the merchant manual and `LSD 0006 <https://lsd.gnunet.org/lsd0006/>`_) +lets merchants pre-define mostly static contracts. Wallets can prompt the user +for an amount or order summary, then instantiate an order without the merchant +needing online infrastructure. This is valuable for: + +* Offline and low-connectivity points-of-sale where only the customer's device + has network access. +* Static Web sites that want to embed a payment link or QR code without running + dynamic backend logic. +* Donation flows where the payer sets the amount but contract metadata stays + stable. + +However, the current model fails to cover micro-merchants who want to publish a +small inventory, have the wallet enforce contract terms, and still avoid +operating a PoS or e-commerce website. Today they would need one QR code per product or +fall back to a free-form amount entry workflow, neither of which captures stock +keeping, category-based selections, or cart validation. + +Requirements +============ + +* Introduce a template type system that distinguishes fixed-order templates from + inventory-driven ones extending existing REST templates. +* Define merchant-side configuration for product selection, supporting: + + * all inventory, + * category-filtered subsets, and + * explicitly enumerated product IDs; combinations must be merged without + duplicates. +* Describe wallet-side UX affordances for choosing exactly one product or + multiple products, driven by a ``choose_one`` style flag. +* Extend the public ``GET /templates/$TEMPLATE_ID`` response to surface type, + product descriptors, selection rules, and customer-editable defaults. +* Extend the template instantiation ``POST`` to carry the selected products and + quantities, reusing ``TemplateDetails`` object. +* Remain compatible with templates from protocol versions v13+. + +Proposed Solution +================= + +Schema extensions +----------------- + +Introduce an explicit template type discriminator so existing contracts remain +stable and new behaviour can be negotiated per protocol version. + +.. ts:def:: TemplateType + + type TemplateType = "fixed-order" | "inventory-cart"; + +Extend ``TemplateAddDetails`` and ``TemplateDetails`` to advertise the new type and, when +``template_type`` equals ``"inventory-cart"``, nest the inventory-specific +contract. + +.. ts:def:: TemplateAddDetails + + interface TemplateAddDetails { + + // Template ID to use. + template_id: string; + + // Template type defaults to ``fixed-order`` when missing + // Must be either ``fixed-order`` or ``inventory-cart`` + // This prescribes, which template_contract is expected + // TemplateContractDetails for ``fixed-order`` + // TemplateInventoryContractDetails for ``inventory-cart`` + template_type?: TemplateType; + + // Human-readable description for the template. + template_description: string; + + // OTP device ID. + // This parameter is optional. + otp_id?: string; + + // Fixed contract information for orders created from + // this template. + template_contract: TemplateContractDetails | TemplateInventoryContractDetails; + + // Key-value pairs matching a subset of the + // fields from ``template_contract`` that are + // user-editable defaults for this template. + // Since protocol **v13**. + editable_defaults?: Object; + } + +.. ts:def:: TemplateDetails + + interface TemplateDetails { + + // Human-readable description for the template. + template_description: string; + + // Template type + template_type: TemplateType; + + // Fixed contract information for orders created from + // this template. + template_contract: TemplateContractDetails | TemplateInventoryContractDetails; + + //Same as current ... + } + +.. ts:def:: TemplateInventoryContractDetails + + interface TemplateInventoryContractDetails { + + // Human-readable summary for the template. + summary?: string; + + // Required currency for payments to the template. + // The user may specify any amount, but it must be + // in this currency. + currency?: string; + + // Minimum age buyer must have (in years). Default is 0. + minimum_age: Integer; + + // The time the customer need to pay before his order will be deleted. + // It is deleted if the customer did not pay and if the duration is over. + pay_duration: RelativeTime; + + // Inventory details + inventory_details: InventoryTemplateDetails; + } + +The optional fields default to legacy behaviour (``template_type`` = +``"fixed-order"``). Wallets that do not +recognise ``"inventory-cart"`` must reject the template with a descriptive error +message. + +.. ts:def:: InventoryTemplateDetails + + interface InventoryTemplateDetails { + + select_all?: boolean; + + // ignored if select_all is true + select_categories?: string[]; + + // ignored if select_all is true + select_products?: string[]; + + // describes whether wallet imposes selection + // of only one object from user + choose_one?: boolean; + + // Possibility to impose limits for each product + item_limits?: InventoryItemLimit[]; + + // Possibility to predefine default quantity for user + item_default?: InventoryItemDefault[]; + } + +.. ts:def:: InventoryItemLimit + + interface InventoryItemLimit { + + product_id: string; + + // format of "NUMERIC.NUMERIC" check the + // DD72 for more info on the format + unit_max_quantity?: string; + unit_min_quantity?: string; + } + +.. ts:def:: InventoryItemDefault + + interface InventoryItemDefault { + + product_id: string; + // defaults to "0" + default_quantity_string: string; + } + +The merchant backend merges ``select_categories``, and +``select_products`` into a concrete product list at template instantiation time. +Duplicates are removed, and products outside the merchant's inventory produce a +409 conflict. ``choose_one`` dictates whether the wallet must restrict the user +to a single product/quantity combination (``true``) or allow arbitrary +combinations (``false``/absent). + +Merchant private API updates +---------------------------- + +``POST`` and ``PATCH`` on ``/private/templates`` accept new ``TemplateInventoryContractDetails``. + +SPA +---- +The SPA embeds the new configuration in template +creation forms: + +* Adding support for different ``template_type``. +* Some clever ``template_type`` detection can be introduced, e.g. if the merchant selects the products + automatically changed from ``fixed-order`` to ``inventory-cart``. Manage products from order page can be re-used. +* Inventory selector widgets emit the union of categories and explicit product + selections. +* Optional quantity limits and defaults map to ``item_limits`` and ``item_default``. + + +Wallet discovery API +-------------------- + +Enhance the public ``GET /instances/$INSTANCE/templates/$TEMPLATE_ID`` response +to include both the inventory configuration and the resolved product metadata. + +.. ts:def:: WalletTemplateDetails + + interface WalletTemplateDetails { + template_type: TemplateType; + template_contract: TemplateContractDetails | TemplateInventoryContractDetailsWallet; + editable_defaults?: Object; + required_currency?: string; + } + +.. ts:def:: TemplateInventoryContractDetailsWallet + + interface TemplateInventoryContractDetailsWallet { + + // Human-readable summary for the template. + summary?: string; + + // Required currency for payments to the template. + // The user may specify any amount, but it must be + // in this currency. + currency?: string; + + // Minimum age buyer must have (in years). Default is 0. + minimum_age: Integer; + + // The time the customer need to pay before his order will be deleted. + // It is deleted if the customer did not pay and if the duration is over. + pay_duration: RelativeTime; + + // Information on the products received + inventory_payload?: WalletInventoryPayload; + } + +.. ts:def:: WalletInventoryPayload + + interface WalletInventoryPayload { + products: WalletInventoryProduct[]; + pagination?: InventoryPaginationCursor; + } + +.. ts:def:: WalletInventoryProduct + + interface WalletInventoryProduct { + product_id: string; + product_name: string; + description: string; + description_i18n: { [lang_tag: string]: string }; + taxes?: Tax[]; + unit: string; + //if non standard unit is used to which merchant defined translations + unit_name_short_i18n?: { [lang_tag: string]: string }; + unit_price: Amount; + unit_allow_fraction: boolean; + unit_precision_level: Integer; + max_quantity: string; + min_quantity: string; + default_quantity: string; + categories?: Integer[]; + image?: string; + } + +.. ts:def:: InventoryPaginationCursor + + interface InventoryPaginationCursor { + next_offset?: string; + total_count?: number; + } + +Merchant may opt into pagination when the resolved inventory exceeds REST +payload thresholds (20 products per page). Wallets that encounter ``pagination`` must fetch additional +pages before allowing product selection, ensuring the entire subset is in sync +with backend rules. + +Template instantiation +---------------------- + +Introduce a specialised request body for inventory-driven templates while +keeping ``UsingTemplateDetails`` valid for legacy flows. + +.. ts:def:: InventoryTemplateUseDetails + + interface InventoryTemplateUseDetails { + summary?: string; + amount?: Amount; + inventory_selection: InventoryTemplateSelection[]; + } + +.. ts:def:: InventoryTemplateSelection + + interface InventoryTemplateSelection { + product_id: string; + quantity: string; + } + +``amount`` lets the wallet supply a precalculated total; +backends recompute the authoritative order amount and reject mismatches. +Wallets submit ``InventoryTemplateUseDetails`` to ``POST +/instances/$INSTANCE/templates/$TEMPLATE_ID`` when the template advertises +``template_type`` = ``"inventory-cart"``. Classic templates continue to use +``UsingTemplateDetails``. + +Backend order creation logic verifies every selected product: + +1. Resolve the template and compute the eligible product set. +2. Ensure user selections are a subset of the resolved list and satisfy + ``choose_one`` / quantity bounds. +3. Construct the contract terms by embedding the selected products as line items + in ``TemplateContractDetails`` before calling the internal order creation + path (same code as ``POST /private/orders``). +4. Record the chosen products in order metadata for fulfilment and reporting. + +Wallet UX +--------- + +Wallets handle inventory templates as follows: + +1. Fetch ``WalletTemplateDetails`` and cache the resolved inventory. +2. Render a cart builder respecting ``choose_one`` and field defaults. +3. Clamp quantities to the provided limits and pre-fill from ``item_defaults``. +4. Show a running total computed from per-product prices; totals must match the + backend response before displaying the payment acceptance dialog. +5. Gracefully handle outdated caches by retrying the ``GET`` when the ``POST`` + returns a conflict due to inventory changes. + +Compatibility rules +------------------- + +* Docs mark templates containing ``template_type`` = + ``"inventory-cart"`` as requiring protocol vSOME or later. +* QR codes stay in the same pay-template URI parameters. + +Definition of Done +================== + +* REST API changes and schema extensions are ratified by wallet, merchant, and + SPA. +* Integration tests cover single-product and multi-product cart creation via + the new template type. +* Updated reference documentation (merchant manual) describes the new + template type and associated fields. +* Wallet and merchant SPA have defined workflows and designs + for the new template + + +Alternatives +============ + +* Keep templates fixed and push cart building to merchant-hosted Web flows, + trading offline capability for implementation simplicity. +* Require merchants to mint one template per product, keeping the current API + untouched but exacerbating QR code sprawl and inventory maintenance. + +Drawbacks +========= + +* Larger template payloads may increase wallet fetch + times, especially for templates with many products. +* More complex validation paths in both wallet and merchant codebases. +* Risk of inconsistent order totals. + +Discussion / Q&A +================ + +What to do, if customer wants to leave tip? diff --git a/design-documents/index.rst b/design-documents/index.rst @@ -84,4 +84,5 @@ Design documents that start with "XX" are considered deprecated. 070-alias-directory-mailbox 071-auto-refresh 072-products-units + 073-extended-merchant-template 999-template