13.73. DD 73: Extended Merchant Template#

13.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.

13.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.

13.73.3. 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+.

13.73.4. Proposed Solution#

13.73.4.1. Schema extensions#

Introduce an explicit template type discriminator so existing contracts remain stable and new behaviour can be negotiated per protocol version.

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.

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;
}
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 ...
}
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.

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[];
}
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;
}
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).

13.73.4.2. Merchant private API updates#

POST and PATCH on /private/templates accept new TemplateInventoryContractDetails.

13.73.4.3. 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.

13.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.

interface WalletTemplateDetails {
  template_type: TemplateType;
  template_contract: TemplateContractDetails | TemplateInventoryContractDetailsWallet;
  editable_defaults?: Object;
  required_currency?: string;
}
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;
}
interface WalletInventoryPayload {
  products: WalletInventoryProduct[];
  pagination?: InventoryPaginationCursor;
}
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;
}
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.

13.73.4.5. Template instantiation#

Introduce a specialised request body for inventory-driven templates while keeping UsingTemplateDetails valid for legacy flows.

interface InventoryTemplateUseDetails {
  summary?: string;
  amount?: Amount;
  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". 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.

13.73.4.6. 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.

13.73.4.7. 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.

13.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

13.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.

13.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.

13.73.8. Discussion / Q&A#

What to do, if customer wants to leave tip?