merchant-frontend-examples

ZZZ: Inactive/Deprecated
Log | Files | Refs

tutorial.texi (19924B)


      1 \input texinfo @c -*-texinfo-*-
      2 @c %**start of header
      3 @setfilename tutorial.info
      4 @include ../../common/texi/version.texi
      5 @include ../../common/texi/syntax.texi
      6 @settitle The GNU Taler tutorial for Python Web shop developers @value{VERSION}
      7 
      8 @c Define a new index for options.
      9 @defcodeindex op
     10 @c Combine everything into one index (arbitrarily chosen to be the
     11 @c concept index).
     12 @syncodeindex op cp
     13 @c %**end of header
     14 
     15 @copying
     16 This tutorial is about implementing a merchant frontend to run against a
     17 GNU Taler merchant backend (version @value{VERSION}, @value{UPDATED}),
     18 
     19 Copyright @copyright{} 2016, 2017, 2018 Taler Systems SA
     20 
     21 @quotation
     22 Permission is granted to copy, distribute and/or modify this document
     23 under the terms of the GNU Free Documentation License, Version 1.3 or
     24 any later version published by the Free Software Foundation; with no
     25 Invariant Sections, with no Front-Cover Texts, and with no Back-Cover
     26 Texts.  A copy of the license is included in the section entitled
     27 ``GNU Free Documentation License''.
     28 @end quotation
     29 @end copying
     30 @c If your tutorial is published on paper by the FSF, it should include
     31 @c The standard FSF Front-Cover and Back-Cover Texts, as given in
     32 @c maintain.texi.
     33 @c
     34 @c Titlepage
     35 @c
     36 @titlepage
     37 @title The GNU Taler tutorial for Python Web shops
     38 @subtitle Version @value{VERSION}
     39 @subtitle @value{UPDATED}
     40 @author Christian Grothoff (@email{christian@@grothoff.org})
     41 @author Marcello Stanisci (@email{marcello.stanisci@@inria.fr})
     42 @author Florian Dold (@email{florian.dold@@inria.fr})
     43 @page
     44 @vskip 0pt plus 1filll
     45 @insertcopying
     46 @end titlepage
     47 
     48 @summarycontents
     49 @contents
     50 
     51 @ifnottex
     52 @node Top
     53 @top The GNU Taler tutorial for Python Web shops
     54 @insertcopying
     55 @end ifnottex
     56 
     57 @menu
     58 * Introduction::                                  What this tutorial is about
     59 * Setting up a simple donation page::             How to set up a donation page
     60 * Back-office-integration::                       How to integrate with the back office
     61 * Advanced topics::                               Detailed solutions to specific issues
     62 * Reference::                                     Merchant integration reference
     63 
     64 
     65 Appendices
     66 
     67 * GNU-LGPL::                     The GNU Lesser General Public License says how you
     68                                  can use the code of libtalermerchant.so in your own projects.
     69 * GNU-FDL::                      The GNU Free Documentation License says how you
     70                                  can copy and share the documentation of GNU Taler.
     71 
     72 Indices
     73 
     74 * Concept Index::               Index of concepts and programs.
     75 @end menu
     76 
     77 
     78 @node Introduction
     79 @chapter Introduction
     80 
     81 @section About GNU Taler
     82 
     83 GNU Taler is an open protocol for an electronic payment system with a
     84 free software reference implementation.  GNU Taler offers secure, fast
     85 and easy payment processing using well understood cryptographic
     86 techniques.  GNU Taler allows customers to remain anonymous, while
     87 ensuring that merchants can be held accountable by governments.
     88 Hence, GNU Taler is compatible with anti-money-laundering (AML) and
     89 know-your-customer (KYC) regulation, as well as data protection
     90 regulation (such as GDPR).
     91 
     92 
     93 @section About this tutorial
     94 
     95 This tutorial is for Python Web developers and addresses how to integrate GNU
     96 Taler with Web shops. It describes how to create a Web shop that
     97 processes payments with the help of a GNU Taler merchant
     98 @emph{backend}.   In the second chapter, you will learn how to trigger
     99 the payment process from the Web site, how to communicate with the
    100 backend, how to generate a order and process the payment.  The
    101 third chapter covers the integration of a back office with the
    102 backend, which includes tracking payments for orders, matching
    103 payments to orders, and persisting and retrieving contracts.
    104 
    105 @cindex examples
    106 @cindex git
    107 You can download all of the code examples given in this tutorial from
    108 @url{https://git.taler.net/merchant-frontend-examples.git/tree/python/example/}.
    109 
    110 
    111 @section Architecture overview
    112 
    113 The Taler software stack for a merchant consists of the following
    114 main components:
    115 
    116 @itemize
    117 @cindex frontend
    118 @item A frontend which interacts with the customer's browser. The
    119   frontend enables the customer to build a shopping cart and place
    120   an order.  Upon payment, it triggers the respective business logic
    121   to satisfy the order.  This component is not included with Taler,
    122   but rather assumed to exist at the merchant. This tutorial
    123   describes how to develop a Taler frontend.
    124 @cindex back office
    125 @item A back office application that enables the shop operators to
    126   view customer orders, match them to financial transfers, and possibly
    127   approve refunds if an order cannot be satisfied.  This component is
    128   again not included with Taler, but rather assumed to exist at the
    129   merchant. This tutorial will describe how to integrate such a component
    130   to handle payments managed by Taler.  Such integration is shown by
    131   adding the back office functionality to the frontend implemented
    132   in the second part of this tutorial.
    133 @cindex backend
    134 @item A Taler-specific payment backend which makes it easy for the
    135   frontend to process financial transactions with Taler.  For this
    136   tutorial, you will use a public backend, but for a production
    137   deployment a merchant-specific backend will need to be setup
    138   by a system administrator.
    139 @end itemize
    140 
    141 The following image illustrates the various interactions of these
    142 key components:
    143 
    144 @image{arch_nobo, 3in, 4in}
    145 
    146 
    147 The backend provides the cryptographic protocol support,
    148 stores Taler-specific financial information and communicates
    149 with the GNU Taler exchange over the Internet.  The frontend accesses
    150 the backend via a RESTful API.  As a result, the frontend never has to
    151 directly communicate with the exchange, and also does not deal with
    152 sensitive data.  In particular, the merchant's signing keys and bank
    153 account information are encapsulated within the Taler backend.
    154 
    155 
    156 @node Setting up a simple donation page
    157 @chapter Setting up a simple donation page
    158 
    159 This section describes how to setup a simple shop, which exposes a
    160 button to get donations via Taler. The expected behaviour is that once
    161 the ``donate'' button is clicked, the customer will receive a
    162 proposal to make a fixed donation,
    163 for example to donate 1.0 KUDOS to the charity operating the shop.
    164 
    165 All the code samples shown below in the tutorial can be found at
    166 @url{https://git.taler.net/merchant-frontend-examples.git/tree/python/example/}.
    167 Each sample is part of a functional frontend.
    168 The language is Python, and the Web is served by
    169 Flask@footnote{http://flask.pocoo.org/}.
    170 
    171 @c NOTE: include explaining wallet installation to Web developer here!
    172 
    173 @c Next sentence is inconsistent with PHP version. Why?
    174 An error message will be shown to the user if no Taler wallet is
    175 installed in the browser.
    176 
    177 @section Specifying the backend
    178 
    179 @cindex backend
    180 @cindex configuration
    181 @cindex currency
    182 For many critical operations, the frontend needs to communicate
    183 with a Taler backend. Assuming that you do not yet have a backend
    184 configured@footnote{https://docs.taler.net/current/merchant-backend/manual.html},
    185 you can use the public backend provided by the Taler
    186 project for testing.  This public backend has been set-up at
    187 @code{http://backend.test.taler.net/} specifically for testing
    188 frontends.  It uses the currency ``TESTKUDOS'' and all payments will
    189 go into the ``Tutorial'' account at the Taler ``bank'' running at
    190 @code{https://bank.test.taler.net/public-accounts}.
    191 
    192 In our example, backend and currency are specified by setting two
    193 global variables, as shown below from @code{python/example/example.py}:
    194 
    195 @smallexample
    196 ..
    197 CURRENCY = "TESTKUDOS"
    198 BACKEND_URL = "http://backend.test.taler.net/"
    199 ..
    200 @end smallexample
    201 
    202 
    203 @section Talking to the backend
    204 
    205 @cindex backend
    206 The frontend needs to issue HTTP POST requests to the backend; this can be
    207 done using the @emph{requests}@footnote{https://docs.taler.net/current/merchant-backend/manual.html}
    208 library:
    209 
    210 @smallexample
    211 import flask
    212 import requests
    213 from urllib.parse import urljoin
    214 ..
    215 
    216 # In this example we use the /proposal API offered by the
    217 # backend, which is in charge of signing orders.
    218 r = requests.post(urljoin(BACKEND_URL, 'proposal'), json=dict(order=order))
    219 
    220 if r.status_code != 200:
    221     logger.error("failed to POST to '%s'", url)
    222     return r.text, r.status_code
    223 @end smallexample
    224 
    225 
    226 @section Prompting for payment
    227 
    228 @cindex button
    229 Our goal is to trigger a Taler payment once the customer has clicked
    230 on a donation button.  We will use a button that issues an HTTP GET
    231 to the frontend @code{/donate} URL.  For this, the HTML would be as
    232 follows:
    233 
    234 @smallexample
    235 <!-- ../example/templates/index.html -->
    236 @verbatiminclude ../example/templates/index.html
    237 @end smallexample
    238 
    239 When the server-side handler for @code{/donate} receives the form submission,
    240 it will return a HTML page and HTTP header that will take care of:
    241 
    242 @itemize
    243 @item showing a error message if no wallet is installed
    244 @item instruct the wallet to download the proposal related to this donation
    245 @end itemize
    246 
    247 The @code{/donate} endpoint looks like this:
    248 
    249 @cindex pay handler
    250 @cindex 402
    251 @cindex X-Taler-Contract-Url
    252 @smallexample
    253 @@app.route("/donate")
    254 def donate():
    255    response = flask.Response("No wallet installed!", status=402)
    256    response.headers["X-Taler-Contract-Url"] = "/generate-proposal"
    257    return response
    258 @end smallexample
    259 The wallet detects the 402 status and reacts by downloading
    260 the proposal from @code{/generate-proposal}.  The proposal
    261 is then presented to the user.
    262 
    263 If the wallet is not present, the error message ``No wallet
    264 installed!'' will be shown and the Taler ``X-Taler-Contract-Url''
    265 header and the 402 status code ought to be ignored by the browser.
    266 
    267 @section A helper function to generate the order
    268 
    269 We make distinction between @emph{three} different stages of what it
    270 informally called "contract".
    271 
    272 In a very first stage, we call it the @emph{order}: that occurs when
    273 the frontend generates the first JSON that misses some information
    274 that the backend is supposed to add.  When the backend completes the
    275 order and signs it, we have a @emph{proposal}.  The proposal is what
    276 the user is prompted with, and allows them to confirm the purchase.
    277 Once the user confirms the purchase, the wallet makes a signature over
    278 the proposal, turning it into a @emph{contract}.
    279 
    280 The next step is to generate a proposal whenever the wallet makes a GET
    281 @code{/generate-proposal} request.  In our example, this logic is implemented
    282 by the function @code{generate_proposal()}:
    283 
    284 @c FIXME: rename package to just 'taler' or 'gnu.taler'? (Check with Florian)
    285 @cindex contract
    286 @cindex proposal
    287 @cindex order
    288 @smallexample
    289 ..
    290 from pytaler import amount
    291 ..
    292 @@app.route("/generate-proposal")
    293 def generate_proposal():
    294     DONATION = amounts.string_to_amount("0.1:%s" % CURRENCY)
    295     MAX_FEE = amounts.string_to_amount("0.05:%s" % CURRENCY)
    296     ORDER_ID = "tutorial-%X-%s" % (randint(0, 0xFFFFFFFF), datetime.today().strftime("%H_%M_%S"))
    297     order = dict(
    298         nonce=flask.request.args.get("nonce"),
    299         order_id=ORDER_ID,
    300         amount=DONATION,
    301         max_fee=MAX_FEE,
    302         products=[
    303             dict(
    304                 description="Donation",
    305                 quantity=1,
    306                 product_id=0,
    307                 price=DONATION,
    308             ),
    309         ],
    310         fulfillment_url=make_url("/fulfillment", ("order_id", ORDER_ID)),
    311         pay_url=make_url("/pay"),
    312         merchant=dict(
    313             address="nowhere",
    314             name="Donation tutorial",
    315             jurisdiction="Ursa Minor",
    316         ),
    317     )
    318 
    319     url = urljoin(BACKEND_URL, "proposal")
    320 
    321     r = requests.post(url, json=dict(order=order))
    322     if r.status_code != 200:
    323         logger.error("failed to POST to '%s'", url)
    324         return r.text, r.status_code
    325     proposal_resp = r.json()
    326     return flask.jsonify(**proposal_resp)
    327 @end smallexample
    328 @c CHECK ** with Florian, consider inlining...
    329 
    330 The function @code{amounts.string_to_amount()} is defined by the
    331 @emph{pytaler} library, and it is used to convert amounts given as strings
    332 (in the form @code{"1.2:EUR"}) to amount as `dict` (in the form
    333 @code{@{value:1, fraction:20000000, currency:"EUR"@}}).
    334 @cindex signature
    335 @cindex backend
    336 @cindex proposal
    337 One important thing that @code{generate_proposal()} needs to do is to
    338 POST the ``order'' to the backend.  This is needed because the backend
    339 has to fill some missing fields and sign the whole order; once the backend
    340 has done that, it returns the completed data and its signature to the
    341 frontend that can eventually relay it to the wallet.
    342 
    343 The @code{make_url} function is used to ``attach'' paths to the shop's
    344 base URL.  For example, if the shop is run at @code{https://shop.com},
    345 then @code{make_url("/path", ("a", 5))} would result in @code{https://shop.com/path?a=5}.
    346 
    347 
    348 @section Initiating the payment process
    349 
    350 @cindex fulfillment URL
    351 After the wallet has fetched the proposal, the user will be
    352 given the opportunity to affirm the payment.  Assuming the user
    353 affirms, the browser will navigate to the ``fulfillment_url'' that
    354 was specified in the offer.
    355 
    356 The fulfillment page can be called by users that have already paid for
    357 the item, as well as by users that have not yet paid at all.  The
    358 fulfillment page must thus use the HTTP session state to detect if the
    359 payment has been performed already, and if not request payment from
    360 the wallet.
    361 
    362 The fulfillment handler at @code{/fulfillment} must thus first figure out
    363 if the user has already paid, and if so merely confirm the donation.
    364 If the user has not yet paid, it must instead return another ``402 Payment
    365 Required'' header, requesting the wallet to pay:
    366 
    367 @cindex 402 payment required
    368 @smallexample
    369 @@app.route("/fulfillment")
    370 def fulfillment():
    371     # Ask the state whether the user has paid or not
    372     paid = flask.session.get("paid", False)
    373     if paid:
    374         # Please note that flask.session["order_id"] takes its value
    375         # from the response the _backend_ gave for /pay. This way, the fulfillment
    376         # page only shows what the wallet paid for.
    377         return "Thank you! Your order id is: <b>%s</b>." % flask.session["order_id"]"
    378 
    379     # At this point, the user did not pay yet, so we set some
    380     # appropriate HTTP headers that will instruct the wallet to
    381     # make the payment, assuming the user already accepted the
    382     # proposal.
    383     response = flask.Response(status=402)
    384 
    385     # At this URL, the wallet may request a regeneration of the proposal.
    386     response.headers["X-Taler-Contract-Url"] = make_url("/generate-proposal")
    387     # This URL will be visited in case the user has opened
    388     # on someone else's fulfillment URL.  As a result, the
    389     # user will be offered a fresh proposal.
    390     response.headers["X-Taler-Offer-Url"] = make_url("/donate")
    391 
    392     return response
    393 @end smallexample
    394 @c FIXME: check with Florian: isn't 'x-taler-contract-query' dead by now?
    395 
    396 The @code{X-Taler-Contract-Query} header is crucial for implementing replayable
    397 payments.  In fact, upon receiving such a header, the wallet will look in its
    398 internal database if a payment to the current fulfillment URL has already been
    399 sent.  If that's the case, then the coins from that previous payment will be
    400 sent to the @emph{pay_url}.
    401 That is exactly what happens when the user visits some bookmarked fulfillment
    402 page in order to see again what they already paid for.
    403 That header is scheduled to be removed in future versions of the wallet,
    404 as it only works with the value @code{"fulfillment_url"}.
    405 
    406 @section Receiving payments via Taler
    407 
    408 The final next step for the frontend is to receive the payment from the
    409 wallet.  For this, the frontend must implement a payment handler at
    410 the URI specified in the @code{pay_url} field of the proposal, so @code{/pay}
    411 in our example.
    412 
    413 The role of the @code{/pay} handler is to receive the payment
    414 from the wallet and forward it to the backend.  The backend
    415 executes the payment.  If it reports that the payment was successful
    416 by returning a "200 OK" status code, the handler needs to update
    417 the session state with the browser to remember that the user paid.
    418 If the backend reports a failure, the error response is passed on to
    419 the wallet.
    420 
    421 In our example, that is done by the @code{pay} function; see below.
    422 
    423 @smallexample
    424 @@app.route("/pay", methods=["POST"])
    425 def pay():
    426     # Here we get the payment from the wallet.  The
    427     # "payment" is a JSON containing coins and proposal
    428     # signed by the wallet, plus some other metadata.
    429     deposit_permission = flask.request.get_json()
    430     if deposit_permission is None:
    431         e = flask.jsonify(error="no json in body")
    432         return e, 400
    433 
    434     # Forwarding the payment to the backend that will cryptographically
    435     # verify it and persist the proof of payment.
    436     r = requests.post(urljoin(BACKEND_URL, 'pay'), json=deposit_permission)
    437 
    438     # Pass errors back to the wallet.
    439     if 200 != r.status_code:
    440         return r.text, r.status_code
    441 
    442     # The payment went through, so we can set the state as "paid".
    443     # Note that once this page will return "200 OK", the wallet will
    444     # automatically re-visit the fulfillment page (and get the "Thank
    445     # you" message this time).
    446     flask.session["paid"] = True
    447 
    448     return flask.Response(status=200)
    449 @end smallexample
    450 
    451 
    452 @section Running the Example
    453 
    454 The example depends on the @code{pytaler} library. The next commands
    455 show how to install it:
    456 
    457 @smallexample
    458 $ cd python/lib/
    459 $ export TALER_PREFIX=<YOUR-PREFIX>
    460 $ make install
    461 @end smallexample
    462 
    463 Make sure your python code will look for libraries within the
    464 @code{<YOUR-PREFIX>} directory.
    465 
    466 The file @code{python/example/example.py} contains all the code samples
    467 seen so far, incuding the @code{make_url()} helper function.
    468 It is run as a typical Flask application, using the following commands:
    469 
    470 @smallexample
    471 $ cd python/example/
    472 $ export FLASK_APP=example.py
    473 $ flask run
    474 @end smallexample
    475 
    476 At this point you should have the site running at @code{localhost} on port 5000.
    477 
    478 To do a test payment, you first need to visit
    479 @code{https://taler.net/wallet} from where you can install the Taler
    480 wallet.  Then, you need to withdraw a few coins from our demonstration
    481 bank running at @code{https://bank.test.taler.net/}.  After that, you
    482 should be able to point your browser at @code{http://localhost:5000/}
    483 and make a donation.
    484 
    485 @node Back-office-integration
    486 @chapter Integration with the back office
    487 
    488 Taler ships the back-office feature as a stand-alone Web application.
    489 See how to run it, on its own documentaion: @url{https://docs.taler.net/backoffice/html/manual.html}.
    490 
    491 @node Advanced topics
    492 @chapter Advanced topics
    493 
    494 @menu
    495 * Detecting the presence of the Taler wallet::  Detecting the presence of the Taler wallet
    496 * The Taler proposal format::                   The Taler proposal format
    497 @c * Inline proposals::                            Sending proposals with the HTTP header
    498 * Instances::                                   Instances
    499 * The fulfillment page::                        The fulfillment page
    500 * Normalized base URLs::                        Normalized base URLs
    501 @end menu
    502 
    503 @include ../../common/texi/wallet-detection.texi
    504 @include ../../common/texi/proposals.texi
    505 @c @include inline-proposals.texi  -- not yet written for Python!
    506 @include ../../common/texi/instances.texi
    507 @include ../../common/texi/fulfillment-page.texi
    508 @include ../../common/texi/normalized-base-url.texi
    509 
    510 
    511 
    512 @c ************ Reference chapter ***********
    513 @include ../../common/texi/api-reference.texi
    514 
    515 
    516 
    517 @c **********************************************************
    518 @c *******************  Appendices  *************************
    519 @c **********************************************************
    520 
    521 @node GNU-LGPL
    522 @unnumbered GNU-LGPL
    523 @cindex license
    524 @cindex LGPL
    525 @include ../../common/texi/lgpl.texi
    526 
    527 @node GNU-FDL
    528 @unnumbered GNU-FDL
    529 @cindex license
    530 @cindex GNU Free Documentation License
    531 @include ../../common/texi/fdl-1.3.texi
    532 
    533 @node Concept Index
    534 @unnumbered Concept Index
    535 
    536 @printindex cp
    537 
    538 @bye