20.96. DD 96: Partial Payments#
20.96.1. 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.
20.96.2. 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.
20.96.3. Requirements#
Orders and choices must be able to express mixed payment amounts.
The existing plain
Amountform must remain valid for backwards compatibility.The split amount form must always include the
talermethod.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.paymentsin 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.
20.96.4. Proposed Solution#
20.96.4.1. 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.amountOrderChoice.amountContractTermsV0.amountContractChoice.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.
20.96.4.2. Payment Method Names#
The initial reserved method names are:
talerfor GNU Talercashfor cash accepted by the merchant or cashier
Other names are allowed for integrations, but they should be stable ASCII identifiers.
20.96.4.3. 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.
20.96.4.4. Payment Flow#
The POS or integrating application is responsible for orchestrating mixed payments:
Create or update the order with an
AmountSpecthat reflects the intended split.Run all non-Taler payment steps, such as cash handling or card terminal authorization.
Start the Taler payment as the final step.
Complete the sale only after the merchant backend confirms the Taler payment, unless the
taleramount 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.
20.96.4.5. 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.
20.96.4.6. 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.
20.96.4.7. 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.
20.96.4.8. 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.
20.96.5. Test Plan#
Merchant backend tests for accepting both plain
AmountandAmountObjectin v0 orders and v1 choices.Merchant backend tests rejecting split amounts with missing
talermethod inAmountObject, mixed currencies or invalid method names.Merchant backend tests preserving
extra.paymentspayment 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.
20.96.6. 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
talerand 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
talerportion.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.paymentsas a generic table without method-specific renderers.Documentation explains that non-Taler refunds and failure recovery are owned by the integrating application.
20.96.7. Alternatives#
20.96.7.1. 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.
20.96.7.2. 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.
20.96.8. 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.
20.96.9. 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?