39. pepsi-stage-auto-pay¶
Pay GNU Taler delivery demands automatically (the sending side of pay-to-send).
39.1. Role¶
pepsi-stage-auto-pay has two auto-detected roles. On the inbound path it
is the sending counterpart to 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: pepsi-stage-auto-pay(1).
A demand is recognised by the Taler: header field, one of the two private
header extensions documented in SMTP Protocol 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.
39.2. Wallet self-service (submission path)¶
When RESPONSE_STAGE is set, a locally-originated (authenticated) message to
<CONTROL_LOCAL_PART>@<served-domain> (default local part pepsi-wallet) lets
the sender operate their own wallet by e-mail — parallel to
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 <taler://pay-push/…> (accept a peer push; the URI may be in the body). The
localised reply (wallet.<lang>.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.
39.3. Decision¶
Driven by the message’s headers (not its state):
no
Taler:header → not a demand; forward toNEXT_STAGEuntouched (so the stage is safe to place early on the inbound path).a
Taler:header, but the demand is not provably ours — no validPepsi-Origin:proof (forged/expired, or any demand when[pepsi-origin]is unconfigured) → never pay; forward toNEXT_STAGE.provably ours → for each
taler://pay/…URI, driving the privileged pepsi-helper-auto-pay(1) wallet helper: quote its price (the helper’spreview), book it against the per-original-message budget (MAX_TOTAL), and if it fits, settle (the helper’spay) 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_STAGEso 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.
39.4. 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
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
SMTP Protocol Extensions and pepsi-stage-relay-to-internet (which stamps
Pepsi-Origin on outbound mail and records the nonce).
39.5. Wallet account and the helper¶
All wallet interaction is performed by the setuid-root
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.
39.6. Configuration¶
[stage-<name>]: 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 pepsi-stage-auto-pay(1).
39.7. State¶
Inputs: the message’s
Taler:andPepsi-Origin:headers (the body is also scanned for an embeddedPepsi-Origin). The spend ledger ispepsi.origin_nonce.amount, not the messagestate.Outputs: none on the message; it is advanced to
NEXT_STAGEor deleted.
39.8. See also¶
pepsi-stage-anti-spam, pepsi-stage-relay-to-internet, pepsi-stage-edit-settings, SMTP Protocol Extensions, pepsi-stage-auto-pay(1).