Un ghid practic: construiește un magazin Stripe bazat pe conținut în Profound CMS — un catalog pe care comerciantul îl poate edita fără cod, două rute parametrice, un coș headless și un checkout găzduit de Stripe.
Magazinul finalizat în acțiune — navighează într-o categorie, deschide un produs, adaugă-l în coș și finalizează comanda.
Un ghid practic pentru construirea unui magazin bazat pe conținut în Profound CMS: un catalog de produse (categorii și articole) modelat în CMS, pagini de listare și detalii pornind de la un singur set de rute și un checkout găzduit de Stripe, livrat ca o componentă headless.
Structura principală este un proiect Next.js scris manual, împreună cu panoul de administrare Profound. Claude Code (prin Profound MCP) se ocupă de trei sarcini — popularea catalogului, conectarea sistemului de design și scrierea componentelor magazinului (inclusiv coșul headless). Trei părți: Configurare, Construire, Producție.
Plățile într-o singură frază. Folosim Checkout găzduit de Stripe: cumpărătorul plătește pe pagina Stripe, nu pe a ta. Aplicația ta face doar două lucruri pe server — creează o sesiune Checkout și verifică un webhook. Fără câmpuri de card, fără Stripe Elements, fără sarcina conformității PCI.
/products/{item_code}, /categories/{category_code}) și un /cart static, toate pornind de la același set de componente.useCart) și un checkout găzduit de Stripe, cu prețul rezolvat întotdeauna pe server folosind un Stripe Price ID.curl -fsSL https://bun.sh/install | bashstripe login) pentru webhook-uri locale.gh) și un cont Vercel conectat la GitHub.Profound separă conținutul de randare:
category, item); una etichetată UI Element poate fi plasată pe o pagină (nav, product_grid etc.).meta.params.* în CEL, routeParams în React).cms-renderer; Stripe este adăugat sub forma unor rute API obișnuite.Regula care modelează construcția: CEL conectează doar câmpuri de tip string/number. Prin urmare, elementele scalare ale interfeței (brandul navigației, subsolul, titlurile) sunt conectate cu CEL, în timp ce orice conținut bogat sau colecție (o grilă de produse, o galerie de imagini, text îmbogățit) este preluat în componenta React folosind parametrul rutei. Iar Stripe este sursa adevărului pentru prețuri — price din CMS este doar pentru afișare; suma este întotdeauna rezolvată pe server dintr-un Stripe Price ID.
Rezultatul final: un catalog mic și publicat, aplicația conectată pentru citirea lui, Stripe instalat și designul pregătit — încă nu se randează nimic.
Înregistrează-te la Profound (autentificare WorkOS). Creează un site numit store, apoi copiază ID-ul site-ului (UUID-ul din URL-ul panoului de administrare) și o cheie API de nivel read (Deployments → Create API key). Aplicația doar citește; popularea catalogului se va face ulterior prin MCP, care se autentifică separat.
bunx create-profound-next store
cd store
bun add stripe
Scaffold-ul este un proiect Next.js App Router configurat pentru Profound (SDK-ul cms-renderer, o rută catch-all, un script generate-schemas și un <Refresher>). Nu include stilizare. bun add stripe instalează SDK-ul de server — singura dependență de plăți necesară pentru checkout-ul găzduit.
Adaugă valorile în .env.local:
# CMS
PROFOUND_API_KEY=<your read key>
NEXT_PUBLIC_PROFOUND_WEBSITE_ID=<your website id>
NEXT_PUBLIC_CMS_API_URL=https://cms.dev.tryprofound.com
NEXT_PUBLIC_BUNNY_CDN_URL=https://cms-profound.b-cdn.net # serves CMS-hosted images
# Stripe
STRIPE_SECRET_KEY=sk_test_... # test key here; swap to your live key when you go live
STRIPE_WEBHOOK_SECRET=whsec_... # filled in Build step 4
NEXT_PUBLIC_SITE_URL=http://localhost:3000
Obține STRIPE_SECRET_KEY din Stripe → Developers → API keys. Folosim o cheie de test (sk_test_…), astfel încât construirea să nu transfere bani reali; treci la cheia live când ești gata să accepți plăți reale. Rulează bun dev și deschide localhost:3000 — aplicația inițială se va randa.
Checkout-ul găzduit redirecționează browserul către un URL Stripe, deci cheia secretă de server este tot ce are nevoie Stripe — fără cheie publicabilă și fără SDK Stripe pentru client.
category, product_image și itemCreează trei Custom Components (Components → Create new component) — acestea sunt sursa datelor, deci nu adăuga eticheta UI Element. Setează fiecare componentă ca Active.
CMS-ul nu are un câmp „array de imagini”, astfel că o galerie este o matrice de referințe către o componentă mică product_image. Creează category și product_image (și activează-le) înainte de item — un câmp de referință poate indica doar componente active.
category — name (Text), code (Text, Route Slug), description (Rich text), heroImage (Image)product_image — image (Image)item — name (Text), code (Text, Route Slug), description (Rich text), images (array de referințe → product_image), price (Number, cenți — doar pentru afișare), currency (Select, usd), stripePriceId (Text), category (Reference → category), active (Boolean)Lasă toate câmpurile opționale. Panoul de administrare transformă numele câmpurilor în snake_case cu litere mici („Stripe Price Id” → stripe_price_id) — pe acestea se bazează codul tău, așa că verifică numele reale după rularea generate-schemas. Numim identificatorul rutabil code (nu slug): este atât Route Slug, cât și cheia pentru un apel curat documents.getByCode ulterior.
Componenta item — code ca Route Slug, images ca referințe către product_image, plus stripePriceId și o referință category.
bun run generate-schemas
Scrie schemele și tipurile Zod în generated/cms-schemas.ts (categorySchema/Category, itemSchema/Item). Comanda verifică și conexiunea — acreditările greșite vor eșua aici.
Instalează și autentifică MCP o singură dată:
claude mcp add --transport http Profound http://107.21.107.99:8081/mcp
Rulează mcp__Profound__authenticate, finalizează fluxul WorkOS, apoi cere-i lui Claude:
Generează un catalog e-commerce mic pentru un magazin numit Edison's Inventions — trei categorii și produsele de mai jos, fiecare cu o
descriptionscurtă și corectă pentru epocă, unpriceîn cenți,currency: "usd"șiactive: true:
- Lighting & Power (
code: lighting): Incandescent Lightbulb (incandescent-lightbulb, $24), Electric Dynamo (electric-dynamo, $890), Electric Pen (electric-pen, $49)- Sound Recording (
code: sound): Tinfoil Phonograph (tinfoil-phonograph, $249), Carbon Microphone (carbon-microphone, $59), Dictaphone (dictaphone, $179)- Motion Pictures (
code: motion): Kinetoscope (kinetoscope, $399), Kinetograph Camera (kinetograph, $549)Fiecare categorie are nevoie de un
nameși de acelcodecu litere mici; fiecare articol are nevoie dename,codecu litere mici,description,price(în cenți),currencyșiactive. Salvează datele îndata/catalog.jsonși validează-le conform componentelorcategoryșiitem. Apoi folosește Profound MCP pentru a crea fiecare element ca document publicat: creează mai întâi categoriile, salvează ID-urile lor, apoi creează produsele cucategorysetată ca referință —{ "_type": "reference", "_ref": "<category-id>", "_schema": "category" }. LasăstripePriceIdgol pentru moment. Creează produsele în paralel.
Claude scrie data/catalog.json, îl validează și lansează apeluri paralele create_document (status: "published"). Populează categoriile înaintea produselor, astfel încât referințele să indice ID-uri deja existente.
Cele trei categorii populate, publicate și Live.
Cele opt produse populate, fiecare asociat unei categorii.
Catalogul se află în CMS; acum atribuie câtorva produse un preț Stripe real — sarcina comerciantului, realizată în două panouri de administrare, fără cod:
price_…).stripePriceId și salvează.CMS-ul păstrează catalogul; Stripe păstrează prețul oficial; legătura este un singur șir de caractere introdus de comerciant. (Vrei să automatizezi? Stripe MCP oficial poate crea produsele și prețurile — lipește ID-urile returnate în același mod.)
Scaffold-ul nu are stilizare. Pune un DESIGN.md (un bloc Tailwind v4 @theme și tokenuri) în rădăcina proiectului — unul creat de tine sau descărcat de la refero.design. Apoi cere-i lui Claude, limitând solicitarea la stilizare:
Citește fișierul de design pe care tocmai l-am adăugat. Configurează Tailwind dacă este necesar, apoi conectează tema și fonturile astfel încât stilizarea să funcționeze. Folosește
next/fontpentru fonturi — nu le încărca de la Google în timpul rulării. Doar stilizarea — nu construi încă pagini sau componente.
Verifică dacă src/app/globals.css conține @import "tailwindcss"; și blocul @theme, iar localhost:3000 afișează tokenurile. Păstrează solicitarea restrânsă și încarcă fonturile prin next/font, niciodată printr-un import Google la runtime.
Opțional — poți ajunge la un checkout funcțional și fără imagini. Pentru a le adăuga: creează câte un document product_image pentru fiecare imagine (încarcă imaginea în câmpul image), apoi creează referințe către acestea în matricea images a produsului. Folosește fotografii proprii sau generează un set coerent cu un model de imagini.
cms-rendererstandalone nu are un helper pentru URL-ul imaginilor, așa că adubuildAssetUrlînsrc/lib/image.ts(aproximativ 40 de linii) — acesta adaugă prefixulNEXT_PUBLIC_BUNNY_CDN_URLși extensia. Componentele din pasul 3 al secțiunii Build îl folosesc.
Construiește stratul de randare și checkout-ul, ajungând la o achiziție reală în modul de test.
Cinci componente, fiecare Active și etichetată UI Element (Settings → Tags), fără Route Slug:
nav → brand · product_grid → heading · product_detail → heading · cart_summary → heading · footer → text (toate Text)Eticheta UI Element face componenta să apară în lista Add UI Element din Page Builder — faptul că este activă nu este suficient. Fiecare câmp este scalar (tipul pe care îl poate conecta CEL); datele catalogului nu sunt un câmp aici — ProductGrid/ProductDetail le preiau după parametrul rutei.
bun run generate-schemas
Un singur prompt poate construi helperul de citire, cele cinci componente, coșul și registrul:
Construiește magazinul nostru în
src/, folosind SDK-ul Profoundcms-renderer.
src/lib/catalog.ts— un cititor CMS pe server. Creează un client cugetCmsClient({ cmsUrl: process.env.NEXT_PUBLIC_CMS_API_URL!, apiKey: process.env.PROFOUND_API_KEY, websiteId: process.env.NEXT_PUBLIC_PROFOUND_WEBSITE_ID! })dincms-renderer/lib/cms-api. ExportăgetItemByCode(code)și returneazăres.document.published_content. ExportălistItems(categoryCode?), filtrează produsele active și, dacă este transmisă o categorie, păstrează produsele asociate documentului categoriei. ExportăresolveImages(refs)pentru rezolvarea referințelor dinitem.imagesși transformarea câmpului imagine într-un URL cubuildAssetUrl.
src/components/— cele cinci componente UI element înregistrate în ruta catch-all după numele componentei, snake_case pentru a corespunde panoului de administrare:{ nav, product_grid, product_detail, cart_summary, footer }.NavșiFootercitesc câmpul scalar din proprietateacontent.ProductGridșiProductDetailsunt componente server asincrone, citescrouteParamsși preiau datele dincatalog.ts:routeParams.<param>este{ value, … }, deci citește.value.ProductGridapeleazălistItems(routeParams.category_code?.value), iarProductDetailapeleazăgetItemByCode(routeParams.item_code?.value).CartSummaryafișează coșul folosinduseCartși un buton de plată.
src/components/AddToCartButton.tsx— buton client ("use client") care primește{ code, name, priceLabel }și apeleazăuseCart().addItem({ code, name, priceLabel, quantity: 1 }).
src/lib/useCart.ts— un coș headless cu elemente{ code, name, priceLabel, quantity }, păstrate înlocalStorage, care expuneaddItem/removeItem/updateQty/subtotalși uncheckout()ce trimite{ lines: [{ code, quantity }] }(doar coduri și cantități — niciodată prețuri) către/api/stripe/checkout, apoi redirecționează către URL-ul primit.Stilizează totul cu sistemul nostru de design, ca propriile componente — nu copia aspectul site-ului sursă.
De reținut după rulare:
content ({ content }: BlockComponentProps<T>). Datele catalogului vin din routeParams și dintr-un fetch în catalog.ts, deoarece CEL nu poate conecta liste sau galerii. Coșul transportă coduri, niciodată prețuri.routeParams.<param> este { value, schemaName, document } — citește .value. Citirile returnează published_content, nu .content. Cheile registrului sunt snake_case.@types/react/@types/react-dom la v19 — scaffold-ul include v18, ceea ce provoacă probleme pentru componentele server asincrone cu React 19.Trei fișiere scurte de server — singurul cod de plată al aplicației. Ele reutilizează getItemByCode, astfel încât suma este rezolvată pe server.
src/lib/stripe.ts:
import Stripe from "stripe";
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
src/app/api/stripe/checkout/route.ts — rezolvă fiecare produs din CMS și folosește prețul Stripe:
import { NextResponse } from "next/server";
import { stripe } from "@/lib/stripe";
import { getItemByCode } from "@/lib/catalog";
export async function POST(req: Request) {
const { lines } = await req.json();
const line_items = await Promise.all(
lines.map(async ({ code, quantity }: { code: string; quantity: number }) => {
const item = await getItemByCode(code);
return { price: item!.stripe_price_id, quantity };
})
);
const session = await stripe.checkout.sessions.create({
mode: "payment",
line_items,
success_url: `${process.env.NEXT_PUBLIC_SITE_URL}/cart?status=success`,
cancel_url: `${process.env.NEXT_PUBLIC_SITE_URL}/cart?status=cancelled`,
});
return NextResponse.json({ url: session.url });
}
src/app/api/stripe/webhook/route.ts — semnalul de încredere pentru îndeplinirea comenzii:
import { stripe } from "@/lib/stripe";
export async function POST(req: Request) {
const body = await req.text();
const sig = req.headers.get("stripe-signature")!;
let event;
try {
event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
} catch {
return new Response("Bad signature", { status: 400 });
}
if (event.type === "checkout.session.completed") {
// finalizează comanda: înregistrează comanda / trimite chitanța.
}
return new Response(null, { status: 200 });
}
Corecție necesară pentru scaffold:
src/proxy.tsredirecționează toate rutele/api/*către CMS, astfel încât rutele Stripe nu ar rula. Permite-le mai întâi să treacă local. Verifică folosindcurl -X POST localhost:3000/api/stripe/webhook -d x, care trebuie să returnezeBad signature.
Rulează Stripe CLI pentru webhook-uri locale:
stripe login
stripe listen --forward-to localhost:3000/api/stripe/webhook
# copiază whsec_... în STRIPE_WEBHOOK_SECRET și repornește bun dev
whsec_… este valabil pentru fiecare sesiune. Două reguli asigură securitatea: checkout-ul recalculează prețul din CMS după code (un coș modificat nu poate schimba prețul), iar webhook-ul verifică semnătura folosind corpul brut al cererii.
Admin → Pages → Create page, de trei ori. Asociază fiecare parametru cu componenta sa (câmpul slug code):
/products/{item_code} → item/categories/{category_code} → category/cart — pagină statică (introdu literal /cart, nu /{cart})Pentru fiecare rută: Page Builder → Add UI Element → Custom → adaugă componentele în ordine, completează câmpurile scalare (valoare statică sau CEL) și apasă Publish.
/products/{item_code}: nav, product_detail, footer/categories/{category_code}: nav, product_grid, footer/cart: nav, cart_summary, footerSetează nav.brand și footer.text ca șiruri statice; titlurile pot fi etichete statice.
Particularitate a Page Builder pentru rute parametrice: pe cele două rute parametrice, adăugarea elementelor UI nu se salvează corect (blocurile rămân fără asociere și pagina este goală). Până la remediere, conectează direct
block_idsprin Profound MCPupdate_page, apoi publică. Pagina statică/cartse atașează normal.
/categories/lighting → grila. Apasă pe un produs → detalii și Add to cart. /cart → Pay./cart?status=success, iar stripe listen afișează checkout.session.completed.Doar produsele cu preț sunt cumpărabile — cumpără unul dintre cele aproximativ trei produse pentru care ai creat un preț la pasul 6.
Opțional — internaționalizare. Tradu fiecare componentă (toate cele 35 de limbi simultan), adaugă un segment
/{language}/…asociat componentei de sistemlanguageși schimbă câmpurile conectate prin CEL ladocuments.translated. Consultă tutorialul pentru directorul aeroportului, Partea 2, pasul 7.
git init && git add -A && git commit -m "Stripe storefront"
gh repo create store --private --source=. --push # --public este de asemenea în regulă
Comanda de build:
generated/cms-schemas.tseste ignorat de git, deci configurează build-ul pentru a-l regenera — adaugăvercel.json:{ "$schema": "https://openapi.vercel.sh/vercel.json", "buildCommand": "bun run generate-schemas && next build" }
În Vercel: Add New → Project, importă store și adaugă variabilele de mediu — PROFOUND_API_KEY, NEXT_PUBLIC_PROFOUND_WEBSITE_ID, NEXT_PUBLIC_CMS_API_URL, NEXT_PUBLIC_BUNNY_CDN_URL, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET (valoarea endpointului publicat) și NEXT_PUBLIC_SITE_URL (URL-ul de producție). Implementează aplicația.
Apoi configurează webhook-ul publicat: Stripe → Developers → Webhooks → + Add endpoint → https://<prod>/api/stripe/webhook, evenimentul checkout.session.completed. Copiază whsec_… în Vercel și implementează din nou.
Variabilele de mediu lipsă înseamnă „funcționează local, este gol în producție” — cea mai frecventă problemă la implementare. Aici folosim chei de test; schimbă STRIPE_SECRET_KEY și secretul webhook-ului cu valorile live când ești gata să accepți plăți reale.
Ambele funcționalități sunt incluse în scaffold.
<Refresher> actualizează pagina previzualizată când un editor salvează în panoul de administrare — fără redeploy. Vizitatorii văd conținutul publicat la revalidarea normală.?edit_mode=true la orice URL pentru suprapunerile de editare. Vizitatorii publici văd pagina normală.Adaugă ruta de previzualizare omisă de scaffold. Panoul admin încarcă iframe-ul de previzualizare la
/cms-preview_<path>; fără această rută, toate previzualizările returnează 404. Adaugă ruta și pagina pentru rădăcina segmentului, conform exemplului din scaffold.
Un magazin Stripe bazat pe conținut: un catalog CMS, pagini de listare și detalii pornind de la un singur set de rute și un checkout găzduit funcțional. AI a populat catalogul, a conectat designul și a scris cititorul catalogului, componentele și coșul headless; tu ai creat componentele, legăturile către prețurile Stripe, cele trei rute, elementele CEL și cele trei fișiere Stripe scurte. CEL conectează elementele interfeței; componentele preiau catalogul. Iar Stripe a rămas simplu — un apel sessions.create și un webhook semnat, cumpărătorul plătind pe pagina proprie Stripe.