summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2017-03-03 17:02:20 +0100
committerFlorian Dold <florian.dold@gmail.com>2017-03-03 17:02:20 +0100
commit7bf82f8dffebe39b0a557a58d0dd030f080658f7 (patch)
tree630100cd3a22da53601d887837ba68be7c7d296b /docs
parentaf275b9bbfa5967ba7d3ff30c8bb15600f3916c6 (diff)
downloaddocs-7bf82f8dffebe39b0a557a58d0dd030f080658f7.tar.gz
docs-7bf82f8dffebe39b0a557a58d0dd030f080658f7.tar.bz2
docs-7bf82f8dffebe39b0a557a58d0dd030f080658f7.zip
remove outdated merchant store example, obsoleted by tutorial
Diffstat (limited to 'docs')
-rw-r--r--docs/example-essay-store.rst625
1 files changed, 0 insertions, 625 deletions
diff --git a/docs/example-essay-store.rst b/docs/example-essay-store.rst
deleted file mode 100644
index c76ce519..00000000
--- a/docs/example-essay-store.rst
+++ /dev/null
@@ -1,625 +0,0 @@
-..
- This file is part of GNU TALER.
-
- Copyright (C) 2014, 2015, 2016 INRIA
-
- 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 Marcello Stanisci
-
-====================
-Example: Essay Store
-====================
-
-This section shows how to set up a merchant :ref:`frontend <merchant-arch>`, and is
-inspired by our demonstration shop running at `https://shop.demo.taler.net/`.
-It is recommended that the reader is already familiar with the
-:ref:`payment protocol and terminology <payprot>`.
-
-The code we are going to describe is available at
-https://git.taler.net/merchant-frontends.git/tree/talerfrontends/blog
-and is implemented in Python+Flask.
-
-The desired effect is the homepage showing a list of buyable articles, and once the
-user clicks one of them, they will either get the Taler
-`contract <https://api.taler.net/api-merchnat.html#contract>`_
-or a credit card paywall if they have no Taler wallet installed.
-
-Each article thus links to a `offer URL`, whose layout is shown below.
-
- `https://shop.demo.taler.net/essay/Appendix_A:_A_Note_on_Software`
-
-Once the server side logic receives a request for a offer URL, it needs to
-instruct the wallet to retrieve a Taler contract. This action can be taken
-either with or **without** the use of JavaScript, see the next section.
-
------------------------
-Triggering the contract
------------------------
-
-It is important to note that the contract is not returned simply
-as the offer URL's response, but rather the frontend `instructs`
-the browser on how to retrieve the contract. That is needed for
-the right handling of the cases where the wallet is not installed.
-
-.. note::
-
- The code samples shown below are intentionally "incomplete", as often
- one function contains logic for multiple actions. Thus in order to not
- mix concepts form different actions under one section, parts of code not
- related to the section being documented have been left out.
-
-**With JavaScript**
-
-In this case, the objective is to call the function ``taler.offerContractFrom()`` into the user browser, which will then retrieve the
-contract. In order to do that, we return a HTML page, whose
-template is in ``talerfrontends/blog/templates/purchase.html``,
-that imports ``taler-wallet-lib.js``, so that the function
-``taler.offerContractFrom()`` can be invoked into the user's
-browser.
-
-The server side handler for a offer URL needs to render ``purchase.html`` by passing
-the right parameters to ``taler.offerContractFrom()``.
-
-The rendering is done by the ``article`` function at ``talerfrontends/blog/blog.py``,
-and is done by Flask's ``render_template()``, see below.
-
-.. sourcecode:: python
-
- # 'name' is the article name, and is set to the right value
- # by Flask
- # The 'data' parameter is used to send images along
- # the articles, however its use is beyond the scope of
- # this survey.
- def article(name, data=None):
- ...
- ...
-
- return render_template('templates/purchase.html',
- article_name=name,
- no_contract=1,
- contract_url=quote(contract_url),
- data_attribute="data-taler-contractoffer=%s" % contract_url)
-
-After the rendering, (part of) ``purchase.html`` will look like shown below.
-
-.. sourcecode:: html
-
- <html>
- <head>
- <!-- ... -->
- <script src="/static/web-common/taler-wallet-lib.js" type="application/javascript"></script>
- <script src="/static/purchase.js" type="application/javascript"></script>
- <!-- ... -->
- <meta name="contract_url" value="https://shop.demo.taler.net/generate-contract?article_name=Appendix_A:_A_Note_on_Software">
-
- </head>
- <body>
- <!-- ... -->
- <!-- ... -->
-
- <div id="ccfakeform" class="fade">
- <p>
- Oops, it looks like you don't have a Taler wallet installed. Why don't you enter
- all your credit card details before reading the article? <em>You can also
- use GNU Taler to complete the purchase at any time.</em>
- </p>
- <form>
- First name<br> <input type="text"></input><br>
- Family name<br> <input type="text"></input><br>
- Age<br> <input type="text"></input><br>
- Nationality<br> <input type="text"></input><br>
- Gender<br> <input type="radio" name="gender">Male</input>
- CC number<br> <input type="text"></input><br>
- <input type="radio" name="gender">Female</input><br>
- </form>
-
- <form method="get" action="/cc-payment/{{ article_name }}">
- <input type="submit"></input>
- </form>
- </div>
-
- <div id="talerwait">
- <em>Processing payment with GNU Taler, please wait <span id="action-indicator"></span></em>
- </div>
- <!-- ... -->
- </body>
- </html>
-
-
-The script ``purchase.js`` is now in charge of implementing the behaviour we seek.
-It needs to register two handlers: one called whenever the wallet is detected in the
-browser, the other if the user has no wallet installed.
-
-That is done with:
-
-.. sourcecode:: javascript
-
- taler.onPresent(handleWalletPresent);
- taler.onAbsent(handleWalletAbsent);
-
-Note that the ``taler`` object is exported by ``taler-wallet-lib.js``, and contains all
-is needed to communicate with the wallet.
-
-
-``handleWalletAbsent`` doesn't need to do much: it has to only hide the "please wait"
-message and uncover the credit card pay form. See below.
-
-.. sourcecode:: javascript
-
- function handleWalletAbsent() {
- // Hide "please wait" message
- document.getElementById("talerwait").style.display = "none";
- // Uncover credit card pay form
- document.body.style.display = "";
- }
-
-On the other hand, ``handleWalletPresent`` needs to firstly hide the credit card
-pay form and show the "please wait" message. After that, it needs to fetch the
-contract URL from the responsible ``meta`` tag, and finally invoke ``taler.offerContractFrom()`` using it. See below both parts.
-
-.. sourcecode:: javascript
-
- function handleWalletPresent() {
- // Hide credit card paywall
- document.getElementById("ccfakeform").style.display = "none";
- // Show "please wait" message
- document.getElementById("talerwait").style.display = "";
- ...
- ...
- // Fetch contract URL from 'meta' tag.
- let contract_url = document.querySelectorAll("[name=contract_url]")[0];
- // If this call is successful, it will obtain the contract,
- // hand it to the wallet, so the wallet can eventually
- // show it to the user.
- taler.offerContractFrom(decodeURIComponent(contract_url.getAttribute("value")));
- ...
- }
-
-.. note::
-
- In order to get our code validated by W3C validators, we can't have inline
- JavaScript in our pages, we are forced to import any used script instead.
-
-**Without JavaScript**
-
-This case is handled by the function ``article`` defined in
-``talerfrontends/blog/blog.py``. Its objective is to set the "402 Payment
-Required" HTTP status code, and the HTTP header ``X-Taler-Contract-Url``
-to the actual contract's URL for this purchase.
-
-Upon returning such a response, the wallet will automatically fetch the
-contract from the URL indicated by ``X-Taler-Contract-Url``, and show it
-to the user.
-
-Below is shown how the function ``article`` prepares and returns such a
-response.
-
-.. sourcecode:: python
-
- # 'name' is the article name, and is set to the right value
- # by Flask
- # The 'data' parameter is used to send images along
- # the articles, however its use is beyond the scope of
- # this survey.
- def article(name, data=None):
- ...
- ...
-
- # Create response.
- response = make_response(render_template('templates/fallback.html'), 402)
- # Set "X-Taler-Contract-Url" header to the contract's URL.
- response.headers["X-Taler-Contract-Url"] = contract_url
- return response
-
-The ``make_response`` function is exported by Flask, so it's beyond the scope
-of this document to explain it; however, it returns a "response object" having
-the "402 Payment Required" as HTTP status code, and the
-HTML file ``talerfrontends/blog/templates/fallback.html`` as the body.
-``fallback.html`` contains the credit card pay form, so that if the wallet is
-not installed, the browser would keep that page shown.
-
-``contract_url`` is defined in the earlier steps of the same function; however,
-in this example it looks like:
-
- `https://shop.demo.taler.net/essay/generate-contract?article_name=Appendix_A:_A_Note_on_Software`.
-
-The frontend will also have to provide the contract. That is done
-by the handler ``generate_contract``, defined in
-``talerfrontends/blog/blog.py``. See below.
-
-.. sourcecode:: python
-
- def generate_contract():
- now = int(time.time())
- tid = random.randint(1, 2**50)
- article_name = expect_parameter("article_name")
- contract = make_contract(article_name=article_name, tid=tid, timestamp=now)
- contract_resp = sign_contract(contract)
- logger.info("generated contract: %s" % str(contract_resp))
- return jsonify(**contract_resp)
-
-
-Its task is to feed the ``make_contract`` subroutine with all the
-values it needs to generate a contract. Those values are: the timestamp
-for the contract, the transaction ID, and the article name; respectively,
-``now``, ``tid``, and ``article_name``.
-
-After ``make_contract`` returns, the variable ``contract`` will hold a
-`dict` type that complies with a contract
-`proposition <https://api.taler.net/api-merchnat.html#proposition>`_
-We then call ``sign_contract`` feeding it with the proposition, so that
-it can forward it to the backend and return it signed. Finally we return
-the signed proposition, complying with the
-`Offer <https://api.taler.net/api-merchant.html#contract>`_ object.
-
-For simplicity, any article costs the same price, so the frontend
-doesn't need to map articles to prices.
-
-Both ``make_contract`` and ``sign_contract`` are defined in
-``talerfrontends/blog/helpers.py``.
-
-At this point, the user can accept the contract, which triggers the wallet
-to visit the fulfillment page. The main logic for a fulfillment page handler
-is to (1) return the claimed product, if it has been paid, or (2) instruct the
-wallet to send the payment.
-
------------------
-Fulfillment logic
------------------
-
-The state accounts for a product being paid or not, so the fulfillment handler
-will firstly check that:
-
-.. sourcecode:: python
-
- # 'name' is the article name, and is set to the right value
- # by Flask
- # The 'data' parameter is used to send images along
- # the articles, however its use is beyond the scope of
- # this survey.
- def article(name, data=None):
- # Get list of payed articles from the state
- payed_articles = session.get("payed_articles", [])
-
- if name in payed_articles:
- ...
- # The articles has been paid, so return it to the
- # customer.
- return send_file(get_article_file(article))
- ...
-
-In case the article has not been paid yet, the fulfillment handler needs
-to `reconstruct` the contract, in order to get a precise reference about the
-purchase in being served.
-
-All the information needed to reconstruct the contract is contained in the
-fulfillment URL parameters. See below the URL layout:
-
- `https://shop.demo.taler.net/essay/Appendix_A:_A_Note_on_Software?uuid=<CONTRACT-HASHCODE>&timestamp=<TIMESTAMP>tid=<TRANSACTION_ID>`
-
-The way the contract is reconstructed is exactly the same as it was generated
-in the previous steps: we need to call ``make_contract`` to get the original
-`proposition <https://api.taler.net/api-merchnat.html#proposition>`_ and then
-``sign_contract``. Recall that aside from allowing the backend to add missing
-fields to the proposition, ``sign_contract`` returns the contract hashcode also,
-that we should compare with the ``uuid`` parameter provided by the wallet.
-
-In our blog, all the fulfillment logic is implemented in the function ``article``,
-defined in ``talerfrontends/blog/blog.py``. It is important to note that this
-function is `the same` function that runs the offer URL; in fact, as long as your
-URL design allows it, it is not mandatory to split up things. In our example, the
-offer URL differs from the fulfillment URL respect to the number (and type) of
-parameters, so the ``article`` function can easily decide whether it has to handle
-a "offer" or a "fulfillment" case. See below how the function detects the right
-case and reconstructs the contract.
-
-.. sourcecode:: python
-
- # 'name' is the article name, and is set to the right value
- # by Flask
- # The 'data' parameter is used to send images along
- # the articles, however its use is beyond the scope of
- # this survey.
- def article(name, data=None):
-
- ...
- hc = request.args.get("uuid")
- tid_str = request.args.get("tid")
- timestamp_str = request.args.get("timestamp")
- if hc is None or tid_str is None or timestamp_str is None:
- # Offer URL case.
- contract_url = make_url("/generate-contract", ("article_name",name))
- ... # Go on operating the offer URL and return
-
- # Fulfillment URL case from here on.
- try:
- tid = int(tid_str)
- except ValueError:
- raise MalformedParameterError("tid")
- try:
- timestamp = int(timestamp_str)
- except ValueError:
- raise MalformedParameterError("timestamp")
-
- restored_contract = make_contract(article_name=name, tid=tid, timestamp=timestamp)
- contract_resp = sign_contract(restored_contract)
-
- # Return error if uuid mismatch with the hashcode coming from the backend
- if contract_resp["h_proposal_data"] != hc:
- e = jsonify(error="contract mismatch", was=hc, expected=contract_resp["h_proposal_data"])
- return e, 400
-
- # We save the article's name in the state since after
- # receiving the payment this value will point to the
- # article to be delivered to the customer. Note how the
- # contract's hashcode is used to index the state.
- session[hc] = si = session.get(hc, {})
- si['article_name'] = name
-
-
-After a successful contract reconstruction, the handler needs to instruct
-the wallet to actually send the payment. There are as usual two ways this
-can be accomplished: with and without JavaScript.
-
-**With JavaScript**
-
-..
- Mention that the template is the same we used for a offer URL!
-
-We return a HTML page, whose template is in
-``talerfrontends/blog/templates/purchase.html``, that imports ``taler-wallet-lib.js``,
-so that the function ``taler.executePayment()`` can be invoked into the user's
-browser.
-
-The fulfillment handler needs to render ``purchase.html`` so that the right
-parameters get passed to ``taler.executePayment()``.
-
-See below how the function ``article`` does the rendering.
-
-.. sourcecode:: python
-
- # 'name' is the article name, and is set to the right value
- # by Flask
- # The 'data' parameter is used to send images along
- # the articles, however its use is beyond the scope of
- # this survey.
- def article(name, data=None):
-
- ...
- ...
-
- return render_template('templates/purchase.html',
- hc=hc,
- pay_url=quote(pay_url),
- offering_url=quote(offering_url),
- article_name=name,
- no_contract=0,
- data_attribute="data-taler-executecontract=%s,%s,%s" % (hc, pay_url, offering_url))
-
-After the rendering, (part of) ``purchase.html`` will look like shown below.
-
-.. sourcecode:: html
-
- ...
- <script src="/static/web-common/taler-wallet-lib.js" type="application/javascript"></script>
- <script src="/static/purchase.js" type="application/javascript"></script>
- ...
- <meta name="pay_url" value="https://shop.demo.taler.net/pay">
- <meta name="offering_url" value="https://shop.demo.taler.net/essay/Appendix_A:_A_Note_on_Software">
- <!-- Fake hashcode -->
- <meta name="hc" value="D7D5HDJRP36GTBBRGHXP7204VR773HHQBNFFCY5YY4P18026PAJ0">
-
- ...
- ...
-
- <div id="ccfakeform" class="fade">
- <p>
- Oops, it looks like you don't have a Taler wallet installed. Why don't you enter
- all your credit card details before reading the article? <em>You can also
- use GNU Taler to complete the purchase at any time.</em>
- </p>
-
- <form>
- <!-- Credit card pay form. -->
- </form>
- </div>
-
- <div id="talerwait">
- <em>Processing payment with GNU Taler, please wait <span id="action-indicator"></span></em>
- </div>
- ...
-
-The script ``purchase.js`` is now in charge of calling ``taler.executePayment()``.
-It will try to register two handlers: one called whenever the wallet is detected in the
-browser, the other if the user has no wallet installed.
-
-That is done with:
-
-.. sourcecode:: javascript
-
- taler.onPresent(handleWalletPresent);
- taler.onAbsent(handleWalletAbsent);
-
-.. note::
-
- So far, the template and the imported script (``purchase.js``)
- are exactly the same as the offer URL case, since we use them
- for both cases. See below how the script distinguishes "offer"
- from "fulfillment" case.
-
-Note that the ``taler`` object is exported by ``taler-wallet-lib.js``, and contains all
-is needed to communicate with the wallet.
-
-
-``handleWalletAbsent`` doesn't need to do much: it has to only hide the "please wait"
-message and uncover the credit card pay form. See below.
-
-.. sourcecode:: javascript
-
- function handleWalletAbsent() {
- // Hide "please wait" message
- document.getElementById("talerwait").style.display = "none";
- // Uncover credit card pay form
- document.body.style.display = "";
- }
-
-On the other hand, ``handleWalletPresent`` needs to firstly hide the credit card
-pay form and show the "please wait" message. After that, it needs to fetch the
-needed parameters from the responsible ``meta`` tags, and finally invoke
-``taler.offerContractFrom()`` using those parameters. See below its whole definition.
-Note, that since we are in the fulfillment case, the credit card pay form is `almost`
-useless, as it is highly unlikely that the wallet is not installed.
-
-.. sourcecode:: javascript
-
- function handleWalletPresent() {
- // Hide the credit card pay form
- document.getElementById("ccfakeform").style.display = "none";
- // Show "please wait" message
- document.getElementById("talerwait").style.display = "";
-
- // The `no_contract` value is provided by the function `article` via a
- // 'meta' tag in the template. When this value equals 1, then we are in the
- // "offer" URL case, otherwise we are in the "fulfillment" URL case.
- let no_contract = document.querySelectorAll("[name=no_contract]")[0];
- if (Number(no_contract.getAttribute("value"))) {
- // "Offer" case
- let contract_url = document.querySelectorAll("[name=contract_url]")[0];
- taler.offerContractFrom(decodeURIComponent(contract_url.getAttribute("value")));
- }
- else {
- // "Fulfillment" case.
- let hc = document.querySelectorAll("[name=hc]")[0];
- let pay_url = document.querySelectorAll("[name=pay_url]")[0];
- let offering_url = document.querySelectorAll("[name=offering_url]")[0];
- taler.executePayment(hc.getAttribute("value"),
- decodeURIComponent(pay_url.getAttribute("value")),
- decodeURIComponent(offering_url.getAttribute("value")));
- }
- }
-
-Once the browser executes ``taler.executePayment(...)``, the wallet will send the coins
-to ``pay_url``. Once the payment succeeds, the wallet will again visit the
-fulfillment URL, this time getting the article thanks to the "payed" status set by
-the ``pay_url`` handler.
-
-**Without JavaScript**
-
-This case is handled by the function ``article`` defined in
-``talerfrontends/blog/blog.py``. Its objective is to set the "402 Payment
-Required" HTTP status code, along with the HTTP headers ``X-Taler-Contract-Hash``,
-``X-Taler-Pay-Url``, and ``X-Taler-Offer-Url``.
-
-..
- FIXME:
- Are those three parameters previously introduced?
-
-Upon returning such a response, the wallet will automatically send the
-payment to the URL indicated in ``X-Taler-Pay-Url``.
-
-The excerpt below shows how the function ``article`` prepares and returns such a
-response.
-
-.. sourcecode:: python
-
- # 'name' is the article name, and is set to the right value
- # by Flask
- # The 'data' parameter is used to send images along
- # the articles, however its use is beyond the scope of
- # this survey.
- def article(name, data=None):
- ...
-
- # 'make_response' is exported by Flask. It returns a
- # "response object" with customizable status code, HTTP
- # headers and body
- response = make_response(render_template('templates/fallback.html'), 402)
- response.headers["X-Taler-Contract-Hash"] = hc
- response.headers["X-Taler-Pay-Url"] = pay_url
- response.headers["X-Taler-Offer-Url"] = offering_url
- return response
-
-The template ``fallback.html`` contains the credit card pay form, which will be
-used in the rare case where the wallet would not be detected in a fulfillment
-session. Once the payment succeeds, the wallet will again visits the
-fulfillment URL, this time getting the article thanks to the "payed" status set by
-the ``pay_url`` handler.
-
----------
-Pay logic
----------
-
-The pay handler for the blog is implemented by the function
-``pay`` at ``talerfrontends/blog/blog.py``. Its main duty is to receive the
-`deposit permission <https://api.taler.net/api-merchant.html#DepositPermission>`_
-from the wallet, forward it to the backend, and return the outcome
-to the wallet. See below the main steps of its implementation.
-
-.. sourcecode:: python
-
- def pay():
- # Get the uploaded deposit permission
- deposit_permission = request.get_json()
-
- if deposit_permission is None:
- e = jsonify(error="no json in body")
- return e, 400
-
- # Pick the contract's hashcode from deposit permission
- hc = deposit_permission.get("h_proposal_data")
-
- # Return error if no hashcode was found
- if hc is None:
- e = jsonify(error="malformed deposit permission", hint="h_proposal_data missing")
- return e, 400
-
- # Get a handle to the state for this contract, using the
- # hashcode from deposit permission as the index
- si = session.get(hc)
-
- # If no session was found for this contract, then either it
- # expired or one of the hashcodes (the one we got from
- # reconstructing the contract in the fulfillment handler,
- # and the one we just picked from the deposit permission)
- # is bogus. Note how using the contract's hashcode as index
- # makes harder for the wallet to use different hashcodes
- # in different steps of the protocol.
- if si is None:
- e = jsonify(error="no session for contract")
- return e, 400
-
- # Forward the deposit permission to the backend
- r = requests.post(urljoin(BACKEND_URL, 'pay'), json=deposit_permission)
-
- # Return error if the backend returned a HTTP status code
- # other than 200 OK
- if 200 != r.status_code:
- raise BackendError(r.status_code, r.text)
-
- # The payment went through..
- ...
-
- # Resume the article name
- article = si["article_name"]
-
- # Set the article's state as "payed". This is realized by
- # appending it to a *list* of articles the customer is currently
- # allowed to read.
- payed_articles = session["payed_articles"] = session.get("payed_articles", [])
- if article not in payed_articles:
- payed_articles.append(article)
-
- ...
-
- # Return success
- return r.text, 200