Skip to main content

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:

  1. Permission: Is the module valid (deployed via factory)?
  2. Quote: Call module's quoteExecution(id) — returns failure reason or execution context (payer, recipient, token, amount, windowId)
  3. Idempotency: Has this window already been processed?
  4. Allowance: Does the payer have sufficient Permit2 allowance?
  5. Transfer: Execute atomic batch transfer via Permit2
  6. Finalize: Call module's onExecute to 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 allowlist
  • minPerTx(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:

FailureCauseResult
ExecutionNotAllowedModule says noEvent emitted, no transfer
NotYetDuePayment window not openEvent emitted, no transfer
AlreadyProcessedIdempotency guardSilent return
InsufficientAllowanceUser reduced allowanceEvent emitted, no transfer
AllowanceExpiredPermit2 deadline passedEvent emitted, no transfer
InsufficientBalanceUser lacks fundsTransfer 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.