45. pepsi-stage-auto-pay¶
Pay GNU Taler delivery demands automatically (the sending side of pay-to-send).
45.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. (The stage holds no merchant credential:
it pays through a wallet, and only the charging side,
pepsi-stage-anti-spam, authenticates to a merchant backend.)
45.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.
45.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 the paying wallet’s daily budget (MAX_TOTAL_PER_DAY), and if it fits both, settle (the helper’spay) it.every demand settled → discard the now-redundant bounce (delete the row);
a demand that cannot be priced, exceeds either 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 both ledgers (the un-spent payment must not consume either 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.
45.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).
MAX_TOTAL alone bounds one message, not a correspondent: everybody who
received one of our messages holds a valid proof, so somebody who received k
of them can demand MAX_TOTAL k times. MAX_TOTAL_PER_DAY is the
aggregate: what one wallet may spend in any 24 hours — the user’s own under
WALLET_MODE = local-user, the sender’s under shared/per-sender, the
deployment’s under shared/unified. It is never per correspondent, whom
mailing lists and aliases make unknowable. The same auto_pay_try_spend call
checks both budgets and records the booking in pepsi.auto_pay_spend —
serialised per wallet, so concurrent demands for different messages cannot
overrun it — and auto_pay_refund deletes the booking again when the payment
fails to settle. The window is a rolling 24 hours; the limit is single-currency,
in MAX_TOTAL’s currency; unset, there is none.
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).
45.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
Everything that depends on the taler-wallet-cli version is confined to
pepsi-helper-auto-pay: the subcommand and flag spelling each operation
uses (build_wallet_args), and the small parsers that read an amount, a
payto: URI, a taler:// URI and a KYC link out of its output. The stage
itself speaks only to the helper, so a future CLI change is confined there.
45.6. Configuration¶
[stage-<name>]: PROGRAM = pepsi-stage-auto-pay, MAX_TOTAL (a Taler
amount CURRENCY:VALUE bounding total spend per original message, required),
MAX_TOTAL_PER_DAY (optional, same currency, bounding one wallet’s spend in any
24 hours),
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).
45.7. State¶
Inputs: the message’s
Taler:andPepsi-Origin:headers (the body is also scanned for an embeddedPepsi-Origin). The spend ledgers arepepsi.origin_nonce.amount(per message) andpepsi.auto_pay_spend(per wallet, last 24 hours), not the messagestate.Outputs: none on the message; it is advanced to
NEXT_STAGEor deleted.
45.8. See also¶
pepsi-stage-anti-spam, pepsi-stage-relay-to-internet, pepsi-stage-edit-settings, SMTP Protocol Extensions, pepsi-stage-auto-pay(1).