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