PaymentProcessor
What the PaymentProcessor Is
The PaymentProcessor is the execution core of the amser protocol.
It is the single contract through which all authorization instruments must pass to execute payments. Subscriptions, credits, and any future module types all route through it.
It is:
- stateless with respect to business logic
- module-agnostic
- non-custodial
- the sole authority for payment execution
It is not:
- a billing engine
- a scheduler
- a decision-maker about what should be charged
The PaymentProcessor enforces rules. Modules define semantics.
Why It Exists
Authorization instruments differ in their triggers and state models. Subscriptions trigger on time intervals. Credits trigger on usage exhaustion. Future instruments may trigger on other conditions.
Despite these differences, all instruments share common execution requirements:
- validate that execution is allowed
- prevent double-execution
- verify funding is available
- transfer funds atomically
- update module state
The PaymentProcessor centralizes these concerns. Without it, each module would re-implement Permit2 handling, idempotency, fee routing, and transfer logic.
This is the narrow waist of the protocol. Everything above it (modules) defines what should happen. Everything below it (Permit2, tokens) moves value. The processor is the bridge.
What It Guarantees
Atomicity
Every execution either:
- completes fully (transfers succeed, state updates, event emits)
- fails cleanly (no partial state, no value moved)
There is no intermediate state. A payment cannot be half-processed.
Idempotency
Every billing window can be processed at most once.
The processor maintains a bitmap of processed windows per module and subscription. Before any transfer, it checks and marks the window. Duplicate attempts are rejected.
This guarantee is unconditional. It does not depend on keeper behavior, indexer accuracy, or timing.
Non-Custodial Execution
The processor never holds user funds.
All transfers occur directly from the user's wallet to recipients (merchant, keeper, treasury) via Permit2. Funds pass through the processor logically but not custodially.
If the processor is paused, upgraded, or compromised, user funds in their wallets remain safe.
Module Agnosticism
The processor does not understand subscriptions or credits.
It interacts with modules through two interfaces:
IRegistrableModule: for initial setup (what token, what amount)IExecutableModule: for execution (can it run, what are the parameters, finalize state)
Any contract implementing these interfaces can use the processor. New authorization instruments require no processor modifications.
What It Does Not Do
Make Billing Decisions
The processor does not decide when a payment is due, how much to charge, or whether a subscription should renew.
Modules answer these questions via quoteExecution(uint256 id), which returns execution eligibility, failure reason (if any), and context (payer, recipient, token, amount, windowId). The processor only asks and enforces.
Store Business State
The processor stores only:
- idempotency bitmaps (which windows have been processed)
- configuration (FeeSchedule, TokenRegistry, treasury, authorized keepers)
It does not store plans, subscriptions, credit balances, or user data. That belongs to modules.
Retry Failed Payments
If execution fails (insufficient balance, expired allowance, paused subscription), the processor emits a failure event and returns. It does not schedule retries.
Retry logic belongs to the off-chain layer. The processor is stateless with respect to failure history.
Route Funds Through Itself
Funds never enter the processor's balance. Permit2's transferFrom moves tokens directly from payer to recipients in a single atomic batch.
The Validation Pipeline
When a keeper calls execute(module, id), the processor runs this pipeline:
- Permission: Is the module valid (deployed via factory)?
- Quote: Call module's
quoteExecution(id)— returns failure reason or execution context (payer, recipient, token, amount, windowId) - Idempotency: Has this window already been processed?
- Allowance: Does the payer have sufficient Permit2 allowance?
- Transfer: Execute atomic batch transfer via Permit2
- Finalize: Call module's
onExecuteto update state
If any step fails, execution halts. No partial execution is possible.
Keepers can also use batchExecute(module, ids[]) to process multiple IDs in one transaction. The processor validates the module and resolves fees once for the entire batch; each ID is processed independently with soft-fail semantics (failures emit events, never revert the batch).
Fee Routing
Fees are a side effect of execution, not a goal.
The processor calculates:
- Protocol fee: percentage of payment amount
- Keeper fee: percentage of protocol fee
- Treasury fee: remainder of protocol fee
- Merchant amount: payment minus protocol fee
All four transfers occur atomically. If any fails, all fail.
FeeSchedule
Fee rates are resolved per module via the FeeSchedule contract. The processor calls resolveFees(module) to get the effective protocol and keeper fee rates (in basis points). This enables tier-based fee structures (e.g. different rates for different merchant tiers). The FeeSchedule is independently upgradeable; the protocol owner can swap it via setFeeSchedule().
TokenRegistry
The TokenRegistry enforces per-token constraints. Before each transfer, the processor checks:
isEnabled(token): the token must be on the allowlistminPerTx(token)/maxPerTx(token): optional per-token amount bounds (0 means no constraint)
Unsupported tokens or amounts outside the configured range cause execution to fail. The registry is upgradeable via setTokenRegistry().
Failure Modes
The following failures are normal and handled gracefully:
| Failure | Cause | Result |
|---|---|---|
ExecutionNotAllowed | Module says no | Event emitted, no transfer |
NotYetDue | Payment window not open | Event emitted, no transfer |
AlreadyProcessed | Idempotency guard | Silent return |
InsufficientAllowance | User reduced allowance | Event emitted, no transfer |
AllowanceExpired | Permit2 deadline passed | Event emitted, no transfer |
InsufficientBalance | User lacks funds | Transfer reverts |
None of these compromise correctness. They are expected conditions handled predictably.
Relationship to RAI
The PaymentProcessor is the enforcement layer of Recurring Authorization Infrastructure.
RAI separates:
- Authorization: what a user has consented to (stored in modules)
- Execution: when and how that authorization is acted upon (handled by processor)
The processor does not grant authorization. It enforces existing authorization. A payment can only execute if the module confirms authorization is active, the allowance covers the amount, and the window has not been processed.
Authorization is the product. Execution is mechanical.
Non-Goals
The following are out of scope:
-
Scheduling: The processor does not know when payments are due. Modules and indexers handle timing.
-
Subscription management: Creating, pausing, or canceling subscriptions happens in modules.
-
User interaction: Users never call the processor directly. They interact with modules.
-
Multi-token payments: Each execution involves one token.
-
Partial payments: Either the full amount transfers or nothing does.
-
Dispute resolution: The processor executes what modules authorize. Disputes are resolved elsewhere.