Praktični vodič: zgradite na vsebini temelječo Stripeovo izložbo na Profound CMS — katalog, ki ga trgovec ureja brez kode, dve parametrični poti, brezglavo košarico in Stripeov gostovani zaključek nakupa.
Končana trgovina v akciji — brskajte po kategoriji, odprite izdelek, dodajte v košarico, zaključite nakup.
Praktični vodič, ki zgradi na vsebinah temelječo trgovino na Profound CMS: katalog izdelkov (kategorije + artikli), modeliran v CMS-ju, seznamne in podrobnostne strani iz istega nabora poti ter Stripeov gostovani zaključek nakupa, dostavljen kot brezglavna komponenta.
Osnova je ročno napisan Next.js skupaj z administracijo Profound. Claude Code (prek Profound MCP) opravi glavnino dela pri treh nalogah — sejanju kataloga, povezovanju dizajnerskega sistema in pisanju komponent za izložbo (vključno z brezglavo košarico). Trije deli: Nastavitev, Gradnja, Produkcija.
Plačila v eni vrstici. Uporabljamo Stripeov gostovani Checkout: kupec plača na Stripeovi strani, ne na vaši. Vaša aplikacija na strežniku naredi le dve stvari — ustvari sejo Checkout in preveri en webhook. Brez polj za kartice, brez Stripe Elements, brez bremena PCI.
/products/{item_code}, /categories/{category_code}) ter statična /cart, vse iz istega nabora komponent.useCart) in Stripeov gostovani zaključek nakupa, pri čemer se cena vedno razreši na strežniku iz Stripeovega ID-ja cene.curl -fsSL https://bun.sh/install | bashstripe login) za lokalne webhoke.gh) in račun Vercel, povezan z GitHubom.Profound loči vsebino od upodabljanja:
category, item); ena, označena kot UI Element, je postavljiva na stran (nav, product_grid, …).meta.params.* v CEL, routeParams v Reactu).cms-renderer; Stripe dodate kot navadne API-poti.Edino pravilo, ki oblikuje gradnjo: CEL veže samo polja string/number. Zato se enostaven okvir (blagovna znamka v navigaciji, noga, naslovi) veže s CEL, medtem ko se vse, kar je bogato ali zbirka (mreža izdelkov, galerija slik, bogato besedilo), pridobi znotraj Reactove komponente glede na parameter poti. In Stripe je vir resnice za cene — CMS price je zgolj prikaz; bremenitev se vedno razreši na strežniku iz Stripeovega ID-ja cene.
Končno stanje: majhen objavljen katalog, aplikacija povezana za branje, Stripe nameščen, dizajn na mestu — še nič se ne izrisuje.
Prijavite se v Profound (WorkOS avtentikacija). Ustvarite spletno mesto z imenom store, nato kopirajte njegov ID spletnega mesta (UUID v administrativnem URL-ju) in API-ključ za raven branja (Deployments → Create API key). Aplikacija le bere; sejanje kataloga kasneje poteka prek MCP, ki se avtenticira ločeno.
bunx create-profound-next store
cd store
bun add stripe
Ogrodje je projekt Next.js App Router, vnaprej povezan s Profound (SDK cms-renderer, lovilna pot, skripta generate-schemas, <Refresher>). Ne priloži slogov.
bun add stripe doda strežniški SDK — edino odvisnost, ki jo gostovani zaključek nakupa potrebuje.
Dodajte svoje vrednosti v .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 # streže slike, gostovane v CMS-ju
# Stripe
STRIPE_SECRET_KEY=sk_test_... # testni ključ tukaj; zamenjajte ga z živim, ko greste v produkcijo
STRIPE_WEBHOOK_SECRET=whsec_... # izpolnite v koraku Gradnja 4
NEXT_PUBLIC_SITE_URL=http://localhost:3000
STRIPE_SECRET_KEY vzemite iz Stripe → Developers → API keys. Uporabljamo testni ključ (sk_test_…), da gradnja ne premika pravega denarja; ko ste pripravljeni na prava plačila, preklopite na živ ključ. Zaženite bun dev in odprite localhost:3000 — začetni projekt se izriše.
Gostovani checkout preusmeri brskalnik na Stripeov URL, zato Stripe potrebuje le strežniški skrivni ključ — brez javnega ključa, brez odjemalskega Stripe SDK.
category, product_image in itemUstvarite tri poljubne komponente (Components → Create new component) — vir podatkov, zato brez oznake UI Element. Vsako nastavite na Active.
CMS nima polja »array of image«, zato je galerija tabela referenc na majhno komponento product_image. Ustvarite category in product_image (ter ju nastavite na Active) pred item — referenčno polje lahko cilja le 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 (array of references → product_image), price (Number, centi — le prikaz), currency (Select, usd), stripePriceId (Text), category (Reference → category), active (Boolean)Vsa polja pustite neobvezna. Administracija pretvori imena polj v spodnjo kačjo pisavo ("Stripe Price Id" → stripe_price_id) — to so imena, na katera se naslanja vaša koda, zato kasneje preberite prava imena iz generate-schemas. Routabilno ročico poimenujemo code (ne slug): je Route Slug in ključ za čist documents.getByCode.
Komponenta item — code kot Route Slug, images kot reference na product_image, plus stripePriceId in referenca na category.
bun run generate-schemas
Ustvari Zod sheme + tipe v generated/cms-schemas.ts (categorySchema/Category, itemSchema/Item). Hkrati preveri povezavo — napačne poverilnice tukaj spodletijo.
MCP namestite in avtenticirajte enkrat:
claude mcp add --transport http Profound http://107.21.107.99:8081/mcp
Zaženite mcp__Profound__authenticate, zaključite WorkOS potek, nato Claudeu napišite:
Ustvari majhen katalog spletne trgovine za trgovino Edison's Inventions — tri kategorije in te izdelke, z kratkim časovno ustreznim
descriptionza vsakega,pricev centih,currency: "usd"inactive: 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 $)Vsaka kategorija potrebuje
namein ta mala črkovnicode; vsak artikel potrebujename, ta mala črkovnicode,description,price(v centih),currencyinactive. Shrani vdata/catalog.jsonin validiraj glede na naši komponenticategoryinitem. Nato uporabi Profound MCP, da ustvari vsak dokument kot objavljen: najprej ustvari kategorije, zabeleži njihove ID-je, nato ustvari artikle zcategory, nastavljeno na referenco —{ "_type": "reference", "_ref": "<category-id>", "_schema": "category" }. Artikle obdelaj vzporedno.
Claude napiše data/catalog.json, ga validira in razpošlje vzporedne klice create_document (status: "published"). Najprej sejte kategorije, da reference kažejo na obstoječe ID-je.
Tri zasejane kategorije, objavljene in Live.
Osem zasejanih izdelkov, vsak povezan s kategorijo.
Katalog je v CMS-ju; zdaj dodelite nekaj izdelkov realni Stripeovi ceni — delo trgovca, opravljeno v dveh administracijskih vmesnikih, brez kode:
price_…).stripePriceId, shranite.CMS hrani katalog; Stripe hrani referenčno ceno; povezava je en niz, ki ga trgovec prilepi. (Želite avtomatizacijo? Uradni Stripe MCP lahko ustvari Products/Prices namesto vas — vrnjene ID-je prilepite enako.)
Ogrodje je neobdelano. Postavite DESIGN.md (Tailwind v4 @theme blok + žetoni) v koren projekta — svojega ali prenesenega z refero.design. Nato Claudeu podajte natančno sporočilo, omejeno na oblikovanje:
Preberi oblikovno datoteko, ki sem jo pravkar dodal. Če je treba, nastavi Tailwind, nato poveži temo in pisave, da bo oblikovanje delovalo. Za pisave uporabi
next/font— ne nalagaj jih iz Googla med izvajanjem. Samo oblikovanje — brez gradnje strani ali komponent.
Preverite, ali src/app/globals.css vsebuje @import "tailwindcss"; + @theme blok in ali localhost:3000 prikazuje žetone. Naj bo poziv kratek (če je preveč odprt, bo agent postavil celotno domačo stran) in nalagajte pisave prek next/font, nikoli z runtime uvozom Google.
Neobvezno — do delujočega zaključka nakupa lahko pridete brez slik. Če jih želite dodati: ustvarite dokument product_image za vsako sliko (s sliko v njenem polju image), nato jih referencirajte iz images posameznega izdelka. Prinesite svoje fotografije ali ustvarite usklajen nabor z modelom za slike (Claude naj izpelje slogovne napotke iz DESIGN.md in zaklene eno Midjourney --sref, da so posnetki skladni).
Samostojni
cms-renderernima pomožnega orodja za URL-je slik, zato dodajte funkcijobuildAssetUrlvsrc/lib/image.ts(~40 vrstic) — doda predponoNEXT_PUBLIC_BUNNY_CDN_URLin pripne končnico. Komponente v koraku Gradnja 3 jo uporabljajo.
Zgradite plast za izris in zaključek nakupa, na koncu opravite resničen nakup v testnem načinu.
Pet komponent, vsaka Active in označena kot UI Element (Settings → Tags), brez Route Slug:
nav → brand · product_grid → heading · product_detail → heading · cart_summary → heading · footer → text (vsa polja tipa Text)Oznaka UI Element je tisto, kar komponento prikaže na seznamu Page Builderja Add UI Element — Active samo po sebi ni dovolj. Vsako polje je skalar (vrsta, ki jo veže CEL); dejanski podatki kataloga niso polje tukaj — ProductGrid/ProductDetail jih pridobita prek parametra poti (korak 3).
bun run generate-schemas
En poziv zgradi bralnik, pet komponent, košarico in register:
Zgradimo izložbo v
src/, z uporabo Profound SDKcms-renderer.
src/lib/catalog.ts— strežniški bralnik CMS. Ustvari odjemalca zgetCmsClient({ 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. IzvozigetItemByCode(code)→cms.documents.getByCode.query({ websiteId, schemaName: "item", code }), ki vrneres.document.published_content. IzvozilistItems(categoryCode?)→cms.documents.list.query({ websiteId, schemaName: "item", status: "published", limit: 100 }), preslikajres.documentsv.published_content, filtrirajactive !== false, in če je podancategoryCode, ohrani artikle, katerihcategory._refje enakdocument.idkategorije. IzvoziresolveImages(refs), ki razreši vsako referencoitem.imagesprekcms.documents.get.query({ websiteId, id: ref._ref })in njegovo slikovno polje pretvori v URL s funkcijobuildAssetUrl(1. del, korak 8).
src/components/— pet komponent UI, registriranih v registru lovilne poti po imenu komponente, v snake_case, da se ujema z administracijo:{ nav, product_grid, product_detail, cart_summary, footer }.NavinFooterprebereta skalarno polje iz prop-acontent(tipBlockComponentProps<T>izcms-renderer/lib/types).ProductGridinProductDetailsta asinhroni strežniški komponenti, ki preberetarouteParamsin pridobita podatke izcatalog.ts:routeParams.<param>je{ value, … }— preberi.value, takoProductGridkličelistItems(routeParams.category_code?.value)(kartice povežejo na/products/{code}),ProductDetailpa kličegetItemByCode(routeParams.item_code?.value)(galerija prekresolveImages, bogato besedilo opisa, cena, gumb Dodaj v košarico).CartSummaryupodobi košarico izuseCartz gumbom za plačilo.formatPricepostavi v čisto datotekosrc/lib/format.ts, da odjemalske komponente ne uvozijo strežniškegacatalog.ts.
src/components/AddToCartButton.tsx— gumb z"use client", ki sprejme{ code, name, priceLabel }in kličeuseCart().addItem({ code, name, priceLabel, quantity: 1 }). Uporabi ga vProductDetail.
src/lib/useCart.ts— brezglava košarica: postavke{ code, name, priceLabel, quantity }v stanju, shranjene vlocalStorage, z metodamiaddItem/removeItem/updateQty/subtotalincheckout(), ki pošljePOSTz{ lines: [{ code, quantity }] }(samo kode in količine — nikoli cen) na/api/stripe/checkout, nato preusmeri na vrnjeniurl.Vse oblikuj z našim dizajnerskim sistemom, kot naše lastne komponente — ne kopiraj postavitve iz izvorne strani.
Tri stvari, ki jih je treba vedeti po končanju:
content ({ content }: BlockComponentProps<T>). Če polja destrukturirate kot zgornje propse, bo blok prazen. Podatki kataloga pridejo iz routeParams + pridobitev v catalog.ts, ker CEL ne veže seznamov ali galerij. Košarica prenaša kode artiklov, nikoli cen.routeParams.<param> je { value, schemaName, document } — preberite .value. Vrnitev daje
published_content, ne .content. Ključi registra so snake_case, da se ujemajo z administracijo.@types/react/@types/react-dom na v19 — ogrodje je priloženo z v18, kar pokvari asinhrone
strežniške komponente v Reactu 19.Tri kratke strežniške datoteke — edina plačilna koda v aplikaciji. Ponovno uporabijo getItemByCode, zato se znesek razreši na strežniku.
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 — razreši vsak artikel iz CMS-ja, zaračuna Stripeovo ceno:
import { NextResponse } from "next/server";
import { stripe } from "@/lib/stripe";
import { getItemByCode } from "@/lib/catalog"; // strežniška koda, ključ za branje
export async function POST(req: Request) {
const { lines } = await req.json(); // [{ code, quantity }] — brez cen s strani odjemalca
const line_items = await Promise.all(
lines.map(async ({ code, quantity }: { code: string; quantity: number }) => {
const item = await getItemByCode(code); // strežnik razreši iz CMS-ja
return { price: item!.stripe_price_id, quantity }; // cena iz CMS-ja, nikoli z odjemalca
})
);
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 }); // odjemalec preusmeri sem
}
src/app/api/stripe/webhook/route.ts — zanesljiv signal za izpolnitev:
import { stripe } from "@/lib/stripe";
export async function POST(req: Request) {
const body = await req.text(); // SUROVO telo — potrebno za preverjanje podpisa
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") {
// izpolni naročilo: zabeleži ga / pošlji potrdilo.
}
return new Response(null, { status: 200 });
}
Potrebni popravek ogrodja:
src/proxy.tsogrodja posreduje vsak/api/*v CMS, zato vaše Stripeove poti nikoli ne tečejo. Najprej jih spustite skozi: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(); // obravnavaj lokalno } return cmsProxy(request as unknown as Parameters<typeof cmsProxy>[0]); }; // obdržite nespremenjen `export const config = { matcher: [...] }` ogrodjaPreverjanje:
curl -X POST localhost:3000/api/stripe/webhook -d xvrneBad signature.
Za lokalne webhoke zaženite Stripe CLI:
stripe login
stripe listen --forward-to localhost:3000/api/stripe/webhook
# kopirajte whsec_... v STRIPE_WEBHOOK_SECRET, ponovno zaženite bun dev
whsec_… je vezan na sejo. Varnost zagotavljata dve pravili: checkout ponovno izpelje ceno iz CMS-ja prek code (spremenjena košarica je ne more spremeniti), webhook pa podpis preveri na podlagi surovega telesa.
Admin → Pages → Create page, trikrat. Vsak parameter preslikajte na komponento (polje slug code):
/products/{item_code} → item/categories/{category_code} → category/cart — statična stran (vnesite dobesedno /cart, ne /{cart})Za vsako pot: Page Builder → Add UI Element → Custom → dodajte komponente po vrsti, izpolnite skalarna polja (statična vrednost ali CEL), Publish.
/products/{item_code}: nav, product_detail, footer/categories/{category_code}: nav, product_grid, footer/cart: nav, cart_summary, footernav.brand in footer.text nastavite na statične nize; naslove na statične oznake.
Page Builder na kategorijski poti — izbran element product_grid, njegov naslov vezan s CEL.
Page Builder na produktni poti — element product_detail na vezavi izdelka.
Težava s parametričnim Page Builderjem: na dveh parametričnih poteh dodani elementi UI ne obstanejo (bloki ostanejo brez povezave in stran je prazna). Dokler ne popravijo, te strani povežite z
block_idsneposredno prek Profound MCPupdate_page, nato objavite. (Statična/cartse pripne normalno.) Iz istega razlogaProductGridizpelje naslov iz kategorije, ki jo pridobi, ne prek CEL.
/categories/lighting → mreža. Kliknite izdelek → podrobnosti + Dodaj v košarico. /cart → Plačaj./cart?status=success, stripe listen pa pokaže checkout.session.completed.Upodobljena produktna stran — galerija, cena in gumb Dodaj v košarico.
Košarica — postavke in en gumb Plačaj s Stripe.
Kupiti je mogoče le izdelke z dodeljeno ceno — kupite enega izmed ~3, ki ste jih cenovno povezali v koraku 6.
Neobvezno — internacionalizirajte. Prevedite vsako komponento (vseh 35 jezikov hkrati), dodajte segment
/{language}/…, preslikan na vgrajeno sistemsko komponentolanguage, in preklopite polja, vezana s CEL, nadocuments.translated. Glejte vodič za letališki imenik, 2. del, korak 7.
git init && git add -A && git commit -m "Stripe storefront"
gh repo create store --private --source=. --push # --public je tudi v redu
Ukaz za gradnjo:
generated/cms-schemas.tsje izključen iz gita, zato je treba gradnjo prisiliti, da ga obnovi — dodajtevercel.json:{ "$schema": "https://openapi.vercel.sh/vercel.json", "buildCommand": "bun run generate-schemas && next build" }
V Vercelu: Add New → Project, uvozite store in dodajte okoljske spremenljivke — PROFOUND_API_KEY, NEXT_PUBLIC_PROFOUND_WEBSITE_ID, NEXT_PUBLIC_CMS_API_URL, NEXT_PUBLIC_BUNNY_CDN_URL, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET (vrednost za nameščeno okolje, spodaj) in NEXT_PUBLIC_SITE_URL (vaš produkcijski URL). Namestite.
Nato povežite produkcijski webhook (lokalni stripe listen je bil le za lokalno okolje): Stripe → Developers → Webhooks → + Add endpoint → https://<prod>/api/stripe/webhook, dogodek checkout.session.completed. Kopirajte whsec_… v Vercel in ponovno namestite.
Manjkajoče okoljske spremenljivke = »dela lokalno, v produkciji prazno« — najpogostejša težava pri namestitvi. Tukaj nameščamo s testnimi ključi; ko ste pripravljeni sprejemati prava plačila, STRIPE_SECRET_KEY in skrivni ključ webhooka preklopite na žive vrednosti.
Oboje je priloženo ogrodju.
<Refresher> posodobi stran, ki jo urednik predogleda, ko v administraciji shrani — brez ponovne namestitve. (To je predogled za urednika; obiskovalci vidijo objavljeno vsebino z običajno revalidacijo.)?edit_mode=true v kateri koli URL za urejevalske sloje. Javnim obiskovalcem se prikaže čista stran.Dodajte predogledno pot, ki je ogrodje ne priloži. Administracija nalaga svoj okvir predogleda na
/cms-preview_<path>; brez te poti vsak predogled vrne 404. Dodajte:// src/app/cms-preview_/[...slug]/page.tsx import { ParametricRoutePreviewPage } from "cms-renderer/lib/renderer"; import { registry } from "../../registry"; // registracijo izvlecite v skupni modul export default async function Page({ params, searchParams }) { const { slug } = await params; ;const PreviewPage = ParametricRoutePreviewPage as any; // asinhroni RSC; tipi React 19 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 tudi
src/app/cms-preview_/page.tsx(enako,slug: []) za korenski segment.
Na vsebini osnovana Stripeova izložba: CMS-katalog, seznamne in podrobnostne strani iz enega nabora poti ter delujoč gostovani zaključek nakupa. UI je zasejal katalog, povezal dizajn in napisal bralnik kataloga + komponente + brezglavo košarico; vi ste pripravili komponente, Stripeove povezave cen, tri poti, kromirane elemente CEL in tri kratke Stripeove datoteke. CEL veže okvir; komponente pridobijo katalog. Stripe je ostal majhen — en klic sessions.create in en podpisani webhook, s kupcem, ki plača na Stripeovi lastni strani.