Praktični vodič: izradite Stripe trgovinu vođenu sadržajem na Profound CMS-u — katalog koji trgovac uređuje bez koda, dvije parametarske rute, headless košaricu i naplatu putem Stripea.
Dovršena trgovina u radu — pregledajte kategoriju, otvorite proizvod, dodajte ga u košaricu i dovršite kupnju.
Praktični vodič za izradu trgovine vođene sadržajem na Profound CMS-u: katalog proizvoda (kategorije i stavke) modeliran u CMS-u, stranice popisa i pojedinosti iz jednog skupa ruta te naplata putem Stripea isporučena kao headless komponenta.
Temelj čine ručno napisani Next.js i Profound administracija. Claude Code (putem Profound MCP-a) obavlja većinu posla u tri područja — početno punjenje kataloga, povezivanje sustava dizajna i izrada komponenti trgovine, uključujući headless košaricu. Vodič ima tri dijela: Postavljanje, Izrada, Produkcija.
Plaćanja u jednom retku. Koristimo Stripe-hosted Checkout: kupac plaća na Stripeovoj stranici, a ne na vašoj. Aplikacija na poslužitelju radi samo dvije stvari — stvara Checkout sesiju i provjerava jedan webhook. Nema polja za kartice, Stripe Elementsa ni PCI opterećenja.
/products/{item_code}, /categories/{category_code}) i statičnu rutu /cart, sve iz jednog skupa komponenti.useCart) i naplatu putem Stripea, pri čemu se cijena uvijek razrješava na poslužitelju iz Stripe Price ID-ja.curl -fsSL https://bun.sh/install | bashstripe login) za lokalne webhookove.gh) i Vercel račun povezan s GitHubom.Profound razdvaja sadržaj od prikaza:
category, item); komponenta označena kao UI Element može se postaviti na stranicu (nav, product_grid itd.).meta.params.* u CEL-u, routeParams u Reactu).cms-renderera, a Stripe se dodaje kao obične API rute.Jedino pravilo koje oblikuje izradu: CEL povezuje samo polja tipa string/number. Zato se skalarni elementi sučelja (brand navigacije, podnožje i naslovi) povezuju CEL-om, dok se obogaćeni sadržaj ili zbirke (mreža proizvoda, galerija slika i obogaćeni tekst) dohvaćaju unutar React komponente pomoću parametra rute. Stripe je izvor istine za cijene — CMS price služi samo za prikaz, a stvarna se naplata uvijek razrješava na poslužitelju iz Stripe Price ID-ja.
Krajnji rezultat: mali objavljeni katalog, aplikacija povezana s njim, instaliran Stripe i postavljen dizajn — još se ništa ne prikazuje.
Registrirajte se na Profoundu (WorkOS autentikacija). Izradite web-mjesto naziva store, zatim kopirajte njegov ID web-mjesta (UUID u URL-u administracije) i API ključ za čitanje (Deployments → Create API key). Aplikacija samo čita podatke; punjenje kataloga kasnije se obavlja putem MCP-a, koji se autentificira zasebno.
bunx create-profound-next store
cd store
bun add stripe
Predložak je Next.js App Router projekt unaprijed povezan s Profoundom (cms-renderer SDK, catch-all ruta, skripta generate-schemas i <Refresher>). Ne sadrži stilove. bun add stripe dodaje poslužiteljski SDK — jedinu ovisnost potrebnu za hosted checkout.
Dodajte vrijednosti u .env.local:
# CMS
PROFOUND_API_KEY=<vaš ključ za čitanje>
NEXT_PUBLIC_PROFOUND_WEBSITE_ID=<vaš ID web-mjesta>
NEXT_PUBLIC_CMS_API_URL=https://cms.dev.tryprofound.com
NEXT_PUBLIC_BUNNY_CDN_URL=https://cms-profound.b-cdn.net # poslužuje slike iz CMS-a
# Stripe
STRIPE_SECRET_KEY=sk_test_... # ovdje testni ključ; pri objavi zamijenite ga produkcijskim
STRIPE_WEBHOOK_SECRET=whsec_... # popunjava se u 4. koraku izrade
NEXT_PUBLIC_SITE_URL=http://localhost:3000
STRIPE_SECRET_KEY preuzmite u Stripeu → Developers → API keys. Koristite testni ključ (sk_test_…) kako se tijekom izrade ne bi premještao stvarni novac; pri spremnosti za stvarna plaćanja prijeđite na produkcijski ključ. Pokrenite bun dev i otvorite localhost:3000 — prikazat će se početna aplikacija.
Hosted checkout preusmjerava preglednik na Stripeov URL, pa je poslužiteljski tajni ključ sve što Stripeu treba — nema javnog ključa ni klijentskog Stripe SDK-a.
category, product_image i itemIzradite tri Custom Componenta (Components → Create new component) — oni su izvor podataka i ne trebaju oznaku UI Element. Svaki postavite kao Active.
CMS nema polje "array of image", pa je galerija niz referenci na malu komponentu product_image. Izradite category i product_image (i postavite ih kao Active) prije komponente item, jer referentno polje može ciljati samo aktivne komponente.
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 (niz referenci → product_image), price (Number, centi — samo za prikaz), currency (Select, usd), stripePriceId (Text), category (Reference → category), active (Boolean)Sva polja ostavite neobaveznima. Administracija pretvara nazive polja u lower snake case ("Stripe Price Id" → stripe_price_id) — to su ključevi koje koristi kod, pa stvarne nazive provjerite nakon generate-schemas. Routabilni ključ nazivamo code, a ne slug: on je Route Slug i ključ za kasniji čisti poziv documents.getByCode.
Komponenta item — code kao Route Slug, images kao reference na product_image, uz stripePriceId i referencu category.
bun run generate-schemas
Zapisuje Zod sheme i tipove u generated/cms-schemas.ts (categorySchema/Category, itemSchema/Item). Služi i kao provjera veze — pogrešne vjerodajnice ovdje će uzrokovati pogrešku.
Jednom instalirajte i autentificirajte MCP:
claude mcp add --transport http Profound http://107.21.107.99:8081/mcp
Pokrenite mcp__Profound__authenticate, dovršite WorkOS postupak, a zatim Claudeu zadajte:
Izradi mali katalog za e-trgovinu pod nazivom Edison's Inventions — tri kategorije i sljedeće proizvode, za svaki kratki povijesno odgovarajući
description,priceu centima,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)Svaka kategorija treba imati
namei navedeni kod malim slovima; svaka stavka treba imatiname, kod,description,priceu centima,currencyiactive. Spremi podatke udata/catalog.jsoni provjeri ih prema komponentamacategoryiitem. Zatim putem Profound MCP-a izradi svaku stavku kao objavljeni dokument: prvo izradi kategorije, zabilježi njihove ID-jeve, a zatim izradi stavke s referencomcategory—{ "_type": "reference", "_ref": "<category-id>", "_schema": "category" }. Za sada ostavistripePriceIdpraznim. Stavke izradi paralelno.
Claude zapisuje data/catalog.json, provjerava ga i paralelno poziva create_document (status: "published"). Kategorije izradite prije stavki kako bi reference pokazivale na već postojeće ID-jeve.
Tri početno dodane kategorije, objavljene i uživo.
Osam početno dodanih proizvoda, svaki povezan s kategorijom.
Katalog je u CMS-u; sada nekoliko proizvoda treba dobiti stvarnu Stripe cijenu — posao trgovca koji se obavlja u dvije administracije, bez koda:
price_…).stripePriceId i spremite.CMS sadrži katalog, Stripe službenu cijenu, a poveznica je jedan niz koji trgovac zalijepi. Za automatizaciju možete upotrijebiti službeni Stripe MCP, koji može izraditi proizvode i cijene; vraćene ID-jeve zalijepite na isti način.
Predložak nema stilove. U korijen projekta dodajte DESIGN.md (blok Tailwind v4 @theme i tokene), vlastiti ili preuzet s refero.design. Zatim Claudeu zadajte samo zadatke vezane uz stilove:
Pročitaj datoteku dizajna koju sam upravo dodao. Ako je potrebno, postavi Tailwind, a zatim poveži temu i fontove tako da stilovi rade. Za fontove koristi
next/font— nemoj ih učitavati iz Googlea tijekom rada. Radi samo stiliziranje; još nemoj izrađivati stranice ni komponente.
Provjerite da src/app/globals.css sadrži @import "tailwindcss"; i blok @theme, te da localhost:3000 prikazuje tokene. Fontove učitavajte putem next/font, nikad putem Google importa tijekom rada.
Slike nisu nužne za funkcionalnu naplatu. Za njihovu upotrebu izradite jedan dokument product_image po slici, učitajte sliku u polje image, a zatim ih navedite u nizu images proizvoda. Možete koristiti vlastite fotografije ili generirati usklađeni skup pomoću modela za slike.
Samostalni
cms-renderernema pomoćnu funkciju za URL slike, pa prenesitebuildAssetUrlusrc/lib/image.ts(otprilike 40 redaka). Ona dodaje prefiksNEXT_PUBLIC_BUNNY_CDN_URLi ekstenziju. Komponente u 3. koraku izrade koriste je.
Izradite sloj prikaza i naplatu te završite stvarnom kupnjom u testnom načinu rada.
Izradite pet komponenti, svaku postavite kao Active i označite kao UI Element (Settings → Tags), bez Route Sluga:
nav → brand · product_grid → heading · product_detail → heading · cart_summary → heading · footer → text (sve Text)Oznaka UI Element omogućuje pojavljivanje komponente na popisu Add UI Element u Page Builderu — samo Active nije dovoljno. Svako polje je skalarno, odnosno ono koje CEL može povezati; stvarni podaci kataloga nisu polje ovdje — ProductGrid i ProductDetail dohvaćaju ih prema parametru rute.
bun run generate-schemas
Jednim upitom izradite pomoćnik za čitanje, pet komponenti, košaricu i registar:
Izgradi našu trgovinu u
src/koristeći Profoundovcms-rendererSDK.
src/lib/catalog.ts— poslužiteljski čitač CMS-a. Izradi klijent pomoćugetCmsClient({ cmsUrl: process.env.NEXT_PUBLIC_CMS_API_URL!, apiKey: process.env.PROFOUND_API_KEY, websiteId: process.env.NEXT_PUBLIC_PROFOUND_WEBSITE_ID! })izcms-renderer/lib/cms-api. IzvezigetItemByCode(code)koji pozivacms.documents.getByCode.query({ websiteId, schemaName: "item", code })i vraćares.document.published_content. IzvezilistItems(categoryCode?)koji pozivacms.documents.list.query({ websiteId, schemaName: "item", status: "published", limit: 100 }), mapirares.documentsna.published_content, filtriraactive !== false, a ako je zadancategoryCode, zadržava stavke čijicategory._refodgovara ID-ju dokumenta kategorije. IzveziresolveImages(refs)koji svaku referencuitem.imagesrazrješava prekocms.documents.get.query({ websiteId, id: ref._ref })i njezino polje slike pretvara u URL pomoću prenesenogbuildAssetUrla.
src/components/— pet UI komponenti registriranih u registru catch-all rute prema nazivu komponente, snake_case oblikom kao u administraciji:{ nav, product_grid, product_detail, cart_summary, footer }.NaviFooterčitaju skalarno polje iz svojstvacontent(tipiziranog sBlockComponentProps<T>izcms-renderer/lib/types).ProductGridiProductDetailsu asinkrone poslužiteljske komponente koje čitajurouteParamsi dohvaćaju podatke izcatalog.ts:routeParams.<param>je{ value, … }— čitajte.value.ProductGridpozivalistItems(routeParams.category_code?.value), kartice vode na/products/{code}, aProductDetailpozivagetItemByCode(routeParams.item_code?.value), dohvaća galeriju putemresolveImages, prikazuje obogaćeni opis i cijenu te gumb za dodavanje u košaricu.CartSummaryprikazuje košaricu izuseCarti gumb Pay. DržiteformatPriceu čistomsrc/lib/format.tskako klijentske komponente ne bi uvozile poslužiteljskicatalog.ts.
src/components/AddToCartButton.tsx— gumb s direktivom"use client"koji prima{ code, name, priceLabel }i pozivauseCart().addItem({ code, name, priceLabel, quantity: 1 }). Upotrijebite ga uProductDetail.
src/lib/useCart.ts— headless košarica: stavke{ code, name, priceLabel, quantity }u stanju, spremljene ulocalStorage, s funkcijamaaddItem/removeItem/updateQty/subtotali funkcijomcheckout()koja POST-a{ lines: [{ code, quantity }] }(samo kodovi i količine — nikad cijene) na/api/stripe/checkout, a zatim preusmjerava na vraćeniurl.Sve stiliziraj našim sustavom dizajna kao vlastite komponente — nemoj kopirati raspored izvorne stranice.
Važne napomene:
content ({ content }: BlockComponentProps<T>); kataloški podaci dolaze iz routeParams i dohvaćanja u catalog.ts, jer CEL ne može povezati popise ni galerije. Košarica sadrži kodove stavki, nikad cijene.routeParams.<param> je { value, schemaName, document } — čitajte .value. Čitanja vraćaju published_content, a ne .content. Ključevi registra moraju biti snake_case kao u administraciji.@types/react i @types/react-dom na v19 — predložak dolazi s verzijom 18, koja uzrokuje probleme s asinkronim serverskim komponentama i Reactom 19.Tri kratke poslužiteljske datoteke čine sav kod za plaćanje. Ponovno koriste getItemByCode, pa se naplata razrješava na poslužitelju.
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 razrješava svaku stavku iz CMS-a i naplaćuje Stripe cijenu:
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 pouzdan je signal za ispunjavanje narudžbe:
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") {
// ispunite narudžbu: zabilježite je / pošaljite račun.
}
return new Response(null, { status: 200 });
}
Obavezni popravak predloška:
src/proxy.tspredloška prosljeđuje svaki/api/*CMS-u, pa se Stripe rute nikad ne izvršavaju. Prvo im omogućite lokalnu obradu:import { createCmsProxy } from "cms-renderer/lib/proxy"; import { NextResponse, type NextRequest } from "next/server"; import { cmsConfig } from "@/lib/cms-config"; const cmsProxy = createCmsProxy({ upstream: cmsConfig.cmsUrl }); const LOCAL_API_PREFIXES = ["/api/stripe"]; export const proxy = async (request: NextRequest) => { if (LOCAL_API_PREFIXES.some((p) => request.nextUrl.pathname.startsWith(p))) { return NextResponse.next(); } return cmsProxy(request as unknown as Parameters<typeof cmsProxy>[0]); }; // export const config = { matcher: [...] } ostavite nepromijenjenimProvjerite:
curl -X POST localhost:3000/api/stripe/webhook -d xvraćaBad signature.
Za lokalne webhookove pokrenite Stripe CLI:
stripe login
stripe listen --forward-to localhost:3000/api/stripe/webhook
# kopirajte whsec_... u STRIPE_WEBHOOK_SECRET i ponovno pokrenite bun dev
whsec_… vrijedi za pojedinu sesiju. Sigurnost se oslanja na dva pravila: checkout ponovno dohvaća cijenu iz CMS-a prema code, pa izmijenjena košarica ne može promijeniti cijenu, a webhook provjerava potpis nad izvornim tijelom zahtjeva.
Admin → Pages → Create page, tri puta. Svaki parametar povežite s komponentom (polje sluga je code):
/products/{item_code} → item/categories/{category_code} → category/cart — statična stranica (upišite doslovno /cart, a ne /{cart})Za svaku rutu otvorite Page Builder → Add UI Element → Custom, dodajte komponente redom, ispunite skalarna polja (statičnom vrijednošću ili CEL-om) i objavite.
/products/{item_code}: nav, product_detail, footer/categories/{category_code}: nav, product_grid, footer/cart: nav, cart_summary, footernav.brand i footer.text postavite kao statične nizove, a naslove kao statične oznake.
Page Builder na ruti kategorije — odabran je element product_grid, a naslov je povezan CEL-om.
Page Builder na ruti proizvoda — element product_detail povezan je s proizvodom.
Napomena za parametarski Page Builder: na dvije parametarske rute dodavanje UI elemenata se ne sprema (blokovi ostaju bez vlasnika i stranica se prikazuje prazna). Dok se problem ne popravi, njihove
block_idspostavite izravno pomoću Profound MCP-aupdate_page, a zatim objavite. Statična ruta/cartnormalno prihvaća elemente. Iz istog razlogaProductGridnaslov izvodi iz dohvaćene kategorije, a ne putem CEL-a.
/categories/lighting → mreža proizvoda. Kliknite proizvod → pojedinosti i Add to cart. /cart → Pay./cart?status=success, a stripe listen prikazuje checkout.session.completed.Prikazana stranica proizvoda — galerija, cijena i dodavanje u košaricu.
Košarica — stavke i jedan gumb za plaćanje putem Stripea.
Kupovati se mogu samo proizvodi s cijenom — kupite jedan od približno tri proizvoda kojima ste dodijelili cijenu u 6. koraku.
Neobavezno — internacionalizacija. Prevedite svaku komponentu (svih 35 jezika odjednom), dodajte segment
/{language}/…povezan s ugrađenom System komponentomlanguagei polja povezana CEL-om prebacite nadocuments.translated. Pogledajte vodič za direktorij zračne luke, 2. dio, 7. korak.
git init && git add -A && git commit -m "Stripe storefront"
gh repo create store --private --source=. --push # --public je također u redu
Naredba za izgradnju:
generated/cms-schemas.tsnalazi se u gitignoreu, pa izgradnju treba postaviti tako da ga ponovno generira — dodajtevercel.json:{ "$schema": "https://openapi.vercel.sh/vercel.json", "buildCommand": "bun run generate-schemas && next build" }
U Vercelu odaberite Add New → Project, uvezite store i dodajte varijable okruženja — PROFOUND_API_KEY, NEXT_PUBLIC_PROFOUND_WEBSITE_ID, NEXT_PUBLIC_CMS_API_URL, NEXT_PUBLIC_BUNNY_CDN_URL, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET (vrijednost za objavljenu krajnju točku) i NEXT_PUBLIC_SITE_URL (produkcijski URL). Zatim objavite.
Povežite objavljeni webhook (tajna vrijednost iz lokalnog stripe listen bila je samo lokalna): Stripe → Developers → Webhooks → + Add endpoint → https://<prod>/api/stripe/webhook, događaj checkout.session.completed. Kopirajte njegov whsec_… u Vercel i ponovno objavite.
Nedostajuće varijable okruženja uzrokuju problem "radi lokalno, prazno je u produkciji" — najčešću pogrešku pri objavi. Ovdje se objavljujemo s testnim ključevima; kad budete spremni za stvarna plaćanja, zamijenite STRIPE_SECRET_KEY i webhook tajnu produkcijskim vrijednostima.
Obje mogućnosti dolaze s predloškom.
<Refresher> ažurira stranicu koju pregledavate kada urednik spremi promjene u administraciji — bez ponovne objave. Posjetitelji uobičajeno vide objavljeni sadržaj nakon ponovne validacije.?edit_mode=true za slojeve za uređivanje. Javni posjetitelji vide čistu stranicu.Dodajte rutu za pregled koju predložak izostavlja. Administracija učitava iframe pregleda na
/cms-preview_<path>; bez te rute svaki pregled vraća 404. Dodajte je:// src/app/cms-preview_/[...slug]/page.tsx import { ParametricRoutePreviewPage } from "cms-renderer/lib/renderer"; import { registry } from "../../registry"; export default async function Page({ params, searchParams }) { const { slug } = await params; const PreviewPage = ParametricRoutePreviewPage as any; return <PreviewPage registry={registry} apiKey={process.env.PROFOUND_API_KEY ?? ""} websiteId={process.env.NEXT_PUBLIC_PROFOUND_WEBSITE_ID ?? ""} cmsUrl={process.env.NEXT_PUBLIC_CMS_API_URL ?? "https://cms.dev.tryprofound.com"} params={Promise.resolve({ slug })} searchParams={searchParams} />; }Dodajte i
src/app/cms-preview_/page.tsx(isto, sslug: []) za korijen segmenta.
Stripe trgovina vođena sadržajem: katalog u CMS-u, stranice popisa i pojedinosti iz jednog skupa ruta te funkcionalna hosted naplata. Umjetna inteligencija početno je napunila katalog, povezala dizajn i napisala čitač kataloga, komponente i headless košaricu; vi ste izradili komponente, poveznice Stripe cijena, tri rute, CEL elemente sučelja i tri kratke Stripe datoteke. CEL povezuje elemente sučelja, a komponente dohvaćaju katalog. Stripe je ostao malen — jedan poziv sessions.create i jedan potpisani webhook, dok kupac plaća na Stripeovoj vlastitoj stranici.