Hands-on-opastus: rakenna sisältövetoisesti ohjattu Stripe-myymälä Profound CMS:ään — luettelo, jota kauppias muokkaa ilman koodia, kaksi parametrireittiä, headless-ostoskori ja Stripen isännöimä kassaprosessi.
Valmis kauppa liikkeessä — selaa kategoriaa, avaa tuote, lisää ostoskoriin, siirry kassalle.
Käytännön opastus, jossa rakennetaan sisältövetoisesti ohjattu kauppa Profound CMS:ään: tuoteluettelo (kategoriat + tuotteet) mallinnettuna CMS:ään, listaus- ja tuotesivut yhdestä reitityskokonaisuudesta sekä Stripen isännöimä kassaprosessi toimitettuna headless-komponenttina.
Runkona toimii käsin kirjoitettu Next.js yhdistettynä Profoundin hallintaan. Claude Code (Profound MCP:n kautta) hoitaa raskaan työn kolmessa tehtävässä — luettelon siementäminen, design-järjestelmän kytkentä ja myymäläkomponenttien kirjoittaminen (mukaan lukien headless-ostoskori). Kolme vaihetta: Setup, Build, Production.
Maksut yhdellä rivillä. Käytämme Stripen isännöimää Checkoutia: ostaja maksaa Stripen sivulla, ei sinun. Sovelluksesi tekee vain kaksi palvelinpuolen asiaa — luo Checkout Sessionin ja vahvista yhden webhookin. Ei korttikenttiä, ei Stripe Elementsia, ei PCI-taakkaa.
/products/{item_code}, /categories/{category_code}) sekä staattinen /cart, kaikki samasta komponenttijoukosta.useCart) ja Stripen isännöimä kassa, jossa hinta ratkaistaan aina palvelinpuolella Stripe Price ID:n perusteella.curl -fsSL https://bun.sh/install | bashstripe login) paikallisia webhookeja varten.gh) ja GitHubiin yhdistetty Vercel-tili.Profound erottaa sisällön renderöinnistä:
category, item); yksi UI Element -tagattu on sijoitettavissa sivulle (nav, product_grid, …).meta.params.* CEL:ssä, routeParams Reactissa).cms-renderer-kirjaston kautta; Stripe lisätään tavallisina API-reitteinä.Ainoa sääntö, joka ohjaa toteutusta: CEL sitoo vain string/number-kentät. Joten skalaari-kromi (navin brändi, footer, otsikot) sidotaan CEL:llä, kun taas kaikki rikas tai kokoelma (tuoteruudukko, kuvagalleria, rich text) haetaan React-komponentin sisällä reittiparametrin avulla. Ja Stripe on hinnoittelun totuuden lähde — CMS:n price on vain näyttöä varten; veloitus ratkaistaan aina palvelinpuolella Stripe Price ID:stä.
Lopputila: pieni julkaistu luettelo, sovellus kytketty lukemaan sen, Stripe asennettuna, design paikoillaan — mitään ei vielä renderöidä.
Rekisteröidy Profoundissa (WorkOS-tunnistus). Luo sivusto nimeltä store, kopioi sen website ID (UUID admin-URL:ssä) ja read-tier API -avain (Deployments → Create API key). Sovellus ainoastaan lukee; luettelon siemennys tapahtuu myöhemmin MCP:n kautta, joka tunnistautuu erikseen.
bunx create-profound-next store
cd store
bun add stripe
Scaffold on Next.js App Router -projekti, joka on valmiiksi kytketty Profoundiin (cms-renderer-SDK, catch-all-reitti, generate-schemas-skripti, <Refresher>). Se ei sisällä tyylitystä.
bun add stripe hakee palvelin-SDK:n — ainoa maksuriippuvuus, jonka isännöity kassa tarvitsee.
Lisää arvosi tiedostoon .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 # palvelee CMS:n hostaamat kuvat
# Stripe
STRIPE_SECRET_KEY=sk_test_... # testiavain tähän; vaihda live-avaimeen, kun julkaiset
STRIPE_WEBHOOK_SECRET=whsec_... # täytetään Build-vaiheen kohdassa 4
NEXT_PUBLIC_SITE_URL=http://localhost:3000
Hae STRIPE_SECRET_KEY Stripe → Developers → API keys -näkymästä. Käytämme testiavain
(sk_test_…), jotta rakentaminen ei liikuttele oikeaa rahaa; vaihda live-avaimeen, kun olet valmis ottamaan vastaan maksuja. Aja bun dev ja avaa localhost:3000 — aloitussivu renderöityy.
Isännöity kassa ohjaa selaimen Stripe-URL-osoitteeseen, joten palvelimen salainen avain on kaikki mitä Stripe tarvitsee — ei julkista avainta, ei Stripe-asiakas-SDK:ta.
category-, product_image- ja item-komponentitLuo kolme Custom Componentia (Components → Create new component) — tietolähteet, joten ei UI Element -tagia. Aseta jokainen Active.
CMS:ssä ei ole "array of image" -kenttää, joten galleria on viitteiden taulukko pieneen product_image-komponenttiin. Luo category ja product_image (ja aseta Active) ennen item-komponenttia — viitekenttä voi osoittaa vain aktiivisiin komponentteihin.
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 (viitteiden taulukko → product_image), price (Number, sentit — vain näyttöön), currency (Select, usd), stripePriceId (Text), category (Reference → category), active (Boolean)Jätä kaikki kentät valinnaisiksi. Admin muuttaa kenttien nimet pieniksi snake-case -muotoon ("Stripe Price Id" →
stripe_price_id) — koodisi käyttää niitä, joten lue oikeat nimet takaisin generate-schemas-skriptillä myöhemmin. Nimeämme reititettävän tunnisteen code: se on sekä Route Slug että avain siistiin documents.getByCode -hakuun myöhemmin.
item-komponentti — code Route Slugina, images viitteinä product_imageen, lisäksi stripePriceId ja category-viite.
bun run generate-schemas
Kirjoittaa Zod-skeemat + tyypit tiedostoon generated/cms-schemas.ts (categorySchema/Category,
itemSchema/Item). Toimii myös yhteystarkistuksena — väärät tunnukset kaatuvat tässä.
Asenna ja todenna MCP kerran:
claude mcp add --transport http Profound http://107.21.107.99:8081/mcp
Aja mcp__Profound__authenticate, suorita WorkOS-kirjautuminen ja ohjeista Claudea:
Luo pieni verkkokaupan luettelo nimeltä Edison's Inventions — kolme kategoriaa ja nämä tuotteet, joille jokaiselle lyhyt ajanmukainen
description,pricesenteissä,currency: "usd"jaactive: 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 $)Jokaisella kategorialla on
nameja tuo pienicode; jokaisella tuotteellaname, sama pienicode,description,price(senteissä),currencyjaactive. Tallenna se tiedostoondata/catalog.jsonja validoicategory- jaitem-komponenttejamme vastaan. Käytä sitten Profound MCP:tä luodaksesi jokaisen julkaistuna dokumenttina: luo kategoriat ensin, tallenna niiden ID:t, luo sitten tuotteetcategory-kentällä, joka on viite —{ "_type": "reference", "_ref": "<category-id>", "_schema": "category" }. JätästripePriceIdtyhjäksi toistaiseksi. Tee tuotteet rinnakkain.
Claude kirjoittaa data/catalog.json -tiedoston, validoi sen ja lähettää rinnakkaiset create_document-kutsut (status: "published"). Siemennä kategoriat ennen tuotteita, jotta viitteet osoittavat jo olemassa oleviin ID:ihin.
Kolme siemennettyä kategoriaa, julkaistuna ja Live-tilassa.
Kahdeksan siemennettyä tuotetta, kukin linkitettynä kategoriaan.
Luettelo on CMS:ssä; anna nyt muutamalle tuotteelle oikea Stripen hinta — kauppiaan työ, tehdään kahdessa hallintapaneelissa ilman koodia:
price_…).stripePriceId -kenttään, tallenna.CMS säilyttää luettelon; Stripe on hinnan lähde; linkki on yksi merkkijono, jonka kauppias liittää. (Haluatko automatisoida? Virallinen Stripe MCP voi luoda Products/Prices sinulle — liitä palautetut ID:t samalla tavalla.)
Scaffold tulee ilman tyylejä. Aseta DESIGN.md (Tailwind v4 @theme -blokki + tokenit) projektin juureen — oma tai lataa sellainen osoitteesta refero.design.
Pyydä sitten Claudea, rajattuna pelkkään tyylitykseen:
Lue juuri lisäämäni design-tiedosto. Asenna Tailwind tarvittaessa, kytke teema ja fontit niin, että tyylitys toimii. Käytä fontteihin
next/font-kirjastoa — älä lataa niitä Googlen kautta ajonaikaisesti. Vain tyylitys — älä rakenna sivuja tai komponentteja vielä.
Varmista, että src/app/globals.css sisältää @import "tailwindcss"; + @theme-blokki ja että localhost:3000 näyttää tokenit. Pidä pyyntö tarkkana (avoin tehtävä ja agentti scaffoldaa kokonaisen etusivun), ja lataa fontit next/fontin kautta — ei ajonaikaista Google-tuontia.
Valinnainen — pääset toimivaan kassaan ilman kuvia. Lisätäksesi ne: luo yksi product_image -dokumentti kutakin kuvaa kohden (lataa kuva sen image-kenttään), viittaa sitten niihin tuotteen images-arraysta. Tuo omat tuotekuvasi tai generoi yhtenäinen setti kuvamallilla (pyydä Claudea johtamaan brändin mukaiset promptit DESIGN.md:stä ja lukitse yksi Midjourney --sref, jotta jokainen kuva sopii).
Erillinen
cms-rendererei tarjoa image-URL-helperia, joten otabuildAssetUrlkäyttöön tiedostossasrc/lib/image.ts(~40 riviä) — se prefiksaaNEXT_PUBLIC_BUNNY_CDN_URL-osoitteen ja lisää tiedostopäätteen. Build-vaiheen kohdan 3 komponentit käyttävät sitä.
Rakenna renderöinti-kerros ja kassaprosessi, päätyen oikeaan testitilan ostoon.
Viisi komponenttia, kukin Active ja tagattu UI Element -tagilla (Settings → Tags), ei Route Slugia:
nav → brand · product_grid → heading · product_detail → heading ·
cart_summary → heading · footer → text (kaikki Text)UI Element -tagi tekee komponentista näkyvän Page Builderin Add UI Element -listalla — pelkkä Active ei riitä. Jokainen kenttä on skalaari (sellainen, jonka CEL sitoo); varsinainen luettelodata ei ole kenttä täällä — ProductGrid/ProductDetail hakee sen reittiparametrin perusteella (kohta 3).
bun run generate-schemas
Yksi pyyntö rakentaa lukuhelperin, viisi komponenttia, ostoskorin ja rekisterin:
Rakenna myymälämme hakemistoon
src/käyttäen Profoundincms-renderer-SDK:ta.
src/lib/catalog.ts— palvelinpuolen CMS-lukija. Luo asiakas funktiollagetCmsClient({ cmsUrl: process.env.NEXT_PUBLIC_CMS_API_URL!, apiKey: process.env.PROFOUND_API_KEY, websiteId: process.env.NEXT_PUBLIC_PROFOUND_WEBSITE_ID! })moduulistacms-renderer/lib/cms-api. ViegetItemByCode(code)→cms.documents.getByCode.query({ websiteId, schemaName: "item", code }), joka palauttaares.document.published_content. VielistItems(categoryCode?)→cms.documents.list.query({ websiteId, schemaName: "item", status: "published", limit: 100 }), mapres.documents→.published_content, suodataactive !== false, ja joscategoryCodeannetaan, pidä tuotteet, joidencategory._refvastaa kategoriandocument.id:tä. VieresolveImages(refs), joka hakee jokaisenitem.images-viitteencms.documents.get.query({ websiteId, id: ref._ref })-kutsulla ja muuntaa sen kuvan URL:ksi vendoroidunbuildAssetUrl-funktion avulla (osa 1, kohta 8).
src/components/— viisi UI-elementtikomponenttia rekisteröitynä catch-all-reitin rekisteriin komponenttien nimillä, snake_case adminin mukaisesti:{ nav, product_grid, product_detail, cart_summary, footer }.NavjaFooterlukevat skalaarikenttänsäcontent-propista (tyyppiBlockComponentProps<T>moduulistacms-renderer/lib/types).ProductGridjaProductDetailovat async-palvelinkomponentteja, jotka lukevatrouteParams-arvoja ja hakevatcatalog.ts-tiedostosta:routeParams.<param>on{ value, … }— lue.value, jotenProductGridkutsuulistItems(routeParams.category_code?.value)(kortit linkittävät/products/{code}-reitille) jaProductDetailkutsuugetItemByCode(routeParams.item_code?.value)(galleriaresolveImages-funktion kautta, rich text -kuvaus, hinta, Lisää ostoskoriin).CartSummaryrenderöi ostoskorinuseCart:sta Pay-painikkeen kanssa. SäilytäformatPricepuhtaana moduulissasrc/lib/format.ts, jotta asiakaskomponentit eivät tuo palvelinpuolencatalog.ts:ää.
src/components/AddToCartButton.tsx—"use client"-painike, joka ottaa propsit{ code, name, priceLabel }ja kutsuuuseCart().addItem({ code, name, priceLabel, quantity: 1 }). Käytä sitäProductDetail-komponentissa.
src/lib/useCart.ts— headless-ostoskori: rivit{ code, name, priceLabel, quantity }tilassa, tallennettunalocalStorageen, tarjoaaaddItem-,removeItem-,updateQty- jasubtotal-funktiot sekächeckout()-metodin, joka tekee POST-pyynnön{ lines: [{ code, quantity }] }(vain koodit ja määrät — ei koskaan hintoja) reitille/api/stripe/checkoutja ohjaa sitten palautettuunurl-osoitteeseen.Tyylitä kaikki design-järjestelmällämme omilla komponenteillamme — älä kopioi esimerkkisivuston layoutia.
Kolme asiaa tiedettäväksi, kun se on valmis:
content-propista ({ content }: BlockComponentProps<T> ) — purkamalla kentät ylätason propseiksi blokki renderöityy tyhjänä. Luettelodata tulee routeParamsista + catalog.ts -hausta, koska CEL ei sido listoja tai gallerioita. Ostoskori kantaa tuotteiden koodeja, ei hintoja.routeParams.<param> on { value, schemaName, document } — lue .value. Haut palauttavat
published_content, eivät .content. Rekisterin avaimet ovat snake_case adminin mukaisesti.@types/react/@types/react-dom versioon 19 — scaffold toimitetaan versiolla 18, joka rikkoo async-palvelinkomponenttiblokit React 19:n kanssa.Kolme lyhyttä palvelintiedostoa — ainoa maksukoodi sovelluksessa. Ne käyttävät getItemByCode-funktiota, joten veloitus ratkaistaan palvelimella.
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 — ratkaise jokainen tuote CMS:stä, veloita Stripen hinnalla:
import { NextResponse } from "next/server";
import { stripe } from "@/lib/stripe";
import { getItemByCode } from "@/lib/catalog"; // palvelinpuoli, read-tier-avain
export async function POST(req: Request) {
const { lines } = await req.json(); // [{ code, quantity }] — ei hintoja asiakkaalta
const line_items = await Promise.all(
lines.map(async ({ code, quantity }: { code: string; quantity: number }) => {
const item = await getItemByCode(code); // palvelin ratkaisee CMS:stä
return { price: item!.stripe_price_id, quantity }; // hinta CMS:stä, ei asiakkaalta
})
);
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 }); // asiakas uudelleenohjaa tänne
}
src/app/api/stripe/webhook/route.ts — luotettava täyttösignaali:
import { stripe } from "@/lib/stripe";
export async function POST(req: Request) {
const body = await req.text(); // RAaka runko — vaaditaan allekirjoituksen tarkistukseen
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") {
// täytä: kirjaa tilaus / lähetä kuitti.
}
return new Response(null, { status: 200 });
}
Pakollinen scaffold-korjaus: scaffoldin
src/proxy.tsohjaa jokaisen/api/*-pyynnön CMS:ään, joten Stripe-reittisi eivät koskaan toimi. Päästä nämä ensin läpi: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(); // käsittele paikallisesti } return cmsProxy(request as unknown as Parameters<typeof cmsProxy>[0]); }; // pidä scaffoldin `export const config = { matcher: [...] }` muuttumattomanaVarmista:
curl -X POST localhost:3000/api/stripe/webhook -d xpalauttaaBad signature.
Aja Stripe CLI paikallisia webhookeja varten:
stripe login
stripe listen --forward-to localhost:3000/api/stripe/webhook
# kopioi whsec_... STRIPE_WEBHOOK_SECRET -kenttään, käynnistä bun dev uudelleen
whsec_… on sessiokohtainen. Kaksi sääntöä pitää turvallisuuden: checkout johtaa hinnan CMS:stä code-kentän avulla (manipuloitu ostoskori ei voi muuttaa sitä), ja webhook vahvistaa allekirjoituksen raakaan runkoon.
Admin → Pages → Create page, kolme kertaa. Kartoit reitit parametreineen komponentteihin (slug-kenttä code):
/products/{item_code} → item/categories/{category_code} → category/cart — staattinen sivu (syötä kirjaimellisesti /cart, ei /{cart})Jokaiselle reitille: Page Builder → Add UI Element → Custom → lisää komponentit järjestyksessä, anna skalaari-kentille arvot (staattinen arvo tai CEL), Publish.
/products/{item_code}: nav, product_detail, footer/categories/{category_code}: nav, product_grid, footer/cart: nav, cart_summary, footerAseta nav.brand ja footer.text staattisiksi merkkijonoiksi; otsikot staattisiksi nimilapuiksi.
Page Builder kategoriasivulla — product_grid-elementti valittuna, sen heading sidottu CEL:llä.
Page Builder tuotesivulla — product_detail-elementti tuotteen sidonnalla.
Parametrisen Page Builderin sudenkuoppa: kahdella parametrisella reitillä UI-elementtien lisääminen ei pysy (blokit orpoutuvat ja sivu renderöityy tyhjänä). Kunnes se korjataan, kytke noiden sivujen
block_idssuoraan Profound MCP:nupdate_page-kutsun kautta, ja julkaise. (Staattinen/cartkiinnittyy normaalisti.) Samasta syystäProductGridjohtaa otsikkonsa hakemastaan kategoriasta CEL:n sijaan.
/categories/lighting → ruudukko. Klikkaa tuotetta → tuotesivu + Lisää ostoskoriin. /cart → Pay./cart?status=success -sivulle, ja stripe listen näyttää
checkout.session.completed -tapahtuman.Renderöity tuotesivu — galleria, hinta ja Lisää ostoskoriin.
Ostoskori — rivit ja yksi Pay-with-Stripe -painike.
Vain hinnalla varustetut tuotteet ovat ostettavissa — osta jokin niistä ~3 tuotteesta, joille annoit hinnan kohdassa 6.
Valinnainen — kansainvälistä. Käännä jokainen komponentti (kaikki 35 kieltä kerralla), lisää
/{language}/…-segmentti, joka on yhdistetty sisäänrakennettuunlanguage-System-komponenttiin, ja vaihda CEL-sidotut kentätdocuments.translated-muotoon. Katso lentokenttähakemiston opas, osa 2, kohta 7.
git init && git add -A && git commit -m "Stripe storefront"
gh repo create store --private --source=. --push # --public käy myös
Build-komento:
generated/cms-schemas.tson gitignorattu, joten naulaa build generoimaan se uudelleen — lisäävercel.json:{ "$schema": "https://openapi.vercel.sh/vercel.json", "buildCommand": "bun run generate-schemas && next build" }
Vercelissä: Add New → Project, tuo store, ja lisää ympäristömuuttujat — PROFOUND_API_KEY,
NEXT_PUBLIC_PROFOUND_WEBSITE_ID, NEXT_PUBLIC_CMS_API_URL, NEXT_PUBLIC_BUNNY_CDN_URL,
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET (julkaistun päätepisteen arvo, alla) ja
NEXT_PUBLIC_SITE_URL (tuotantourlisi). Julkaise.
Kytke sitten julkaistu webhook (paikallisen stripe listen -komennon salaisuus oli vain paikallinen): Stripe → Developers → Webhooks → + Add endpoint → https://<prod>/api/stripe/webhook, tapahtuma
checkout.session.completed. Kopioi sen whsec_… Verceliin ja julkaise uudelleen.
Puuttuvat ympäristömuuttujat = "toimii lokaalisti, tyhjä tuotannossa" — suurin deploy-sudenkuoppa. Julkaisemme täällä testiavainilla; vaihda STRIPE_SECRET_KEY ja webhook-salaisuus live-arvoihisi, kun olet valmis vastaanottamaan oikeita maksuja.
Molemmat toimitetaan scaffoldin mukana.
<Refresher> päivittää esikatseltavan sivun, kun toimittaja tallentaa muutoksen hallinnassa — ei uudelleenjulkaisua. (Se on esikatselu toimittajalle; vierailijat näkevät julkaistun sisällön normaalilla revalidoinnilla.)?edit_mode=true mihin tahansa URL-osoitteeseen editaatioverlayn saamiseksi. Julkiset vierailijat näkevät puhtaan sivun.Lisää esikatselureitti, jonka scaffold jättää pois. Hallinta lataa esikatselun iframeen osoitteessa
/cms-preview_<path>; ilman tuota reittiä jokainen esikatselu päätyy 404:ään. Lisää se:// src/app/cms-preview_/[...slug]/page.tsx import { ParametricRoutePreviewPage } from "cms-renderer/lib/renderer"; import { registry } from "../../registry"; // vie rekisterisi yhteiseen moduuliin export default async function Page({ params, searchParams }) { const { slug } = await params; const PreviewPage = ParametricRoutePreviewPage as any; // async RSC; React 19 -tyypit 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} />; }Lisää myös
src/app/cms-preview_/page.tsx(sama,slug: []) segmenttijuurelle.
Sisältövetoisesti ohjattu Stripe-myymälä: CMS-luettelo, listaus + tuotesivut yhdestä reittijoukosta ja toimiva isännöity checkout. Tekoäly siemensi luettelon, kytki designin ja kirjoitti luettelolukijan + komponentit + headless-ostoskoriin; sinä teit komponentit, Stripen hintalinkit, kolme reittiä, CEL-kromin ja kolme lyhyttä Stripe-tiedostoa. CEL sitoo kromin; komponentit hakevat luettelon. Ja Stripe pysyi pienenä — yksi sessions.create -kutsu ja yksi allekirjoitettu webhook, ostajan maksaessa Stripen omalla sivulla.