profound-logoProfound CMS
⌘K
Admin
Theme
DocsTutorialBlogPhilosophy
DocsTutorialBlogPhilosophy

Tutorials

Build & Ship an Airport DirectoryDeploymentsBuild & Ship a Stripe Storefront

Feature

Documentation Site TemplateFeature Template BuilderTranslation ServiceOrganizations & Website HeirarchyConnect Profound CMS to your AI clientSettings Integrationsसेटिंग्स API कुंजियाँSettings UsageSettings Websites
All Systems Operational
Powered Byprofound-logo
Theme

Build & Ship a Stripe Storefront

एक व्यावहारिक मार्गदर्शिका: Profound CMS पर एक कंटेंट-प्रधान Stripe स्टोरफ्रंट बनाएं — ऐसा कैटलॉग जिसे व्यापारी बिना कोड के संपादित करे, दो पैरामीट्रिक रूट, हेडलेस कार्ट, और Stripe-होस्टेड चेकआउट।

पूर्ण हुआ स्टोर चलते हुए — एक श्रेणी ब्राउज़ करें, उत्पाद खोलें, कार्ट में जोड़ें, चेकआउट करें।

एक व्यावहारिक मार्गदर्शिका जो Profound CMS पर एक कंटेंट-प्रधान स्टोर बनाती है: उत्पाद कैटलॉग (श्रेणियाँ + आइटम) जिसे CMS में मॉडल किया गया है, एक ही सेट की रूटों से सूची और विवरण पेज, और Stripe-होस्टेड चेकआउट जिसे हेडलेस घटक के रूप में भेजा गया है।

रीढ़ है हाथ से लिखी Next.js और Profound एडमिन। Claude Code (Profound MCP के माध्यम से) तीन कामों पर भारी उठान करता है — कैटलॉग को सीड करना, डिज़ाइन सिस्टम को जोड़ना, और स्टोरफ्रंट घटकों को लिखना (हेडलेस कार्ट सहित)। तीन भाग: सेटअप, बिल्ड, प्रोडक्शन।

एक लाइन में पेमेंट। हम Stripe-होस्टेड Checkout का उपयोग करते हैं: खरीदार भुगतान Stripe के पेज पर करता है, आपके नहीं। आपका ऐप सिर्फ दो सर्वर-साइड काम करता है — एक Checkout Session बनाना और एक webhook सत्यापित करना। कोई कार्ड फ़ील्ड नहीं, कोई Stripe Elements नहीं, कोई PCI भार नहीं।

आप क्या बनाएँगे

  • CMS में एक छोटा प्रकाशित कैटलॉग — तीन श्रेणियाँ और आठ उत्पाद ("Edison's Inventions" डेमो) — प्रत्येक को व्यापारी बिना कोड के संपादित कर सकता है।
  • दो पैरामीट्रिक रूट (/products/{item_code}, /categories/{category_code}) और एक स्थिर /cart, सब एक ही सेट के घटकों से।
  • एक हेडलेस कार्ट (useCart) और Stripe-होस्टेड चेकआउट, जिसमें मूल्य हमेशा सर्वर-साइड Stripe Price ID से तय होता है।
  • स्टोर को Vercel पर लाइव प्रीव्यू और टीम के लिए इन-प्लेस एडिटिंग के साथ डिप्लॉय करना।

पूर्वापेक्षित

  • Bun ≥ 1.3 — curl -fsSL https://bun.sh/install | bash
  • Claude Code के साथ Profound MCP (भाग 1 इसे स्थापित करता है)।
  • एक Profound CMS खाता।
  • एक Stripe खाता। यह ट्यूटोरियल टेस्ट मोड में चलता है ताकि आप बनाते समय वास्तविक पैसे चार्ज न हों — लेकिन प्रवाह लाइव कुंजियों के साथ समान है, तो यदि चाहें तो अपनी वास्तविक खाता कुंजियाँ उपयोग करें। (टेस्ट मोड को किसी व्यवसाय या बैंक विवरण की आवश्यकता नहीं होती।)
  • Stripe CLI (stripe login) स्थानीय वेबहुक के लिए।
  • डिप्लॉय के लिए: GitHub CLI (gh) और GitHub से जुड़ा एक Vercel खाता।

टुकड़े कैसे एक-दूसरे से मेल खाते हैं

Profound कंटेंट को रेंडरिंग से अलग करता है:

  • कंपोनेंट्स कंटेंट का आकार परिभाषित करते हैं। एक Custom Component जिसमें Route Slug फ़ील्ड है, रूट योग्य होता है (category, item); एक टैग किए गए UI Element को पेज पर रखा जा सकता है (nav, product_grid, …)।
  • डॉक्यूमेंट्स कंटेंट हैं (एक उत्पाद, एक श्रेणी)।
  • UI एलिमेंट्स पेज सेक्शन हैं; प्रत्येक स्केलर फ़ील्ड एक स्थिर मान या CEL एक्सप्रेशन लेता है, जिसे रेंडर पर मूल्यांकन किया जाता है।
  • पैरामीट्रिक रूट किसी URL को डॉक्यूमेंट + UI एलिमेंट्स से मैप करते हैं, रूट पैराम (meta.params.* CEL में, routeParams React में) पास करते हुए।
  • आपका Next.js ऐप इसे cms-renderer के माध्यम से पढ़ता है; Stripe को साधारण API रूट के रूप में जोड़ा जाता है।

बिल्ड को आकार देने वाला एक नियम: CEL सिर्फ string/number फ़ील्ड्स को बाँधता है। इसलिए स्केलर क्रोम (नेव ब्रांड, फुटर, हेडिंग्स) CEL से बाउंड होती है, जबकि अत्यधिक या संग्रहवादी चीज़ें (उत्पाद ग्रिड, इमेज गैलरी, रिच टेक्स्ट) को React कंपोनेंट के अंदर रूट पैराम द्वारा फ़ेच किया जाता है। और Stripe मूल्य निर्धारण का सत्य का स्रोत है — CMS का price केवल प्रदर्शित करने के लिए है; शुल्क हमेशा सर्वर-साइड Stripe Price ID से तय होता है।

भाग 1 — सेटअप

अंतिम स्थिति: एक छोटा प्रकाशित कैटलॉग, ऐप इसे पढ़ने के लिए जुड़ा हुआ, Stripe स्थापित, डिज़ाइन तैयार — अभी कुछ रेंडर नहीं हुआ है।

1. साइन अप करें और वेबसाइट बनाएँ

Profound पर साइन अप करें (WorkOS प्रमाणीकरण)। store नामक एक वेबसाइट बनाएँ, फिर उसका website ID (एडमिन URL में UUID) और एक रीड-स्तर का API की (Deployments → Create API key) कॉपी करें। ऐप केवल पढ़ता है; बाद में कैटलॉग सीड MCP से गुजरता है, जो अलग से प्रमाणित करता है।

2. ऐप स्कैफ़ोल्ड करें, कनेक्ट करें, और Stripe जोड़ें

bunx create-profound-next store
cd store
bun add stripe

स्कैफ़ोल्ड एक Next.js App Router प्रोजेक्ट है जो Profound के लिए पहले से वायर्ड है (cms-renderer SDK, एक कैच-ऑल रूट, generate-schemas स्क्रिप्ट, <Refresher>)। इसमें कोई स्टाइलिंग शिप नहीं होती। bun add stripe सर्वर SDK लाता है — होस्टेड चेकआउट को यही भुगतान निर्भरता चाहिए।

अपने मान .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   # CMS द्वारा होस्ट की गई छवियों को सर्व करता है

# Stripe
STRIPE_SECRET_KEY=sk_test_...            # यहाँ टेस्ट कुंजी; लाइव होने पर अपनी लाइव कुंजी लगाएँ
STRIPE_WEBHOOK_SECRET=whsec_...          # बिल्ड स्टेप 4 में भरा जाएगा
NEXT_PUBLIC_SITE_URL=http://localhost:3000

STRIPE_SECRET_KEY Stripe → Developers → API keys से लें। हम टेस्ट कुंजी (sk_test_…) का उपयोग करते हैं ताकि बिल्ड वास्तविक पैसे ट्रांसफर न करे; जब आप वास्तविक भुगतान लेने के लिए तैयार हों तो अपनी लाइव कुंजी पर स्विच करें। bun dev चलाएँ और localhost:3000 खोलें — स्टार्टर रेंडर होता है।

होस्टेड चेकआउट ब्राउज़र को Stripe URL पर रीडायरेक्ट करता है, इसलिए सर्वर सीक्रेट कुंजी ही Stripe को चाहिए — कोई publishable key नहीं, कोई client Stripe SDK नहीं।

3. category, product_image, और item कंपोनेंट्स परिभाषित करें

तीन Custom Components बनाएँ (Components → Create new component) — ये डेटा स्रोत हैं, इसलिए कोई UI Element टैग नहीं। प्रत्येक को Active सेट करें।

CMS में "array of image" फ़ील्ड नहीं है, इसलिए गैलरी संकेतों के array के रूप में होती है जो छोटे product_image कंपोनेंट को संदर्भित करती है। item से पहले category और product_image (और उन्हें Active करें) बनाएँ — संदर्भ फ़ील्ड केवल 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 → product_image), price (Number, सेंट्स में — केवल प्रदर्शन), currency (Select, usd), stripePriceId (Text), category (Reference → category), active (Boolean)

सभी फ़ील्ड वैकल्पिक छोड़ें। एडमिन फ़ील्ड नामों को लोअर-स्नेक-केस करता है ("Stripe Price Id" → stripe_price_id) — आपका कोड इन्हीं नामों पर निर्भर करता है, इसलिए उन्हें generate-schemas से वापस पढ़ें। हम रूट योग्य हैंडल को code नाम देते हैं (ना कि slug): यह Route Slug भी है और बाद में साफ़ documents.getByCode लुकअप के लिए कुंजी भी।

item कंपोनेंट — Route Slug के रूप में code, product_image संदर्भों के रूप में images, साथ ही stripePriceId और category संदर्भ।

4. कंपोनेंट्स को स्थानीय टाइप्स में खींचें

bun run generate-schemas

generated/cms-schemas.ts में Zod स्कीमा + टाइप्स लिखता है (categorySchema/Category, itemSchema/Item)। यह कनेक्शन जाँच के रूप में भी काम करता है — गलत क्रेडेंशियल्स यहाँ विफल होंगे।

5. Profound MCP के माध्यम से कैटलॉग सीड करें

एक बार MCP इंस्टॉल करें और प्रमाणीकरण करें:

claude mcp add --transport http Profound http://107.21.107.99:8081/mcp

mcp__Profound__authenticate चलाएँ, WorkOS फ़्लो पूरा करें, फिर Claude से कहें:

Edison's Inventions नामक स्टोर के लिए एक छोटा ईकॉमर्स कैटलॉग जनरेट करें — तीन श्रेणियाँ और ये उत्पाद, प्रत्येक के लिए एक छोटा दौर-विशिष्ट description, सें्ट्स में price, currency: "usd", और active: 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)

प्रत्येक श्रेणी को name और वही लोअरकेस code चाहिए; प्रत्येक आइटम को name, वही लोअरकेस code, description, price (सेंट्स में), currency, और active चाहिए। इसे data/catalog.json में सहेजें और हमारे category और item कंपोनेंट्स के विरुद्ध सत्यापित करें। फिर Profound MCP का उपयोग करके प्रत्येक को प्रकाशित डॉक्यूमेंट के रूप में बनाएँ: पहले श्रेणियाँ बनाएँ, उनके IDs कैप्चर करें, फिर आइटम बनाएँ जिनमें category को संदर्भ सेट करें — { "_type": "reference", "_ref": "<category-id>", "_schema": "category" }। stripePriceId अभी खाली छोड़ें। आइटम्स को समानांतर में करें।

Claude data/catalog.json लिखता है, उसे सत्यापित करता है, और समानांतर create_document कॉल्स (status: "published") चलाता है। संदर्भ सही IDs की ओर जाएँ इसलिए आइटम से पहले श्रेणियाँ सीड करें।

तीन सीड की गई श्रेणियाँ, प्रकाशित और लाइव।

आठ सीड किए गए उत्पाद, प्रत्येक एक श्रेणी से जुड़े हुए।

6. Stripe Prices बनाएँ और कुछ उत्पाद जोड़ें

कैटलॉग CMS में है; अब कुछ उत्पादों को वास्तविक Stripe मूल्य दें — व्यापारी का काम, दो एडमिन पैनल में, कोई कोड नहीं:

  1. Stripe डैशबोर्ड → Products → + Add product, एक बार का मूल्य सेट करें, Price ID (price_…) कॉपी करें।
  2. ~3 प्रमुख उत्पादों (जैसे Lightbulb, Phonograph, Kinetoscope) के लिए यह करें।
  3. Profound एडमिन → item → Documents → प्रत्येक Price ID को stripePriceId में पेस्ट करें, सेव करें।

CMS कैटलॉग को रखता है; Stripe रिकॉर्ड का मूल्य रखता है; लिंक वह एक स्ट्रिंग है जिसे व्यापारी पेस्ट करता है। (इसे स्वचालित करना पसंद है? आधिकारिक Stripe MCP आपके लिए Products/Prices बना सकता है — लौटे हुए IDs को उसी तरह पेस्ट करें।)

7. डिज़ाइन सिस्टम जोड़ें और AI से वायर करें

स्कैफ़ोल्ड बिना स्टाइलिंग के आता है। प्रोजेक्ट रूट पर एक DESIGN.md रखें (Tailwind v4 का @theme ब्लॉक + टोकन) — अपना खुद का, या refero.design से डाउनलोड करें। फिर Claude को, केवल स्टाइलिंग तक सीमित रखते हुए, प्रॉम्प्ट करें:

अभी जो डिज़ाइन फ़ाइल मैंने जोड़ी है उसे पढ़ो। Tailwind की आवश्यकता हो तो सेटअप करो, फिर थीम और फॉन्ट्स वायर करो ताकि स्टाइलिंग काम करे। फॉन्ट्स के लिए next/font का उपयोग करो — रनटाइम में Google से उन्हें लोड मत करो। सिर्फ स्टाइलिंग करो — अभी कोई पेज या कंपोनेंट मत बनाओ।

पुष्टि करें कि src/app/globals.css में @import "tailwindcss"; + @theme ब्लॉक है और localhost:3000 पर टोकन दिख रहे हैं। प्रॉम्प्ट को सटीक रखें (खुला छोड़ें तो एजेंट पूरा होमपेज स्कैफ़ोल्ड कर देता है), और फॉन्ट्स को next/font से लोड करें, कभी रनटाइम Google इम्पोर्ट से नहीं।

8. उत्पाद छवियाँ जोड़ें (वैकल्पिक)

वैकल्पिक — आप छवियों के बिना भी कार्यशील चेकआउट तक पहुँच सकते हैं। उन्हें जोड़ने के लिए: प्रत्येक छवि के लिए एक product_image डॉक्यूमेंट बनाएँ (उसके image फ़ील्ड में अपलोड करें), फिर उसे उत्पाद के images array से संदर्भित करें। अपनी खुद की उत्पाद तस्वीरें लाएँ, या किसी इमेज मॉडल से एक समरूप सेट जनरेट करें (Claude से DESIGN.md के आधार पर ब्रांड-उपयुक्त प्रॉम्प्ट तैयार करवाएँ और हर शॉट मैच करने के लिए एक Midjourney --sref लॉक करें)।

स्टैंडअलोन cms-renderer के पास कोई इमेज-URL हेल्पर नहीं है, इसलिए src/lib/image.ts में buildAssetUrl जोड़ें (~40 लाइन) — यह NEXT_PUBLIC_BUNNY_CDN_URL को प्रीफ़िक्स करता है और एक्सटेंशन जोड़ता है। बिल्ड स्टेप 3 के कंपोनेंट्स इसे उपयोग करते हैं।

भाग 2 — बिल्ड

रेंडरिंग लेयर और चेकआउट बनाएँ, अंत में एक वास्तविक टेस्ट-मोड खरीद के साथ।

1. पाँच UI एलिमेंट कंपोनेंट्स परिभाषित करें

पाँच कंपोनेंट्स, प्रत्येक Active और UI Element (Settings → Tags) से टैग्ड, कोई Route Slug नहीं:

  • nav → brand · product_grid → heading · product_detail → heading · cart_summary → heading · footer → text (सब Text)

UI Element टैग ही वह है जो किसी कंपोनेंट को पेज बिल्डर की Add UI Element सूची में दिखाता है — Active होना अकेला काफी नहीं। प्रत्येक फ़ील्ड स्केलर है (जिसे CEL बाँधता है); वास्तविक कैटलॉग डेटा यहाँ फ़ील्ड नहीं है — ProductGrid/ProductDetail रूट पैराम से फ़ेच करते हैं (स्टेप 3)।

2. टाइप्स फिर से जनरेट करें

bun run generate-schemas

3. कैटलॉग रीडर, कंपोनेंट्स, और हेडलेस कार्ट जनरेट करें

एक प्रॉम्प्ट रीड हेल्पर, पाँच कंपोनेंट्स, कार्ट, और रजिस्ट्री बनाता है:

हमारा स्टोरफ्रंट src/ में बनाओ, Profound cms-renderer SDK का उपयोग करते हुए।

src/lib/catalog.ts — सर्वर-साइड CMS रीडर। cms-renderer/lib/cms-api से getCmsClient({ cmsUrl: process.env.NEXT_PUBLIC_CMS_API_URL!, apiKey: process.env.PROFOUND_API_KEY, websiteId: process.env.NEXT_PUBLIC_PROFOUND_WEBSITE_ID! }) से क्लाइंट बनाओ। getItemByCode(code) एक्सपोर्ट करो → cms.documents.getByCode.query({ websiteId, schemaName: "item", code }) लौटाए res.document.published_content। listItems(categoryCode?) एक्सपोर्ट करो → cms.documents.list.query({ websiteId, schemaName: "item", status: "published", limit: 100 }), res.documents को .published_content में मैप करो, active !== false फ़िल्टर करो, और यदि categoryCode दिया हो तो केवल वे आइटम रखें जिनका category._ref संबंधित श्रेणी के document.id के बराबर हो। resolveImages(refs) एक्सपोर्ट करो जो प्रत्येक item.images संदर्भ को cms.documents.get.query({ websiteId, id: ref._ref }) से रिज़ॉल्व करता है और उसके इमेज फ़ील्ड को vendored buildAssetUrl (भाग 1 स्टेप 8) से URL में बदलता है।

src/components/ — पाँच UI एलिमेंट कंपोनेंट्स जिन्हें कैच-ऑल रूट की रजिस्ट्री में कंपोनेंट नाम के अनुसार पंजीकृत किया गया है, एडमिन से मेल खाने के लिए snake_case में: { nav, product_grid, product_detail, cart_summary, footer }। Nav और Footer अपने स्केलर फ़ील्ड content प्रॉप से पढ़ते हैं (cms-renderer/lib/types से BlockComponentProps<T> टाइप्ड)। ProductGrid और ProductDetail async सर्वर कंपोनेंट्स हैं जो routeParams पढ़ते हैं और catalog.ts से फ़ेच करते हैं: routeParams.<param> { value, … } होता है — .value पढ़ें, इसलिए ProductGrid listItems(routeParams.category_code?.value) कॉल करता है (कार्ड /products/{code} से लिंक करें) और ProductDetail getItemByCode(routeParams.item_code?.value) कॉल करता है (गैलरी resolveImages से, रिच-टेक्स्ट वर्णन, मूल्य, Add-to-cart)। CartSummary कार्ट को useCart से रेंडर करता है और Pay बटन के साथ। formatPrice को src/lib/format.ts में शुद्ध रखें ताकि क्लाइंट कंपोनेंट्स सर्वर-केवल catalog.ts आयात न करें।

src/components/AddToCartButton.tsx — "use client" बटन जो { code, name, priceLabel } लेता है और useCart().addItem({ code, name, priceLabel, quantity: 1 }) कॉल करता है। इसे ProductDetail के अंदर उपयोग करें।

src/lib/useCart.ts — हेडलेस कार्ट: लाइन आइटम { code, name, priceLabel, quantity } को स्टेट में रखता है, localStorage में स्थायी बनाता है, addItem/removeItem/updateQty/subtotal और checkout() एक्सपोज़ करता है जो { lines: [{ code, quantity }] } (केवल कोड और मात्रा — कभी भी कीमतें नहीं) को /api/stripe/checkout पर POST करता है, फिर लौटी हुई url पर रीडायरेक्ट करता है।

सब कुछ हमारे डिज़ाइन सिस्टम से स्टाइल करें, अपने खुद के कंपोनेंट्स के रूप में — स्रोत साइट का लेआउट कॉपी न करें।

यह चलने के बाद जानने योग्य तीन बातें:

  • स्केलर क्रोम content पर आता है ({ content }: BlockComponentProps<T>) — फ़ील्ड्स को शीर्ष-स्तरीय प्रॉप्स की तरह डीस्ट्रक्चर करें और ब्लॉक खाली रेंडर होगा। कैटलॉग डेटा routeParams + catalog.ts फ़ेच से आता है, क्योंकि CEL सूचियों या गैलरियों को बाँध नहीं सकता। कार्ट आइटमों के कोड ले जाता है, कीमतें कभी नहीं।
  • routeParams.<param> { value, schemaName, document } होता है — .value पढ़ें। रीड्स published_content लौटाते हैं, .content नहीं। रजिस्ट्री की कुंजियाँ एडमिन से मेल खाने के लिए snake_case में हैं।
  • @types/react/@types/react-dom को v19 पर बढ़ाएँ — स्कैफ़ोल्ड v18 शिप करता है, जो React 19 के async सर्वर-कंपोनेंट ब्लॉकों में टूटता है।

4. Stripe सर्वर कोड लिखें (रीढ़)

तीन छोटे सर्वर फ़ाइलें — ऐप में एकमात्र भुगतान कोड। ये getItemByCode दोबारा उपयोग करते हैं, इसलिए शुल्क सर्वर-साइड तय होता है।

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 — प्रत्येक आइटम को CMS से रिज़ॉल्व करें, 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();                // [{ code, quantity }] — क्लाइंट से कोई कीमत नहीं
  const line_items = await Promise.all(
    lines.map(async ({ code, quantity }: { code: string; quantity: number }) => {
      const item = await getItemByCode(code);          // सर्वर CMS से रिज़ॉल्व करता है
      return { price: item!.stripe_price_id, quantity }; // मूल्य CMS से, कभी क्लाइंट से नहीं
    })
  );
  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 — विश्वसनीय फुलफ़िलमेंट सिग्नल:

import { stripe } from "@/lib/stripe";

export async function POST(req: Request) {
  const body = await req.text();                      // RAW बॉडी — हस्ताक्षर सत्यापन के लिए आवश्यक
  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") {
    // fulfill: ऑर्डर रिकॉर्ड करें / रसीद भेजें।
  }
  return new Response(null, { status: 200 });
}

आवश्यक स्कैफ़ोल्ड सुधार: स्कैफ़ोल्ड का src/proxy.ts हर /api/* को CMS की ओर फॉरवर्ड करता है, इसलिए आपके Stripe रूट कभी नहीं चलते। पहले उन्हें पास होने दें:

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: [...] }` अपरिवर्तित रखें

सत्यापित करें: curl -X POST localhost:3000/api/stripe/webhook -d x Bad signature लौटाता है।

Stripe CLI को स्थानीय वेबहुक के लिए चलाएँ:

stripe login
stripe listen --forward-to localhost:3000/api/stripe/webhook
# whsec_... कॉपी करें और STRIPE_WEBHOOK_SECRET में डालें, bun dev रीस्टार्ट करें

whsec_… प्रति सत्र है। सुरक्षा के दो नियम हैं: चेकआउट कार्ट से code लेकर मूल्य दोबारा CMS से निकालता है (छेड़छाड़ किया कार्ट इसे बदल नहीं सकता), और वेबहुक कच्ची बॉडी के विरुद्ध हस्ताक्षर सत्यापित करता है।

5. रूट बनाएँ

एडमिन → Pages → Create page, तीन बार। प्रत्येक पैराम को उसके कंपोनेंट से मैप करें (slug फ़ील्ड code):

  1. /products/{item_code} → item
  2. /categories/{category_code} → category
  3. /cart — एक स्थिर पेज (शाब्दिक /cart दर्ज करें, /{cart} नहीं)

6. UI एलिमेंट जोड़ें, CEL वायर करें, प्रकाशित करें

प्रत्येक रूट के लिए: Page Builder → Add UI Element → Custom → कंपोनेंट क्रम से जोड़ें, स्केलर फ़ील्ड भरें (स्थिर मान या CEL), Publish करें।

  • /products/{item_code}: nav, product_detail, footer
  • /categories/{category_code}: nav, product_grid, footer
  • /cart: nav, cart_summary, footer

nav.brand और footer.text को स्थिर स्ट्रिंग सेट करें; हेडिंग्स को स्थिर लेबल दें।

श्रेणी रूट पर पेज बिल्डर — product_grid एलिमेंट चुना हुआ, उसका heading CEL से बाउंड।

उत्पाद रूट पर पेज बिल्डर — product_detail एलिमेंट एक उत्पाद बाइंडिंग पर।

पैरामीट्रिक पेज बिल्डर सावधानी: दो पैरामीट्रिक रूट पर UI एलिमेंट जोड़ना स्थायी नहीं होता (ब्लॉक्स अलग हो जाते हैं और पेज खाली रेंडर होता है)। जब तक यह ठीक नहीं होता, उन पेजों के block_ids को सीधे Profound MCP update_page से वायर करें, फिर प्रकाशित करें। (स्थिर /cart सामान्य रूप से जुड़ता है।) इसी कारण ProductGrid अपना heading उस श्रेणी से निकालता है जिसे वह फ़ेच करता है, CEL से नहीं।

7. रेंडर करें और खरीदें

  • /categories/lighting → ग्रिड। एक उत्पाद पर क्लिक करें → विवरण + Add to cart। /cart → Pay।
  • भुगतान Stripe-होस्टेड चेकआउट पर रीडायरेक्ट करता है। टेस्ट कार्ड 4242 4242 4242 4242 का उपयोग करें, कोई भी भविष्य की expiry/CVC। आप /cart?status=success पर लौटते हैं, और stripe listen checkout.session.completed दिखाता है।

रेंडर किया गया उत्पाद पेज — गैलरी, मूल्य, और Add to cart।

कार्ट — लाइन आइटम और एक Pay-with-Stripe बटन।

केवल कीमत वाले आइटम खरीदे जा सकते हैं — स्टेप 6 में जिन ~3 को आपने कीमत दी थी उनमें से एक खरीदें।

वैकल्पिक — अंतरराष्ट्रीयकरण। प्रत्येक कंपोनेंट का अनुवाद करें (एक साथ सभी 35 भाषाएँ), /{language}/… सेगमेंट जोड़ें जिसे बिल्ट-इन language सिस्टम कंपोनेंट से मैप करें, और CEL-बाउंड फ़ील्ड्स को documents.translated पर स्विच करें। विवरण के लिए एयरपोर्ट डायरेक्टरी ट्यूटोरियल, भाग 2 स्टेप 7 देखें।

भाग 3 — प्रोडक्शन

1. इसे शिप करें: GitHub, फिर Vercel

git init && git add -A && git commit -m "Stripe storefront"
gh repo create store --private --source=. --push   # --public भी ठीक है

बिल्ड कमांड: generated/cms-schemas.ts gitignored है, इसलिए बिल्ड को इसे फिर से जनरेट करने पर पिन करें — vercel.json जोड़ें:

{ "$schema": "https://openapi.vercel.sh/vercel.json", "buildCommand": "bun run generate-schemas && next build" }

Vercel में: Add New → Project, store इम्पोर्ट करें, और env vars जोड़ें — PROFOUND_API_KEY, NEXT_PUBLIC_PROFOUND_WEBSITE_ID, NEXT_PUBLIC_CMS_API_URL, NEXT_PUBLIC_BUNNY_CDN_URL, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET (डिप्लॉय किए गए एंडपॉइंट का मान, नीचे), और NEXT_PUBLIC_SITE_URL (आपका प्रोड URL)। डिप्लॉय करें।

फिर डिप्लॉय किए गए वेबहुक को वायर करें (स्थानीय stripe listen सीक्रेट केवल स्थानीय था): Stripe → Developers → Webhooks → + Add endpoint → https://<prod>/api/stripe/webhook, इवेंट checkout.session.completed। उसका whsec_… Vercel में कॉपी करें और पुनः डिप्लॉय करें।

गुम env vars = "लोकली काम करता है, प्रोड में खाली" — सबसे बड़ी डिप्लॉय समस्या। यहाँ हम टेस्ट कुंजियों से डिप्लॉय करते हैं; वास्तविक भुगतान लेने के लिए तैयार होने पर STRIPE_SECRET_KEY और वेबहुक सीक्रेट को अपनी लाइव मानों में स्विच करें।

2. लाइव प्रीव्यू और इन-प्लेस एडिटिंग

दोनों स्कैफ़ोल्ड के साथ आते हैं।

  • लाइव प्रीव्यू: <Refresher> उस पेज को अपडेट करता है जिसे एडिटर प्रीव्यू कर रहा है जब वह एडमिन में सेव करता है — कोई रीडिप्लॉय नहीं। (यह एडिटर के लिए प्रीव्यू है; आगंतुक प्रकाशित कंटेंट को सामान्य रिवैलिडेशन पर देखते हैं।)
  • इन-प्लेस एडिटिंग: किसी भी URL में ?edit_mode=true जोड़ें ताकि एडिट ओवरले दिखें। सार्वजनिक आगंतुक साफ़ पेज देखते हैं।

वह प्रीव्यू रूट जोड़ें जो स्कैफ़ोल्ड छोड़ देता है। एडमिन अपना प्रीव्यू iframe /cms-preview_<path> पर लोड करता है; उस रूट के बिना हर प्रीव्यू 404 होता है। इसे जोड़ें:

// 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; // async RSC; 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} />;
}

src/app/cms-preview_/page.tsx भी जोड़ें (उसी तरह, slug: []) सेगमेंट रूट के लिए।

बस इतना ही बिल्ड है

एक कंटेंट-प्रधान Stripe स्टोरफ्रंट: CMS कैटलॉग, एक ही सेट की रूटों से सूची + विवरण पेज, और काम करता हुआ होस्टेड चेकआउट। AI ने कैटलॉग सीड किया, डिज़ाइन वायर किया, और कैटलॉग रीडर + कंपोनेंट्स + हेडलेस कार्ट लिखा; आपने कंपोनेंट्स, Stripe मूल्य लिंक, तीन रूट, CEL क्रोम, और तीन छोटे Stripe फ़ाइलें कीं। CEL क्रोम बाँधता है; कंपोनेंट्स कैटलॉग फ़ेच करते हैं। और Stripe छोटा रहा — एक sessions.create कॉल और एक साइन किया हुआ वेबहुक, जिसमें खरीदार Stripe के अपने पेज पर भुगतान करता है।

Continue Reading
Previous‹Deployments