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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Projects

  • Status
    Backlog

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions