Online Storefront
One seller, one catalog, many buyers. Product types that extend product:base_product, a cart that converts into an order, and a payment record that tracks the money — assembled from platform base types rather than written from scratch.
The Project
- •A catalog with prices, a currency, and a visible/hidden distinction.
- •A cart that survives navigation and belongs to a specific buyer.
- •A checkout step that produces an order record you can still read a year later.
- •A payment record whose lifecycle is separate from the order lifecycle, because refunds outlive fulfilment.
- •Customer-written reviews that a staff member moderates before they appear.
- •Staff who work orders after the fact: confirm, fulfil, cancel, refund.
ℹ️ The exemplar behind this page
Architecture & Design
{
"name": "book",
"namespace": "chapter_verse",
"schema_type": "object",
"schema_content": {
"name": "book",
"parent_type": "tenant",
"extends": "product:base_product",
"attributes": [
{"name": "title", "type": "string", "mandatory": true},
{"name": "author", "type": "string", "mandatory": true},
{"name": "isbn", "type": "string", "mandatory": true},
{"name": "category", "type": "BookCategory", "mandatory": true},
{"name": "cover_image", "type": "Image"},
{"name": "publisher", "type": "string"},
{"name": "page_count", "type": "integer"},
{"name": "stock_quantity", "type": "integer"},
{"name": "average_rating", "type": "float"},
{"name": "is_visible", "type": "boolean"}
]
}
}| Type in chapter_verse | Extends | What that inheritance buys |
|---|---|---|
| book | product:base_product | price, currency, sku, and a status of active, inactive or discontinued |
| store_cart | cart:base_cart | subtotal, item_count, expires_at, and a status of open, checked_out, abandoned or expired |
| store_cart_item | cart:base_cart_item | line-item shape; parented to store_cart rather than to the tenant |
| store_order | order:base_order | subtotal, tax_amount, shipping_amount, discount_amount, total_amount, source_cart_uuid, and a status of pending, confirmed, fulfilled, cancelled or refunded |
| store_order_item | order:base_order_item | line-item shape; parented to store_order |
| store_payment | payment:base_payment | captured_amount, refunded_amount, external_charge_id, and a status of pending, authorized, captured, refunded, partially_refunded, voided or failed |
| book_review | feedback:base_feedback | rating, verified_purchase, helpful_count, and a status of draft, published, hidden, flagged or deleted |
| customer | customer:base_customer | the buyer profile, parented to a user account rather than to the tenant |
💡 The rest of the 29
The End-to-End Flow
- 1
Browse
The public catalog reads book records through the ordinary CRUD surface. Visibility is a field on the record — is_visible on the book, plus the inherited status — so hiding a title is a data change, not a deployment.
- 2
Add to cart
cart / add_to_cart. The first call materialises a store_cart for the buyer and creates their customer profile if it does not exist yet. That customer-profile create is subject to your access policy, so a self-signup buyer needs to be permitted to have one made for them — this is the single most common reason a checkout fails before Stripe is ever reached.
- 3
Recompute
cart / recompute_totals rolls the line items back up into subtotal and item_count on the cart header. Quantity edits and removals go through the same service rather than a direct write.
- 4
Checkout
cart / checkout_cart converts the cart into a store_order and copies the store_cart_item rows into store_order_item rows. The cart moves to checked_out; the order arrives at pending with source_cart_uuid pointing back at where it came from.
- 5
Take payment
stripe_checkout / create_checkout_session hands the buyer to Stripe and records a checkout session. The payment service then tracks the money in its own vocabulary — authorize_payment, then capture_payment, and refund_payment later if it comes to that.
- 6
Confirm and fulfil
order / place_order and order / fulfill_order move the order along its own state machine, independently of the payment. cancel_order exists for the other outcome. Each transition stamps its own timestamp — confirmed_at, fulfilled_at, cancelled_at — so the history is on the record, not in a log you have to join against.
- 7
Review
The buyer writes a book_review. It starts as draft and a staff member publishes it, because base_feedback has a real status rather than a boolean.
ℹ️ Operations are guarded, not advisory
What You Get Without Building It
- •The cart-to-order conversion, including line-item copying and the totals rollup.
- •Five state machines — product, cart, order, payment, feedback — with idempotent operations and 409 on an illegal transition.
- •Stripe Checkout as a configured integration rather than an SDK you embed: two keys, stripe_api_key and stripe_webhook_secret, held in the keystore and masked on read.
- •Transactional and marketing email through SendGrid, with campaign, template, sender, message and suppression records already modelled.
- •File storage on your own S3 bucket for cover art and review images.
- •Two audiences on one dataset: a public landing page with hero, catalog, product-detail, cart and order-detail blocks, and an admin panel generated per type from the base UI schemas.
- •Metric cards and charts declared as records — the bookstore ships total revenue, active books, pending orders, a revenue trend and orders by status.
- •Multi-tenancy, the platform roles, and record-level ownership, without a tenant_id column anywhere in your model.
How to Implement in Supero
Model it
- 1
Build the schema
Describe the app in App Studio — it generates the schemas and a reviewable UI — or author it over MCP from your editor. Aim at the object model above: a type opts into a service by extending its base type. Schemas are live on the CRUD API the moment they upload — there is no separate build step to run.
- 2
Configure the services
Imported services arrive uncredentialed. Put each provider’s keys in the keystore — here that is Stripe for checkout, plus email and SMS for order and shipping notifications — see Email & Notifications. Until a service is credentialed, its calls fail with a clear error rather than silently doing nothing.
What you write yourself
⚠️ Stock is a number, not a lock
| Decision | Why the platform does not make it for you |
|---|---|
| Tax and shipping | base_order carries tax_amount and shipping_amount as fields. The rates, the jurisdiction rules and the carrier quote are yours. |
| Discounts and promotions | discount_amount is a field. There is no coupon type, no eligibility rule and no stacking policy in the base types. |
| average_rating on a book | It is a float that nothing computes. Aggregate it on review publication with a workflow, or compute it at read time. |
| Review moderation | base_feedback gives you the draft/published/hidden/flagged states. Who may move a review between them, and on what evidence, is an access policy you write. |
| Buyer permissions at checkout | add_to_cart creates a customer profile on the buyer's behalf. A self-signup tenant_user whose policy default is no access will be refused, and the buyer sees a permission error rather than a Stripe page. Decide the policy before you demo it. |
| Refund policy | refund_payment is an operation, not a rule. Windows, partial refunds and restocking are yours. |
| Order-to-payment coupling | The two state machines are deliberately independent. Whether a fulfilled order requires a captured payment is your invariant to enforce, most naturally as a workflow. |
Deployment
- 1
Preview while you iterate
A preview deploy answers at
https://preview-{domain}-{project}.supero.livefor a short lease while you build and test. Publish Your App covers how preview and live differ. - 2
Go Live
Go Live publishes to a permanent, Supero-hosted URL —
https://live-{domain}-{project}.supero.live— with no cloud account to connect and nothing to provision. It is asynchronous: you get a202and a poll URL, then the live address. The full contract is in Publish Your App. - 3
Watch the logs
When a deploy fails or the live app misbehaves, the **Cloud Deploy Logs** page in the dashboard shows the published service’s container logs — pick the Live lane to read the published app rather than the preview build (up to ~24 hours back, ~500 lines per request). For platform activity — requests, auth, errors across your domain —
GET /api/v1/logsis a filtered read, and a single request traces across services at/api/v1/logs/trace/{correlation_id}when you set your ownX-Correlation-ID. See Operations & Observability.
Final Thoughts
Where to Go Next
The mechanism
Transactional Services
The services/execute contract, the state machines, idempotency, and the 409 you will meet. Read it
The model
Schema Design
Attributes, references and roles, parent types, and how extends is resolved. Read it
The money
Managed Integrations
Stripe Checkout and SendGrid as configured credentials in the keystore rather than code. Read it
The policy
Access Control
What a self-signup buyer may do, and why checkout is where that question surfaces. Read it
On this page