commit 9fc64995dd8fe9106d600b92a87d23bea80ee806
parent fa579727df1f58159f8a4cfc86768bdf01861ccc
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date: Mon, 22 Jun 2026 18:59:19 +0200
adding dd96 about partial payment
Diffstat:
5 files changed, 304 insertions(+), 1 deletion(-)
diff --git a/README b/README
@@ -5,6 +5,7 @@ To build things on a Debian (-based) system, install these packages:
- python3-sphinx
- python3-myst-parser
- python3-sphinx-book-theme
+ - python3-sphinx-design
- graphviz
- texlive-latex-extra
- dvipng
diff --git a/design-documents/095-captcha-100.rst b/design-documents/095-captcha-100.rst
@@ -68,21 +68,25 @@ Proposed Solution
=================
Business:
+
- Open bank accounts in various currencies
- Consider getting no-action letters from regulators
Wallets:
+
- Implement wallet dialect with non-payment captcha-oriented language
- Ensure wallets respect 'no deposit' and 'no-p2p' settings nicely
- Communicate special fee structure clearly on withdraw
Other development:
+
- Implement, test and document Pepsi
- update merchant backend to support wire method 'void'
(auto-configured bank account payto://void/) for some
particular settings.
Administration:
+
- Deploy a single exchange in CAPTCHAS and hook it up
to all of these accounts with some reasonable currency
conversion ratio
@@ -129,4 +133,3 @@ Drawbacks
Discussion / Q&A
================
-
diff --git a/design-documents/096-partial-payments.rst b/design-documents/096-partial-payments.rst
@@ -0,0 +1,296 @@
+DD 96: Partial Payments
+#######################
+
+Summary
+=======
+
+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.
+
+Motivation
+==========
+
+In person purchases might involve mixed payments. A customer may pay part of
+an order in cash and the rest with Taler, or a cashier may need to combine
+Taler with a card terminal, voucher system or other local payment method.
+Today, the merchant backend and wallet assume that the amount in the contract
+is the amount the wallet pays with Taler. This model cannot represent a
+single receipt and order that is settled by multiple methods.
+
+The goal is not to make the merchant backend process card or cash payments.
+The goal is to let the merchant backend, wallet core and POS applications agree
+on the order total, the Taler portion and the non-Taler portions that must have
+already been completed outside of Taler.
+
+Requirements
+============
+
+* Orders and choices must be able to express mixed payment amounts.
+* The existing plain ``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 POS or other accommodating application must execute all non-Taler
+ payments before the Taler payment.
+* The Taler payment is always the last payment step.
+* If the Taler payment fails after other payments succeeded, the POS must
+ either modify the order and retry the Taler step or refund the already
+ 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.
+* The design must not require the wallet to validate that cash, card or other
+ non-Taler payments actually happened.
+
+Proposed Solution
+=================
+
+Amount Variant
+--------------
+
+Introduce a new amount type for order creation and contract terms:
+
+::
+
+ type AmountSpec = Amount | AmountObject;
+
+ interface AmountObject {
+ // Amount paid with GNU Taler. This key is mandatory.
+ taler: Amount;
+
+ // Additional payment methods, such as "cash", "card" or
+ // integration-specific identifiers.
+ [method: string]: Amount;
+ }
+
+The new ``AmountSpec`` type replaces ``Amount`` in:
+
+* ``OrderV0.amount``
+* ``OrderChoice.amount``
+* ``ContractTermsV0.amount``
+* ``ContractChoice.amount``
+
+The existing ``Amount`` string remains the short form for a pure Taler
+payment. It is equivalent to:
+
+::
+
+ {
+ "taler": Amount;
+ }
+
+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.
+
+Payment Method Names
+--------------------
+
+The initial reserved method names are:
+
+* ``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;
+
+ // 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[];
+ }
+
+For cash payments, additional fields may include the cashier name, cashier
+number, register identifier or similar local information. For card payments,
+additional fields may include the terminal identifier, acquirer reference,
+transaction ID or authorization code. Other systems may add the fields they
+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
+rendering format for each payment method.
+
+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.
+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.
+
+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.
+
+Failure Handling
+----------------
+
+Mixed payments introduce a failure mode where a non-Taler payment has already
+succeeded but the final Taler payment fails. The merchant backend cannot
+automatically repair this state because it does not control the external
+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;
+* cancel the order and refund or void the completed non-Taler payments;
+* proceed with different payment method, and make Taler part lower or 0.
+
+Receipt Handling
+----------------
+
+For normal wallet flows, the customer can access the Taler receipt after the
+wallet payment. In POS deployments this may not be enough. Some jurisdictions
+require a printed or otherwise directly provided receipt, and in a mixed
+payment flow the customer may not receive a Taler receipt if the POS
+application performs self-pickup or the Taler amount is zero.
+
+POS applications and other accommodating applications must therefore support a
+mode where they retrieve the receipt themselves from the merchant backend and
+provide it to the customer through the locally required channel, such as a
+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 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.
+
+Refunds
+-------
+
+Taler refunds can only refund the amount actually paid with Taler. Refunds for
+cash, card or other methods remain the responsibility of the POS or external
+payment integration. When an order has a split amount, APIs and UIs should
+avoid wording that implies the merchant backend can refund the whole order
+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``,
+ 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.
+* 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.
+* 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
+ method-specific renderers.
+* Documentation explains that non-Taler refunds and failure recovery are owned
+ by the integrating application.
+
+Alternatives
+============
+
+Create Separate Orders
+----------------------
+
+The POS could create one Taler order only for the Taler amount and track cash
+or card payments in its own system. This avoids changing the contract amount
+type, but it loses the single-order receipt and reporting model. It also makes
+customer-facing order totals harder to verify. As well it looses the backup
+and synchronisation between device possibilities.
+
+Let Taler Run Before Other Methods
+----------------------------------
+
+Running Taler before cash or card would make the Taler part successful while
+the external payment can still fail. That leaves the merchant with a paid
+Taler contract for an order that may not be otherwise settled. Requiring Taler
+to be last gives the POS a clearer recovery path because external payments can
+still be voided, refunded or used to recompute the remaining Taler amount. As
+well it can create problems when refund deadline for Taler option was set as 0
+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.
+* Reporting and refund UIs must distinguish total order amount from Taler-paid
+ amount.
+
+Open Questions
+==============
+
+* Should money pots store full totals, per-method totals, or both? Should
+ merchant backend auto create new pots per each new payment method found in
+ order?
+* Should payment method names be centrally registered, or is validation of
+ stable ASCII identifiers sufficient?
+* Should there be a dedicated status for orders where non-Taler payments
+ succeeded but the final Taler payment failed? or we can just delete them?
+* Should the merchant backend expose explicit receipt self-pickup endpoints for
+ POS devices, or are existing private order status APIs sufficient?
+
+Discussion / Q&A
+================
diff --git a/design-documents/index.rst b/design-documents/index.rst
@@ -106,4 +106,6 @@ Design documents that start with "XX" are considered deprecated.
092-incremental-backup-sync
093-checkout-page
094-discounts-passes-wallet
+ 095-captcha-100
+ 096-partial-payments
999-template
diff --git a/flake.nix b/flake.nix
@@ -60,6 +60,7 @@
python-pkgs.sphinx-book-theme
python-pkgs.myst-parser
python-pkgs.sphinxcontrib-httpdomain
+ python-pkgs.sphinx-design
]))
];