096-partial-payments.rst (14164B)
1 DD 96: Partial Payments 2 ####################### 3 4 Summary 5 ======= 6 7 This document proposes support for orders where only part of the total amount 8 is paid with Taler and the remaining amount is paid with other payment 9 methods, such as cash, card, vouchers or others. 10 11 The protocol change must be additive. The existing :ts:type:`Amount` field of 12 an order or choice continues to represent the amount paid with Taler. A new 13 optional ``amount_external`` field carries externally handled payment amounts 14 and the reconciliation metadata needed by POS applications and merchant 15 back-office users. 16 17 Motivation 18 ========== 19 20 In person purchases might involve mixed payments. A customer may pay part of 21 an order in cash and the rest with Taler, or a cashier may need to combine 22 Taler with a card terminal, voucher system or other local payment method. 23 Today, the merchant backend and wallet assume that the amount in the contract 24 is the amount the wallet pays with Taler. This model cannot represent a 25 single receipt and order that is settled by multiple methods. 26 27 The goal is not to make the merchant backend process card or cash payments. 28 The goal is to let the merchant backend, wallet core and POS applications agree 29 on the order total, the Taler portion and the non-Taler portions that must have 30 already been completed outside of Taler. 31 32 Requirements 33 ============ 34 35 * Orders and choices must be able to express mixed payment amounts. 36 * The existing plain :ts:type:`Amount` form must remain valid for backwards 37 compatibility. 38 * The type and meaning of existing ``amount`` fields must not change. 39 * The existing ``amount`` field remains the amount paid with Taler. 40 * The optional external payment field must not include Taler entries. 41 * The total order amount is the sum of the existing ``amount`` field and all 42 entries in ``amount_external``. 43 * The wallet must only pay the existing ``amount`` field. 44 * The POS or other accommodating application must execute all non-Taler 45 payments before the Taler payment. 46 * The Taler payment is always the last payment step. 47 * If the Taler payment fails after other payments succeeded, the POS must 48 either modify the order and retry the Taler step or refund the already 49 completed non-Taler payments. 50 * The merchant backend must preserve enough information for receipts, 51 reporting and order inspection to show how the total was split. 52 * Per-method payment information must be stored in a flat structure that the 53 merchant portal can render as a generic table. 54 * The design must not require the wallet to validate that cash, card or other 55 non-Taler payments actually happened. 56 57 Proposed Solution 58 ================= 59 60 Additive Payment Field 61 ---------------------- 62 63 Keep all existing :ts:type:`Amount` fields unchanged. In particular, 64 :ts:type:`OrderV0`.``amount``, :ts:type:`OrderChoice`.``amount`, 65 :ts:type:`ContractTermsV0`.``amount`` and 66 :ts:type:`ContractChoice`.``amount`` remain plain :ts:type:`Amount` values and 67 represent the amount the wallet pays with Taler. 68 69 Add a new optional ``amount_external`` field next to these existing ``amount`` 70 fields: 71 72 .. ts:def:: ExternalPaymentInfo 73 74 interface ExternalPaymentInfo { 75 // External payment method, for example "cash" or "card". 76 // Must never be "taler". 77 method: string; 78 79 // Identifier of the payment action within the order. 80 // Examples: "cash1", "sumup1", "sumup2". 81 id: string; 82 83 // Amount covered by this payment action. 84 // Must always be present 85 amount: Amount; 86 87 // Additional method-specific fields. These fields must be 88 // stored only at this level. 89 [field: string]: string | Amount | Integer | boolean | null; 90 } 91 92 .. ts:def:: PartialPaymentFields 93 94 interface PartialPaymentFields { 95 // Payments handled outside of Taler. 96 amount_external?: ExternalPaymentInfo[]; 97 } 98 99 The proposed extension applies to the amount-bearing order and contract 100 objects: 101 102 :: 103 104 type OrderV0 = ExistingOrderV0 & PartialPaymentFields; 105 type OrderChoice = ExistingOrderChoice & PartialPaymentFields; 106 type ContractTermsV0 = ExistingContractTermsV0 & PartialPaymentFields; 107 type ContractChoice = ExistingContractChoice & PartialPaymentFields; 108 109 If ``amount_external`` is absent, the order is a regular pure Taler order and 110 the existing ``amount`` field is the total amount. If ``amount_external`` is 111 present, the existing ``amount`` field remains the Taler amount. The full 112 order or choice total is the sum of the existing ``amount`` field and all 113 entries in ``amount_external``. 114 115 For example, an order where the customer pays CHF 30 in cash and CHF 20 in 116 Taler keeps ``amount`` as ``CHF:20`` and adds ``amount_external``: 117 118 :: 119 120 { 121 "amount": "CHF:20", 122 "amount_external": [ 123 { 124 "method": "cash", 125 "id": "cash1", 126 "amount": "CHF:30", 127 "cashier_number": "7" 128 } 129 ] 130 } 131 132 This is backwards compatible for old wallets because they continue to see a 133 plain :ts:type:`Amount` in ``amount``. Such wallets may not render the full 134 mixed-payment total, but they can still pay the Taler portion. Updated wallets 135 should render both the full total and the selected Taler amount clearly. 136 137 Payment Method Names 138 -------------------- 139 140 The initial reserved method name is: 141 142 * ``cash`` for cash accepted by the merchant or cashier 143 144 Other names are allowed for integrations, but they should be stable ASCII 145 identifiers. 146 147 The name ``taler`` is reserved and must not be used in ``amount_external``. 148 Taler is represented by the existing ``amount`` field. 149 150 Payment Details 151 --------------- 152 153 For cash payments, additional fields may include the cashier name, cashier 154 number, register identifier or similar local information. For card payments, 155 additional fields may include the terminal identifier, acquirer reference, 156 transaction ID or authorization code. Other systems may add the fields they 157 need for reconciliation or audit. 158 159 The additional fields must be stored only one level below the payment entry. 160 Nested method-specific objects should not be used. This allows the merchant 161 portal to render ``amount_external`` as a simple table without knowing a custom 162 rendering format for each payment method. 163 164 Payment Flow 165 ------------ 166 167 The POS or integrating application is responsible for orchestrating mixed 168 payments: 169 170 1. Create or update the order with ``amount_external`` that reflects the 171 intended externally handled payment amount. 172 2. Run all non-Taler payment steps, such as cash handling or card terminal 173 authorization. 174 3. Start the Taler payment as the final step. 175 4. Complete the sale only after the merchant backend confirms the Taler 176 payment, unless the ``taler`` amount is zero. 177 178 The wallet receives the contract terms and computes the payable Taler amount 179 from the existing ``amount`` field. It may use ``amount_external`` to render 180 the full total so that the customer understands why the Taler amount is lower 181 than the order total. 182 183 Failure Handling 184 ---------------- 185 186 Mixed payments introduce a failure mode where a non-Taler payment has already 187 succeeded but the final Taler payment fails. The merchant backend cannot 188 automatically repair this state because it does not control the external 189 payment method. 190 191 The POS or integrating application must therefore choose one of these recovery 192 paths: 193 194 * modify the order payment split and retry the Taler payment; 195 * cancel the order and refund or void the completed non-Taler payments; 196 * proceed with different payment method, and make Taler part lower or zero. 197 198 Receipt Handling 199 ---------------- 200 201 For normal wallet flows, the customer can access the Taler receipt after the 202 wallet payment. In POS deployments this may not be enough. Some jurisdictions 203 require a printed or otherwise directly provided receipt, and in a mixed 204 payment flow the customer may not receive a Taler receipt if the POS 205 application performs self-pickup or the Taler amount is zero. 206 207 POS applications and other accommodating applications must therefore support a 208 mode where they retrieve the receipt themselves from the merchant backend and 209 provide it to the customer through the locally required channel, such as a 210 printer, terminal display, e-mail or another regulated receipt mechanism. 211 212 Reporting 213 --------- 214 215 The merchant backend should store ``amount_external`` as part of the contract 216 terms and expose it through order status and history APIs. Existing reporting 217 that expects a single amount should continue to show the Taler amount from the 218 existing ``amount`` field. Detailed views should show the externally handled 219 amounts and the full order total. 220 221 The merchant portal should render ``amount_external`` as a table. Common 222 columns are ``method``, ``id`` and ``amount``. Additional columns can be 223 derived from the union of the flat method-specific fields present in the 224 payment entries. The merchant portal should not need method-specific 225 rendering logic to show this information. 226 227 Refunds 228 ------- 229 230 Taler refunds can only refund the amount actually paid with Taler. Refunds for 231 cash, card or other methods remain the responsibility of the POS or external 232 payment integration. When an order has a split amount, APIs and UIs should 233 avoid wording that implies the merchant backend can refund the whole order 234 through Taler. 235 236 Test Plan 237 ========= 238 239 * Merchant backend tests for accepting existing plain :ts:type:`Amount` fields 240 unchanged. 241 * Merchant backend tests accepting optional ``amount_external`` in v0 orders 242 and v1 choices. 243 * Merchant backend tests rejecting ``amount_external`` with ``taler`` entries, 244 mixed currencies or invalid method names. 245 * Merchant backend tests preserving ``amount_external`` entries with flat 246 method-specific fields. 247 * Wallet core tests for paying the existing ``amount`` field and rendering the 248 full total from ``amount_external`` when present. 249 * POS integration tests for a successful cash/card-first and Taler-last flow. 250 * POS integration tests for Taler failure after a non-Taler payment succeeded. 251 252 Definition of Done 253 ================== 254 255 * Merchant backend supports the new additive ``amount_external`` field for 256 order creation, contract terms, order status and history. 257 * Merchant backend keeps all existing ``amount`` fields as plain 258 :ts:type:`Amount` values. 259 * Merchant backend validates that ``amount_external`` has no ``taler`` entries 260 and that all entries use the same currency as ``amount``. 261 * Merchant backend preserves per-method payment details in ``amount_external``. 262 * Wallet core pays the existing ``amount`` field and does not require 263 ``amount_external`` to complete the Taler payment. 264 * Wallet UIs can display the total and the selected Taler amount clearly. 265 * POS and other accommodating applications support the required orchestration: 266 non-Taler payments first, Taler payment last. 267 * Merchant portal renders ``amount_external`` as a generic table without 268 method-specific renderers. 269 * Documentation explains that non-Taler refunds and failure recovery are owned 270 by the integrating application. 271 272 Alternatives 273 ============ 274 275 Change the Amount Field Type 276 ---------------------------- 277 278 The initial proposal changed the existing ``amount`` fields from 279 :ts:type:`Amount` to ``Amount | AmountObject``. This was rejected because it 280 would be a destructive protocol change: every component that currently parses 281 ``amount`` as a string would have to handle a new object shape. Keeping 282 ``amount`` unchanged and adding ``amount_external`` preserves backwards 283 compatibility. 284 285 Store Payment Details in Extra 286 ------------------------------ 287 288 Another initial proposal stored the payment split under ``extra.payments``. 289 This was rejected because ``extra`` is intended for proprietary 290 merchant-specific information. Official protocol fields should be explicit 291 top-level fields, not hidden under the merchant extension area. 292 293 Create Separate Orders 294 ---------------------- 295 296 The POS could create one Taler order only for the Taler amount and track cash 297 or card payments in its own system. This avoids changing the contract amount 298 type, but it loses the single-order receipt and reporting model. It also makes 299 customer-facing order totals harder to verify. As well it looses the backup 300 and synchronisation between device possibilities. 301 302 Let Taler Run Before Other Methods 303 ---------------------------------- 304 305 Running Taler before cash or card would make the Taler part successful while 306 the external payment can still fail. That leaves the merchant with a paid 307 Taler contract for an order that may not be otherwise settled. Requiring Taler 308 to be last gives the POS a clearer recovery path because external payments can 309 still be voided, refunded or used to recompute the remaining Taler amount. As 310 well it can create problems when refund deadline for Taler option was set as 0 311 and other method of payment failed. 312 313 Drawbacks 314 ========= 315 316 * POS implementations must handle partial failure and external refunds 317 carefully. 318 * Old wallets may only render the Taler amount and not the full mixed-payment 319 total until they learn the new ``amount_external`` field. 320 * Reporting and refund UIs must distinguish total order amount from Taler-paid 321 amount. 322 323 Open Questions 324 ============== 325 326 * Should money pots store full totals, per-method totals, or both? Should 327 merchant backend auto create new pots per each new payment method found in 328 order? 329 * Should payment method names be centrally registered, or is validation of 330 stable ASCII identifiers sufficient? 331 * Should there be a dedicated status for orders where non-Taler payments 332 succeeded but the final Taler payment failed? or we can just delete them? 333 * Should the merchant backend expose explicit receipt self-pickup endpoints for 334 POS devices, or are existing private order status APIs sufficient? 335 336 Discussion / Q&A 337 ================ 338 339 * Feedback from Florian Dold: ``extra`` must remain reserved for proprietary 340 merchant fields and must not carry official protocol data. Protocol changes 341 should be additive, so the existing ``amount`` field should not change type. 342 The design was updated accordingly: the existing ``amount`` remains the 343 Taler amount, while a new additive ``amount_external`` field carries the 344 externally handled amounts and reconciliation metadata.