.. This file is part of PEPSI. Copyright (C) 2026 Pepsi contributors PEPSI is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. ==================== pepsi-stage-auto-pay ==================== *Pay GNU Taler delivery demands automatically (the sending side of pay-to-send).* Role ==== ``pepsi-stage-auto-pay`` has **two** auto-detected roles. On the **inbound** path it is the **sending** counterpart to :doc:`pepsi-stage-anti-spam`: when mail this deployment relayed is held at the far end behind a pay-to-send gate, that gate e-mails the original sender a payment demand, and this stage detects such demands and settles them automatically, so the held message is released and delivered. On the **submission** path it is a wallet **self-service** endpoint (enabled by ``RESPONSE_STAGE``; see below). Reference: :manpage:`pepsi-stage-auto-pay(1)`. A demand is recognised by the ``Taler:`` header field, one of the two private header extensions documented in :doc:`../smtp-extensions`; several URIs in one field and several fields are both accepted. Detection is not authorisation: the stage pays only for mail this deployment provably originated, which it establishes from the ``Pepsi-Origin:`` header recovered out of the returned message — an RFC 2104 HMAC over the sending host, sender and a nonce that must still be tracked in ``pepsi.origin_nonce``. Neither header is registered under RFC 3864, and neither carries an ``X-`` prefix, per RFC 6648. The ``taler://`` URI they carry is GNU Taler's own scheme rather than an IANA-registered one (RFC 7595 sets that procedure); the wire-transfer instructions the self-service ``withdraw`` command returns use the ``payto:`` URI scheme of **RFC 8905**, and the merchant credential the stage authenticates with is an RFC 8959 ``secret-token:`` URI. Wallet self-service (submission path) ===================================== When ``RESPONSE_STAGE`` is set, a **locally-originated** (authenticated) message to ``@`` (default local part ``pepsi-wallet``) lets the sender operate **their own** wallet by e-mail — parallel to :doc:`pepsi-stage-edit-settings`, but keyed on the envelope sender. The command is the message **Subject:**: ``balance``, ``withdraw CUR:VAL EXCHANGE-URL`` (a manual top-up from the named GNU Taler exchange — chosen per request, since a wallet is multi-currency and multi-exchange — the reply gives bank wire instructions), ``pull CUR:VAL [subject]`` (a peer pull — the reply gives a ``taler://pay-pull/…`` URI to forward) or ``push `` (accept a peer push; the URI may be in the body). The localised reply (``wallet..body`` template, null sender) is injected at ``RESPONSE_STAGE`` and the original consumed; a malformed request gets usage instructions. If the wallet hits a **KYC** requirement, the reply carries the KYC link instead (self-service only — never while paying inbound demands). Any other message passes through to ``NEXT_STAGE``. Decision ======== Driven by the message's headers (not its ``state``): * no ``Taler:`` header → not a demand; **forward** to ``NEXT_STAGE`` untouched (so the stage is safe to place early on the inbound path). * a ``Taler:`` header, but the demand is **not provably ours** — no valid ``Pepsi-Origin:`` proof (forged/expired, or *any* demand when ``[pepsi-origin]`` is unconfigured) → never pay; **forward** to ``NEXT_STAGE``. * provably ours → for each ``taler://pay/…`` URI, driving the privileged :manpage:`pepsi-helper-auto-pay(1)` wallet helper: **quote** its price (the helper's ``preview``), **book** it against the per-original-message budget (``MAX_TOTAL``), and if it fits, **settle** (the helper's ``pay``) it. * every demand settled → **discard** the now-redundant bounce (delete the row); * a demand that cannot be priced, exceeds the budget, or differs in currency → **forward** to ``NEXT_STAGE`` so the message proceeds as an ordinary bounce and the sender is informed; * a demand booked under the budget that then **fails to pay** → the reservation is **refunded** to the ledger (the un-spent payment must not consume the budget) and the message is **forwarded**. Auto-pay tries to pay; if it cannot, it does nothing (logs the error) and lets the bounce through. Cumulative budget ================= The budget is keyed on the **Pepsi-Origin nonce**, which identifies the original outbound message. A message fanned out to a mailing list draws several demands — one per member whose site charges — that all carry the **same** nonce echoed from the original, so they **share one budget**. The per-message ledger lives in ``pepsi.origin_nonce.amount``; each settlement advances it atomically (the ``auto_pay_try_spend`` function locks the row), and a demand that would push the running total past ``MAX_TOTAL`` is refused. The cap is **single-currency**. An account can override its own ``MAX_TOTAL`` by e-mail through :doc:`pepsi-stage-edit-settings` (the override is keyed on the account that receives the demand — the original sender). Verification reuses the proof-of-origin machinery: see :doc:`../smtp-extensions` and :doc:`pepsi-stage-relay-to-internet` (which stamps ``Pepsi-Origin`` on outbound mail and records the nonce). Wallet account and the helper ============================= All wallet interaction is performed by the setuid-root :manpage:`pepsi-helper-auto-pay(1)` helper, which drops to the right user before running ``taler-wallet-cli``. ``WALLET_MODE`` chooses the account: ``local-user`` pays from the bounce recipient's own local wallet (the original sender; the recipient must resolve to a permitted local account), while ``shared`` (the default) pays from a single dedicated ``WALLET_USER`` account (default ``pepsi-wallets``) whose home holds the wallet database(s) — one per sender (``WALLET_SCOPE = per-sender``) or a single unified wallet shared by everyone (``WALLET_SCOPE = unified``, e.g. a business). Because the stage execs the helper, its binary is installed standalone and **SGID** ``pepsi-wallets`` (the helper is setuid-root, ``root:pepsi-wallets``, mode ``4750``). .. note:: The one wallet-version-dependent point is how ``pepsi-helper-auto-pay`` reads a demand's price from ``taler-wallet-cli``'s ``preview`` output; it is isolated in a single function so a future CLI change is a one-line fix. Configuration ============= ``[stage-]``: ``PROGRAM = pepsi-stage-auto-pay``, ``MAX_TOTAL`` (a Taler amount ``CURRENCY:VALUE`` bounding total spend per original message, required), ``NEXT_STAGE`` (required — where a non-demand or unpaid message is forwarded), and the wallet knobs ``WALLET_MODE``, ``WALLET_USER``, ``WALLET_SCOPE``, ``WALLET_CLI``, ``WALLET_PAY_OPTIONS`` and ``HELPER``; the self-service role adds ``RESPONSE_STAGE`` (enables it), ``CONTROL_LOCAL_PART``, ``RESPONSE_FROM`` and ``RESPONSE_SUBJECT`` (a ``withdraw`` names its exchange in the request, so none is configured). The proof-of-origin secret is the shared ``[pepsi-origin]`` section. See :manpage:`pepsi-stage-auto-pay(1)`. State ===== * **Inputs:** the message's ``Taler:`` and ``Pepsi-Origin:`` headers (the body is also scanned for an embedded ``Pepsi-Origin``). The spend ledger is ``pepsi.origin_nonce.amount``, not the message ``state``. * **Outputs:** none on the message; it is advanced to ``NEXT_STAGE`` or deleted. See also ======== :doc:`pepsi-stage-anti-spam`, :doc:`pepsi-stage-relay-to-internet`, :doc:`pepsi-stage-edit-settings`, :doc:`../smtp-extensions`, :manpage:`pepsi-stage-auto-pay(1)`.