summaryrefslogtreecommitdiff
path: root/integration-merchant.rst
diff options
context:
space:
mode:
Diffstat (limited to 'integration-merchant.rst')
-rw-r--r--integration-merchant.rst230
1 files changed, 196 insertions, 34 deletions
diff --git a/integration-merchant.rst b/integration-merchant.rst
index b07f1339..f3221617 100644
--- a/integration-merchant.rst
+++ b/integration-merchant.rst
@@ -31,26 +31,27 @@ Interaction with merchant websites
The payment process
+++++++++++++++++++
-By design, the Taler payment process ensures the following three properties:
+By design, the Taler payment process ensures the following properties:
1. The user must see and accept a contract in a secure context before the payment happens.
+ That contract accounts for all the items which are supposed to be bought.
+
2. The payment process must be idempotent, that is at any later time the customer must
- be able to replay the payment and again retrieve the online resource he paid for.
+ be able to replay the payment and retrieve the resource he paid for.
In case where a physical item was bought, this online resource is the merchant's
order status page, which may contain tracking information for the customer.
- Note that by `replay the payment` we mean reusing the `same coins` used to pay for
+ Note that by `replaying the payment` we mean reusing the `same coins` used to pay for
the product the first time to get the `same product` the user got the first time.
- So the replay will NOT subtract further credit from the user's total budget.
+ So the replay does NOT subtract further credit from the user's total budget.
-3. The user must be able to *share* the link to both the page with the unpaid offer or
- the order status page. If the links are shared with another user, they should
- typically allow the other user to perform the same purchase (assuming the item
- is still available).
+3. Purchases are shareable: any purchase is given a URL that allows other users to
+ buy the same item(s).
We call an *offer URL* any URL at the merchant's Web site that notifies the
wallet that the user needs to pay for something. The offer URL must take into
account that the user has no wallet installed, and manage the situation accordingly
-(for example, by showing a credit card paywall).
+(for example, by showing a credit card paywall). The notification can happen either
+via JavaScript or via HTTP headers.
The merchant needs to have a *contract URL* which generates the JSON
contract for Taler. Alternatively, the contract may be embedded
@@ -61,9 +62,17 @@ The merchant must also provide a *pay URL* to which the wallet can
transmit the payment. Again, how this URL is made known from the merchant
to the wallet, it is managed by the HTTP headers- or JavaScript-based protocol.
-The merchant must have a *fulfillment URL* which is in charge of doing
-two thigs: give to the user what he paid for, or redirect the user
-to the offer URL in case he did not pay.
+The merchant must also have a *fulfillment URL*, that addresses points 2 and 3 above.
+In particular, fulfillment URL is responsible for:
+
+* Deliver the final product to the user after the payment
+* Instruct the wallet to send the payment to the pay URL
+* Redirect the user to the offer URL in case they hit a shared fulfillment URL.
+
+Again, Taler provides two ways of doing that: JavaScript- and HTTP headers-based.
+
+Taler helps merchants on the JavaScript-based interaction by providing the
+``taler-wallet-lib``. See https://git.taler.net/web-common.git/tree/taler-wallet-lib.ts
-------
Example
@@ -106,6 +115,8 @@ his wallet will not be able to replay the payment. Instead, his wallet
will automatically redirect Bob to the offer URL and allow him to
purchase the movie himself.
+.. _offer:
+
---------------
Making an offer
---------------
@@ -113,9 +124,19 @@ Making an offer
When a user visits a offer URL, the merchant returns a page that can interact
with the wallet either via JavaScript or by returning a "402 Payment Required".
This page's main objective is to inform the wallet on where it should get the
-contract. In case of JavaScript interaction, this is done by FIXME, whereas
-in case of "402 Payment Required", a `X-Taler-contract-url` HTTP header will
-be set to the contract's location. (FIXME: is that right?).
+contract. In case of JavaScript interaction, the merchant should just return
+a page whose javascript contains an invocation to ``offerContractFrom(<CONTRACT-URL>)``
+from ``taler-wallet-lib``. This function will download the contract from
+`<CONTRACT-URL>` and hand it to the wallet.
+
+In case of HTTP headers-based protocol, the merchant needs to set the header
+`X-Taler-contract-url` to the contract URL. Once this information reaches the
+browser, the wallet will takes action by reading that header and downloading
+the contract.
+
+Either way, the contract gets to the wallet which then renders it to the user.
+
+.. _fulfillment:
-------------------------------
Fulfillment interaction details
@@ -123,31 +144,172 @@ Fulfillment interaction details
A payment process is triggered whenever the user visits a fulfillment
URL and he has no rights in the session state to get the items
-accounted in the fulfillment URL. Note that when the user is not
-visiting a fulfillment URL he got from someone else, it is the wallet
-which points the browser to a fulfillment URL after the user accepts
-the contract.
+accounted in the fulfillment URL. Note that after the user accepts a
+contract, the wallet will automatically point the browser to the
+fulfillment URL.
-A fulfillment URL must carry all the details necessary to reconstruct
-a contract. For simple contracts, a Web shop should encode the unique
-contract details (in particular, the transaction identifier) in the
-URL. This way, the Web shop can generate fulfillment URLs without
-actually having to write the full contract proposal to its database.
-This allows the merchant to delay disk (write) operations until
-customers actually pay.
+Becasue fulfillment URLs implements replayable and shareable payments
+(see points 2,3 above), fulfillment URL parameter must encompass all the
+details necessary to reconstruct a contract.
-Once the payment process has been started, the merchant will then
-reconstruct the contract and re-hash it, sending back to the client
-a "402 Payment required" status code and some HTTP headers which will
-help the wallet to manage the payment, they are:
+That saves the merchant from writing contracts to disk upon every contract
+generation, and defer this operation until customers actually pay.
+
+..................
+HTTP headers based
+..................
+
+Once the fulfillment URL gets visited, deliver the final product if the user has
+paid, otherwise: the merchant will reconstruct the contract and re-hash it, sending
+back to the client a "402 Payment required" status code and some HTTP headers which
+will help the wallet to manage the payment. Namely:
* `X-taler-contract-hash`
* `X-taler-pay-URL`
* `X-taler-offer-URL`
-By looking at `X-taler-contract-hash`, the wallet can face two situations:
+The wallet then looks at `X-taler-contract-hash`, and can face two situations:
+
+1. This hashcode is already present in the wallet's database (meaning that the user did accept the related contract), so the wallet can send the payment to `X-taler-pay-URL`. During this operation, the wallet associates the coins it sent to `X-taler-pay-URL` with this hashcode, so that it can replay payments whenever it gets this hashcode again.
+
+2. This hashcode is unknown to the wallet (meaning that the user visited a shared fulfillment URL). The wallet then points the browser to `X-taler-offer-URL`, which is in charge of generating a contract referring to the same items accounted in the fulfillment URL. Of course, the user is then able to accept or not the contract.
+
+................
+JavaScript based
+................
+
+Once the fulfillment URL gets visited, deliver the final product if the user has paid, otherwise:
+the merchant will reconstruct the contract and re-hash it. Then it will return a page whose JavaScript
+needs to include a call to ``taler.executeContract(..)``. See the following example:
+
+.. sourcecode:: html
+
+ <html>
+ <head>
+ <script src="path/to/taler-wallet-lib.js"></script>
+ <script type="application/javascript">
+ // Imported from taler-wallet-lib.js
+ taler.executePayment(<CONTRACT-HASHCODE>, <PAY-URL>, <OFFERING-URL>);
+ </script>
+ </head>
+ ..
+
+ </html>
+
+The logic which will take place is the same as in the HTTP header based protocol.
+Once ``executePayment(..)`` gets executed in the browser, it will hand its three
+parameters to the wallet, which will:
+
+1. Send the payment to `<PAY-URL>` if `<CONTRACT-HASH>` is found in its database (meaning that the user accepted it).
+2. Redirect the browser to `<OFFER-URL>`, if `<CONTRACT-HASH>` is NOT found in its database, meaning that the user visited a shared fulfillment URL.
+
+--------------------
+Example: Essay Store
+--------------------
+
+This section is a high-level description of a merchant :ref:`frontend <merchant-arch>`,
+and is inspired by our demonstration essay store running at `https://blog.demo.taler.net/`.
+Basically, it tells how the frontend reacts to clients visiting `offer` and `fulfillment`
+URLs.
+
+The website is implemented in Python+Flask, and is available at
+https://git.taler.net/merchant-frontends.git/tree/talerfrontends/blog.
+
+The desired effect is that the homepage has a list of buyable articles, and once the
+user clicks on one of them, they will either get the Taler :ref:`contract <contract>`
+or a credit card paywall if they have no Taler wallet installed.
+
+In particular, any buyable article on the homepage links to an `offer URL`:
+
+.. sourcecode:: html
+
+ <html>
+ ...
+ <h3><a href="/essay/How_to_write_a_frontend">How to write a frontend</a></h3>
+ ...
+ </html>
+
+whence the offer URL design is as follows::
+
+ https://<BASEURL>/essay/<ARTICLE-NAME>
+
+`<ARTICLE-NAME>` is just a token that uniquely identifies the article within the shop.
+
+The server-side handler for the offer URL will return a special page to the client that
+will either HTTP GET the contract from the frontend, or show the credit card paywall.
+See `above <offer>`_ how this special page works.
+
+It is interesting to note that the fulfillment URL is just the offer URL plus
+two additional parameters. It looks as follows::
+
+ https://<BASEURL>/essay/<ARTICLE-NAME>?tid=<TRANSACTION-ID>&timestamp=<TIMESTAMP>
+
+.. note::
+
+ Taler does not require that offer and fulfillment URL have this kind of relationship.
+ In fact, it is perfectly acceptable for the fulfillment URL to be hosted on a different
+ server under a different domain name.
+
+The fulfillment URL server-side handler implements the following logic: it checks the state
+to see if `<ARTICLE-NAME>` has been payed, and if so, returns the article to the user.
+If the user didn't pay, then it `executes` the contract by returning a special page to the
+browser. The contract execution is the order to pay that the frontend gives to the wallet.
+
+Basically, the frontend points the wallet to the hashcode of the contract which is to be paid
+and the wallet responds by giving coins to the frontend. Because the frontend doesn't perform
+any cryptographic work by design, it forwards `<ARTICLE-NAME>`, `<TRANSACTION-ID>` and
+`<TIMESTAMP>` to the frontend in order to get the contract's hashcode.
+
+See `above <fulfillment>`_ for a detailed description of how the frontend triggers the
+payment in the wallet.
+
+..................
+State and security
+..................
+
+The server-side state gets updated in two situations, (1) when an article is
+"about" to be bought, which means when the user visits the fulfillment URL,
+and (2) when the user actually pays. For (1), we use the contract hascode to
+access the state, whereas in (2) we just define a list of payed articles.
+For example:
+
+.. sourcecode:: python
+
+ session[<HASHCODE>] = {'article_name': 'How_to_write_a_frontend'} # (1)
+ session['payed_articles'] = ['How_to_write_a_frontend', 'How_to_install_a_backend'] # (2)
+
+The list of payed articles is used by the frontend to deliver the article to the user:
+if the article name is among ``session['payed_articles']``, then the user gets what they
+paid for.
+
+The reason for using `<HASHCODE>` as the key is to prevent the wallet to send bogus
+parameters along the fulfillment URL. `<HASHCODE>` is the contract hashcode that
+the fulfillment handler gets from the backend using the fulfillment URL parameters.
+
+In fact, when the wallet sends the payment to the frontend pay handler, it has to provide
+both coins and contract hashcode. That hascode is (1) verified by the backend when it
+receives the coins, (2) used by the frontend to update the list of payed articles.
+
+See below an example of pay handler:
+
+.. sourcecode:: python
+
+ ...
+
+ # 'deposit_permission' is the JSON object sent by the wallet
+ # which contains coins and the contract hashcode.
+ response = send_payment_to_backend(deposit_permission)
-1. This hashcode is already present in the wallet's database, so the wallet can send the payment to `X-taler-pay-URL`. During this operation, the wallet associates the data it sent to `X-taler-pay-URL` with the received hashcode, so that it can replay payments whenever it gets this hashcode again.
-2. This hashcode is unknown to the wallet, so the wallet can point the browser to `X-taler-offer-URL`, so the user will get the contract and decide to accept it or not. This happens when the user gets the fulfillment URL from someone else.
+ # The backend accepted the payment
+ if 200 == response.status_code:
+ # Here we pick the article name from the state defined at
+ # fulfillment time.
+ # deposit_permission['H_contract'] is the contract hashcode
+ payed_article = session[deposit_permission['H_contract']]['article_name']
+ session['payed_articles'].append(payed_article)
+
-FIXME: explain the JavaScript way
+So the wallet is forced to send a valid contract hashcode along the payment,
+and since that hashcode is then used to update the list of payed articles,
+the wallet is forced to send fulfillment URL parameters that match that hashcode,
+therefore being valid parameters.