taler-docs

Documentation for GNU Taler components, APIs and protocols
Log | Files | Refs | README | LICENSE

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.