taler-docs

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

commit a1dcec86e89f9acd7d3b6a118f67d9a60d78555a
parent 3c6d0b8536e21287d1333986743e282277a66995
Author: Florian Dold <dold@taler.net>
Date:   Wed, 19 Aug 2026 12:34:23 +0200

DD 101: add product selectors and free-item policy

Diffstat:
Mdesign-documents/101-semantic-token-families.rst | 125++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
1 file changed, 86 insertions(+), 39 deletions(-)

diff --git a/design-documents/101-semantic-token-families.rst b/design-documents/101-semantic-token-families.rst @@ -54,41 +54,65 @@ also contain unrelated top-level members. } A subscription supports percentage and capped-flat benefits. A discount -supports both of those and a free-item benefit. +supports both of those and a free-item benefit. Either family can omit an +automatic redemption benefit while a discount continues to issue tokens. .. ts:def:: ExperimentalSubscription - type ExperimentalSubscription = RedemptionCategories & - (PercentageBenefit | FlatBenefit); + type ExperimentalSubscription = RedemptionProducts & + (PercentageBenefit | FlatBenefit | NoRedemptionBenefit); .. ts:def:: ExperimentalDiscount - type ExperimentalDiscount = RedemptionCategories & - (PercentageBenefit | FlatBenefit | FreeItemBenefit) & { - // Positive safe integer. This many tokens are consumed on redemption. - required_tokens: Integer; + type ExperimentalDiscount = + | (RedemptionProducts & + (PercentageBenefit | FlatBenefit | FreeItemBenefit) & { + // Positive safe integer. This many tokens are consumed on redemption. + required_tokens: Integer; - // Rule for earning one token from a paid order. - issuance: DiscountIssuance; - }; + // Rule for earning one token from a paid order. + issuance: DiscountIssuance; + }) + | (RedemptionProducts & NoRedemptionBenefit & { + issuance: DiscountIssuance; + }); -.. ts:def:: RedemptionCategories +.. ts:def:: RedemptionProducts - interface RedemptionCategories { - // Non-empty array. A line item is eligible when it has any listed ID. - product_categories: CategorySnapshot[]; + interface RedemptionProducts { + // A non-empty selector array restricts redemption to matching line items. + // "*" uses the whole-order amount and also supports amount-only orders. + product_selectors: ProductSelector[] | "*"; } -.. ts:def:: CategorySnapshot +.. ts:def:: ProductSelector + + type ProductSelector = CategorySelector | InventoryProductSelector; + +.. ts:def:: CategorySelector - interface CategorySnapshot { - // Positive safe integer inventory category ID. + interface CategorySelector { + type: "category"; + + // Positive safe-integer inventory category ID. id: Integer; // Non-empty display-name snapshot. It is not used for matching. name: string; } +.. ts:def:: InventoryProductSelector + + interface InventoryProductSelector { + type: "product"; + + // Non-empty inventory product ID. + id: string; + + // Non-empty display-name snapshot. It is not used for matching. + name: string; + } + .. ts:def:: PercentageBenefit interface PercentageBenefit { @@ -112,30 +136,45 @@ supports both of those and a free-item benefit. precision: string; } +.. ts:def:: AmountString + + // Canonical Taler amount in ``CURRENCY:VALUE`` form. + type AmountString = string; + .. ts:def:: FlatBenefit interface FlatBenefit { type: "flat"; - // Positive Taler amount. The reduction is capped at the eligible - // subtotal and applies only when its currency matches the order currency. - amount: AmountString; + // One positive amount, or a non-empty list containing at most one positive + // amount for each currency. The reduction is capped at the eligible + // subtotal and applies only when a cap matches the order currency. + amount: AmountString | AmountString[]; } .. ts:def:: FreeItemBenefit interface FreeItemBenefit { - // Valid only in ExperimentalDiscount. One unit of the lowest-priced - // eligible item is deducted. + // Valid only in ExperimentalDiscount. One eligible unit is deducted. type: "free_item"; + + // Defaults to "cheapest" when omitted. + price_selection?: "cheapest" | "most_expensive"; + } + +.. ts:def:: NoRedemptionBenefit + + interface NoRedemptionBenefit { + // No token-consuming order choice is generated. + type: "none"; } .. ts:def:: DiscountIssuance interface DiscountIssuance { - // A non-empty category array restricts issuance to the matching subtotal. + // A non-empty selector array restricts issuance to the matching subtotal. // "*" uses the whole-order amount and also supports amount-only orders. - product_categories: CategorySnapshot[] | "*"; + product_selectors: ProductSelector[] | "*"; // Optional positive Taler amount. The threshold is inclusive and applies // only when its currency matches the order currency. @@ -149,21 +188,26 @@ supports both of those and a free-item benefit. The WebUI accepts only canonical percentage and precision strings: leading zeroes, trailing fractional zeroes, signs and exponent notation are invalid. When ``rounding`` is absent, percentage reductions round down to Taler's -smallest amount fraction. Category arrays must be non-empty; repeated IDs are -coalesced. The WebUI writes only the semantic key matching the family kind, -does not read the earlier unprefixed prototype keys, and preserves unrelated +smallest amount fraction. Selector arrays must be non-empty; repeated pairs +of selector type and ID are coalesced. The WebUI writes only the semantic key +matching the family kind, does not read the earlier unprefixed prototype keys +or the ``product_categories`` selector format, and preserves unrelated top-level ``extra_data`` members. Additional members inside a semantic object are not part of the schema and may be discarded when the family is edited. Behavior -------- -Categories are snapshots. Matching uses only the ID; the name is display -metadata refreshed when an existing family is saved. +Categories and inventory products are snapshots. Matching uses only the ID; +the name is display metadata refreshed when an existing family is saved. A +line item is eligible when its inventory product ID matches a product selector +or any of its category IDs matches a category selector. Mixed selectors use +OR semantics. Ad-hoc line items have no inventory product ID and can only +match the wildcard. A discount additionally has ``required_tokens`` and an ``issuance`` object. -Its ``product_categories`` is either category snapshots or ``"*"`` for every -paid merchant order. ``minimum_purchase`` is optional and inclusive, and +Its ``product_selectors`` is either product/category selectors or ``"*"`` for +every paid merchant order. ``minimum_purchase`` is optional and inclusive, and ``issue_on_redemption`` defaults to false. For example:: { @@ -171,9 +215,12 @@ paid merchant order. ``minimum_purchase`` is optional and inclusive, and "type": "flat", "amount": "CHF:10", "required_tokens": 5, - "product_categories": [{"id": 1, "name": "Coffee"}], + "product_selectors": [ + {"type": "category", "id": 1, "name": "Coffee"}, + {"type": "product", "id": "espresso", "name": "Espresso"} + ], "issuance": { - "product_categories": "*", + "product_selectors": "*", "minimum_purchase": "CHF:5", "issue_on_redemption": false } @@ -184,17 +231,17 @@ The WebUI calculates redemption choices separately from earned-token outputs. It adds qualifying outputs to the full-price choice and every alternative, except that same-family discount redemption suppresses issuance by default. Outputs for the same family are coalesced. Eligibility and thresholds use the -exact pre-benefit subtotal; category rules require trustworthy line items, +exact pre-benefit subtotal; selector rules require trustworthy line items, while wildcard rules also support amount-only orders. Test Plan ========= -Test metadata parsing and writing, exact benefit arithmetic, category and -wildcard eligibility, free-item selection, issuance thresholds, same-family -suppression, and output coalescing. WebUI tests cover editing, order creation, -and PoS previews. The harness earns tokens on paid orders and later redeems -the configured threshold. +Test metadata parsing and writing, exact benefit arithmetic, mixed selectors +and wildcard eligibility, cheapest and most-expensive free-item selection, +issuance thresholds, same-family suppression, and output coalescing. WebUI +tests cover editing, order creation, and PoS previews. The harness earns +tokens on paid orders and later redeems the configured threshold. Definition of Done ==================