summaryrefslogtreecommitdiff
path: root/taler-mcig.rst
blob: 692c05e0c2afc15f3714838490c69124ed48a78e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
..
  This file is part of GNU TALER.
  Copyright (C) 2021 Taler Systems SA

  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
  Foundation; either version 2.1, or (at your option) any later version.

  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License along with
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>

  @author Thien-Thi Nguyen


Merchant/Customer Interaction Guide
###################################

The audience for the Mechant/Customer Interaction Guide is the merchant
who wishes to set up a "web shop" that works with the Taler payment system.


Introduction
============

.. include:: frags/taler-payment-cycle.rst

This guide focuses on step 4, the interaction between the customer and the
merchant.  In particular, we first review two basic interaction flows
(with and without shopping cart), then describe Taler features involved in the
interaction, the decisions you (the merchant) must make, and
how to configure the Taler merchant backend to best support those decisions.
Lastly, we present protocol *traces* for various fictitious interaction flows.


Two Basic Flows
===============

.. index:: shopping cart experience
.. index:: individual product selection / purchase experience
.. index:: inventory management
.. index:: repurchase detection / prevention

There are two basic payment flows, the first involving a shopping cart,
and the second, without (individual product selection / purchase).
We distinguish these because for some purchases, a shopping cart is overkill.
In either case, Taler can integrate
with your *inventory management* system.
Additionally, Taler offers *repurchase detection / prevention*,
most suitable for digital goods.

In the shopping cart experience, you first offer a product on the website.
The customer adds the product to their shopping cart, at which point you may
optionally *lock* the product in the inventory system for a certain period of
time.
The accumulated set of products in the shopping cart is the *order*.
This process repeats until the customer is ready to move to the
*checkout* phase.

At checkout, you may optionally support different payment methods (and make
this choice available to the customer) for the order.
This guide assumes you and the customer agree to use the Taler payment system.

At this point, you generate a *contract* and present it to the customer for
authorization.
The contract includes:
- the total amount due;
- a short summary;
- a *fulfillment URI*;
- the *duration* of the offer
  (how long the customer has to authorize before timeout);
- (optional) an itemized product list, with
  - (optional) some kind of identification for the selected product(s);
- (optional) applicable taxes and fee limits;
- (optional) an order ID (if omitted, the backend will auto-generate one);
- (optional) information which details are *forgettable*;
- (optional) a *claim token* that the customer can use later;
- (optional) information on the *refund deadline*;
- (optional) information on the the *auto-refund period* (how long does
  the wallet check for refunds without user prompting for it).

If the customer does nothing (timeout / the contract expires),
the merchant backend automatically *unlocks* the product(s),
allowing other consumers to add more items of the limited stock
to their orders.

On the other hand, if the customer authorizes payment,
the customer's wallet transfers payment coins to you,
previously locked products are removed from inventory,
and (if possible) the wallet redirects the customer
to the *fulfillment URI*.

The individual product selection / purchase experience is like the shopping
cart experience with the following exceptions:
- there is no shopping cart -- the order is solely the selected product;
- Taler payment method is assumed;
- customer selection moves directly to checkout;
- *repurchase detection / prevention* can be useful (for digital products).


Taler Details
=============

This section describes aspects of Taler involved
in the basic payment flows in more detail.
Each aspect also includes one or more backend API calls that
are demonstrated in the next section.

**product locking**
  Taler can integrate with your inventory system to set aside
  a certain quantity of a product for some duration of time.
  This is called *product locking*.
  This is useful for physical goods, or for goods that have a limited supply,
  such as airline tickets.
  Even for digital goods, product locking may be useful to effect exclusivity.

  To lock a product, use:
  :http:post:`[/instances/$INSTANCE]/private/products/$PRODUCT_ID/lock`,
  specifying a ``duration`` and a ``quantity``.

  If the customer removes a product from the shopping cart, you can *unlock*
  the product by using the same API call, specifying a ``quantity`` of 0 (zero).
  (Products are also unlocked automatically on timeout / contract expiration.)

  Before you can lock products, you need to manage the inventory, creating
  an entry for the product (assigning a $PRODUCT_ID) and configure the
  available stock. This can be done using the
  Taler merchant backoffice Web interface.

  .. note::

     Once we have documentation for that web interface, we should link to it here.

**taxes**
  The default taxes for each product is part of the product ``price``
  maintained by the backend.
  Taxes can be set when the product is added to the inventory,
  prior to any customer purchase experience
  (see :http:post:`[/instances/$INSTANCE]/private/products`,
  :http:get:`[/instances/$INSTANCE]/private/products`,
  and :http:get:`[/instances/$INSTANCE]/private/products/$PRODUCT_ID`)
  or specified explicitly by the frontend when adding
  products to an order that are not managed by the backend inventory
  (see :http:post:`[/instances/$INSTANCE]/private/orders`).

**fees**
  The Taler protocol charges a *deposit fee* (see step 5, above),
  which you may choose to pay or to pass on to the customer.
  This can be configured to a maximum amount, per order.

  You can set ``default_max_deposit_fee`` in :http:post:`/private/instances`,
  or override the default by setting ``max_fee`` when creating an order.

  There is also the *wire fee* (see step 6, above),
  which you may choose to pay or to pass on to the customer.

  You can set ``default_max_wire_fee`` in :http:post:`/private/instances`,
  and ``max_wire_fee`` in the contract.
  If unspecified, the default value is zero (meaning you bear the entire fee).

  You can *amortize* the wire fee across a number of customers
  by setting ``default_wire_fee_amortization`` in :http:post:`/private/instances`,
  and ``wire_fee_amortization`` in the contract.
  This is the number of customer transactions over which you expect to
  amortize wire fees on average.
  If unspecified, the default value is one.

  .. Note:: :http:post:`/private/instances` must be done at
     instance-setup time (prior to any purchase).

**forgettable customer details**
  Although Taler allows the customer to remain anonymous, you may need to
  collect customer details (e.g. for shipping).
  Taler has support for forgetting such details, to comply with GDPR
  (for example).
  This can occur even in the face of refunds (see below).

  To forget a set of details, first the details that are to be forgotten
  must be marked by including the names of the respective fields
  in one or more special ``_forgettable`` field(s) in the contract.

  Then, you can use:
  :http:patch:`[/instances/$INSTANCE]/private/orders/$ORDER_ID/forget`
  to forget those details.

**claim token**
  The claim token is a sort of handle on the order and its payment.
  It is useful when the order ID is easily guessable
  (e.g. incrementing serial number),
  to prevent one customer hijacking the order of another.
  On the other hand, even if the order ID is not easily guessable,
  if you don't care about order theft (e.g. infinite supply, digital goods)
  and you wish to reduce the required processing (e.g. smaller QR code),
  you can safely disable the claim token.

  By default, Taler creates a claim token for each order.
  To disable this, you can specify ``create_token`` to be ``false``
  in :http:post:`[/instances/$INSTANCE]/private/orders`.

**refund deadline**
  The refund deadline specifies the time after which you will prohibit
  refunds.
  Refunds may be full or partial.
  Refunds do not require customer details.
  You can configure the deadline to expire immediately to effect
  an "all sales are final" policy.

  To set the deadline, specify ``refund_delay``
  in :http:post:`[/instances/$INSTANCE]/private/orders`.
  To disable refunds altogether, omit this field.

**auto-refund period**
  The Taler protocol can automatically offer refunds to the customer's
  wallet without their explicit prompting during the auto-refund period.

  This is useful in the case where the purchase cannot be fulfilled
  (e.g. jammed vending machine), but there is no way to notify the
  customer about a refund.

  If specified, after contract authorization, the customer's wallet will
  repeatedly check for either fulfillment or refund, up to the end of
  the auto-refund period.
  (If neither occur within that period, the customer should complain
  to you for breach of contract.)

  To set the auto-refund period, specify ``auto_refund``
  in :http:post:`[/instances/$INSTANCE]/private/orders`.

**repurchase detection / prevention**
  Taler can detect a repurchase attempt and prevent it from going through.
  This feature allows customers to purchase a digital good only once,
  but to later access the same digital good repeatedly (e.g. reload
  in browser, after network trouble, etc.) without having to pay again.

  This feature is automatic in the protocol;
  you do not need to do anything to enable it.

  .. note::
     For repurchase detection / prevention to work reliably,
     you must use the same fulfillment URI for the same product
     and likewise different fulfillment URIs for different products.

**fulfillment URI**
  This may be the actual product (digital goods),
  or a tracking URL (physical goods).
  If you issue a claim token with the contract, the customer can
  access the fulfillment URI from a different device than the
  one where the wallet is installed.

  The fulfillment URI is normally included in the contract.
  You specify it in :http:post:`[/instances/$INSTANCE]/private/orders`.

  If the fulfillment URI contains the literal string ``${ORDER_ID}``
  (including curly braces), that will be replaced by the order ID when
  POSTing to the merchant.  (FIXME: What does "POSTing to the merchant" mean?)
  This is useful when the backend auto-generates the order ID.


Sample Interaction Traces
=========================

In the following descriptions, ``C`` stands for *customer*, ``W`` stands for
*customer's wallet*, ``M`` stands for *merchant* (you), and ``E`` stands for
*exchange*.
Unless otherwise noted, all API calls are directed toward the Taler backend.

Also, all the traces share the initial pre-sales configuration step.


Pre-Sales Configuration
-----------------------

In the pre-sales configuration step, you set up the *default instance*,
and add products to the inventory.

NOTE: not sure we want to ultimately document this HERE. Most merchants
should do _this_ part via the Merchant Web interface that Sebastian is
building right now, and for that we want a separate guide that explains
the API (as you do here), and the Web interface.  In this document,
we should focus on how the merchant integrates the (Web)front-end with the
backend, not how the backend itself is configured.
(This also applies to the other instance setup parts you described
above => refer to other guide, but of course specify how we can
override defaults from instance setup per-order.)


M: :http:post:`/private/instances`

.. code-block:: javascript

   // InstanceConfigurationMessage
   {
     "payto_uris": ["payto://iban/CH9300762011623852957"],
     "id": "default",
     "name": "Pretty Pianos",
     "auth":
     // InstanceAuthConfigurationMessage
     {
       "method": "external",
       "token": "secret-token:eighty-eight-keys"
     },
     "default_max_wire_fee": "KUDOS:5.0",
     "default_wire_fee_amortization": 1,
     "default_max_deposit_fee": "KUDOS:10.0",
     "default_wire_transfer_delay": "2 days",
     "default_pay_delay": "5 hours"
   }
   // (backend returns 204 No content)

The fictitious store, Pretty Pianos, has only two products:
- pianos (physical good);
- *Beethoven Sonatas* (sheet music PDF files, digital good).

M: :http:post:`/instances/default/private/products`

.. code-block:: javascript

   // ProductAddDetail
   {
     "product_id": "p001",
     "description": "piano",
     "unit": "unit",
     "image": "data:image/png;base64,AAA=",
     "price": "KUDOS:20000.0",
     "taxes": [],
     "total_stock": 3,
     "next_restock": "2021-04-22",
     "_forgettable": ["image"]
   }
   // (backend returns 204 No content)

Note that the ``image`` field is mentioned by name in the ``_forgettable``
field's list value.
This means the ``image`` value is *marked as forgettable*.
This will come into play later (see below).

M: :http:post:`/instances/default/private/products`

.. code-block:: javascript

   // ProductAddDetail
   {
     "product_id": "f001",
     "description": "Beethoven Sonatas",
     "unit": "file",
     "price": "KUDOS:9.87",
     "taxes": [],
     "total_stock": -1
   }
   // (backend returns 204 No content)

Note that there is no ``next_restock`` field in this ``ProductAddDetail``
object.
This is because the ``total_stock`` field has value ``-1`` (meaning "infinite")
since the product is a PDF file.


Scenario 1: Simple File Purchase
--------------------------------

The first scenario is a simple file purchase, without shopping cart,
similar to the `GNU Essay demo <https://shop.demo.taler.net/en/>`_ experience.

.. We hardcode "en/" for now because support for other
   languages is not yet available (at time of writing).
   (FIXME: Drop "en/" when other languages are supported.)

Because there are infinite supplies of product ``f001``,
there is really no need for inventory management.
However, you choose to anyway generate a separate order ID
in the backend for accounting purposes.
Also, since the product is an easily reproduced digital good,
you decline to offer the customer the ability to select a "quantity"
other than 1 (one), and decide that "all sales are final"
(no refund possible).
On the other hand, you wish to enable repurchase detection /
prevention feature, so that once customers pay for the PDF file,
they need never pay again for it.

When the customer clicks on the product's "buy" button,
you first POST to ``/private/orders`` to create an order:

M: :http:post:`/instances/default/private/orders`

.. code-block:: javascript

   // PostOrderRequest
   {
     "order":
     // Order (MinimalOrderDetail)
     {
       "amount": "KUDOS:9.87",
       "summary": "Beethoven Sonatas",
       "fulfillment_URI": "https://example.com/f001?${ORDER_ID}"
     },
     "create_token": true
   }

Notes:
- There is no ``refund_delay`` field (no refunds possible).
- We show the ``create_token`` field with value ``true`` even though
  that is the default (for illustrative purposes).
- The ``order`` value is actually a ``MinimalOrderDetail`` object.
- The ``fulfillment_URI`` value includes the product ID and the literal
  string ``${ORDER_ID}``, to be replaced by the backend-generated order ID.

The backend returns ``200 OK`` with the body:

.. code-block:: javascript

   // PostOrderResponse
   {
     "order_id": "G93420934823",
     "token": "TEUFHEFBQALK"
   }

Notes:
- The backend-generated order ID is ``G93420934823``.
- The claim token is ``TEUFHEFBQALK``.

(FIXME: Replace w/ more realistic examples?)

Now that there is an order in the system, the wallet *claims* the order.

W: :http:post:`/orders/G93420934823/claim`

.. code-block:: javascript

   // ClaimRequest
   {
     "nonce": "lksjdflaksjfdlaksjf",
     "token": "TEUFHEFBQALK"
   }

Notes:
- The ``nonce`` value is a randomly-generated string.
- The POST endpoint includes the order ID ``G93420934823``.
- The ``token`` value is the claim token ``TEUFHEFBQALK``
  received in the ``PostOrderResponse``.

The backend returns ``200 OK`` with body:

.. code-block:: javascript

   // ContractTerms
   {
     "summary": "one copy of Beethoven Sonatas",
     "order_id": "G93420934823",
     "amount": "KUDOS:9.87000000",
     "fulfillment_url": "https://example.com/f001?G93420934823",
     "max_fee": "KUDOS:0.01500000",
     "max_wire_fee": "KUDOS:0.01500000",
     "wire_fee_amortization": 1,
     "products": [
       // Product
       {
         "product_id": "f001",
         "description": "Beethoven Sonatas"
       }
     ],
     "timestamp": { "t_ms": 1616537665000 },
     "refund_deadline": { "t_ms": 1616537665000 },
     "pay_deadline": { "t_ms": 1616537725000 },
     "wire_transfer_deadline": { "t_ms": 1616537785000 },
     "merchant_pub": FIXME,
     "merchant_base_url": "https://example.com/",
     "merchant":
       // Merchant
       {
       },
     "h_wire": FIXME,
     "wire_method": FIXME,
     "auditors": [
       // Auditor
     ],
     "exchanges": [
       // Exchange
     ],
     "nonce": "lksjdflaksjfdlaksjf"
   }

Notes:
- The backend determined both fees to be 0.015 KUDOS.
  Because the amortization is 1 (one), both fees (processing and wire
  transfer) are included in full.
  Thus, the total due by the customer is 9.87 + 0.015 + 0.015 = 9.900 KUDOS.
- The ``order_id`` value is the one given in the ``PostOrderResponse``.
- The ``timestamp`` value represents 2021-03-23 22:14:25 UTC
  in milliseconds after the `epoch <https://en.wikipedia.org/wiki/Unix_epoch>`__.
- The ``refund_deadline`` value is the same as the ``timestamp`` value
  (no refunds possible).
- The ``pay_deadline`` value is one minute after the ``timestamp`` value.
- The ``wire_transfer_deadline`` value is two minutes after
  the ``timestamp`` value.
- The ``products`` value is a list of one element (one ``Product`` object),
  which omits the ``price`` field since that is included in the
  ``ContractTerms.amount`` value.  Also, its ``quantity`` defaults to 1 (one).
- The ``nonce`` value is the same one specified by the wallet.

At this point, the wallet displays the contract terms (or a subset of them)
to the customer, who now has the option to accept the contract or reject it
(either explicitly by pressing a "cancel" button, or implicitly by waiting
for the offer to time out).

The customer accepts the contract:

W: :http:post:`/orders/G93420934823/pay`

.. code-block:: javascript

   // PayRequest
   {
     "coins": [
       // CoinPaySig
       {
         "coin_sig": ...,
         "coin_pub": ...,
         "ub_sig": ...,
         "h_denom": ...,
         "contribution": "KUDOS:8.0",
         "exchange_url": ...
       },
       {
         "coin_sig": ...,
         "coin_pub": ...,
         "ub_sig": ...,
         "h_denom": ...,
         "contribution": "KUDOS:2.0",
         "exchange_url": ...
       }
     ]
   }

Notes:
- There is no session ID in the ``PayRequest`` object.
- The total of the contribution is 8.0 + 2.0 = 10.0 KUDOS,
  which is enough to cover the purchase price (9.900 KUDOS
  from 9.87 + 0.015 + 0.015).

The backend returns ``200 OK`` with body:

.. code-block:: javascript

   // PaymentResponse
   {
     "sig": "..." // EddsaSignature
   }

FIXME: At this point, does the wallet need to query (status)?
Also, does the frontend need to do anything else?

The wallet then redirects to the fulfillment URI, which displays
(or makes available for download) the PDF file "Beethoven Sonatas".




TODO/FIXME: Add more scenarios (including JSON).