Examples
Commerce
A small shop selling mugs, notebooks and prints: a Next.js storefront and admin, a Hono API on Postgres, and Stripe Checkout, Resend receipts and S3 pictures.
A small shop selling mugs, notebooks and prints: a Next.js storefront and admin, a Hono API on Postgres, and Stripe Checkout, Resend receipts and S3 pictures. See it on GitHub.
What it shows
- A whole stack behind one proxy: Stripe Checkout and its webhooks, Resend receipts and S3 pictures, all emulated
- Invariants that read the shop's database against Stripe and Resend: one order per payment, stock taken once, one receipt per order
danwo matrixfaulting Stripe, Resend, S3, the webhooks and Postgres itself (86 trials), and the shop surviving every oneCOMMERCE_BUGswitches that turn one safeguard off, so you can watch the matrix catch it
Stack
Next.js App RouterHonoPostgresDrizzleTailwind CSSEmulates: Stripe, Resend, S3 and Tigris. Runs on: A Next.js storefront and a Hono API in Chromium, with Postgres in Docker.
Run it
Copy examples/commerce anywhere, then from its folder:
npm install
npx @danwo/cli testIts scripts:
npm run build # next build web
npm run typecheck # tsc --noEmit && tsc --noEmit -p web
npm run test # danwo test
npm run test:agentic # danwo test tests/agentic.danwo.ts
npm run matrix # danwo matrix tests/deterministic.danwo.ts --grep "pays for a mug"
npm run matrix:plan # danwo matrix tests/deterministic.danwo.ts --grep "pays for a mug" --plan
npm run matrix:refund # danwo matrix tests/deterministic.danwo.ts --grep "refunds an order"
npm run serve # danwo serveIts README says what else it needs and what the last full matrix found.
Until the danwo version it pins is on npm, install it from a local build instead: from the repository's root, bun run release <version> --pack, then node examples/link.ts commerce, which installs a copy and prints where it is.
Real stacks explains what this example leans on.
The test
import { resendInbox, test as base } from "@danwo/sdk"
import {
CUSTOMER,
DECLINED_CARD,
expectBooksBalance,
expectOneSessionPerCart,
pay,
type ShopFixtures,
} from "./shop.ts"
const test = base.extend<ShopFixtures>()
test("browses the shop and fills a cart", async ({ app, screen, expect }) => {
await app.open("/")
await expect(screen.getByRole("heading", { name: "Things for slow mornings" })).toBeVisible()
await expect(screen.getByRole("link", { name: /Tide print/ })).toContainText("Sold out")
await screen.getByRole("link", { name: "Mugs", exact: true }).click()
await expect(screen.getByRole("link", { name: /Hills print/ })).toBeHidden()
await screen.getByRole("link", { name: /Speckled mug/ }).click()
await expect(screen.getByRole("heading", { name: "Speckled mug" })).toBeVisible()
await expect(screen.getByRole("img", { name: "Speckled mug" })).toBeVisible()
await screen.getByLabel("Quantity").selectOption("2")
await screen.getByRole("button", { name: "Add to cart" }).click()
await expect(screen.getByTestId("cart-total")).toHaveText("$36.00")
await expect(screen.getByRole("link", { name: "Cart, 2 items" })).toBeVisible()
}).invariant(async (fixtures) => {
// A full cart is not an order: nothing is paid, so nothing is owed.
await expectBooksBalance(fixtures)
fixtures.expect(await fixtures.sql("select id from orders")).toHaveLength(0)
})
test("pays for a mug through Stripe Checkout", async ({ app, screen, expect }) => {
await pay({ app, screen, expect }, { product: "little-mug", email: "[email protected]" })
await expect(screen.getByRole("heading", { name: "Thank you for your order" })).toBeVisible()
await expect(screen.getByTestId("order-total")).toHaveText("$14.00")
await expect(screen.getByRole("link", { name: "Cart, 0 items" })).toBeVisible()
}).invariant(async (fixtures) => {
await expectBooksBalance(fixtures)
await expectOneSessionPerCart(fixtures)
})
test("a declined card shows Stripe's error and places no order", async ({
app,
screen,
expect,
}) => {
await pay(
{ app, screen, expect },
{ product: "hills-print", email: "[email protected]", card: DECLINED_CARD },
)
await expect(screen.getByRole("alert")).toHaveText("Your card was declined.")
await app.open("/orders?email=kit%40paperandclay.test")
await expect(screen.getByText("No orders yet")).toBeVisible()
}).invariant(async (fixtures) => {
await expectBooksBalance(fixtures)
fixtures.expect(await fixtures.sql("select id from orders")).toHaveLength(0)
fixtures.expect(resendInbox(fixtures.resend).list("[email protected]")).toHaveLength(0)
})
test(
"refunds an order from the admin",
{ session: "returning-customer" },
async ({ app, screen, expect }) => {
await app.open("/admin/orders")
await expect(screen.getByRole("row", { name: new RegExp(CUSTOMER) })).toContainText("Paid")
await screen.getByRole("button", { name: /^Refund order PC-/ }).click()
await expect(screen.getByRole("status")).toContainText("was refunded")
await expect(screen.getByRole("row", { name: new RegExp(CUSTOMER) })).toContainText("Refunded")
},
).invariant(async (fixtures) => {
await expectBooksBalance(fixtures)
})Login with Clerk
Signing in with Clerk on Next.js: Clerk's own components for sign-up and sign-in by emailed code or Google, organizations and invitations, and the app's users in Postgres from Clerk's webhooks.
Background jobs
A team digest sent by background jobs on Inngest: a React page, a Hono API on Postgres, and a worker whose cron, fan-out, retries and onFailure send through Resend and bill through Stripe.