dánwòdánwò

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 matrix faulting Stripe, Resend, S3, the webhooks and Postgres itself (86 trials), and the shop surviving every one
  • COMMERCE_BUG switches that turn one safeguard off, so you can watch the matrix catch it

Stack

Next.js App RouterHonoPostgresDrizzleTailwind CSS

Emulates: 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 test

Its 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 serve

Its 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

tests/deterministic.danwo.ts
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)
})

On this page

Run it on every pull request

A GitHub check and one comment per pull request, with the flight records attached.

Set up CI