paivana-httpd-manual.rst (33064B)
1 .. 2 This file is part of GNU TALER. 3 4 Copyright (C) 2026 Taler Systems SA 5 6 TALER is free software; you can redistribute it and/or modify it under the 7 terms of the GNU Affero General Public License as published by the Free Software 8 Foundation; either version 3.0, or (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 11 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 13 14 You should have received a copy of the GNU Affero General Public License along with 15 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 16 17 @author Christian Grothoff 18 19 .. _Paivana-httpd: 20 21 Paivana-httpd 22 ============= 23 24 This chapter documents the installation and operation of the Paivana 25 reverse proxy ``paivana-httpd``. The reverse proxy sits between the 26 public Internet and an upstream Web service, intercepting requests 27 that have not yet been paid for and presenting the client with a 28 GNU Taler paywall. Once a payment has been confirmed by the 29 configured GNU Taler merchant backend, ``paivana-httpd`` forwards 30 subsequent requests of that client to the upstream service. 31 32 The full list of command-line options is documented in 33 :manpage:`paivana-httpd(1)`; the configuration file is 34 documented in :manpage:`paivana.conf(5)`. 35 36 37 .. _Paivana-Motivation: 38 39 Motivation and use cases 40 ------------------------ 41 42 ``paivana-httpd`` answers a single question for every incoming 43 request — *"has the client paid for this?"* — but operators deploy it 44 for two rather different reasons. Which one applies to you mostly 45 changes how you *price* the templates (see :ref:`Paivana-Templates`), 46 not how you install or run the daemon. 47 48 Selling access: a paywall with revenue 49 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 50 51 The classic use case is to charge money for access to digital content 52 or services: a news article, a research dataset, an API call, a file 53 download. Because Paivana builds on GNU Taler, this comes with 54 properties that distinguish it from traditional paywalls: 55 56 - **No accounts, no sign-up.** A visitor pays directly from a Taler 57 wallet; there is no registration, no login, and no credit-card form. 58 This removes the friction that kills conversion on per-article 59 purchases. 60 - **Micropayments are practical.** Taler's costs per transaction are 61 low enough that charging fractions of a cent for a single page is 62 economically meaningful, enabling true pay-per-article or 63 pay-per-request business models. 64 - **Privacy for the buyer.** Taler is privacy-preserving by design: 65 the seller (and Paivana) learns that *an* order was paid, not *who* 66 paid it. No tracking profile is required to gate the content. 67 - **Subscriptions and discounts.** A template's ``choices`` can offer 68 a one-off payment *or* the purchase/redemption of a subscription 69 token, so returning subscribers pass the paywall automatically 70 (see :ref:`Paivana-Templates`). 71 - **Agentic payments.** Every paywall response also carries a 72 machine-readable ``Paivana:`` HTTP header (see 73 :ref:`Paivana-Customizing`). An automated client — for example an 74 AI agent provisioned with a Taler wallet — can therefore pay and 75 fetch the resource without rendering the HTML page, while the 76 operator caps financial exposure to whatever was loaded into that 77 agent's wallet. 78 79 Rate-limiting bots and abuse: DDoS and scraper protection 80 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 81 82 The second use case treats the payment not as a source of revenue but 83 as a *cost imposed on the client* — economic rate limiting. Here you 84 set the price very low (a fraction of a cent, effectively a "CAPTCHA 85 token"): negligible for a human visitor reading a handful of pages, 86 but ruinous for a botnet or scraper that wants to issue millions of 87 requests. An attacker sending a million requests must spend a million 88 micropayments; a legitimate user spends almost nothing. 89 90 This is a deliberate alternative to two older defenses whose 91 cost-asymmetry has eroded: 92 93 - **Proof-of-work puzzles** (as used by tools such as Anubis) ask the 94 client to burn CPU before being served. But CPU is cheap and 95 abundant for a well-resourced attacker or botnet, and the puzzle 96 burdens low-powered legitimate devices (phones, assistive tech) the 97 most. 98 - **CAPTCHAs** ask the client to solve a perception task. Modern 99 LLMs and automated solvers now clear text, image and even 100 "behavioral" CAPTCHAs cheaply and at scale, so they increasingly 101 inconvenience humans while barely slowing down the bots they were 102 meant to stop. 103 104 A Taler payment sidesteps both problems: it does not rely on a human 105 being slow, or on a machine being computationally weak. It relies on 106 the one resource a flooding attacker genuinely cannot conjure for free 107 — money. Bots *can* solve puzzles and CAPTCHAs; they cannot mint 108 funds. For this use case you typically combine a low per-request 109 price with a subscription-token ``choice`` so that a paying or 110 authenticated user buys a token once and then sails through, while 111 anonymous floods keep paying per request. 112 113 .. note:: 114 115 The two use cases are not mutually exclusive: the same deployment 116 can earn revenue on premium URLs and simultaneously throttle abuse 117 on cheap ones, simply by defining several templates with different 118 prices and ``website_regex`` scopes. 119 120 121 Architecture overview 122 --------------------- 123 124 ``paivana-httpd`` does not implement any payment logic of its own. 125 Instead, every Paivana deployment combines three components: 126 127 1. **The upstream web service.** This is the existing HTTP service 128 whose content should be sold (a static website, a cgit service, 129 a REST API, …). It does not need to be modified to 130 work with Paivana. 131 2. **A GNU Taler merchant backend** (``taler-merchant-httpd``). The 132 merchant backend manages templates, creates orders, talks to one 133 or more Taler exchanges, and ultimately reports back whether a 134 given order has been paid. See the 135 :ref:`Taler Merchant Backend Operator Manual 136 <taler-merchant-backend-operator-manual>` for full details. 137 3. **``paivana-httpd`` itself.** This is the reverse proxy that 138 gates the upstream service. It reads a single 139 :ref:`paivana.conf <Paivana-Configuration>` configuration file 140 that points at both the merchant backend and the upstream 141 service. 142 143 Typically a TLS-terminating reverse proxy (Nginx or Apache) is 144 deployed in front of ``paivana-httpd`` to handle HTTPS and to route 145 multiple virtual hosts; see :ref:`Paivana-ReverseProxy` below. 146 147 In normal operation the request flow is: 148 149 :: 150 151 client ──▶ Nginx/Apache (TLS) ──▶ paivana-httpd ──▶ upstream 152 │ 153 ▼ 154 taler-merchant-httpd 155 │ 156 ▼ 157 Taler exchange 158 159 160 Installation 161 ------------ 162 163 Installing from source 164 ^^^^^^^^^^^^^^^^^^^^^^ 165 166 The package sources can be found in our 167 `download directory <http://ftpmirror.gnu.org/taler/>`__. 168 169 GNU Taler components follow the ``MAJOR.MINOR.MICRO`` version 170 scheme. The general rule for compatibility is that ``MAJOR`` and 171 ``MINOR`` must match across components; exceptions are noted in the 172 release notes. For example, ``paivana-httpd`` 1.6.x is expected to 173 work with ``taler-merchant-httpd`` 1.6.x. A ``MAJOR`` version of 0 174 indicates experimental development; in that case you should always 175 run the *latest* releases of every component together. 176 177 The following packages must be installed before compiling 178 ``paivana-httpd``: 179 180 - GNUnet (``libgnunetutil``) matching the Taler release 181 - GNU Taler exchange libraries (``libtalerexchange``, 182 ``libtalerutil``) 183 - GNU Taler merchant client library (``libtalermerchant``) 184 - GNU Taler HTTP daemon helpers (``libtalermhd``, 185 ``libtalertemplating``) 186 - libmicrohttpd, libcurl, libjansson, libgcrypt, zlib 187 188 Build and install with: 189 190 .. code-block:: shell-session 191 192 $ ./bootstrap 193 $ ./configure --prefix=$PREFIX 194 $ make 195 $ sudo make install 196 197 198 Installing the binary packages on Debian 199 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 200 201 .. include:: frags/installing-debian.rst 202 203 To install ``paivana-httpd`` you can now simply run: 204 205 .. code-block:: shell-session 206 207 # apt install paivana-httpd 208 209 The package does not perform any deployment-specific configuration 210 work; it only sets up the ``paivana-httpd`` system user, the systemd 211 service and socket units, and installs example configuration 212 snippets for Nginx and Apache under ``/etc/nginx/sites-available/`` 213 and ``/etc/apache2/sites-available/``. You still must configure the 214 HTTP request routing and the Paivana templates as described below. 215 216 217 Installing the binary packages on Ubuntu 218 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 219 220 .. include:: frags/installing-ubuntu.rst 221 222 To install ``paivana-httpd``, run: 223 224 .. code-block:: shell-session 225 226 # apt install paivana-httpd 227 228 As on Debian, the package does not perform any deployment-specific 229 configuration work. 230 231 232 .. _Paivana-Configuration: 233 234 Configuring paivana-httpd 235 ------------------------- 236 237 The main configuration file is ``/etc/paivana/paivana.conf``. Its 238 syntax follows the standard GNUnet configuration file format and is 239 documented in full in :manpage:`paivana.conf(5)`. Default values 240 shipped with the package live under 241 ``/usr/share/paivana/config.d/``; values in ``paivana.conf`` 242 override those defaults. 243 244 All Paivana-specific keys live in the ``[paivana]`` section. At a 245 minimum, the file must specify three things: 246 247 - where ``paivana-httpd`` should listen for incoming requests 248 (``SERVE``, ``UNIXPATH`` / ``PORT``); 249 - where it should forward paid requests to 250 (``DESTINATION_BASE_URL``); 251 - how it should reach the merchant backend 252 (``MERCHANT_BACKEND_URL`` and ``MERCHANT_ACCESS_TOKEN``). 253 254 A typical configuration that listens on a UNIX domain socket 255 managed by systemd and forwards to a local upstream server looks 256 like this: 257 258 .. code-block:: ini 259 260 [paivana] 261 # Listen on the socket provided by paivana-httpd.socket. 262 SERVE = unix 263 UNIXPATH = /run/paivana/httpd/paivana-http.sock 264 UNIXPATH_MODE = 660 265 266 # Public base URL of this paywall as seen by clients. 267 # Used when the Host/X-Forwarded-Host headers are unavailable. 268 BASE_URL = https://paywall.example.com/ 269 270 # Upstream service that gets proxied after payment. 271 DESTINATION_BASE_URL = http://127.0.0.1:8080/ 272 273 # Merchant backend used to create and verify orders. 274 MERCHANT_BACKEND_URL = http://localhost:9966/ 275 MERCHANT_ACCESS_TOKEN = secret-token:CHANGE-ME 276 277 # Stable secret used to MAC the access cookie. 278 # If unset, a random value is generated at every startup, 279 # invalidating all previously issued cookies. 280 SECRET = please-change-this-to-a-long-random-value 281 282 # Resources that should never trigger the paywall, e.g. 283 # logos, stylesheets or favicons. 284 WHITELIST = ^/(favicon\.ico|assets/.*|robots\.txt)$ 285 286 The exhaustive list of supported keys (``SERVE``, ``PORT``, 287 ``BIND_TO``, ``UNIXPATH``, ``UNIXPATH_MODE``, ``BASE_URL``, 288 ``DESTINATION_BASE_URL``, ``MERCHANT_BACKEND_URL``, 289 ``MERCHANT_BACKEND_UNIX_PATH``, ``MERCHANT_ACCESS_TOKEN``, 290 ``SECRET``, ``WHITELIST``) is documented in 291 :manpage:`paivana.conf(5)`. 292 293 If you reach the merchant backend over a UNIX domain socket on the 294 same host (recommended for a single-machine deployment), replace 295 the ``MERCHANT_BACKEND_URL`` block with: 296 297 .. code-block:: ini 298 299 MERCHANT_BACKEND_URL = http://localhost/ 300 MERCHANT_BACKEND_UNIX_PATH = /run/taler-merchant/merchant.sock 301 302 .. note:: 303 304 ``MERCHANT_ACCESS_TOKEN`` and ``SECRET`` are sensitive values. 305 Make sure ``paivana.conf`` is only readable by the 306 ``paivana-httpd`` user. The Debian package installs the file 307 accordingly. 308 309 When ``paivana-httpd`` runs behind a trusted reverse proxy 310 (Nginx/Apache), pass ``-f`` / ``--respect-forwarded-headers`` in the 311 systemd unit's ``ExecStart=`` so the real client address is taken 312 from ``X-Forwarded-For``. See :manpage:`paivana-httpd(1)` for the 313 remaining command-line flags (in particular ``-g`` to require only 314 a single payment per site and ``-n`` to disable the paywall for 315 debugging). 316 317 318 Starting and stopping the service 319 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 320 321 The Debian/Ubuntu package ships a socket-activated systemd unit. 322 After editing ``/etc/paivana/paivana.conf`` enable and start it: 323 324 .. code-block:: shell-session 325 326 # systemctl enable --now paivana-httpd.socket 327 # systemctl status paivana-httpd 328 329 The socket listens on ``/run/paivana/httpd/paivana-http.sock`` with 330 group ``www-data``, which lets a co-located Nginx or Apache talk to 331 the daemon without granting it broader filesystem access. Logs are 332 sent to the journal: 333 334 .. code-block:: shell-session 335 336 # journalctl -u paivana-httpd -f 337 338 339 .. _Paivana-Whitelisting: 340 341 Whitelisting unpaid resources 342 ----------------------------- 343 344 Most sites contain resources that must remain freely accessible even 345 to clients that have not paid: the stylesheets, fonts, logos and 346 scripts referenced by the paywall page itself, a ``favicon.ico``, a 347 ``robots.txt``, or a health-check endpoint polled by a load balancer. 348 If these were paywalled, the paywall page could not even render. 349 350 The ``WHITELIST`` configuration key holds a single POSIX *extended* 351 regular expression that is matched against the request path (which 352 always begins with ``/``). When it matches, the request bypasses both 353 the paywall and the access-cookie check and is proxied straight to the 354 upstream: 355 356 .. code-block:: ini 357 358 [paivana] 359 WHITELIST = ^/(favicon\.ico|robots\.txt|assets/.*|.*\.css|.*\.js)$ 360 361 A few details worth knowing: 362 363 - The expression is matched against the URL path only, not against the 364 host or the request method. A match anywhere in the path counts, so 365 anchor with ``^`` and ``$`` if you mean "the whole path". 366 - An invalid regular expression is a fatal configuration error: 367 ``paivana-httpd`` logs the problem and refuses to start. 368 - The internal endpoints ``POST /.well-known/paivana`` (payment 369 callback) and ``GET /.well-known/paivana/templates/...`` (paywall 370 pages) are always handled by ``paivana-httpd`` itself and are not 371 affected by the whitelist. 372 - Running with ``-n`` / ``--no-payment`` whitelists *everything*; the 373 daemon then behaves as a transparent reverse proxy and never 374 consults the merchant backend. 375 376 .. note:: 377 378 Whitelisting is coarse — it is keyed purely on the URL. Per-URL 379 *pricing* (charging different amounts for different paths) is done 380 with merchant templates and their ``website_regex``, described in 381 :ref:`Paivana-Templates`, not with ``WHITELIST``. 382 383 384 .. _Paivana-AccessControl: 385 386 Access control: per-page vs. site-wide payment 387 ---------------------------------------------- 388 389 Once a client has paid, ``paivana-httpd`` issues an HMAC-protected 390 access cookie (``Paivana-Cookie``) instead of asking the merchant 391 backend again on every subsequent request. The cookie is a keyed hash 392 over *(expiration time, website, client address)*; it carries no 393 server-side state, so any of the (possibly several) ``paivana-httpd`` 394 processes can validate it. 395 396 Two aspects of this are operator-visible: 397 398 Scope of a payment 399 ^^^^^^^^^^^^^^^^^^^ 400 401 By default a payment grants access to **the specific URL that was 402 paid for**: the cookie's ``Path`` is set to that path and the website 403 is bound into the hash. This is what you want when each page is sold 404 individually. 405 406 Passing ``-g`` / ``--global-payment`` changes this so that a single 407 payment unlocks the **entire site**: the website component is dropped 408 from the hash and the cookie is issued with ``Path=/``. Use this when 409 one purchase (or one subscription) should cover everything behind the 410 proxy, including for the abuse-mitigation use case where you simply 411 want a paying client to stop hitting the paywall. 412 413 Binding to the client address 414 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 415 416 The cookie is tied to the client's network address so that it cannot 417 simply be copied to another machine. When ``paivana-httpd`` runs 418 behind a TLS-terminating reverse proxy, the socket peer is always that 419 proxy, so **every** client would appear to share one address. Run the 420 daemon with ``-f`` / ``--respect-forwarded-headers`` in that setup so 421 the real client address is taken from ``X-Forwarded-For`` (only do this 422 behind a proxy you trust to set that header — see 423 :ref:`Paivana-ReverseProxy`). 424 425 Cookie lifetime is derived from the order's ``max_pickup_delay``; 426 configure it on the template (``max_pickup_duration``) to control how 427 long a paid client may keep accessing the resource before paying 428 again. Because the cookie is keyed by ``SECRET``, leaving ``SECRET`` 429 unset means a new random key is chosen at every startup and all 430 outstanding cookies are invalidated on restart. 431 432 433 .. _Paivana-Templates: 434 435 Configuring Paivana templates 436 ----------------------------- 437 438 ``paivana-httpd`` does not store any per-site pricing or URL-matching rules 439 itself. Instead, all rules are expressed as :ref:`merchant templates 440 <template>` of type ``paivana`` in the merchant backend. When 441 ``paivana-httpd`` starts up it asks the merchant backend for every template 442 configured for the instance identified by ``MERCHANT_BACKEND_URL`` and uses 443 the ``website_regex`` field of each template to decide which template (and 444 therefore which payment options) applies to an incoming request URL. 445 446 The corresponding REST API is documented in detail in the 447 :ref:`Merchant Backend HTTP API <merchant-api>`; see in particular 448 the 449 `POST /private/templates 450 <https://docs.taler.net/core/api-merchant.html#post--private-templates>`__ 451 endpoint and the 452 :ts:type:`TemplateContractPaivana` definition. 453 454 Prerequisites 455 ^^^^^^^^^^^^^ 456 457 Before creating a template you need: 458 459 - a running ``taler-merchant-httpd`` (see the 460 :ref:`Launching-the-backend` section of the merchant manual); 461 - a merchant :ref:`instance <Instance-setup>` with at least one 462 configured :ref:`bank account <instance-bank-account>`; 463 - the access token of that instance (used as 464 ``MERCHANT_ACCESS_TOKEN`` in ``paivana.conf``). 465 466 In the examples below we assume the merchant backend is reachable 467 at ``http://localhost:9966/``, the default instance is ``default``, 468 its access token is ``secret-token:sandbox`` and the currency is 469 ``KUDOS``. Adjust the URLs, tokens and amounts to match your 470 deployment. The 471 `src/backend/test.sh 472 <https://git.taler.net/paivana.git/tree/src/backend/test.sh>`__ 473 script that ships with Paivana sets up exactly this minimal 474 configuration and is a good starting point for experimentation. 475 476 Creating a single global template 477 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 478 479 The simplest Paivana setup uses one template that matches every 480 URL on the site and charges a fixed price. This is the 481 configuration created by ``src/backend/test.sh``: 482 483 .. code-block:: bash 484 485 $ curl -X POST http://localhost:9966/private/templates \ 486 -H 'Authorization: Bearer secret-token:sandbox' \ 487 -H 'Content-Type: application/json' \ 488 -d '{ 489 "template_id": "paivana", 490 "template_description": "A Paivana template", 491 "template_contract": { 492 "template_type": "paivana", 493 "summary": "Access to example.com", 494 "website_regex": ".*", 495 "choices": [ { "amount": "KUDOS:1" } ] 496 } 497 }' 498 499 The ``template_type`` must be ``"paivana"``: this allows 500 ``paivana-httpd`` to pick the template up at startup and 501 also enables some required logic in the merchant backend. The 502 ``website_regex`` is a POSIX extended regular expression that is 503 matched against the request URL; ``.*`` covers everything. Each 504 entry in ``choices`` describes one way the client may pay and is an 505 :ts:type:`OrderChoice` object (so the paywall can also support 506 the use of subscription tokens, discount coupons, etc.). 507 508 A successful create returns HTTP ``204 No Content``. After 509 creating the template, (re)start ``paivana-httpd`` so that it 510 re-reads the template list: 511 512 .. code-block:: shell-session 513 514 # systemctl restart paivana-httpd 515 516 Multiple templates with URL-specific pricing 517 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 518 519 When a single site contains content with different prices, define one template 520 per price bucket and use ``website_regex`` to scope each template to the 521 matching URLs. When several templates match the same URL ``paivana-httpd`` 522 picks the first one if finds that matches. Be careful: if multiple templates 523 match a URL, the result is non-deterministic! 524 525 For example, a news site might charge 2 KUDOS for premium articles 526 and 50 cents (``KUDOS:0.5``) for standard articles: 527 528 .. code-block:: bash 529 530 $ curl -X POST http://localhost:9966/private/templates \ 531 -H 'Authorization: Bearer secret-token:sandbox' \ 532 -H 'Content-Type: application/json' \ 533 -d '{ 534 "template_id": "premium", 535 "template_description": "Premium long-form articles", 536 "template_contract": { 537 "template_type": "paivana", 538 "summary": "Premium article on example.com", 539 "website_regex": "^/premium/.*", 540 "choices": [ { "amount": "KUDOS:2" } ] 541 } 542 }' 543 544 $ curl -X POST http://localhost:9966/private/templates \ 545 -H 'Authorization: Bearer secret-token:sandbox' \ 546 -H 'Content-Type: application/json' \ 547 -d '{ 548 "template_id": "default", 549 "template_description": "Standard articles", 550 "template_contract": { 551 "template_type": "paivana", 552 "summary": "Standard article on example.com", 553 "website_regex": "^/standard/.*", 554 "choices": [ { "amount": "KUDOS:0.5" } ] 555 } 556 }' 557 558 Offering multiple payment options 559 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 560 561 The ``choices`` array lets a single template offer several mutually exclusive 562 ways to pay. A common pattern is to accept either a cash payment or to sell a 563 subscription; the wallet shows both options and the customer picks one. The 564 third option, where the customer already has a subscription, will be used 565 automatically by the wallet for subscribers and the customer will not even 566 have to click to bypass the paywall as a subscriber. See the merchant manual 567 for the details of :ref:`OrderChoice <template-choice>` objects. 568 569 .. code-block:: bash 570 571 $ curl -X POST http://localhost:9966/private/templates \ 572 -H 'Authorization: Bearer secret-token:sandbox' \ 573 -H 'Content-Type: application/json' \ 574 -d '{ 575 "template_id": "article", 576 "template_description": "Single article, paid or via subscription", 577 "template_contract": { 578 "template_type": "paivana", 579 "summary": "Article on example.com", 580 "website_regex": ".*", 581 "choices": [ 582 { "amount": "KUDOS:1", 583 "description": "Pay per article" }, 584 { "amount": "KUDOS:100", 585 "description": "Buy subscription", 586 "outputs": [ { "token": "monthly-subscription" } ] }, 587 { "amount": "KUDOS:0", 588 "description": "Use my subscription", 589 "inputs": [ { "token": "monthly-subscription" } ], 590 "outputs": [ { "token": "monthly-subscription" } ] } 591 ] 592 } 593 }' 594 595 Managing templates 596 ^^^^^^^^^^^^^^^^^^ 597 598 Templates can be listed, updated and deleted through the merchant 599 backend's REST API or through the merchant backend SPA at 600 ``$MERCHANT_BACKEND_URL/``. See the merchant manual section on 601 :ref:`templates <template>` for details, and the API reference for 602 the relevant endpoints: 603 604 - `GET /private/templates 605 <https://docs.taler.net/core/api-merchant.html#get--private-templates>`__ — 606 list all templates of the instance; 607 - `PATCH /private/templates/$TEMPLATE_ID 608 <https://docs.taler.net/core/api-merchant.html#patch--private-templates-$TEMPLATE_ID>`__ — 609 update a template; 610 - `DELETE /private/templates/$TEMPLATE_ID 611 <https://docs.taler.net/core/api-merchant.html#delete--private-templates-$TEMPLATE_ID>`__ — 612 remove a template. 613 614 After any change, restart ``paivana-httpd`` so the new template 615 list takes effect. 616 617 618 .. _Paivana-Customizing: 619 620 Customizing the paywall page 621 ---------------------------- 622 623 The merchant templates of the previous section decide *which* URLs 624 cost *how much*. This section is about the *look* of the page an 625 unpaid visitor sees — the HTML the daemon returns with the 626 ``402 Payment Required`` status. 627 628 How the page is rendered 629 ^^^^^^^^^^^^^^^^^^^^^^^^^^ 630 631 The paywall body is produced from a `Mustache 632 <https://mustache.github.io/>`__ template named ``paywall`` that 633 ``paivana-httpd`` loads, via ``libtalertemplating``, from 634 635 :: 636 637 $PREFIX/share/paivana/templates/ 638 639 Templates are language-specific: the file is named 640 ``paywall.$LANG.must`` and the daemon picks a variant based on the 641 client's ``Accept-Language`` header. The package ships an English 642 default, ``paywall.en.must``. To offer the paywall in additional 643 languages, drop further ``paywall.<lang>.must`` files into the same 644 directory. Rendered responses are cached per language and per 645 content-encoding (and carry ``Cache-Control: public`` and a matching 646 ``Vary`` header), which is what lets the paywall be served as a 647 scalable, mostly-static page even under load. 648 649 When rendering, ``paivana-httpd`` passes the following values into the 650 template: 651 652 ======================== ==================================================== 653 Variable Meaning 654 ======================== ==================================================== 655 ``merchant_backend`` Base URL of the merchant backend (the page 656 long-polls it for payment completion). 657 ``template_id`` ID of the matched merchant template. 658 ``summary`` Human-readable summary from the template, 659 if any. 660 ``choices`` Array of payment options (:ts:type:`OrderChoice`). 661 ``has_choices`` True if more than one payment option exists. 662 ``default_choice`` The first entry of ``choices``. 663 ``max_pickup_delay`` Seconds the order may be picked up within. 664 ======================== ==================================================== 665 666 The shipped page uses these to draw a ``taler://`` QR code and a 667 "Pay now" link, long-poll the backend, and reload the original URL 668 once the wallet confirms. The non-visual integration point — useful 669 for automated and agentic clients — is the ``Paivana:`` HTTP response 670 header, which carries the ``taler://pay-template/...`` URI directly, so 671 a client need not parse the HTML to find out how to pay. 672 673 Editing the appearance 674 ^^^^^^^^^^^^^^^^^^^^^^^^ 675 676 For small tweaks — colours, wording, your logo, a link back to your 677 site — you can edit the installed ``paywall.en.must`` directly; it is 678 ordinary HTML/CSS with Mustache ``{{ ... }}`` placeholders for the 679 variables above. Keep the ``<script>`` block intact: it is what 680 performs the long-poll and the post-payment reload. 681 682 The shipped ``paywall.en.must`` is in fact *generated* at build time 683 from a Jinja2 source, ``src/frontend/paywall.en.must.j2``, which inlines 684 the client-side logic from ``src/frontend/paywall.js`` and a bundled QR 685 library. (To avoid clashing with Mustache's ``{{ }}``, the Jinja2 686 source uses ``@@ ... @@`` and ``@< ... >@`` delimiters.) If you want to 687 change the JavaScript behaviour rather than just the styling, edit the 688 ``.j2`` source and rebuild — the Meson ``custom_target`` regenerates the 689 ``.must`` file and installs it into the templates directory shown 690 above. 691 692 693 .. _Paivana-ReverseProxy: 694 695 Reverse proxy configuration 696 --------------------------- 697 698 ``paivana-httpd`` itself speaks plain HTTP on a UNIX socket (or a 699 local TCP port). In production it is often run behind an Internet-facing 700 reverse proxy that terminates TLS and forwards requests to the 701 Paivana socket. This section gives minimal working examples for 702 both Nginx and Apache. The same approach is used for the merchant 703 backend; see the merchant manual's 704 :ref:`reverse-proxy-configuration` section for additional 705 discussion. 706 707 The examples assume the public domain is ``example.com``, 708 that ``paivana-httpd`` is socket-activated by the shipped 709 ``paivana-httpd.socket`` unit (so its listening socket lives at 710 ``/run/paivana/httpd/paivana-http.sock``) and that TLS termination 711 happens at the reverse proxy. 712 713 .. tab-set:: 714 715 .. tab-item:: Nginx 716 717 Place the snippet below in 718 ``/etc/nginx/sites-available/example.com`` (the 719 Debian package installs a starter template under 720 ``/etc/nginx/sites-available/paivana``), then enable it via 721 ``ln -s ../sites-available/example.com 722 /etc/nginx/sites-enabled/`` and reload Nginx 723 (``systemctl reload nginx``). 724 725 .. code-block:: nginx 726 727 server { 728 listen 443 ssl http2; 729 listen [::]:443 ssl http2; 730 server_name example.com; 731 732 ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; 733 ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; 734 735 location / { 736 proxy_pass http://unix:/run/paivana/httpd/paivana-http.sock; 737 proxy_redirect off; 738 proxy_set_header Host $host; 739 proxy_set_header X-Forwarded-For $remote_addr; 740 proxy_set_header X-Forwarded-Host $host; 741 proxy_set_header X-Forwarded-Proto https; 742 } 743 } 744 745 server { 746 listen 80; 747 listen [::]:80; 748 server_name example.com; 749 return 301 https://$host$request_uri; 750 } 751 752 Make sure ``paivana-httpd`` is started with 753 ``--respect-forwarded-headers`` (see 754 :manpage:`paivana-httpd(1)`) so the ``X-Forwarded-For`` 755 header set above is honoured. 756 757 .. tab-item:: Apache 758 759 Enable the required modules once: 760 761 .. code-block:: shell-session 762 763 # a2enmod proxy proxy_http headers ssl 764 # systemctl reload apache2 765 766 Then drop the following into 767 ``/etc/apache2/sites-available/example.com.conf`` 768 (the Debian package installs a starter template at 769 ``/etc/apache2/sites-available/paivana.conf``), enable it 770 with ``a2ensite example.com`` and reload Apache. 771 772 .. code-block:: apacheconf 773 774 <VirtualHost *:80> 775 ServerName example.com 776 Redirect permanent / https://example.com/ 777 </VirtualHost> 778 779 <VirtualHost *:443> 780 ServerName example.com 781 782 SSLEngine on 783 SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem 784 SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem 785 786 <Location "/"> 787 ProxyPass "unix:/run/paivana/httpd/paivana-http.sock|http://example.com/" 788 ProxyPassReverse "unix:/run/paivana/httpd/paivana-http.sock|http://example.com/" 789 RequestHeader set X-Forwarded-Proto "https" 790 RequestHeader set X-Forwarded-Host "example.com" 791 </Location> 792 </VirtualHost> 793 794 As with Nginx, run ``paivana-httpd`` with 795 ``--respect-forwarded-headers`` so that the client IP is 796 taken from ``X-Forwarded-For``. 797 798 If you operate both Paivana and the merchant backend on the same 799 host, you typically expose them under two different hostnames (e.g. 800 ``example.com`` and ``backend.example.com``); the merchant 801 backend must *never* be proxied through ``paivana-httpd``, only 802 the upstream content service should be. 803 804 805 Verifying the setup 806 ------------------- 807 808 After completing the steps above, a quick smoke test is to request 809 a paywalled URL with ``curl``: 810 811 .. code-block:: shell-session 812 813 $ curl -i https://example.com/some-article 814 815 An unpaid request will first redirect (302 Found) to the static 816 paywall page at ``/.well-known/paivana/templates/{id}#{base64_website}`` 817 which will then return a ``HTTP/1.1 402 Payment Required`` together 818 with a Taler-formatted paywall body containing the ``taler://pay/...`` URI of 819 the freshly created order. Paying that order with any GNU Taler wallet (see 820 the `Wallet documentation <https://docs.taler.net/wallet/>`__) and 821 re-requesting the URL from the same client should then yield the upstream 822 content unchanged. If the page is run in a browser, the client-side 823 JavaScript should automatically trigger the required reload of the page after 824 the wallet made the payment. 825 826 For interactive debugging, ``paivana-httpd -n`` disables the 827 paywall and turns the daemon into a transparent reverse proxy; 828 this is useful to confirm that the network plumbing to the 829 upstream service works before involving the merchant backend. 830 See :manpage:`paivana-httpd(1)` for the other runtime flags.