Ecommerce Template
Minimal Next.js commerce storefront template with a pluggable payment-adapter
seam. A single source scaffolds either supported payment provider; the active
provider for a generated app is recorded in app-config.ts and its keys live in
.env.local.example.
What is included
- App Router storefront routes for product listing, product detail, cart,
checkout, and payment success.
- Adapter boundaries under
lib/commerce and lib/payment.
- Demo commerce and payment adapters backed by
data/mock-catalog.json.
- Automatic
@01.software/sdk/server commerce adapter when 01.software SDK
credentials are present.
- A payment-adapter registry (
lib/payment/provider.server.ts) that selects a
provider from environment credentials, with first-class adapters for:
- PortOne —
@portone/browser-sdk/v2 on the client, @portone/server-sdk
on the server.
- TossPayments — the TossPayments browser SDK on the client, the
TossPayments payment REST API on the server.
- A theming seam: a CSS-variable token layer in
app/globals.css plus Tailwind.
- Server-side order pricing, stock checks, and payment sync invariants.
- Local persisted cart state with Zustand.
Scaffolding vs. this in-repo source
This directory is the single in-repo source and intentionally ships both
payment adapters so it builds and tests with every provider present.
create-01-software-app copies it, prunes the unused payment adapter (and its
dependency), generates app-config.ts and .env.local.example from
templates/registry.json, and pins the SDK version — producing a self-contained
single-provider app.
To add a new payment provider, see Add a payment provider below.
Run locally
pnpm install
pnpm dev
Without SDK or payment credentials, the app uses demo providers:
Mock orders are persisted to .mock-orders.json so redirect and success routes
can observe the same order during local development. This file is ignored and is
not a production store.
To use the Console ecommerce SDK adapter, set both SDK keys:
NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY=pk_...
SOFTWARE_SECRET_KEY=sk_...
SOFTWARE_API_URL=https://your-console-origin.example
SOFTWARE_SHIPPING_AMOUNT=3000
SOFTWARE_FREE_SHIPPING_ABOVE_AMOUNT=100000
SOFTWARE_API_URL, SOFTWARE_SHIPPING_AMOUNT, and
SOFTWARE_FREE_SHIPPING_ABOVE_AMOUNT are optional. The SDK adapter resolves
payment return/webhook requests through the SDK transactions collection first,
then falls back to a local .software-orders.json development index. This file
is ignored and is not a production store.
Payment provider keys
PortOne
PORTONE_API_SECRET=...
NEXT_PUBLIC_PORTONE_STORE_ID=store_...
NEXT_PUBLIC_PORTONE_CHANNEL_KEY=channel-key-...
NEXT_PUBLIC_PORTONE_PAY_METHOD=CARD
PORTONE_WEBHOOK_SECRET=...
NEXT_PUBLIC_PORTONE_PAY_METHOD is optional and defaults to CARD.
PORTONE_WEBHOOK_SECRET is required in production. For local development without
signed webhooks, set PORTONE_ALLOW_UNSIGNED_WEBHOOKS=true. When the secret is
present, the webhook route verifies PortOne webhook signatures through
@portone/server-sdk.
TossPayments
TOSSPAYMENTS_SECRET_KEY=test_sk_...
NEXT_PUBLIC_TOSSPAYMENTS_CLIENT_KEY=test_ck_...
NEXT_PUBLIC_TOSSPAYMENTS_CUSTOMER_KEY=customer_...
NEXT_PUBLIC_TOSSPAYMENTS_CUSTOMER_KEY is optional. If omitted, the browser SDK
uses TossPayments' anonymous customer key. TossPayments redirects back with
paymentKey, orderId, and amount; the template validates the returned
amount against the stored order and confirms the payment on
/api/checkout/payment-return before showing the success page.
TOSSPAYMENTS_API_BASE_URL is optional and defaults to
https://api.tosspayments.com/v1.
Add a payment provider
- Add
lib/payment/adapters/<pg>.ts implementing the PaymentProvider port
and exporting create<Pg>Provider plus has<Pg>Credentials.
- Register it in
lib/payment/provider.server.ts inside a matching pair of
scaffold:provider:<id> markers (import + registry entry).
- Add a
ClientPaymentRequest union case in lib/payment/types.ts.
- Add a client branch (and its
scaffold:provider:<id> markers) in
components/checkout/checkout-form.tsx.
- Add the provider to
templates/registry.json so the scaffolder offers it.
The template depends on the published @01.software/sdk package so it can be
copied out of this monorepo and installed as a standalone Next.js app.
The starter software product listing uses a simple products query followed by
per-product detail lookups so the UI can share one normalized product-detail
shape. It isolates individual detail failures with Promise.allSettled. For a
larger storefront, replace that listing path with the Console tenant's preferred
catalog/listing API shape.
Verify
pnpm test
pnpm check-types
pnpm lint
pnpm build
Important boundary
The cart stores only variantId and quantity. Checkout sends those lines plus
customer and shipping details to the server; server code re-fetches variants,
checks stock, recomputes subtotal/shipping/total, creates a pending order, and
then synchronizes payment state.