profound-logoProfound CMS
⌘K
Admin
Theme
DocsTutorialBlogPhilosophy
DocsTutorialBlogPhilosophy

Tutorials

Build & Ship an Airport DirectoryDeploymentsBuild & Ship a Stripe Storefront

Feature

Documentation Site TemplateFeature Template BuilderΜεταφραστής χαρακτηριστικώνOrganizations & Website HeirarchyConnect Profound CMS to your AI clientSettings IntegrationsSettings API KeysSettings UsageSettings Websites
All Systems Operational
Powered Byprofound-logo
Theme

Build & Ship a Stripe Storefront

Ένας πρακτικός οδηγός: δημιουργήστε μια βιτρίνα Stripe με επίκεντρο το περιεχόμενο στο Profound CMS — έναν κατάλογο που ο έμπορος επεξεργάζεται χωρίς κώδικα, δύο παραμετρικές διαδρομές, ένα headless καλάθι και checkout που φιλοξενείται στο Stripe.

Το ολοκληρωμένο κατάστημα σε κίνηση — περιηγηθείτε σε μια κατηγορία, ανοίξτε ένα προϊόν, προσθέστε το στο καλάθι, ολοκληρώστε την αγορά.

Ένας πρακτικός οδηγός που δημιουργεί ένα κατάστημα προσανατολισμένο στο περιεχόμενο στο Profound CMS: έναν κατάλογο προϊόντων (κατηγορίες + αντικείμενα) μοντελοποιημένο στο CMS, σελίδες καταλόγου και λεπτομερειών από ένα σύνολο διαδρομών και checkout που φιλοξενείται στο Stripe αποσταλμένο ως headless στοιχείο.

Η ραχοκοκαλιά είναι χειρόγραφο Next.js μαζί με το Profound admin. Το Claude Code (μέσω του Profound MCP) κάνει το μεγαλύτερο μέρος της δουλειάς σε τρεις εργασίες — γέμισμα του καταλόγου, σύνδεση του συστήματος σχεδίασης και συγγραφή των στοιχείων της βιτρίνας (συμπεριλαμβανομένου του headless καλαθιού). Τρία μέρη: Ρύθμιση, Κατασκευή, Παραγωγή.

Πληρωμές σε μία γραμμή. Χρησιμοποιούμε Checkout που φιλοξενείται στο Stripe: ο αγοραστής πληρώνει στη σελίδα του Stripe, όχι στη δική σας. Η εφαρμογή σας κάνει μόνο δύο πράγματα στην πλευρά του διακομιστή — δημιουργεί ένα Checkout Session και επαληθεύει ένα webhook. Χωρίς πεδία καρτών, χωρίς Stripe Elements, χωρίς βάρος PCI.

Τι θα δημιουργήσετε

  • Έναν μικρό δημοσιευμένο κατάλογο στο CMS — τρεις κατηγορίες και οκτώ προϊόντα (το demo "Edison's Inventions") — καθένα επεξεργάσιμο από έναν έμπορο χωρίς κώδικα.
  • Δύο παραμετρικές διαδρομές (/products/{item_code}, /categories/{category_code}) συν ένα στατικό /cart, όλα από ένα σύνολο στοιχείων.
  • Ένα headless καλάθι (useCart) και checkout που φιλοξενείται στο Stripe, με την τιμή να επιλύεται πάντα στην πλευρά του διακομιστή από ένα Stripe Price ID.
  • Το κατάστημα δημοσιευμένο στο Vercel με ζωντανή προεπισκόπηση και επεξεργασία επί τόπου για την ομάδα.

Προαπαιτούμενα

  • Bun ≥ 1.3 — curl -fsSL https://bun.sh/install | bash
  • Claude Code με το Profound MCP (το Μέρος 1 το εγκαθιστά).
  • Ένας λογαριασμός Profound CMS.
  • Ένας λογαριασμός Stripe. Αυτό το σεμινάριο τρέχει σε λειτουργία δοκιμής οπότε δεν χρεώνεται πραγματικά χρήμα όσο δημιουργείτε — αλλά η ροή είναι πανομοιότυπη με τα live κλειδιά, οπότε χρησιμοποιήστε τα πραγματικά σας κλειδιά αν προτιμάτε. (Η λειτουργία δοκιμής δεν χρειάζεται εταιρικά ή τραπεζικά στοιχεία.)
  • Το Stripe CLI (stripe login) για τοπικά webhooks.
  • Για τη δημοσίευση: το GitHub CLI (gh) και ένας λογαριασμός Vercel συνδεδεμένος με το GitHub.

Πώς δένουν τα κομμάτια

Το Profound διαχωρίζει το περιεχόμενο από την απόδοση:

  • Τα Components ορίζουν τη μορφή του περιεχομένου. Ένα Custom Component με πεδίο Route Slug είναι δρομολογήσιμο (category, item); ένα με ετικέτα UI Element μπορεί να τοποθετηθεί σε σελίδα (nav, product_grid, …).
  • Τα Documents είναι το περιεχόμενο (ένα προϊόν, μια κατηγορία).
  • Τα UI elements είναι ενότητες σελίδων· κάθε μοναδιαίο πεδίο παίρνει μια στατική τιμή ή μια έκφραση CEL, που αξιολογείται κατά την απόδοση.
  • Οι παραμετρικές διαδρομές αντιστοιχούν ένα URL σε ένα έγγραφο + UI elements, μεταβιβάζοντας παραμέτρους διαδρομής (meta.params.* στο CEL, routeParams στο React).
  • Η εφαρμογή Next.js το διαβάζει μέσω του cms-renderer; το Stripe προστίθεται ως συνηθισμένα API routes.

Ο ένας κανόνας που διαμορφώνει την κατασκευή: το CEL δεσμεύει μόνο πεδία string/number. Έτσι το στατικό περίβλημα (brand στο nav, υποσέλιδο, επικεφαλίδες) δεσμεύεται με CEL, ενώ οτιδήποτε πλούσιο ή συλλογή (πλέγμα προϊόντων, συλλογή εικόνων, rich text) ανακτάται μέσα στο React component με βάση την παράμετρο διαδρομής. Και το Stripe είναι η πηγή αλήθειας για την τιμολόγηση — η τιμή στο CMS price είναι μόνο για προβολή· η χρέωση επιλύεται πάντα στην πλευρά του διακομιστή από ένα Stripe Price ID.

Μέρος 1 — Ρύθμιση

Τελική κατάσταση: ένας μικρός δημοσιευμένος κατάλογος, η εφαρμογή συνδεδεμένη για να τον διαβάζει, το Stripe εγκατεστημένο, το design στη θέση του — τίποτα δεν αποδίδεται ακόμη.

1. Εγγραφείτε και δημιουργήστε την ιστοσελίδα

Εγγραφείτε στο Profound (WorkOS auth). Δημιουργήστε έναν ιστότοπο με όνομα store, έπειτα αντιγράψτε το website ID του (το UUID στο URL του admin) και ένα API key μόνο για ανάγνωση (Deployments → Create API key). Η εφαρμογή μόνο διαβάζει· το γέμισμα του καταλόγου αργότερα περνά μέσα από το MCP, που κάνει ξεχωριστή ταυτοποίηση.

2. Στήστε το scaffold, συνδέστε το και προσθέστε το Stripe

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

Το scaffold είναι ένα project Next.js App Router προρυθμισμένο για το Profound (το SDK cms-renderer, μια catch-all διαδρομή, ένα script generate-schemas, ένα <Refresher>). Δεν περιλαμβάνει styling. Το bun add stripe φέρνει το server SDK — η μόνη εξάρτηση πληρωμών που χρειάζεται το φιλοξενούμενο checkout.

Προσθέστε τις τιμές σας στο .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_...            # δοκιμαστικό κλειδί εδώ· αντικαταστήστε με το live κλειδί σας όταν πάτε live
STRIPE_WEBHOOK_SECRET=whsec_...          # συμπληρώνεται στο βήμα Κατασκευής 4
NEXT_PUBLIC_SITE_URL=http://localhost:3000

Πάρτε το STRIPE_SECRET_KEY από Stripe → Developers → API keys. Χρησιμοποιούμε ένα δοκιμαστικό κλειδί (sk_test_…) ώστε η υλοποίηση να μη μετακινεί πραγματικά χρήματα· αλλάξτε στο live κλειδί όταν είστε έτοιμοι για πραγματικές πληρωμές. Τρέξτε bun dev και ανοίξτε localhost:3000 — εμφανίζεται το starter.

Το φιλοξενούμενο checkout ανακατευθύνει τον browser σε ένα URL του Stripe, οπότε το server secret key είναι το μόνο που χρειάζεται το Stripe — χωρίς publishable key, χωρίς client Stripe SDK.

3. Ορίστε τα components category, product_image και item

Δημιουργήστε τρία Custom Components (Components → Create new component) — την πηγή δεδομένων, άρα χωρίς ετικέτα UI Element. Ορίστε καθένα ως Active.

Το CMS δεν έχει πεδίο "array of image", οπότε μια συλλογή είναι array από references σε ένα μικρό component product_image. Δημιουργήστε τα category και product_image (και ορίστε τα Active) πριν το item — ένα πεδίο reference στοχεύει μόνο Active components.

  • 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 από references → product_image), price (Number, cents — μόνο για προβολή), currency (Select, usd), stripePriceId (Text), category (Reference → category), active (Boolean)

Αφήστε όλα τα πεδία προαιρετικά. Το admin μετατρέπει τα ονόματα πεδίων σε lower snake case ("Stripe Price Id" → stripe_price_id) — αυτό χρησιμοποιεί ο κώδικας σας, οπότε διαβάστε τα πραγματικά ονόματα μέσω του generate-schemas στο επόμενο βήμα. Ονομάζουμε τον δρομολογήσιμο αναγνωριστικό code (όχι slug): είναι το Route Slug και το κλειδί για ένα καθαρό documents.getByCode αργότερα.

Το component item — code ως Route Slug, images ως references στο product_image, μαζί με stripePriceId και reference category.

4. Κατεβάστε τα components σε τοπικούς τύπους

bun run generate-schemas

Αποθηκεύει Zod schemas + types στο generated/cms-schemas.ts (categorySchema/Category, itemSchema/Item). Λειτουργεί και ως έλεγχος σύνδεσης — λάθος διαπιστευτήρια αποτυγχάνουν εδώ.

5. Γεμίστε τον κατάλογο μέσω του Profound MCP

Εγκαταστήστε και κάντε authenticate το MCP μία φορά:

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

Τρέξτε mcp__Profound__authenticate, ολοκληρώστε το WorkOS flow και έπειτα ζητήστε από το Claude:

Δημιούργησε έναν μικρό ecommerce κατάλογο για ένα κατάστημα που ονομάζεται Edison's Inventions — τρεις κατηγορίες και αυτά τα προϊόντα, με μια σύντομη περιγραφή ακριβή για την εποχή για κάθε ένα, price σε cents, 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; κάθε item χρειάζεται name, το ίδιο πεζό code, την περιγραφή, price (σε cents), currency και active. Αποθήκευσέ το στο data/catalog.json και επικύρωσέ το έναντι των components μας category και item. Έπειτα χρησιμοποίησε το Profound MCP για να δημιουργήσεις καθένα ως δημοσιευμένο έγγραφο: δημιούργησε πρώτα τις κατηγορίες, κράτησε τα IDs τους, μετά δημιούργησε τα items με category ως reference — { "_type": "reference", "_ref": "<category-id>", "_schema": "category" }. Κάνε τα items σε parallel.

Ο Claude γράφει το data/catalog.json, το επικυρώνει και εκτελεί παράλληλες κλήσεις create_document (status: "published"). Γεμίστε τις κατηγορίες πριν από τα items ώστε τα references να δείχνουν σε ήδη υπάρχοντα IDs.

Οι τρεις κατηγορίες που γεμίστηκαν, δημοσιευμένες και Live.

Τα οκτώ προϊόντα που γεμίστηκαν, το καθένα συνδεδεμένο με μια κατηγορία.

6. Δημιουργήστε Stripe Prices και συνδέστε μερικά προϊόντα

Ο κατάλογος είναι στο CMS· τώρα δώστε σε μερικά προϊόντα μια πραγματική τιμή Stripe — δουλειά του εμπόρου, σε δύο admin panels, χωρίς κώδικα:

  1. Stripe Dashboard → Products → + Add product, ορίστε μια τιμή one-time, αντιγράψτε το Price ID (price_…).
  2. Κάντε το για περίπου 3 κεντρικά προϊόντα (π.χ. Lightbulb, Phonograph, Kinetoscope).
  3. Profound admin → item → Documents → επικολλήστε κάθε Price ID στο stripePriceId, αποθηκεύστε.

Το CMS κρατά τον κατάλογο· το Stripe κρατά την επίσημη τιμή· ο σύνδεσμος είναι ένα string που επικολλά ο έμπορος. (Θέλετε να το αυτοματοποιήσετε; Το επίσημο Stripe MCP μπορεί να δημιουργήσει τα Products/Prices για εσάς — επικολλήστε τα επιστραφέντα IDs με τον ίδιο τρόπο.)

7. Προσθέστε το σύστημα σχεδίασης και συνδέστε το με AI

Το scaffold είναι χωρίς στυλ. Τοποθετήστε ένα DESIGN.md (ένα μπλοκ @theme Tailwind v4 + tokens) στη ρίζα του project — δικό σας ή κατεβάστε ένα από το refero.design. Έπειτα δώστε την εξής εντολή στο Claude, με πεδίο μόνο το styling:

Διάβασε το αρχείο σχεδίασης που μόλις πρόσθεσα. Ρύθμισε το Tailwind αν χρειάζεται, μετά σύνδεσε το θέμα και τις γραμματοσειρές ώστε το styling να λειτουργεί. Χρησιμοποίησε next/font για τις γραμματοσειρές — μην τις φορτώνεις από τη Google κατά το runtime. Μόνο το styling — μην φτιάξεις ακόμα σελίδες ή components.

Επαληθεύστε ότι το src/app/globals.css έχει @import "tailwindcss"; + το μπλοκ @theme και ότι το localhost:3000 εμφανίζει τα tokens. Κρατήστε το prompt αυστηρό (με ανοιχτό prompt, ένας agent μπορεί να στήσει ολόκληρη αρχική σελίδα) και φορτώστε γραμματοσειρές μέσω next/font, ποτέ με runtime import Google.

8. Προσθέστε εικόνες προϊόντων (προαιρετικό)

Προαιρετικό — μπορείτε να φτάσετε σε λειτουργικό checkout χωρίς εικόνες. Για να τις προσθέσετε: δημιουργήστε ένα έγγραφο product_image ανά εικόνα (ανεβάστε την στο πεδίο image), μετά αναφερθείτε σε αυτά από το array images του προϊόντος. Φέρτε δικές σας φωτογραφίες ή δημιουργήστε ένα ομοιογενές σύνολο με ένα μοντέλο εικόνων (ζητήστε από το Claude να παράξει prompts σύμφωνα με το DESIGN.md και κλειδώστε ένα Midjourney --sref ώστε κάθε λήψη να ταιριάζει).

Το standalone cms-renderer δεν έχει helper για URLs εικόνων, οπότε προσθέστε το buildAssetUrl στο src/lib/image.ts (~40 γραμμές) — κάνει prefix το NEXT_PUBLIC_BUNNY_CDN_URL και προσθέτει την κατάλληλη κατάληξη. Τα components στο βήμα Κατασκευής 3 το χρησιμοποιούν.

Μέρος 2 — Κατασκευή

Χτίστε το επίπεδο απόδοσης και το checkout, καταλήγοντας σε μια πραγματική αγορά σε λειτουργία δοκιμής.

1. Ορίστε τα πέντε components UI element

Πέντε components, καθένα Active και με ετικέτα UI Element (Settings → Tags), χωρίς Route Slug:

  • nav → brand · product_grid → heading · product_detail → heading · cart_summary → heading · footer → text (όλα Text)

Η ετικέτα UI Element είναι αυτό που κάνει ένα component να εμφανίζεται στη λίστα Add UI Element του Page Builder — το Active μόνο δεν αρκεί. Κάθε πεδίο είναι scalar (το είδος που δένει το CEL); τα πραγματικά δεδομένα του καταλόγου δεν είναι πεδίο εδώ — τα ProductGrid/ProductDetail τα ανακτούν με βάση την παράμετρο διαδρομής (βήμα 3).

2. Αναδημιουργήστε τους τύπους

bun run generate-schemas

3. Δημιουργήστε τον αναγνώστη καταλόγου, τα components και το headless καλάθι

Ένα prompt δημιουργεί τον helper ανάγνωσης, τα πέντε components, το καλάθι και το registry:

Χτίσε τη βιτρίνα μας στο src/, χρησιμοποιώντας το SDK cms-renderer του Profound.

src/lib/catalog.ts — ένας αναγνώστης CMS στην πλευρά του server. Δημιούργησε έναν client με getCmsClient({ cmsUrl: process.env.NEXT_PUBLIC_CMS_API_URL!, apiKey: process.env.PROFOUND_API_KEY, websiteId: process.env.NEXT_PUBLIC_PROFOUND_WEBSITE_ID! }) από το cms-renderer/lib/cms-api. Εξήγαγε 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, κράτησε items των οποίων το category._ref ισούται με το document.id της κατηγορίας. Εξήγαγε resolveImages(refs) που επιλύει κάθε αναφορά item.images μέσω cms.documents.get.query({ websiteId, id: ref._ref }) και μετατρέπει το πεδίο image σε URL με το buildAssetUrl που προστέθηκε (Μέρος 1 βήμα 8).

src/components/ — πέντε components UI element καταχωρημένα στο registry της catch-all διαδρομής με βάση το όνομα του component, σε snake_case για να ταιριάζει με το admin: { nav, product_grid, product_detail, cart_summary, footer }. Τα Nav και Footer διαβάζουν το scalar πεδίο από το prop content (τυποποιημένο ως BlockComponentProps<T> από cms-renderer/lib/types). Τα ProductGrid και ProductDetail είναι async server components που διαβάζουν τα routeParams και κάνουν fetch από το catalog.ts: το routeParams.<param> είναι { value, … } — διάβασε το .value, ώστε το ProductGrid να καλεί listItems(routeParams.category_code?.value) (οι κάρτες οδηγούν σε /products/{code}) και το ProductDetail να καλεί getItemByCode(routeParams.item_code?.value) (συλλογή μέσω resolveImages, rich-text περιγραφή, τιμή, Add-to-cart). Το CartSummary αποδίδει το καλάθι από το useCart με ένα κουμπί Pay. Κράτησε το formatPrice σε ένα καθαρό src/lib/format.ts ώστε τα client components να μην εισάγουν το server-only catalog.ts.

src/components/AddToCartButton.tsx — ένα κουμπί "use client" που δέχεται { code, name, priceLabel } και καλεί useCart().addItem({ code, name, priceLabel, quantity: 1 }). Χρησιμοποίησέ το μέσα στο ProductDetail.

src/lib/useCart.ts — ένα headless καλάθι: γραμμές { code, name, priceLabel, quantity } σε state, αποθηκευμένο στο localStorage, που εκθέτει addItem/removeItem/updateQty/subtotal και ένα checkout() που κάνει POST { lines: [{ code, quantity }] } (μόνο κωδικούς και ποσότητες — ποτέ τιμές) στο /api/stripe/checkout, έπειτα κάνει redirect στο url που επιστρέφεται.

Στυλάρεις τα πάντα με το δικό μας σύστημα σχεδίασης, ως δικά μας components — μην αντιγράψεις τη διάταξη του πηγαίου site.

Τρία πράγματα που πρέπει να ξέρετε αφού εκτελεστεί:

  • Το scalar chrome έρχεται από το content ({ content }: BlockComponentProps<T>) — αν κάνετε destructure των πεδίων ως top-level props, το block αποδίδει κενό. Τα δεδομένα του καταλόγου έρχονται από τα routeParams + ένα fetch από το catalog.ts, γιατί το CEL δεν δένει λίστες ή galleries. Το καλάθι μεταφέρει κωδικούς αντικειμένων, ποτέ τιμές.
  • Το routeParams.<param> είναι { value, schemaName, document } — διαβάστε το .value. Τα reads επιστρέφουν published_content, όχι .content. Τα κλειδιά του registry είναι snake_case ώστε να ταιριάζουν με το admin.
  • Αναβαθμίστε τα @types/react/@types/react-dom στην έκδοση 19 — το scaffold αποστέλλεται με v18, που σπάει τα async server-component blocks με το React 19.

4. Γράψτε τον κώδικα του Stripe στον διακομιστή (τη ραχοκοκαλιά)

Τρία σύντομα αρχεία server — ο μόνος κώδικας πληρωμών στην εφαρμογή. Επαναχρησιμοποιούν το getItemByCode, ώστε η χρέωση να επιλύεται στην πλευρά του 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 — επιλύει κάθε αντικείμενο από το CMS, χρεώνει την Stripe τιμή:

import { NextResponse } from "next/server";
import { stripe } from "@/lib/stripe";
import { getItemByCode } from "@/lib/catalog"; // server-side, κλειδί μόνο για ανάγνωση

export async function POST(req: Request) {
  const { lines } = await req.json();                // [{ code, quantity }] — καμία τιμή από τον client
  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, ποτέ από τον client
    })
  );
  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 });     // ο client κάνει redirect εδώ
}

src/app/api/stripe/webhook/route.ts — το έμπιστο σήμα εκπλήρωσης:

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") {
    // fulfill: καταγράψτε την παραγγελία / στείλτε απόδειξη.
  }
  return new Response(null, { status: 200 });
}

Απαραίτητη διόρθωση στο scaffold: το src/proxy.ts του scaffold προωθεί κάθε /api/* στο CMS, οπότε τα Stripe routes δεν εκτελούνται. Αφήστε αυτά να περάσουν πρώτα:

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: [...] }` του scaffold όπως είναι

Επαλήθευση: curl -X POST localhost:3000/api/stripe/webhook -d x επιστρέφει Bad signature.

Τρέξτε το Stripe CLI για τοπικά webhooks:

stripe login
stripe listen --forward-to localhost:3000/api/stripe/webhook
# αντιγράψτε το whsec_... στο STRIPE_WEBHOOK_SECRET, κάντε επανεκκίνηση του bun dev

Το whsec_… είναι ανά συνεδρία. Δύο κανόνες κρατούν την ασφάλεια: το checkout αναπαράγει την τιμή από το CMS μέσω code (ένα αλλοιωμένο καλάθι δεν μπορεί να την αλλάξει), και το webhook επαληθεύει την υπογραφή έναντι του ακατέργαστου σώματος.

5. Δημιουργήστε τις διαδρομές

Admin → Pages → Create page, τρεις φορές. Αντιστοιχίστε κάθε παράμετρο στο component της (πεδίο slug code):

  1. /products/{item_code} → item
  2. /categories/{category_code} → category
  3. /cart — μια στατική σελίδα (εισάγετε κυριολεκτικά /cart, όχι /{cart})

6. Προσθέστε UI elements, δέστε CEL, δημοσιεύστε

Για κάθε διαδρομή: Page Builder → Add UI Element → Custom → προσθέστε components με τη σειρά, συμπληρώστε scalar πεδία (στατική τιμή ή 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 σε στατικές συμβολοσειρές· τις επικεφαλίδες σε στατικές ετικέτες.

Το Page Builder στη διαδρομή κατηγορίας — το στοιχείο product_grid επιλεγμένο, η επικεφαλίδα του δεμένη με CEL.

Το Page Builder στη διαδρομή προϊόντος — το στοιχείο product_detail σε binding προϊόντος.

Παραμετρική ιδιαιτερότητα του Page Builder: στις δύο παραμετρικές διαδρομές, η προσθήκη UI elements δεν επιμένει (τα blocks μένουν ορφανά και η σελίδα αποδίδει κενή). Μέχρι να διορθωθεί, δέστε τα block_ids αυτών των σελίδων απευθείας μέσω του Profound MCP update_page, μετά δημοσιεύστε. (Το στατικό /cart συνδέεται κανονικά.) Για τον ίδιο λόγο, το ProductGrid αντλεί την επικεφαλίδα του από την κατηγορία που ανακτά αντί να χρησιμοποιήσει CEL.

7. Αποδώστε και αγοράστε

  • /categories/lighting → το grid. Κάντε κλικ σε προϊόν → λεπτομέρεια + Add to cart. /cart → Pay.
  • Η πληρωμή ανακατευθύνει στο checkout που φιλοξενείται στο Stripe. Χρησιμοποιήστε την δοκιμαστική κάρτα 4242 4242 4242 4242, οποιαδήποτε μελλοντική ημερομηνία/CCV. Επιστρέφετε στο /cart?status=success, και το stripe listen δείχνει checkout.session.completed.

Μια αποδομένη σελίδα προϊόντος — gallery, τιμή και κουμπί Add to cart.

Το καλάθι — γραμμές και ένα μοναδικό κουμπί Pay-with-Stripe.

Μόνο τα προϊόντα με τιμή είναι αγοραστέα — αγοράστε ένα από τα ~3 που τιμολογήσατε στο βήμα 6.

Προαιρετικό — διεθνοποιήστε. Μεταφράστε κάθε component (και τις 35 γλώσσες ταυτόχρονα), προσθέστε ένα τμήμα /{language}/… που αντιστοιχεί στο ενσωματωμένο System component language και αλλάξτε τα πεδία που δένονται με CEL σε documents.translated. Δείτε το tutorial του airport directory, Μέρος 2 βήμα 7.

Μέρος 3 — Παραγωγή

1. Δημοσιεύστε το: GitHub και μετά Vercel

git init && git add -A && git commit -m "Stripe storefront"
gh repo create store --private --source=. --push   # --public είναι επίσης εντάξει

Εντολή build: το generated/cms-schemas.ts δεν παρακολουθείται από git, οπότε σταθεροποιήστε το build ώστε να το αναδημιουργεί — προσθέστε 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 (η τιμή του deployed endpoint, παρακάτω) και NEXT_PUBLIC_SITE_URL (το production URL σας). Δημοσιεύστε.

Έπειτα συνδέστε το deployed webhook (το μυστικό από το stripe listen τοπικά ήταν μόνο για τοπική χρήση): Stripe → Developers → Webhooks → + Add endpoint → https://<prod>/api/stripe/webhook, event checkout.session.completed. Αντιγράψτε το whsec_… στο Vercel και ξαναδημοσιεύστε.

Απόντα env vars = «δουλεύει τοπικά, κενό στο production» — η #1 παγίδα deploy. Δημοσιεύουμε εδώ με δοκιμαστικά κλειδιά· αλλάξτε το STRIPE_SECRET_KEY και το μυστικό webhook στις live τιμές σας όταν είστε έτοιμοι να δεχθείτε πραγματικές πληρωμές.

2. Ζωντανή προεπισκόπηση και επεξεργασία επί τόπου

Και τα δύο συνοδεύουν το scaffold.

  • Ζωντανή προεπισκόπηση: το <Refresher> ενημερώνει τη σελίδα που προεπισκοπεί ένας editor όταν αποθηκεύει στο admin — χωρίς redeploy. (Είναι προεπισκόπηση για τον editor· οι επισκέπτες βλέπουν δημοσιευμένο περιεχόμενο με κανονική revalidation.)
  • Επεξεργασία επί τόπου: προσθέστε ?edit_mode=true σε οποιοδήποτε URL για επεξεργαστικά overlays. Οι δημόσιοι επισκέπτες βλέπουν την καθαρή σελίδα.

Προσθέστε τη διαδρομή προεπισκόπησης που λείπει από το scaffold. Το admin φορτώνει το iframe προεπισκόπησής του στο /cms-preview_<path>· χωρίς αυτή τη διαδρομή κάθε προεπισκόπηση επιστρέφει 404. Προσθέστε την:

// src/app/cms-preview_/[...slug]/page.tsx
import { ParametricRoutePreviewPage } from "cms-renderer/lib/renderer";
import { registry } from "../../registry";   // εξάγετε το registry σας σε κοινόχρηστο module
export default async function Page({ params, searchParams }) {
  const { slug } = await params;
 Χρησιμοποιήστε το PreviewPage = ParametricRoutePreviewPage as any; // async RSC; React 19 types
  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: []) για τη ρίζα του segment.

Αυτό ήταν η κατασκευή

Μια βιτρίνα Stripe με επίκεντρο το περιεχόμενο: ένας κατάλογος στο CMS, σελίδες λίστας + λεπτομερειών από ένα σύνολο διαδρομών και ένα λειτουργικό φιλοξενούμενο checkout. Το AI γέμισε τον κατάλογο, συνέδεσε το design και έγραψε τον αναγνώστη καταλόγου + τα components + το headless καλάθι· εσείς φτιάξατε τα components, τους συνδέσμους τιμής Stripe, τις τρεις διαδρομές, το CEL chrome και τρία σύντομα αρχεία Stripe. Το CEL δένει το chrome· τα components ανακτούν τον κατάλογο. Και το Stripe έμεινε λιτό — μία κλήση sessions.create και ένα υπογεγραμμένο webhook, με τον αγοραστή να πληρώνει στη δική του σελίδα του Stripe.

Continue Reading
Previous‹Deployments