23.97. DD 96: Partial Payments#
- Design status:
Proposed
- Implementation status:
Partial
- DD shepherd:
TBD
- Historical contributors:
Bohdan Potuzhnyi
- First published:
2026-06-22
- Last substantive change:
2026-08-18
- Implementation evidence:
merchant(2026-07-20, not merged into the reviewed HEAD);taler-typescript-core(2026-08-19)- Normative references:
core/api-merchant.rst(upcomingvMixedPaymentsand associated endpoint schemas)
23.97.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 protocol change must be additive. The existing 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.
23.97.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.
23.97.3. Requirements#
Orders must be able to express mixed payment amounts, for both v0 orders and v1 orders with choices.
The existing plain Amount form must remain valid for backwards compatibility.
The type and meaning of existing
amountfields must not change.The existing
amountfield 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
amountfield and all entries inamount_external. This total must be well-defined, so all amounts involved must use a single currency.The wallet must only pay the existing
amountfield.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 abandon the old order, create a replacement order with an adjusted split, and retry the Taler step, or refund the completed non-Taler payments.
Orders with settled external payments and a failed Taler payment must remain visible to merchant-facing applications. They must not be deleted by normal order deletion or by accident.
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 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.
23.97.4. Proposed Solution#
23.97.4.1. Additive Payment Field#
Keep all existing Amount fields unchanged. In particular, OrderV0.``amount``, OrderChoice.``amount`, ContractTermsV0.``amount`` and ContractChoice.``amount`` remain plain Amount values and represent the amount the wallet pays with Taler.
Add a new optional amount_external field to the common part of orders
and contract terms (like products or the deadlines in
OrderCommon):
interface ExternalPaymentInfo {
// External payment method, for example "cash" or "card".
// Must never be "taler".
method: string;
// 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;
}
interface PartialPaymentFields {
// Payments handled outside of Taler.
amount_external?: ExternalPaymentInfo[];
}
The proposed extension applies to the common part shared by all order and contract terms versions:
type OrderCommon = ExistingOrderCommon & PartialPaymentFields;
type ContractTermsCommon = ExistingContractTermsCommon & PartialPaymentFields;
The field is deliberately not attached to the individual v1 choices: external payments are settled before the wallet ever sees the contract, so they are facts about the order that cannot vary with the choice the wallet later selects. A per-choice field would force the POS to duplicate identical entries into every choice and would even allow choices to state contradictory external payments, which the backend could not police.
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 total is the sum of the selected amount field (the top-level
amount for v0, the selected choice’s amount for v1) and all
entries in amount_external.
For this total to be well-defined, the backend rejects orders where
amount_external is present and any amount uses a different currency: all
entries in amount_external must use the currency of the Taler amount
and, for v1 orders, every choice must be denominated in that same currency.
Multi-currency choices remain possible for pure Taler orders without
amount_external.
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:
{
"amount": "CHF:20",
"amount_external": [
{
"method": "cash",
"id": "cash1",
"amount": "CHF:30",
"cashier_number": "7"
}
]
}
For a v1 order the field stays at the top level next to choices; the
CHF 30 cash payment applies regardless of which choice the wallet selects:
{
"version": 1,
"amount_external": [
{ "method": "cash", "id": "cash1", "amount": "CHF:30" }
],
"choices": [
{ "amount": "CHF:20", ... },
{ "amount": "CHF:15", "inputs": [ ... ], ... }
]
}
This is backwards compatible for old wallets because they continue to see a
plain 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.
An order may also have a Taler amount of zero. This allows a POS or merchant portal to use the merchant backend for product tracking, receipts and reports even when the customer paid the whole amount with cash, card or another external method. Such orders are completed using the private collect operation described below.
23.97.4.2. Payment Method Names#
The initial reserved method name is:
cashfor cash accepted by the merchant or cashier
For now, payment method names are not centrally registered. Integrations may
use any stable identifier consisting of ASCII alphanumerics and - (as in
credit-card). The restriction is deliberately tight: it can be relaxed
later without invalidating names already recorded by merchants, whereas
tightening it later could not.
The name taler is reserved and must not be used in amount_external.
Taler is represented by the existing amount field.
23.97.4.3. Payment Details#
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 amount_external as a simple table without knowing a custom
rendering format for each payment method.
The fields beyond method, id and amount are deliberately generic in
this design and must remain flat, with no nested method-specific objects. This
keeps rendering simple and avoids having to standardize every card terminal,
cash register, external voucher or future payment integration up front. The
drawback is that these fields are mostly display and reconciliation metadata.
If GNU Taler later needs to use method-specific fields for reports, cash
tracking, refund UI or automated reconciliation, those fields should be
standardized explicitly in a future design.
23.97.4.4. Payment Flow#
The POS or integrating application is responsible for orchestrating mixed payments:
Create or update the order with
amount_externalthat reflects the intended externally handled payment amount.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. If the Taler amount is zero, use the private collect operation described in Zero-Taler Order Collection.
The wallet receives the contract terms and computes the payable Taler amount
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.
Because the Taler payment is the last step, the wallet only sees the order and contract terms after the externally handled payments have been settled.
If the payment split changes before the Taler payment starts, the expected flow is to delete or abandon the old order and create a new order with updated external amounts. Updating an already visible or claimed order to add or remove external payments is out of scope for this design.
23.97.4.5. Zero-Taler Order Collection#
Orders whose Taler amount is zero, for example because the full total was
settled through amount_external, are completed through a new private
endpoint POST [/instances/$INSTANCE]/private/orders/$ORDER_ID/collect.
The merchant backend acts exactly like a customer wallet collecting a free
order, executing the same two steps a wallet would: it first claims the
order with a backend-generated nonce, and then has the order paid.
Neither step is reimplemented. Claiming calls the same routine the wallet
facing claim endpoint calls, and for the payment the request is turned into
the payment request a wallet would send for a free order – no coins, and the
selected choice_index – and handed to the payment handler itself. Payment
notifications, webhooks, triggers and inventory updates therefore do not merely
resemble those of a wallet payment, they are the same code, and the response of
the collect endpoint is the response of that payment. Adding a step to the
payment flow later does not require touching this endpoint.
Only genuinely free orders are handed over, as the payment logic expects to be paid with coins and reports nothing useful when it is not.
The backend derives the claim nonce deterministically from the instance public key and the order identifier. This makes the collect operation idempotent (repeating it after a success or a crash between the claim and the payment step completes or replays harmlessly) and lets the backend distinguish orders it collected itself from orders claimed by a customer wallet.
Collection is only possible while the order is unclaimed. If a customer wallet
has already claimed the order, the wallet owns the order and must execute the
free payment itself; the collect operation fails and reports that the order
was claimed by a wallet
(TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ID_COLLECT_ALREADY_CLAIMED).
This preserves the customer’s ability to obtain a wallet
receipt: a POS should offer the order QR code first and only collect after the
customer declined collecting it with own wallet.
Collection is restricted to genuinely free Taler payments: the Taler
amount must be zero and, for v1 contracts, the selected choice must have no
inputs and no outputs. Choices that redeem or issue tokens, such as
subscription tokens or donation receipts, require a customer wallet and cannot
be collected
(TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ID_COLLECT_NOT_FREE).
The optional session_id of the collect request is stored like the session
identifier of a wallet payment, so a POS device can tag and later list the
orders it collected. For v1 orders, the choice_index of the collect
request selects the choice to complete and is mandatory: the backend never
picks a choice on its own, exactly as a wallet has to select one when paying.
Collecting a v1 order without a choice_index fails with
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_CHOICE_INDEX_MISSING, and naming a
choice that does not exist – including naming any choice for a v0 order,
which has none – fails with
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_CHOICE_INDEX_OUT_OF_BOUNDS.
Defaulting to the first choice was rejected: which choice was completed
determines the amount, the tokens consumed and issued, and the fulfillment,
so it is a decision the client must make explicitly rather than one the
backend guesses.
For the same reason the choice is part of what makes a repeated request a
repetition. Collecting an order that is already paid, but naming a different
choice than the one it was paid with, describes a payment that never happened
and fails with
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_CHOICE_INDEX_MISMATCH; the response
names the choice the order was actually paid with, so that a client can
correct its request. This is not specific to collecting: deciding that a
request repeats an earlier payment means comparing it against that payment,
and the payment logic compared the coins and the input tokens but not the
choice. For a choice that costs nothing in Taler both are empty, so paying
such an order a second time while naming another choice used to be reported
as success. The comparison was completed rather than worked around in the
collect endpoint, so wallets benefit from it as well.
23.97.4.6. 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:
abandon the old order, create a replacement with a modified payment split, and retry the Taler payment;
cancel the order and refund or void the completed non-Taler payments;
create a replacement order using a different payment method, with the Taler part lower or zero.
Until one of these recovery paths is completed, the order must remain visible
to merchant-facing applications. No dedicated order status value is
introduced for this: the order simply remains in its regular claimed or
unpaid state, and merchant-facing applications can recognize the situation
from the presence of amount_external in an unpaid order. Such orders
must not be deleted through normal order deletion. Any order that records at
least one settled external payment in amount_external – an empty array
records none and does not block anything – can only be deleted by explicitly
passing force=yes
to DELETE /private/orders/$ORDER_ID; a normal deletion attempt fails
with TALER_EC_MERCHANT_PRIVATE_DELETE_ORDERS_EXTERNALLY_PAID. The force
requirement makes it clear that externally handled payments may already
have settled.
23.97.4.7. 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.
23.97.4.8. Reporting#
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 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.
23.97.4.9. External Refunds#
Taler refunds continue to use the existing refund mechanism and are capped at
the amount actually paid with Taler. Since contract terms are hashed and signed
at claim time, external refunds must not modify amount_external. Instead,
the merchant backend records them separately, analogous to how Taler refunds
are stored outside the contract terms.
A new private operation, for example
POST /private/orders/$ORDER_ID/refund-external, records an external refund
entry:
interface ExternalRefundInfo {
// Method by which the funds were returned to the customer,
// for example "cash" or "card". May differ from the methods
// used to pay the order. Must never be "taler".
method: string;
// Identifier of this refund within the order, chosen by the
// merchant. Mandatory, see below.
id: string;
// Optionally, the "id" of the "amount_external" entry this
// refund reverses, when the refund maps to a specific original
// payment, for example a card transaction reversal.
payment_id?: string;
// Amount returned to the customer via the external method.
amount: Amount;
// Human-readable refund justification, mirroring Taler refunds.
reason: string;
}
Unlike amount_external entries, external refunds are stored in dedicated
typed database columns rather than as a JSON blob, so every field the backend
reports is authoritative. Consequently an external refund carries no
method-specific extra fields for now; if a concrete integration needs them,
they can be added later without changing the fields above.
The id makes recording an external refund idempotent. The backend stores a
hash of the request body alongside the entry, mirroring how h_post_data
makes order creation idempotent: repeating a request with the same id and
an identical body succeeds without recording a second refund, whereas reusing
an id with different details fails with
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ID_REFUND_EXTERNAL_ALREADY_EXISTS.
This matters because a point-of-sale application that retries after a network
failure must not consume the refundable amount twice.
The id is mandatory rather than generated by the backend when absent, even
though order identifiers work the other way around. The difference is what a
duplicate costs: a duplicated order is inert and eventually cleaned up, whereas
a duplicated external refund permanently consumes part of the amount that may
still be refunded for a real order. Since a recorded external refund cannot be
removed, the identifier has to come from whoever knows whether two requests
describe the same real-world refund, which is the client.
Being unable to remove a recorded external refund is a deliberate limitation of this design, matching Taler refunds, which can only ever be increased. It does mean a refund recorded in error – a wrong amount, or cash that never actually left the till – cannot be corrected, and deleting the order is not an option because external refunds only exist on paid orders. Should this turn out to be needed in practice, the natural extension is to mark such entries as void rather than to delete them, so that the mistake stays visible for bookkeeping, and to exclude voided entries when computing the refunded total. That is left to a future design document.
The refund channel does not need to match the payment channel: a shop may return the whole amount in cash even when parts of the order were paid by card or with Taler. This is particularly relevant because Taler refunds are constrained by the contract’s refund deadline. Once it has passed, the Taler portion can only be returned through an external method.
For this reason external refunds are available for all paid orders, not
only for orders that carry amount_external: a pure Taler order whose
refund deadline has passed can still be settled with the customer in cash,
and the merchant backend must be able to record that for proper bookkeeping.
External refunds require the order to be paid; recording a refund for an
unpaid order fails with
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ID_REFUND_ORDER_UNPAID. An unpaid
order has no settled Taler payment to reverse; if its external payments need
to be undone, the POS deletes the order (with force) and, if needed, creates
a new one with an updated payment split.
The backend validates external refunds against the order total rather
than against individual payment entries: the cumulative externally refunded
amount must not exceed the full order total minus the amount already refunded
through Taler
(TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ID_REFUND_EXTERNAL_INCONSISTENT_AMOUNT),
and must use the same currency as the order. There is no override for these
checks; they protect the refund ledger against recording errors.
Unlike Taler refunds, external refund entries are bookkeeping only. The POS or
external payment integration performs the actual return of funds, and no wallet
pickup step exists or is needed. Order status APIs expose the recorded entries,
for example as refunds_external, and the merchant portal renders them as a
generic table, like amount_external.
For zero-Taler orders collected without a customer wallet, no Taler refund is ever possible because the Taler amount paid is zero. All refunds on such orders are therefore external by construction.
23.97.4.10. Vouchers and Tokens#
External vouchers can be represented as entries in amount_external. Taler
wallet tokens, discounts, gift vouchers and P2P transfer of such vouchers are a
separate feature area. In particular, this design does not define how a
wallet-held voucher can be partially spent, whether remaining value is
re-issued as a new token, or how voucher tokens can be transferred between
wallets.
23.97.5. Test Plan#
Merchant backend tests for accepting existing plain Amount fields unchanged.
Merchant backend tests accepting optional
amount_externalin the common part of v0 and v1 orders.Merchant backend tests rejecting
amount_externalwithtalerentries, mixed currencies, invalid method names, duplicate entry ids or nested method-specific fields.Merchant backend tests rejecting v1 orders with
amount_externalwhere any choice uses a different currency than the external entries.Merchant backend tests preserving
amount_externalentries with flat method-specific fields.Merchant backend tests rejecting normal deletion of orders that record settled external payments unless an explicit force operation is used.
Merchant backend tests collecting an unclaimed zero-Taler v0 order and a zero-amount v1 choice via the private collect endpoint.
Merchant backend tests rejecting collect for orders with nonzero Taler amounts, for v1 choices with token inputs or outputs, and for orders already claimed by a wallet.
Merchant backend tests rejecting collect of a v1 order that does not select a choice or selects one that does not exist, and of a v0 order that selects a choice although it has none.
Merchant backend tests for collect idempotency.
Merchant backend tests rejecting a second payment of an already paid v1 order that names a different choice, both through the collect endpoint and through the wallet-facing payment endpoint.
Merchant backend tests rejecting normal deletion of an order claimed by a wallet unless an explicit force operation is used.
Merchant backend tests recording external refunds, including refunds through a method different from the original payment methods and refunds on pure Taler orders without
amount_external.Merchant backend tests rejecting external refunds that would exceed the order total minus the amount refunded through Taler, that use a different currency or the method name
taler, or that target an unpaid order.Merchant backend tests recording an external refund twice under the same
id, once with an identical request body, which has to be idempotent, and once with different details, which has to be refused as a conflict.Wallet core tests for paying the existing
amountfield and rendering the full total fromamount_externalwhen 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.
23.97.6. Definition of Done#
Merchant backend supports the new additive
amount_externalfield for order creation, contract terms, order status and history.Merchant backend keeps all existing
amountfields as plain Amount values.Merchant backend validates that
amount_externalhas notalerentries, that all entries use the same currency asamountand that, for v1 orders, every choice uses that currency as well.Merchant backend preserves per-method payment details in
amount_external.Merchant backend keeps orders with settled external payments and a failed Taler payment visible for manual resolution.
Merchant backend prevents normal deletion of such orders and requires an explicit force operation to remove them.
Merchant backend provides the private collect operation for zero-Taler orders, restricted to unclaimed orders and genuinely free choices, with payment notifications identical to a wallet payment, implemented idempotently.
Merchant backend records external refund entries for any paid order, validates them against the combined refund cap, and exposes them through order status APIs.
Wallet core pays the existing
amountfield and does not requireamount_externalto 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
amount_externalas a generic table without method-specific renderers.Merchant portal renders external refunds as a generic table without method-specific renderers.
Documentation explains that external refunds are bookkeeping entries and failure recovery is owned by the integrating application.
23.97.7. Alternatives#
23.97.7.1. Change the Amount Field Type#
The initial proposal changed the existing amount fields from
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.
23.97.7.2. Attach amount_external to Individual Choices#
An earlier revision of this design attached amount_external to each
amount-bearing object, i.e. also to the individual v1 choices
(type OrderChoice = ExistingOrderChoice & PartialPaymentFields). This
was rejected: external payments are settled before the contract is shown to
the wallet, so they cannot differ between choices. Per-choice fields would
have forced the POS to duplicate identical entries into every choice and
would have allowed choices to state contradictory external payments without
the backend being able to reject them. It also left the external refund cap
ill-defined for unpaid multi-choice orders. Placing the field in the common
part avoids all of this at the cost of requiring a single currency across
all choices whenever amount_external is present.
23.97.7.3. 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.
23.97.7.4. 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.
23.97.7.5. 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.
23.97.7.6. Use Templates or Mutable Payment Sessions#
Templates or a new payment-session model could allow the customer to inspect an
order before choosing how to split the payment, and could support adding,
modifying or deleting payment parts before finalization. This would be more
flexible than DD96, but it would require a larger design across merchant
backend, POS apps and wallets. DD96 is limited to the current /orders flow
where external payments are settled before the Taler order is created.
23.97.7.7. Split One Order Across Multiple Taler Wallets#
Multiple customers paying one order with multiple Taler wallets is out of scope. The current workaround is to split the sale into multiple orders or sub-orders, for example based on the products consumed by each customer.
23.97.8. Drawbacks#
POS implementations must handle partial failure and external refunds carefully.
Old wallets may only render the Taler amount and not the full mixed-payment total until they learn the new
amount_externalfield.The customer cannot inspect the order or contract terms in the wallet before externally handled payments, such as cash or card payments, have been settled.
Reporting and refund UIs must distinguish total order amount from Taler-paid amount. They must also distinguish Taler refunds, which require wallet pickup, from external refunds, which are bookkeeping entries only.
23.97.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?
23.97.10. Discussion / Q&A#
Feedback from Florian Dold:
extramust remain reserved for proprietary merchant fields and must not carry official protocol data. Protocol changes should be additive, so the existingamountfield should not change type. The design was updated accordingly: the existingamountremains the Taler amount, while a new additiveamount_externalfield carries the externally handled amounts and reconciliation metadata.Revisions from the implementation review of the merchant backend (2026-07):
amount_externalwas moved from the per-choice objects to the common order/contract terms part, since settled external payments are facts that cannot vary with the wallet’s selection (see Attach amount_external to Individual Choices). As a consequence, orders withamount_externalare restricted to a single currency across all choices, so that the order total and the external refund cap are always well-defined. External refunds were clarified to require a paid order and to be available for all paid orders, including pure Taler orders, so that returns after the Taler refund deadline can be recorded for proper bookkeeping; a force override for the refund cap was considered and rejected, since the strict check protects the refund ledger against recording errors. Collect was specified to be idempotent via a deterministic backend claim nonce, with a mandatorychoice_indexfor v1 orders. No dedicated manual-resolution status value was introduced; blocking non-forced deletion was deemed sufficient. Error codes 2535-2539 (..._COLLECT_NOT_FREE,..._COLLECT_ALREADY_CLAIMED,..._DELETE_ORDERS_EXTERNALLY_PAID,..._REFUND_EXTERNAL_INCONSISTENT_AMOUNT,..._REFUND_EXTERNAL_ALREADY_EXISTS) were registered in GANA for the new failure modes, and 2187 (MERCHANT_POST_ORDERS_ID_PAY_CHOICE_INDEX_MISMATCH) for paying or collecting an order that is already paid with another choice. The latter was given its own code rather than reusing..._PAY_ALREADY_PAID, because it tells the client something it can act on – which choice the order was actually paid with – whileALREADY_PAIDmeans the payment belongs to somebody else and there is nothing to correct.