taler-docs

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

commit e606f5d4612081256597dddc7ae8aec9207790e4
parent 9fc64995dd8fe9106d600b92a87d23bea80ee806
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date:   Tue, 23 Jun 2026 21:08:09 +0200

updated of dd96 based on notes

Diffstat:
Mdesign-documents/096-partial-payments.rst | 248+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
1 file changed, 148 insertions(+), 100 deletions(-)

diff --git a/design-documents/096-partial-payments.rst b/design-documents/096-partial-payments.rst @@ -8,10 +8,11 @@ This document proposes support for orders where only part of the total amount is paid with Taler and the remaining amount is paid with other payment methods, such as cash, card, vouchers or others. -The main protocol change is to allow the amount of an order or order choice to -be either the existing ``Amount`` string or an ``AmountObject`` that splits the -total by payment method. The split must always contain a ``taler`` entry, even -when the Taler amount is 0. +The protocol change must be additive. The existing :ts:type:`Amount` field of +an order or choice continues to represent the amount paid with Taler. A new +optional ``amount_external`` field carries externally handled payment amounts +and the reconciliation metadata needed by POS applications and merchant +back-office users. Motivation ========== @@ -32,11 +33,14 @@ Requirements ============ * Orders and choices must be able to express mixed payment amounts. -* The existing plain ``Amount`` form must remain valid for backwards +* The existing plain :ts:type:`Amount` form must remain valid for backwards compatibility. -* The split amount form must always include the ``taler`` method. -* The total order amount is the sum of all payment-method amounts. -* The wallet must only pay the amount assigned to ``taler``. +* The type and meaning of existing ``amount`` fields must not change. +* The existing ``amount`` field remains the amount paid with Taler. +* The optional external payment field must not include Taler entries. +* The total order amount is the sum of the existing ``amount`` field and all + entries in ``amount_external``. +* The wallet must only pay the existing ``amount`` field. * The POS or other accommodating application must execute all non-Taler payments before the Taler payment. * The Taler payment is always the last payment step. @@ -45,93 +49,106 @@ Requirements completed non-Taler payments. * The merchant backend must preserve enough information for receipts, reporting and order inspection to show how the total was split. -* Per-method payment information must be stored in ``extra.payments`` in a - flat structure that the merchant portal can render as a generic table. +* Per-method payment information must be stored in a flat structure that the + merchant portal can render as a generic table. * The design must not require the wallet to validate that cash, card or other non-Taler payments actually happened. Proposed Solution ================= -Amount Variant --------------- +Additive Payment Field +---------------------- -Introduce a new amount type for order creation and contract terms: +Keep all existing :ts:type:`Amount` fields unchanged. In particular, +:ts:type:`OrderV0`.``amount``, :ts:type:`OrderChoice`.``amount`, +:ts:type:`ContractTermsV0`.``amount`` and +:ts:type:`ContractChoice`.``amount`` remain plain :ts:type:`Amount` values and +represent the amount the wallet pays with Taler. -:: +Add a new optional ``amount_external`` field next to these existing ``amount`` +fields: - type AmountSpec = Amount | AmountObject; +.. ts:def:: ExternalPaymentInfo - interface AmountObject { - // Amount paid with GNU Taler. This key is mandatory. - taler: Amount; + interface ExternalPaymentInfo { + // External payment method, for example "cash" or "card". + // Must never be "taler". + method: string; - // Additional payment methods, such as "cash", "card" or - // integration-specific identifiers. - [method: string]: Amount; + // Identifier of the payment action within the order. + // Examples: "cash1", "sumup1", "sumup2". + id: string; + + // Amount covered by this payment action. + // Must always be present + amount: Amount; + + // Additional method-specific fields. These fields must be + // stored only at this level. + [field: string]: string | Amount | Integer | boolean | null; + } + +.. ts:def:: PartialPaymentFields + + interface PartialPaymentFields { + // Payments handled outside of Taler. + amount_external?: ExternalPaymentInfo[]; } -The new ``AmountSpec`` type replaces ``Amount`` in: +The proposed extension applies to the amount-bearing order and contract +objects: -* ``OrderV0.amount`` -* ``OrderChoice.amount`` -* ``ContractTermsV0.amount`` -* ``ContractChoice.amount`` +:: + + type OrderV0 = ExistingOrderV0 & PartialPaymentFields; + type OrderChoice = ExistingOrderChoice & PartialPaymentFields; + type ContractTermsV0 = ExistingContractTermsV0 & PartialPaymentFields; + type ContractChoice = ExistingContractChoice & PartialPaymentFields; + +If ``amount_external`` is absent, the order is a regular pure Taler order and +the existing ``amount`` field is the total amount. If ``amount_external`` is +present, the existing ``amount`` field remains the Taler amount. The full +order or choice total is the sum of the existing ``amount`` field and all +entries in ``amount_external``. -The existing ``Amount`` string remains the short form for a pure Taler -payment. It is equivalent to: +For example, an order where the customer pays CHF 30 in cash and CHF 20 in +Taler keeps ``amount`` as ``CHF:20`` and adds ``amount_external``: :: { - "taler": Amount; + "amount": "CHF:20", + "amount_external": [ + { + "method": "cash", + "id": "cash1", + "amount": "CHF:30", + "cashier_number": "7" + } + ] } -All amounts in an ``AmountObject`` must use the same currency. Zero amounts -are allowed only where the corresponding payment method is still meaningful to -the application. In particular, ``taler`` may be zero to represent an order -that is tracked by a Taler-aware POS and receipt flow, but paid entirely by -other methods. +This is backwards compatible for old wallets because they continue to see a +plain :ts:type:`Amount` in ``amount``. Such wallets may not render the full +mixed-payment total, but they can still pay the Taler portion. Updated wallets +should render both the full total and the selected Taler amount clearly. Payment Method Names -------------------- -The initial reserved method names are: +The initial reserved method name is: -* ``taler`` for GNU Taler * ``cash`` for cash accepted by the merchant or cashier Other names are allowed for integrations, but they should be stable ASCII identifiers. -Payment Details in Contract Extra ---------------------------------- - -The split in ``AmountObject`` only defines how much is assigned to each payment -method. Additional per-method information must be stored in the contract's -``extra`` object under ``extra.payments``: - -:: - - interface PaymentInfo { - // Payment method, for example "cash", "card"... - method: string; +The name ``taler`` is reserved and must not be used in ``amount_external``. +Taler is represented by the existing ``amount`` field. - // Identifier of the payment action within the order. - // Examples: "cash1", "sumup1", "sumup2". - id: string; - - // Amount covered by this payment action. - amount: Amount; - - // Additional method-specific fields. These fields must be - // stored only at this level. - [field: string]: string | Amount | Integer | boolean | null; - } - - interface PartialPaymentExtra { - payments: PaymentInfo[]; - } +Payment Details +--------------- For cash payments, additional fields may include the cashier name, cashier number, register identifier or similar local information. For card payments, @@ -141,7 +158,7 @@ need for reconciliation or audit. The additional fields must be stored only one level below the payment entry. Nested method-specific objects should not be used. This allows the merchant -portal to render ``extra.payments`` as a simple table without knowing a custom +portal to render ``amount_external`` as a simple table without knowing a custom rendering format for each payment method. Payment Flow @@ -150,15 +167,18 @@ Payment Flow The POS or integrating application is responsible for orchestrating mixed payments: -1. Create or update the order with an ``AmountSpec`` that reflects the intended split. -2. Run all non-Taler payment steps, such as cash handling or card terminal authorization. +1. Create or update the order with ``amount_external`` that reflects the + intended externally handled payment amount. +2. Run all non-Taler payment steps, such as cash handling or card terminal + authorization. 3. Start the Taler payment as the final step. -4. Complete the sale only after the merchant backend confirms the Taler payment, unless the ``taler`` amount is zero. +4. Complete the sale only after the merchant backend confirms the Taler + payment, unless the ``taler`` amount is zero. The wallet receives the contract terms and computes the payable Taler amount -from the selected amount variant. It ignores non-Taler methods for coin -selection and payment signing, but it may render the full split so that the -customer understands why the Taler amount is lower than the order total. +from the existing ``amount`` field. It may use ``amount_external`` to render +the full total so that the customer understands why the Taler amount is lower +than the order total. Failure Handling ---------------- @@ -171,9 +191,9 @@ payment method. The POS or integrating application must therefore choose one of these recovery paths: -* modify the order amount split and retry the Taler payment; +* modify the order payment split and retry the Taler payment; * cancel the order and refund or void the completed non-Taler payments; -* proceed with different payment method, and make Taler part lower or 0. +* proceed with different payment method, and make Taler part lower or zero. Receipt Handling ---------------- @@ -192,16 +212,17 @@ printer, terminal display, e-mail or another regulated receipt mechanism. Reporting --------- -The merchant backend should store the selected amount split as part of the -contract terms and expose it through order status and history APIs. Existing -reporting that expects a single amount should continue to show the total order -amount. Detailed views should show the split by method. +The merchant backend should store ``amount_external`` as part of the contract +terms and expose it through order status and history APIs. Existing reporting +that expects a single amount should continue to show the Taler amount from the +existing ``amount`` field. Detailed views should show the externally handled +amounts and the full order total. -The merchant portal should render ``extra.payments`` as a table. Common columns -are ``method``, ``id`` and ``amount``. Additional columns can be derived from -the union of the flat method-specific fields present in the payment entries. -The merchant portal should not need method-specific rendering logic to show -this information. +The merchant portal should render ``amount_external`` as a table. Common +columns are ``method``, ``id`` and ``amount``. Additional columns can be +derived from the union of the flat method-specific fields present in the +payment entries. The merchant portal should not need method-specific +rendering logic to show this information. Refunds ------- @@ -215,32 +236,35 @@ through Taler. Test Plan ========= -* Merchant backend tests for accepting both plain ``Amount`` and - ``AmountObject`` in v0 orders and v1 choices. -* Merchant backend tests rejecting split amounts with missing ``taler`` method in ``AmountObject``, +* Merchant backend tests for accepting existing plain :ts:type:`Amount` fields + unchanged. +* Merchant backend tests accepting optional ``amount_external`` in v0 orders + and v1 choices. +* Merchant backend tests rejecting ``amount_external`` with ``taler`` entries, mixed currencies or invalid method names. -* Merchant backend tests preserving ``extra.payments`` payment entries with - flat method-specific fields. -* Wallet core tests for computing the payable Taler amount from both variants. -* Wallet core tests for rendering or exposing the full split without attempting - to pay non-Taler amounts. +* Merchant backend tests preserving ``amount_external`` entries with flat + method-specific fields. +* Wallet core tests for paying the existing ``amount`` field and rendering the + full total from ``amount_external`` when present. * POS integration tests for a successful cash/card-first and Taler-last flow. * POS integration tests for Taler failure after a non-Taler payment succeeded. Definition of Done ================== -* Merchant backend supports the new amount variant for order creation, - contract terms, order status and history. -* Merchant backend validates that every split amount contains ``taler`` and - that all split entries use one currency. -* Merchant backend preserves per-method payment details in ``extra.payments``. -* Wallet core supports the new amount variant and pays only the ``taler`` - portion. +* Merchant backend supports the new additive ``amount_external`` field for + order creation, contract terms, order status and history. +* Merchant backend keeps all existing ``amount`` fields as plain + :ts:type:`Amount` values. +* Merchant backend validates that ``amount_external`` has no ``taler`` entries + and that all entries use the same currency as ``amount``. +* Merchant backend preserves per-method payment details in ``amount_external``. +* Wallet core pays the existing ``amount`` field and does not require + ``amount_external`` to complete the Taler payment. * Wallet UIs can display the total and the selected Taler amount clearly. * POS and other accommodating applications support the required orchestration: non-Taler payments first, Taler payment last. -* Merchant portal renders ``extra.payments`` as a generic table without +* Merchant portal renders ``amount_external`` as a generic table without method-specific renderers. * Documentation explains that non-Taler refunds and failure recovery are owned by the integrating application. @@ -248,6 +272,24 @@ Definition of Done Alternatives ============ +Change the Amount Field Type +---------------------------- + +The initial proposal changed the existing ``amount`` fields from +:ts:type:`Amount` to ``Amount | AmountObject``. This was rejected because it +would be a destructive protocol change: every component that currently parses +``amount`` as a string would have to handle a new object shape. Keeping +``amount`` unchanged and adding ``amount_external`` preserves backwards +compatibility. + +Store Payment Details in Extra +------------------------------ + +Another initial proposal stored the payment split under ``extra.payments``. +This was rejected because ``extra`` is intended for proprietary +merchant-specific information. Official protocol fields should be explicit +top-level fields, not hidden under the merchant extension area. + Create Separate Orders ---------------------- @@ -271,11 +313,10 @@ and other method of payment failed. Drawbacks ========= -* The amount field becomes more complex for all components that parse contract - terms. * POS implementations must handle partial failure and external refunds carefully. -* Some old integrations may break when new object is found. +* Old wallets may only render the Taler amount and not the full mixed-payment + total until they learn the new ``amount_external`` field. * Reporting and refund UIs must distinguish total order amount from Taler-paid amount. @@ -294,3 +335,10 @@ Open Questions Discussion / Q&A ================ + +* Feedback from Florian Dold: ``extra`` must remain reserved for proprietary + merchant fields and must not carry official protocol data. Protocol changes + should be additive, so the existing ``amount`` field should not change type. + The design was updated accordingly: the existing ``amount`` remains the + Taler amount, while a new additive ``amount_external`` field carries the + externally handled amounts and reconciliation metadata.