Skip to content

feat(shop): /shop route — affiliate + POD dropshipping + worldwide shipping (zero fixed cost) #121

Description

@sktbrd

Summary

Add a /shop route to the Gnars site to sell three kinds of things, with $0 fixed cost (only revenue-share payment fees, which the customer's payment covers):

  1. Affiliate / external products — curated cards linking out to other stores (with ref params).
  2. Native products / print-on-demand (dropshipping) — Gnars merch fulfilled by Printful, zero inventory, customer-funded per order.
  3. Worldwide shipping — shipping cost baked into product price so the customer sees "free worldwide shipping" and the DAO is never out of pocket (no paid real-time-rate engine).

Build strategy

Many small phases, enabled one at a time, ordered by setup effort (easiest first). Each phase is independently shippable as its own PR and gated behind a feature flag / nav visibility so it can be turned on only when ready. Pure-code phases (no external accounts, no secrets) come first; phases needing third-party accounts come later.

Cost stance (hard requirement: $0 fixed cost)

  • Catalog + route: free, on existing Vercel deploy.
  • Printful: no monthly fee, pay-per-order (customer covers it).
  • Payments: Coinbase Commerce first (~1%, USDC → treasury) — cheapest per sale, no chargebacks, lands in the DAO treasury, fits Base/thirdweb. Stripe Payment Links (~2.9% + $0.30) added later to capture non-crypto buyers.
  • No processor takes 0% — that cut comes out of margin, priced into products.

Payment decision (locked for v1): crypto-first with Coinbase Commerce; Stripe as a fast-follow.

Conventions to follow (this repo)

  • Static catalog mirrors src/data/installations.json shape (id, slug, ...).
  • Any DB work mirrors src/services/rounds.ts: lazy pg Pool, isXConfigured() guard, env precedence, server-only, auto-create tables. Reuse the existing Postgres — no new infra/cost.
  • New data access lives in src/services/shop.ts (canonical layer).
  • Server Components for load/SEO; Client Components only for cart/checkout interaction.
  • i18n: all UI strings via next-intl, EN + PT-BR in the same change.
  • Address/profile links → internal /members/[address], never Basescan.
  • Each phase ships behind a flag so it can be enabled independently.

Phase 1 — Affiliate catalog (easiest: pure code, no accounts, no DB)

Goal: a live /shop showing curated external products that link out. Nothing to buy on-site.

  • src/data/shop.json — schema: id, slug, title, description, images[], priceUSD, type: "affiliate", externalUrl, tags[], status.
  • src/services/shop.ts (server-only) — read + filter catalog.
  • /shop route (server component) — product grid, mirrors existing feature-page layout + skeletons.
  • src/components/shop/ product card — affiliate cards link out with ref param.
  • i18n (EN + PT-BR), nav entry (flag-gated), SEO metadata + OG image.
  • Mobile verified at runtime.

Setup needed: none. Deliverable: PR.

Phase 2 — Native product showcase (pure code, no checkout)

Goal: show Gnars merch as a static catalog + /shop/[slug] detail pages. No cart yet; CTA can be "coming soon" or a temporary external link.

  • Extend shop.json with type: "native" items + detail fields.
  • /shop/[slug] product detail (server component).
  • Card/detail handles both affiliate and native types.
  • i18n + SEO for detail pages.

Setup needed: none. Deliverable: PR.

Phase 3 — Client-side cart (pure code, no backend)

Goal: add-to-cart and cart drawer for native products. State in localStorage/React only.

  • Cart store (client) + cart drawer/sheet (shadcn).
  • Add/remove/quantity; persist to localStorage.
  • Cart badge in nav; empty/edge states; i18n.
  • "Checkout" button present but disabled/"coming soon" until Phase 4.

Setup needed: none. Deliverable: PR.

Phase 4 — Crypto checkout via Coinbase Commerce (first external account)

Goal: customers can actually pay in USDC; revenue lands in the treasury. Fulfillment is manual at this stage.

  • Coinbase Commerce account + API key (server-only env var, added to env.example).
  • Checkout flow: cart → create Coinbase Commerce charge → redirect to hosted checkout (no PCI on our servers).
  • Shipping cost baked into price; "free worldwide shipping" copy (EN + PT-BR).
  • Order confirmation page/state.
  • Flag to enable checkout independently.

Setup needed: 1 account (Coinbase Commerce). Deliverable: PR. Fulfillment handled manually until Phase 5.

Phase 5 — Printful POD auto-fulfillment (second external account + webhook)

Goal: paid orders are placed with Printful automatically and shipped worldwide.

  • Printful account + API key (server-only env var).
  • Map catalog items ↔ Printful variants.
  • Stateless webhook (Vercel route): payment success → place Printful order from line items. Idempotent on charge id.
  • Confirm Printful worldwide shipping coverage; verify baked-in shipping covers POD shipping cost.
  • Sandbox/test order end-to-end.

Setup needed: 1 account (Printful) + webhook secret. Deliverable: PR.

Phase 6 — Card checkout via Stripe Payment Links (capture non-crypto buyers)

Goal: add a card payment option alongside crypto.

  • Stripe account + Payment Links (or Checkout).
  • Card option in checkout UI; route card payments to fulfillment via the same webhook pattern.
  • Reconcile both payment sources to one order shape.

Setup needed: 1 account (Stripe). Deliverable: PR.

Phase 7 — DAO-native order tracking (reuses existing Postgres)

Goal: own order data and tie purchases to member identity. Still $0 (DB already exists for rounds).

  • shop_orders table via the rounds.ts pattern (lazy pool, isShopDatabaseConfigured() guard, auto-create).
  • Webhook reconciliation writes records (idempotent on processor charge id).
  • Link order ↔ wallet address; surface purchases in /members/[address].
  • Optional affiliate click logging / analytics.

Setup needed: none beyond existing DB. Deliverable: PR. On-brand DAO feature; defer until earlier phases are validated.


Test plan (per phase)

  • P1: /shop renders via Server Components, affiliate out-links work, mobile verified, EN + PT-BR present, pnpm lint + pnpm format:check clean.
  • P2: /shop/[slug] detail renders for native + affiliate items.
  • P3: cart add/remove/persist works; survives reload; mobile drawer verified.
  • P4: sandbox Coinbase Commerce charge completes; revenue address = treasury; shipping copy correct both locales.
  • P5: test payment → Printful test order placed via webhook; worldwide shipping confirmed.
  • P6: card payment routes to same fulfillment; order shape consistent.
  • P7: order persists idempotently; member purchase view renders.

Notes

Living plan. Each phase is re-planned and this issue updated with concrete details (file lists, decisions, supplier choices) as we go. Phases ship one at a time, each flag-gated.

Generated with Claude Code

Metadata

Metadata

Labels

No labels
No labels

Projects

Status
Backlog

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions