Checkout And Operations
This page is about Coil’s checkout and operator model first, with Shoppr as a concrete example.
Use it when you want to answer:
- where the public checkout contract comes from
- where payment-provider boundaries live
- how account continuity and operator visibility fit into the same app
- which files to copy when building a real store
The Core Pattern
Coil keeps the commerce lifecycle split across four layers:
- a reusable route and handler contract from the commerce module
- customer templates for cart, checkout, confirmation, account, and admin surfaces
- customer-owned payment-provider configuration
- customer-owned policy in linked Rust where the store needs custom decisions
That is the right mental model to keep while reading Shoppr. The demo is evidence of the pattern, not the pattern itself.
Canonical Commerce Route Contract
The route contract comes from crates/coil-commerce/src/module/platform/manifest.rs.
The important part is that the module contributes named surfaces like:
"/cart"
"/checkout"
"/checkout/start"
"/checkout/complete"
"/checkout/confirmation"
"/webhooks/commerce/payment-provider"
That snippet matters because it shows the reusable platform boundary:
- cart and checkout are module-owned route contracts
- the provider webhook is part of the same commerce lifecycle
- the customer app does not need to invent the route vocabulary first
Shoppr then supplies the implementation-facing pieces around that contract.
Canonical Provider Configuration Shape
The customer app keeps payment-provider configuration in platform config, not inside templates.
Shoppr’s local example in apps/shoppr/platform.dev.toml uses:
[modules."commerce-payments-stripe"]
provider = "stripe"
checkout_mode = "hosted-checkout"
publishable_key = { kind = "env", var = "STRIPE_PUBLISHABLE_KEY" }
webhook_secret = { kind = "env", var = "STRIPE_WEBHOOK_SECRET" }
That is the pattern to copy:
- provider identity and mode stay in config
- secrets stay as env-backed secret refs
- the public checkout template does not become the source of payment truth
Canonical Customer Lifecycle Commands
The customer binary should own the app lifecycle for developers and operators.
Shoppr’s binary entrypoint in apps/shoppr/crates/shoppr-bin/src/main.rs exposes:
enum Command {
Describe,
Validate,
Assets { command: AssetsCommand },
Migrate { command: MigrateCommand },
Serve { bind: Option<String> },
Up { bind: Option<String> },
LinkedBackend { command: LinkedBackendCommand },
}
This is the right operational shape:
- the root platform CLI still exists for global operator workflows
- the customer app owns the app-specific lifecycle a new developer actually runs
What The Customer App Still Owns
The customer app owns the human product surfaces around the reusable module contract.
For Shoppr, that means:
- cart and checkout templates
- confirmation and account continuity
- order-support and admin pages
- any custom review or webhook policy in linked Rust
Shoppr As The Supporting Example
Public checkout
Shoppr’s public checkout templates live in:
apps/shoppr/templates/commerce/cart.htmlapps/shoppr/templates/commerce/checkout.htmlapps/shoppr/templates/commerce/checkout-confirmation.html
Those are useful because they show the public flow without confusing the source of truth:
- cart is still an SSR surface
- checkout explains hosted-provider handoff honestly
- confirmation does not overclaim settlement or membership activation
Account continuity
The post-checkout customer story continues in:
apps/shoppr/templates/pages/account.htmlapps/shoppr/templates/account/dashboard.htmlapps/shoppr/templates/account/orders.htmlapps/shoppr/templates/account/summary-panels.htmlapps/shoppr/templates/memberships/account.html
These files matter because they keep the app honest about what happens after provider return:
- pending payment can still be pending
- order history is part of the account flow
- membership activation is a follow-on lifecycle, not just a marketing claim
First-party policy
Shoppr’s linked Rust boundary lives in:
apps/shoppr/crates/shoppr-backend/src/lib.rsapps/shoppr/backend/shoppr-loyalty-backend/src/lib.rs
This is where customer-specific order review, loyalty, CRM, and verified-webhook decisions belong.
Operator surfaces
Day-one store operations are visible in:
apps/shoppr/templates/admin/dashboard.htmlapps/shoppr/templates/admin/audit.htmlapps/shoppr/templates/commerce/orders.htmlapps/shoppr/templates/commerce/order-detail.htmlapps/shoppr/templates/commerce/catalog-admin.html
These pages show the operator side of the same lifecycle:
- order queue and detail
- refund and fulfillment visibility
- catalog copy and visibility changes
- audit and admin shell
Practical Rules To Copy
- let the commerce module define the core route contract
- keep provider selection and secrets in config
- keep public checkout templates honest about settlement and return flow
- keep account continuity in the same customer app
- put customer-specific review and webhook policy in linked Rust
- make operator pages part of the same product, not an afterthought
Full Implementation Pointers
If you want the full Shoppr implementation after reading the pattern:
apps/shoppr/templates/commerce/cart.htmlapps/shoppr/templates/commerce/checkout.htmlapps/shoppr/templates/commerce/checkout-confirmation.htmlapps/shoppr/templates/pages/account.htmlapps/shoppr/templates/account/dashboard.htmlapps/shoppr/templates/account/orders.htmlapps/shoppr/templates/memberships/account.htmlapps/shoppr/crates/shoppr-backend/src/lib.rsapps/shoppr/backend/shoppr-loyalty-backend/src/lib.rsapps/shoppr/crates/shoppr-app/src/lib.rsapps/shoppr/crates/shoppr-bin/src/main.rs