70.1.17. pepsi-stage-auto-pay

pay GNU Taler delivery demands automatically

Manual section:

1

70.1.17.1.1. Name

pepsi-stage-auto-pay - the automatic pay-to-send settlement stage of the Pepsi pipeline.

70.1.17.1.2. Synopsis

pepsi-stage-auto-pay [GLOBAL-OPTIONS] worker

70.1.17.1.3. Description

pepsi-stage-auto-pay is a stage program run by pepsi-dispatch(1) as a persistent worker reading message ids on standard input. It has two roles, chosen per message:

  • On the inbound path it is the sending counterpart to pepsi-stage-anti-spam(1): when mail this site 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: a locally submitted (authenticated) message addressed to the wallet control address lets the sender operate their own GNU Taler wallet by e-mail — check a balance, top it up, accept a peer push payment, or initiate a peer pull request. This role is enabled only when RESPONSE_STAGE is configured (see Wallet self-service below).

The two roles share one binary and are auto-detected, so a single PROGRAM = pepsi-stage-auto-pay may be placed on either pipeline (or both, as two [stage-<name>] sections).

A demand is recognised by the Taler: header that pepsi-stage-anti-spam(1) adds to its payment-request auto-reply, carrying one or more taler://pay/… URIs. A message with no Taler: header is ordinary mail and is forwarded to NEXT_STAGE untouched, so the stage is safe to place early on the inbound path.

Pepsi only pays for mail it provably originated. The same auto-reply echoes the original message’s Pepsi-Origin: proof-of-origin header (see pepsi-stage-anti-spam(1) and pepsi-stage-relay-to-internet(1)). This stage verifies it the same way a returning bounce is verified — the HMAC must check against our secret and the nonce must still be tracked in pepsi.origin_nonce. A demand that carries no valid proof (including every demand when [pepsi-origin] is not configured) is never paid: it is forwarded to NEXT_STAGE unchanged.

For a verified demand, the stage processes each taler://pay/… URI in turn, driving the wallet through the privileged pepsi-helper-auto-pay(1) helper:

  1. quote its price (the helper’s preview operation reads the order’s contract terms without paying);

  2. book it against a cumulative budget for the original message, capped by MAX_TOTAL; and

  3. if it fits under the budget, settle it (the helper’s pay operation spends wallet coins).

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, so they share one budget: the per-message ledger in pepsi.origin_nonce.amount is advanced atomically as each demand is settled (the auto_pay_try_spend function), and a demand that would push the running total past MAX_TOTAL is refused. The cap is single-currency: a demand whose currency differs from MAX_TOTAL is not paid. An individual account can raise or lower its own cap with pepsi-stage-edit-settings(1) (the override applies to mail addressed to that account — i.e. the original sender, who receives the demand).

When every demand on a message has been settled, the now-redundant bounce is discarded (the row is deleted). If any demand cannot be priced, would exceed the budget, or differs in currency, the message is instead forwarded to NEXT_STAGE, so it proceeds as an ordinary bounce and the original sender is informed of the delivery problem as usual. A demand that was booked under the budget but then fails to pay is rolled back — the reservation is refunded to the per-message ledger (so the failed, un-spent payment does not consume the budget) — and the message is likewise forwarded. In short: auto-pay tries to pay; if it cannot, it does nothing (logging the error) and lets the bounce through.

70.1.17.1.4. Wallet account

The wallet the helper pays from is chosen by WALLET_MODE:

local-user

The helper drops to the bounce recipient’s own local login (the original sender) and uses that user’s default wallet. The recipient must resolve to a permitted local account (the same LOCAL_DOMAINS/TARGETS/RECIPIENT_DELIMITER test as pepsi-stage-relay-to-maildir(1)); a non-local recipient cannot be charged and the message is forwarded.

shared (default)

The helper drops to a single dedicated account (WALLET_USER, default pepsi-wallets) whose home holds the wallet database(s). WALLET_SCOPE selects whether each original sender gets its own wallet database (per-sender, the default) or everyone shares one (unified — e.g. a business where individual wallets make no sense).

All wallet interaction is performed by the setuid-root pepsi-helper-auto-pay(1) helper; this stage spawns it and never touches a wallet directly. The stage binary is therefore installed SGID pepsi-wallets so its unprivileged dispatcher worker may exec the helper (see Installation).

70.1.17.1.5. Wallet self-service

When RESPONSE_STAGE is set, a locally-originated message (state.local_origin true; the submission listener must authenticate and bind the envelope sender) addressed to <CONTROL_LOCAL_PART>@<served-domain> (default local part pepsi-wallet; the domain is one of [pepsi-ingress] ACCEPTED_DOMAINS) is a wallet-control message, operating the sender’s own wallet (resolved by WALLET_MODE/WALLET_SCOPE exactly as for the paying role, but keyed on the sender). Any other message is forwarded to NEXT_STAGE untouched, so the stage is safe to place anywhere on the submission path.

The command is taken from the message Subject: line — one verb plus its arguments:

balance

Report the wallet balance.

withdraw CURRENCY:VALUE EXCHANGE-URL

Set up a manual top-up of the given amount against the GNU Taler exchange at the given base URL. The exchange is named in the request (not configured on the server) because a wallet is multi-currency and can withdraw from many exchanges. The reply carries the bank payto:// target and the wire-transfer subject to use; once the user’s bank transfer arrives, the funds appear in the wallet.

pull CURRENCY:VALUE [subject]

Initiate a peer pull payment. The reply carries a taler://pay-pull/… URI to forward to whoever should pay.

push taler://pay-push/…

Accept an incoming peer push payment. The URI may be given in the Subject or in the message body.

The result is mailed back to the sender as a localised auto-reply (null sender, Auto-Submitted: auto-replied), built from the operator-customisable wallet.<lang>.body template and injected at RESPONSE_STAGE (where it is DKIM-signed and relayed); the original control message is then deleted. A request that cannot be understood is answered with usage instructions.

If the wallet hits a KYC (identity-verification) requirement, the reply instead carries the KYC link for the user to open — this happens only for the self-service role, never while paying inbound demands.

70.1.17.1.6. Configuration

Options live in the stage’s own [stage-<name>] section (PROGRAM = pepsi-stage-auto-pay): the required MAX_TOTAL (a GNU Taler amount CURRENCY:VALUE bounding total spend per original message), a required NEXT_STAGE, and the wallet knobs WALLET_MODE, WALLET_USER, WALLET_SCOPE, WALLET_CLI, WALLET_PAY_OPTIONS and HELPER. The wallet self-service role adds RESPONSE_STAGE (its presence enables the role), CONTROL_LOCAL_PART, RESPONSE_FROM and RESPONSE_SUBJECT (a withdraw names its own exchange base URL in the Subject, so no exchange is configured). They are documented in pepsi.conf(5). The proof-of-origin secret is the shared [pepsi-origin] section (see pepsi-stage-relay-to-internet(1)); without it the stage can verify no demand and therefore pays none (it does not affect self-service).

70.1.17.1.7. Installation

Because the stage execs the setuid-root wallet helper, its binary is installed standalone (not folded into the unified pepsi binary) and SGID pepsi-wallets (mode 2755); the helper is installed setuid-root, root:pepsi-wallets, mode 4750. make install (targets install-auto-pay-stage / install-auto-pay-helper) and the Debian package apply these bits. The pepsi-wallets account and group are created by the package; create them by hand otherwise.

70.1.17.1.8. State

Inputs: the Taler: and Pepsi-Origin: headers of the message (the body is also scanned for an embedded Pepsi-Origin). The per-message spend ledger is pepsi.origin_nonce.amount, not the message state.

Outputs: the stage does not modify the message; it advances or deletes it. The state layout is described in pepsi.state(7).

Transitions: finish (delete) when all demands are settled, or for a wallet self-service control message (after injecting the reply at RESPONSE_STAGE); otherwise advance to NEXT_STAGE (not a demand, unverifiable, unpriceable, over budget, or a settlement failure). The stage never pauses, bounces or rewrites a message.

70.1.17.1.9. Commands

worker

Run as a persistent pepsi-dispatch(1) worker, reading message ids on standard input.

70.1.17.1.10. Global Options

-c FILE, –config FILE

Read the configuration from FILE instead of searching the default locations.

-L LOGLEVEL, –log LOGLEVEL

Set the logging verbosity (default info).

-v, –verbose

Show log messages from all sources.

-h, –help; -V, –version

Print a usage summary / the version and exit.

70.1.17.1.11. Exit Status

0

The message was processed (paid and discarded, or forwarded).

1

An error occurred (message not found or not running, misconfigured stage — e.g. a missing MAX_TOTAL or NEXT_STAGE — or a database error). The reason is written to the log.

70.1.17.1.12. Examples

A pipeline section that pays delivery demands for locally-originated mail, up to five euro per original message, from a single shared pepsi-wallets wallet:

[stage-auto-pay]
PROGRAM = pepsi-stage-auto-pay
MAX_TOTAL = EUR:5
NEXT_STAGE = local-delivery
WALLET_MODE = shared
WALLET_SCOPE = unified

Or, paying from each original sender’s own local wallet:

[stage-auto-pay]
PROGRAM = pepsi-stage-auto-pay
MAX_TOTAL = EUR:5
NEXT_STAGE = local-delivery
WALLET_MODE = local-user

Letting one account spend more (mailed by that account to pepsi@, see pepsi-stage-edit-settings(1)):

[stage-auto-pay]
MAX_TOTAL = EUR:20

A wallet self-service section on the submission path (a message to pepsi-wallet@example.com with a command in the Subject operates the sender’s wallet):

[stage-wallet]
PROGRAM = pepsi-stage-auto-pay
MAX_TOTAL = EUR:0
NEXT_STAGE = dkim-sign
RESPONSE_STAGE = dkim-sign
CONTROL_LOCAL_PART = pepsi-wallet

70.1.17.1.13. See Also

pepsi-helper-auto-pay(1), pepsi-stage-anti-spam(1), pepsi-stage-relay-to-internet(1), pepsi-stage-edit-settings(1), pepsi-config(1), pepsi-dispatch(1), pepsi.conf(5), pepsi.state(7), pepsi-setup(1)

70.1.17.1.14. Bugs

Report bugs to the Pepsi issue tracker.