Mental model
SAPA is a monetization decision engine that you run inside your own Effect runtime. You describe what can be sold, send it commands, and query the state derived from accepted history. SAPA does not replace your product database, authentication, HTTP framework, or payment provider.
The easiest way to understand the system is to separate two planes:
- Definition plane: versioned descriptions of offers and installed capabilities. This answers “what are we allowed to sell and fulfill?”
- Execution plane: commands, authenticated provider events, accepted facts, projections, and background work. This answers “what happened for this customer, and what are they entitled to now?”
Within those planes, SAPA separates four kinds of information that are often mixed together in billing integrations.
Definitions
Section titled “Definitions”Definitions state what a merchant offers: purchase benefits, billing terms, meter identities, access duration, seat constraints, and fulfillment bindings. They are immutable and identified by a merchant-authored key plus revision.
Changing terms means introducing a new revision. Historical purchases continue to refer to the terms they accepted.
For example, workspace.pro@2 can change price or benefits without changing
what a customer who accepted workspace.pro@1 bought. Registration validates
the definition and its installed capability references; it does not create a
checkout or contact a provider.
Commands and accepted facts
Section titled “Commands and accepted facts”A command asks SAPA to perform work, such as creating a checkout or recording usage. Successful processing records authoritative facts. Idempotency keys, provider event identities, and guarded lifecycle transitions prevent a retry or stale writer from inventing a second outcome.
A command is intent, not proof. createCheckout means “attempt to create this
checkout at most once.” It does not mean payment succeeded. A raw payment
notification becomes settlement evidence only after the selected provider
adapter authenticates it and SAPA accepts the resulting lifecycle transition.
Derived state
Section titled “Derived state”Current access, balances, subscription status, seat occupancy, and operational views are derived from accepted facts. These views make reads practical without turning mutable rows into the historical source of truth.
This distinction matters during recovery. SAPA can rebuild or verify a projection from retained history without recreating a provider checkout, resending an email, generating another license key, or calling an external fulfillment system.
Capabilities
Section titled “Capabilities”Storage, payment providers, clocks, cryptography, downloads, mail, and merchant policies are capabilities supplied by the host runtime. SAPA’s core behavior depends on contracts rather than selecting infrastructure implicitly.
A capability is not another command. It is an implementation SAPA may use while processing a command: for example, a payment adapter creates hosted checkout, while a download adapter turns an accepted asset authorization into a bounded URL. Effect Layers install these implementations.
flowchart TD accTitle: SAPA information flow accDescr: Immutable definitions, commands, and host capabilities produce authoritative facts, rebuildable projections, and current access decisions. Definitions["Immutable definitions"] --> Acceptance["Validate and accept command"] Command["Command"] --> Acceptance Capabilities["Host-supplied capabilities"] --> Acceptance Acceptance --> Facts["Authoritative accepted facts"] Facts --> Projections["Rebuildable projections"] Facts --> Decisions["Current access decisions"]
One purchase, classified
Section titled “One purchase, classified”An ebook purchase makes the distinction concrete:
| Step | Kind | Example | What it proves |
|---|---|---|---|
| Register | Definition | ebook.effect@1 provides ebook.pdf@1 | These exact terms are valid and installed |
| Start checkout | Command | createCheckout({ idempotencyKey: "order-42", ... }) | The merchant asked for one hosted checkout |
| Provider call | Capability use | Midtrans returns a checkout URL | A provider attempt has an observed result, not a payment |
| Receive webhook | External evidence | Raw, signed Midtrans payload | Nothing until the provider adapter authenticates it |
| Accept settlement | Fact | payment identity, amount, currency, accepted offer revision | The purchase paid under the retained terms |
| Grant download | Projection | eligible customer + exact asset revision | A fast read derived from accepted facts |
| Authorize URL | Query + capability use | authorizeDownload(...) | The authenticated customer may receive a bounded URL now |
| Send email | Durable work | retained email obligation | Delivery may retry independently of ownership |
The same shape scales to subscriptions, seats, credits, licensing, and metered invoices: definitions describe the promise; authenticated commands and events create facts; projections answer current questions; capabilities perform I/O.
Who owns what?
Section titled “Who owns what?”| Your application owns | SAPA owns | Adapters own |
|---|---|---|
| Authentication, authorization, customers in your product, workspaces, members, routes, UI, and when to invoke a command | Offer validation, immutable accepted terms, idempotency, lifecycle decisions, authoritative histories, benefit provenance, and typed failures | Database rows and transactions, provider protocols, signatures, cryptography, signed URLs, email transport, and external-system calls |
For example, your application proves that the caller administers
workspace-42; SAPA proves that the workspace has an available paid seat; the
storage adapter atomically records the assignment.
The normal lifecycle
Section titled “The normal lifecycle”sequenceDiagram accTitle: One paid purchase from definition to product access accDescr: The merchant registers exact terms, creates checkout, redirects the buyer, accepts an authenticated provider notification, and later queries or fulfills the resulting benefits. participant App as Your application participant Sapa as SAPA facade participant DB as Storage adapter participant Pay as Payment adapter participant Worker as Durable worker App->>Sapa: registerOffers(exact revisions) Sapa->>DB: persist immutable definitions App->>Sapa: createCheckout(command + idempotency key) Sapa->>DB: retain intent and creation claim Sapa->>Pay: create hosted checkout Pay-->>Sapa: checkout URL or uncertain outcome Sapa->>DB: retain observed result Sapa-->>App: pending checkout URL Note over App,Pay: Browser redirect is not settlement evidence Pay-->>App: raw signed webhook App->>Sapa: processPaymentNotification(raw payload) Sapa->>Pay: authenticate and normalize Sapa->>DB: atomically append payment and benefits DB-->>Sapa: accepted or replayed result Sapa-->>App: settled result Worker->>DB: lease durable owed work Worker->>Worker: call email, download, or fulfillment adapter App->>Sapa: checkAccess / authorize / reserve / assign Sapa->>DB: read verified projection and history Sapa-->>App: current decision
Checkout provider I/O is deliberately surrounded by durable state: intent is retained before the call, and its observed result is retained afterward. If the network outcome is unknown, reconciliation uses the same checkout identity; it does not blindly create another provider checkout.
Questions to ask when designing a product
Section titled “Questions to ask when designing a product”- What promise is sold? Model entitlements, downloads, seats, credits, licensing, or external fulfillment in a purchase definition.
- How is it billed? Select one-time, subscription, per-seat, or metered terms without mixing currency and usage units.
- Who receives it? Decide whether the paying customer or a merchant-owned subject such as a workspace is the beneficiary.
- What proves activation? Usually authenticated settlement; postpaid enrollment is a separate explicit merchant decision.
- What can happen asynchronously? Email and external fulfillment become durable work rather than effects inside the payment transaction.
- What must a current read prove? Use access, seat, credit, download, license, or invoice queries instead of trusting cached checkout state.
Common category errors
Section titled “Common category errors”- Checkout is not payment. A URL or success redirect grants nothing.
- An offer is not mutable product state. New terms require a new revision.
- A projection is not the authority. It is a serving view backed by history.
- A seat is not a user account. SAPA accounts for paid occupancy; your host owns members and permissions.
- Credits are not money. They are exact units with their own lifecycle.
- A capability is not globally available. It must be installed in the request or job runtime that executes the operation.
- Settlement is not delivery. External work may remain pending or retry after ownership has been accepted.
Boundary rule
Section titled “Boundary rule”Decode external input once at the edge. Inside the application, pass validated branded values and checked definitions rather than raw strings, numbers, or provider payloads.
Public operations already decode unknown input and return sanitized typed
errors. Keep the raw provider body only long enough to pass it to
processPaymentNotification; do not parse, transform, or log it first. See the
architecture for the runtime boundaries and the
recipes for complete product flows.