14.73. DD 73: Extended Merchant Template#
14.73.1. Summary#
#0010234 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.
14.73.2. Motivation#
The existing template API (see Section 1.4.15 of the merchant manual and LSD 0006) 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.
As such we see two new scenarios:
Tiny shops, farmers’ stands, and unattended kiosks want to publish a QR code next to the shelves so customers can scan once, add multiple items and get order that can be paid
Vending machines that usually dispense only one product per transaction still benefit from exposing a catalogue in the wallet UI, but must constrain the customer to one selection to lower the integration costs.
Another question that arises is how the wallet retrieves products efficiently. Ideally the entire inventory subset arrives in one response so that up to roughly 300-400 items can be listed without paginations. Backends already store image metadata, so the wallet should also be able to fetch product pictures on request instead of embedding them in the initial payload.
14.73.3. Requirements#
Introduce a template type system that distinguishes fixed-order templates from inventory-driven ones extending existing REST templates, and creates a base for new possible template types.
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_onestyle flag.Extend the public
GET /templates/$TEMPLATE_IDresponse to surface type, product descriptors, selection rules, and customer-editable defaults.Extend the template instantiation
POSTto carry the selected products and quantities, reusing theTemplateDetailsobject.Remain compatible with templates from protocol versions v13+.
14.73.4. Proposed Solution#
14.73.4.1. Schema extensions#
Add an endpoint that lets
wallets download product images via GET
/instances/$ID/products/$IMAGE_HASH/image.
Introduce template type discriminator so processing of the template can be done per template version.
type TemplateType = "fixed-order" | "inventory-cart";
type TemplateContractDetailsType =
TemplateContractDetails | TemplateInventoryContractDetails;
Extend TemplateAddDetails and TemplateDetails to advertise the new type and, when
template_type equals "inventory-cart", nest the inventory-specific
contract.
interface TemplateAddDetails {
// Template ID to use.
template_id: string;
// 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: TemplateContractDetailsType;
// 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;
}
interface TemplateDetails {
// Fixed contract information for orders created from
// this template.
template_contract: TemplateContractDetailsType;
// Future fields remain identical to the existing structure.
}
New contract type has next structure:
interface TemplateInventoryContractDetails {
// Template type defaults to fixed-order when missing.
// Must be either fixed-order or inventory-cart.
// This prescribes which template_contract structure is expected.
// TemplateContractDetails for fixed-order.
// TemplateInventoryContractDetails for inventory-cart.
template_type?: TemplateType;
// Human-readable summary for the template.
summary?: string;
// Requests the wallet to offer a tip entry UI. The backend
// verifies that amount equals selected products + tip.
request_tip?: boolean;
// Time window to pay before the order expires unfulfilled.
pay_duration: RelativeTime;
// Selects all products from merchant inventory and overrides
// selected_categories and selected_products.
selected_all?: boolean;
// All products from selected categories are included.
selected_categories?: Integer[];
// Explicit list of product IDs to include.
selected_products?: string[];
// When true the wallet must enforce single-selection behaviour.
choose_one?: boolean;
}
Wallets that do not recognise "inventory-cart" continue to expect
template-level fields such as minimum_age, and when new type supplied
it will inevitably, KABOOM!
The merchant simply saves id’s of selected_categories
and selected_products.
choose_one dictates whether the wallet must restrict the user
to a single product/quantity combination (true) or allow arbitrary
combinations (false/absent).
14.73.4.2. Merchant private API updates#
POST and PATCH on /private/templates accept new TemplateInventoryContractDetails.
14.73.4.3. SPA#
The SPA embeds the new configuration in template creation forms:
Adding support for different
template_type.Some clever
template_typedetection can be introduced, e.g. if the merchant selects the products automatically changed fromfixed-ordertoinventory-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_limitsanditem_default.
14.73.4.4. Wallet discovery API#
Enhance the public GET /instances/$INSTANCE/templates/$TEMPLATE_ID response
to include both the inventory configuration and the resolved product metadata,
by using TemplateWalletContractPayload.
type TemplateWalletContractPayload =
TemplateWalletContractDetails | TemplateInventoryContractDetailsWallet;
type TemplateWalletContractDetails = TemplateContractDetails;
TemplateWalletContractDetails is identical to the TemplateContractDetails
object defined in Templates. Changes relative to the current
protocol are called out below.
interface WalletTemplateDetails {
// Hard-coded information about the contract terms
// for this template.
template_contract: TemplateWalletContractPayload;
// 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;
// Only present when TemplateWalletContractPayload requires it.
// Required currency for payments. Useful if no
// amount is specified in the template_contract
// but the user should be required to pay in a
// particular currency anyway. Merchant backends
// may reject requests if the template_contract
// or editable_defaults do
// specify an amount in a different currency.
// This parameter is optional.
// Since protocol v13.
required_currency?: string;
}
TemplateInventoryContractDetailsWallet intentionally omits a fixed currency
or minimum age to allow multi-currency product listings and leave age checks to
per-product logic when available.
interface TemplateInventoryContractDetailsWallet {
// Human-readable summary for the template.
summary?: string;
// Request the wallet to offer a tip entry UI.
request_tip?: boolean;
// Time the customer has to pay before the order expires unpaid.
pay_duration: RelativeTime;
// Information about the resolved products.
inventory_payload?: WalletInventoryPayload;
}
interface WalletInventoryPayload {
// Contains all products selected by the merchant.
products: WalletInventoryProduct[];
// Contains all categories referenced by the products.
categories: WalletInventoryCategory[];
}
Next structure mirrors the merchant product descriptor (ProductDetail in the
merchant specification) with some extensions (unit_name_short_i18n, ) so that backend, SPA, and wallet
share a single meaning for every field, yet we lower the number of requests between the
wallet and backend.
interface WalletInventoryProduct {
product_id: string;
product_name: string;
description: string;
description_i18n: { [lang_tag: string]: string };
taxes?: Tax[];
unit: string;
// Optional translations for non-standard units.
unit_name_short_i18n?: { [lang_tag: string]: string };
unit_prices: Amount[];
unit_allow_fraction: boolean;
unit_precision_level: Integer;
categories?: Integer[];
image_hash?: string;
}
interface WalletInventoryCategory {
category_id: Integer;
category_name: string;
category_name_i18n?: { [lang_tag: string]: string };
}
This design lets wallets download hundreds of objects in a single request and
fetch images later via the shared GET
/instances/$ID/products/$IMAGE_HASH/image endpoint.
Inventory template responses MUST include the complete product subset in a single payload; QR-code driven flows remain manageable only when the referenced catalog fragment comfortably fits into one REST response. Merchants are expected to keep templates constrained to a practical number of products (tens, not thousands). If extreme use cases ever arise, pagination can be revisited.
14.73.4.5. Template instantiation#
Extend POST /instances/$INSTANCE/templates/$TEMPLATE_ID to support the
following sum type:
type UsingTemplateDetailsType =
UsingTemplateDetails | InventoryTemplateUseDetails
interface InventoryTemplateUseDetails {
// Summary of the template.
summary?: string;
// Amount pre-calculated on the wallet side.
// Do we want to allow null? (e.g. wallet is lazy)
amount: Amount;
// Optional tip selected by the customer and mapped to
// an abstract line item in the resulting order.
tip?: Amount;
// Selected products. When choose_one = true the array must contain
// exactly one entry.
inventory_selection: 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". tip carries the customer-selected
gratuity whenever the template requested it; classic templates consequently
extend UsingTemplateDetails with the same optional field.
Backend order creation logic verifies every selected product:
Resolve the template and compute the eligible product set.
Ensure user selections are a subset of the resolved list and satisfy
choose_one/ quantity bounds.Construct the contract terms by embedding the selected products as line items in
TemplateContractDetailsbefore calling the internal order creation path (same code asPOST /private/orders).Record the chosen products in order metadata for fulfilment and reporting.
When tip is present, it is simply appended as its own line item(product)
in the order.
14.73.4.6. Wallet UX#
Wallets handle inventory templates as follows:
Fetch
WalletTemplateDetailsand cache the resolved inventory.Render a cart builder respecting
choose_one.Show a running total computed from per-product prices; totals must match the backend response before displaying the payment acceptance dialog.
Gracefully handle outdated caches by retrying the
GETwhen thePOSTreturns a conflict due to inventory changes.
14.73.4.7. Compatibility rules#
Docs mark templates containing
template_type="inventory-cart"as requiring protocol vNEXT (final version TBD) or later.QR codes stay in the same pay-template URI parameters.
14.73.5. 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.
14.73.6. 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.
14.73.7. 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.
14.73.8. Discussion / Q&A#
- What should happen when a customer wants to leave a tip?
In the existing template version
tipis supported when the merchant allows amount modifications. For the newinventory-carttype therequest_tipflag makes that intent explicit. The backend simply appends the tip as another product that flows to the same payto target as the base order. Future work can revisit tip splitting, but that extra complexity is explicitly out of scope here.