profound-logoProfound CMS
⌘K
Admin
Theme
DocsTutorialBlogPhilosophy
DocsTutorialBlogPhilosophy

Tutorials

Build & Ship an Airport DirectoryDeploymentsร้านค้า stripe

ฟีเจอร์ CMS

Documentation Site TemplateFeature Template BuilderTranslation ServiceOrganizations & Website HeirarchyConnect Profound CMS to your AI clientSettings Integrationsการตั้งค่า-คีย์ APISettings UsageSettings Websites
All Systems Operational
Powered Byprofound-logo
Theme

ร้านค้า stripe

คู่มือแบบลงมือทำ: สร้างร้านค้า Stripe ที่ขับเคลื่อนด้วยคอนเทนต์บน Profound CMS — แคตตาล็อกที่ผู้ค้าแก้ไขได้โดยไม่ต้องเขียนโค้ด เส้นทางแบบ parametric สองเส้นทาง ตะกร้าแบบ headless และ checkout ที่โฮสต์โดย Stripe

ร้านค้าที่เสร็จสมบูรณ์ — เรียกดูหมวดหมู่ เปิดสินค้า เพิ่มลงตะกร้า และชำระเงิน

คู่มือแบบลงมือทำนี้จะสร้างร้านค้าที่ขับเคลื่อนด้วยคอนเทนต์บน Profound CMS: แคตตาล็อกสินค้า (หมวดหมู่และรายการสินค้า) ที่สร้างแบบจำลองใน CMS, หน้าแสดงรายการและหน้ารายละเอียดจากชุดเส้นทางเดียวกัน และ หน้าชำระเงินที่โฮสต์โดย Stripe ซึ่งทำงานเป็นคอมโพเนนต์แบบ headless

แกนหลักคือ Next.js ที่เขียนขึ้นเองร่วมกับแผงผู้ดูแล Profound โดย Claude Code (ผ่าน Profound MCP) จะช่วยจัดการงานหลักสามอย่าง ได้แก่ การเติมข้อมูลแคตตาล็อก การเชื่อมระบบดีไซน์ และการเขียนคอมโพเนนต์ของร้านค้า รวมถึงตะกร้าแบบ headless ทั้งหมดแบ่งเป็นสามส่วน: การตั้งค่า, การสร้าง, และ ใช้งานจริง

การชำระเงินในบรรทัดเดียว เราใช้ Stripe-hosted Checkout: ผู้ซื้อชำระเงินบนหน้าเว็บของ Stripe ไม่ใช่เว็บไซต์ของคุณ แอปของคุณทำงานฝั่งเซิร์ฟเวอร์เพียงสองอย่าง คือสร้าง Checkout Session และตรวจสอบ webhook หนึ่งรายการ ไม่ต้องมีช่องกรอกบัตร, Stripe Elements หรือภาระ PCI

สิ่งที่จะสร้าง

  • แคตตาล็อก ขนาดเล็กที่เผยแพร่ใน CMS — สามหมวดหมู่และสินค้าแปดรายการ (เดโม “Edison's Inventions”) ซึ่งผู้ค้าสามารถแก้ไขได้โดยไม่ต้องเขียนโค้ด
  • เส้นทางแบบ parametric สองเส้นทาง (/products/{item_code}, /categories/{category_code}) และ /cart แบบสแตติก ทั้งหมดสร้างจากชุดคอมโพเนนต์เดียวกัน
  • ตะกร้าแบบ headless (useCart) และ Stripe-hosted checkout โดยราคาจะถูกตรวจสอบและดึงจาก 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) สำหรับ webhook ในเครื่อง
  • สำหรับการดีพลอย: GitHub CLI (gh) และบัญชี Vercel ที่เชื่อมต่อกับ GitHub

การทำงานร่วมกันของส่วนต่าง ๆ

Profound แยก คอนเทนต์ ออกจาก การเรนเดอร์:

  • คอมโพเนนต์ กำหนดโครงสร้างของคอนเทนต์ Custom Component ที่มีฟิลด์ Route Slug จะรองรับเส้นทาง (category, item) ส่วนคอมโพเนนต์ที่ติดแท็ก UI Element จะสามารถวางบนหน้าได้ (nav, product_grid, …)
  • เอกสาร คือคอนเทนต์ เช่น สินค้าและหมวดหมู่
  • UI elements คือส่วนต่าง ๆ ของหน้า แต่ละฟิลด์แบบสเกลาร์รับค่าคงที่หรือ expression แบบ CEL ซึ่งจะถูกประเมินตอนเรนเดอร์
  • Parametric routes จะแมป URL ไปยังเอกสารและ UI elements พร้อมส่งพารามิเตอร์ของเส้นทาง (meta.params.* ใน CEL และ routeParams ใน React)
  • แอป Next.js ของคุณอ่านข้อมูลผ่าน cms-renderer และเพิ่ม Stripe ในรูปแบบ API routes ทั่วไป

กฎสำคัญที่กำหนดรูปแบบการสร้างระบบ: CEL ผูกได้เฉพาะฟิลด์ string และ number เท่านั้น ดังนั้นองค์ประกอบแบบสเกลาร์ เช่น แบรนด์ใน nav, footer และหัวข้อ จะผูกด้วย CEL ส่วนข้อมูลที่มีโครงสร้างหรือเป็นคอลเลกชัน เช่น ตารางสินค้า แกลเลอรีรูปภาพ และ rich text จะถูกดึงภายในคอมโพเนนต์ React โดยใช้พารามิเตอร์ของเส้นทาง และ Stripe คือแหล่งข้อมูลราคาหลัก — price ใน CMS ใช้แสดงผลเท่านั้น ราคาที่เรียกเก็บจริงจะถูกดึงจาก Stripe Price ID ทางฝั่งเซิร์ฟเวอร์เสมอ

ส่วนที่ 1 — การตั้งค่า

ผลลัพธ์สุดท้ายของส่วนนี้คือ แคตตาล็อกขนาดเล็กที่เผยแพร่แล้ว แอปที่เชื่อมต่อเพื่ออ่านข้อมูล ติดตั้ง Stripe และมีดีไซน์พร้อมใช้งาน แต่ยังไม่มีการเรนเดอร์ใด ๆ

1. สมัครใช้งานและสร้างเว็บไซต์

สมัครที่ Profound (การยืนยันตัวตนผ่าน WorkOS) สร้างเว็บไซต์ชื่อ store จากนั้นคัดลอก website ID (UUID ใน URL ของแผงผู้ดูแล) และ read-tier API key (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, catch-all route, สคริปต์ generate-schemas และ <Refresher>) โดยไม่มีสไตล์มาให้ bun add stripe จะติดตั้ง SDK ฝั่งเซิร์ฟเวอร์ ซึ่งเป็น dependency เดียวที่ hosted 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

# Stripe
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_SITE_URL=http://localhost:3000

รับ STRIPE_SECRET_KEY จาก Stripe → Developers → API keys ใช้ test key (sk_test_…) เพื่อไม่ให้มีการโอนเงินจริงระหว่างสร้างระบบ และเปลี่ยนเป็นคีย์จริงเมื่อพร้อมรับชำระเงิน รัน bun dev แล้วเปิด localhost:3000 เพื่อดูหน้าเริ่มต้น

Hosted checkout จะเปลี่ยนเส้นทางเบราว์เซอร์ไปยัง URL ของ Stripe ดังนั้นเซิร์ฟเวอร์ต้องการเพียง secret key ไม่ต้องใช้ publishable key หรือ Stripe SDK ฝั่งไคลเอนต์

3. สร้างคอมโพเนนต์ category, product_image และ item

สร้าง Custom Components สามรายการ (Components → Create new component) เพื่อเป็นแหล่งข้อมูล จึงไม่ต้องติดแท็ก UI Element และตั้งค่าแต่ละรายการเป็น Active

CMS ไม่มีฟิลด์ “อาร์เรย์ของรูปภาพ” ดังนั้นแกลเลอรีจึงใช้ อาร์เรย์ของ reference ไปยังคอมโพเนนต์ product_image ขนาดเล็ก สร้าง category และ product_image และตั้งเป็น Active ก่อน สร้าง item เนื่องจากฟิลด์ reference จะอ้างอิงได้เฉพาะคอมโพเนนต์ที่ 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 of references → product_image), price (Number, เซนต์ — ใช้แสดงผลเท่านั้น), currency (Select, usd), stripePriceId (Text), category (Reference → category), active (Boolean)

ปล่อยให้ทุกฟิลด์เป็น optional ระบบผู้ดูแลจะแปลงชื่อฟิลด์เป็น snake case ตัวพิมพ์เล็ก (“Stripe Price Id” → stripe_price_id) ซึ่งเป็นชื่อที่โค้ดจะใช้ ดังนั้นควรตรวจสอบชื่อจริงอีกครั้งจาก generate-schemas เราใช้ชื่อ code เป็นตัวระบุเส้นทาง ไม่ใช่ slug เพราะเป็นทั้ง Route Slug และคีย์สำหรับการค้นหาด้วย documents.getByCode

4. ดึงคอมโพเนนต์ลงมาเป็นชนิดข้อมูลในเครื่อง

bun run generate-schemas

คำสั่งนี้จะเขียน Zod schemas และ types ลงใน generated/cms-schemas.ts (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 มีสามหมวดหมู่และสินค้าแปดรายการ พร้อมคำอธิบายสั้น ๆ ที่สอดคล้องกับยุคสมัย ราคาเป็นเซนต์ 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 สร้างเป็นเอกสารที่เผยแพร่แล้ว โดยสร้างหมวดหมู่ก่อน เก็บ ID ของหมวดหมู่ แล้วสร้างสินค้าโดยตั้งค่า category เป็น reference รูปแบบ { "_type": "reference", "_ref": "<category-id>", "_schema": "category" } ปล่อย stripePriceId ว่างไว้ก่อน และสร้างสินค้าแบบขนานกันได้

Claude จะเขียนและตรวจสอบ data/catalog.json จากนั้นเรียก create_document แบบขนาน (status: "published") ต้องสร้าง หมวดหมู่ก่อนสินค้า เพื่อให้ reference ชี้ไปยัง ID ที่มีอยู่แล้ว

6. สร้าง Stripe Prices และเชื่อมสินค้าบางรายการ

เมื่อแคตตาล็อกอยู่ใน CMS แล้ว ให้กำหนด Stripe price จริงให้สินค้าบางรายการ ซึ่งเป็นงานของผู้ค้าและทำได้จากแผงผู้ดูแลสองแห่งโดยไม่ต้องเขียนโค้ด:

  1. Stripe Dashboard → Products → + Add product ตั้งราคาแบบชำระครั้งเดียว แล้วคัดลอก Price ID (price_…)
  2. ทำซ้ำกับสินค้าหลักประมาณสามรายการ เช่น Lightbulb, Phonograph และ Kinetoscope
  3. Profound admin → item → Documents → วาง Price ID ใน stripePriceId แล้วบันทึก

CMS เก็บแคตตาล็อก ส่วน Stripe เก็บราคาที่ใช้เรียกเก็บเงินจริง และการเชื่อมโยงคือสตริงหนึ่งรายการที่ผู้ค้านำไปวาง หากต้องการทำอัตโนมัติ Stripe MCP อย่างเป็นทางการสามารถสร้าง Products และ Prices ให้ได้ จากนั้นนำ ID ที่ได้มาใส่ในลักษณะเดียวกัน

7. เพิ่มระบบดีไซน์และเชื่อมต่อด้วย AI

โครงแอปไม่มีสไตล์มาให้ เพิ่มไฟล์ DESIGN.md (บล็อก @theme ของ Tailwind v4 และ tokens) ที่รากโปรเจกต์ ใช้ไฟล์ของคุณเองหรือดาวน์โหลดจาก refero.design จากนั้นสั่ง Claude ให้อ่านไฟล์ดีไซน์ ตั้งค่า Tailwind หากจำเป็น เชื่อมธีมและฟอนต์โดยใช้ next/font และอย่าโหลดฟอนต์จาก Google ตอน runtime ยังไม่ต้องสร้างหน้าหรือคอมโพเนนต์ใด ๆ

ตรวจสอบว่า src/app/globals.css มี @import "tailwindcss"; และบล็อก @theme และเปิด localhost:3000 เพื่อดู tokens ควรโหลดฟอนต์ผ่าน next/font ไม่ใช่การ import จาก Google ระหว่าง runtime

8. เพิ่มรูปภาพสินค้า (ไม่บังคับ)

คุณสามารถสร้าง checkout ที่ใช้งานได้โดยไม่ต้องมีรูปภาพ หากต้องการเพิ่มรูป ให้สร้างเอกสาร product_image ต่อรูปหนึ่งภาพ อัปโหลดลงในฟิลด์ image แล้วนำ reference ไปใส่ในอาร์เรย์ images ของสินค้า ใช้รูปสินค้าของคุณเอง หรือสร้างชุดรูปที่มีสไตล์สอดคล้องกันด้วยโมเดลสร้างภาพ

cms-renderer แบบ standalone ไม่มี helper สำหรับ URL รูปภาพ ดังนั้นให้คัดลอก buildAssetUrl มาไว้ใน src/lib/image.ts (ประมาณ 40 บรรทัด) เพื่อเติม NEXT_PUBLIC_BUNNY_CDN_URL และนามสกุลไฟล์ คอมโพเนนต์ในส่วนการสร้างจะใช้ฟังก์ชันนี้

ส่วนที่ 2 — การสร้างระบบ

สร้างเลเยอร์การเรนเดอร์และ checkout จนจบด้วยการซื้อจริงในโหมดทดสอบ

1. สร้างคอมโพเนนต์ UI element ห้ารายการ

สร้างคอมโพเนนต์ห้ารายการ ตั้งเป็น 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 ของ Page Builder การตั้ง Active เพียงอย่างเดียวไม่พอ ฟิลด์แต่ละรายการเป็นสเกลาร์ซึ่ง CEL ผูกได้ ส่วนข้อมูลแคตตาล็อกจริงจะถูก ProductGrid และ ProductDetail ดึงด้วยพารามิเตอร์ของเส้นทาง

2. สร้าง types ใหม่

bun run generate-schemas

3. สร้างตัวอ่านแคตตาล็อก คอมโพเนนต์ และตะกร้าแบบ headless

ให้สั่ง Claude สร้าง helper สำหรับอ่านข้อมูล คอมโพเนนต์ทั้งห้า ตะกร้า และ registry ในครั้งเดียว โดยใช้ cms-renderer SDK ใน src/:

  • src/lib/catalog.ts เป็นตัวอ่าน CMS ฝั่งเซิร์ฟเวอร์ สร้าง client ด้วย getCmsClient จาก cms-renderer/lib/cms-api ส่ง cmsUrl, apiKey และ websiteId จาก environment ออกแบบฟังก์ชัน getItemByCode(code) ให้เรียก cms.documents.getByCode.query และคืนค่า res.document.published_content
  • listItems(categoryCode?) เรียก cms.documents.list.query ด้วย schemaName: "item", status: "published" และ limit: 100 จากนั้นกรอง active !== false และกรองหมวดหมู่จาก category._ref เมื่อมี categoryCode
  • resolveImages(refs) แก้ reference ของ item.images ผ่าน cms.documents.get.query และเปลี่ยนฟิลด์ image เป็น URL ด้วย buildAssetUrl
  • ลงทะเบียนคอมโพเนนต์ใน src/components/ ด้วยชื่อแบบ snake_case ได้แก่ { nav, product_grid, product_detail, cart_summary, footer }
  • Nav และ Footer อ่านฟิลด์สเกลาร์จาก content ส่วน ProductGrid และ ProductDetail เป็น async server components ที่อ่าน routeParams และดึงข้อมูลจาก catalog.ts โดย routeParams.<param> มีรูปแบบ { value, … } จึงต้องอ่าน .value
  • CartSummary แสดงตะกร้าจาก useCart พร้อมปุ่ม Pay และเก็บ formatPrice ไว้ใน src/lib/format.ts เพื่อไม่ให้ client component import catalog.ts
  • AddToCartButton.tsx เป็นปุ่ม "use client" รับ { code, name, priceLabel } และเรียก useCart().addItem({ code, name, priceLabel, quantity: 1 })
  • useCart.ts เป็นตะกร้าแบบ headless เก็บรายการ { code, name, priceLabel, quantity } ใน state และ localStorage มี addItem, removeItem, updateQty, subtotal และ checkout() ซึ่งส่ง { lines: [{ code, quantity }] } ไปยัง /api/stripe/checkout ห้ามส่งราคา โดยให้ส่งเฉพาะ code และจำนวน จากนั้นเปลี่ยนเส้นทางไปยัง url ที่ได้รับ

จัดสไตล์ทุกอย่างด้วยระบบดีไซน์ของคุณเอง อย่าคัดลอกเลย์เอาต์ของเว็บไซต์ต้นฉบับ

ข้อควรรู้หลังจากสร้างเสร็จ:

  • องค์ประกอบแบบสเกลาร์มาจาก content ส่วนข้อมูลแคตตาล็อกมาจาก routeParams และการดึงข้อมูลจาก catalog.ts เพราะ CEL ผูกลิสต์หรือแกลเลอรีไม่ได้ ตะกร้าจะเก็บเฉพาะ รหัสสินค้า ไม่ใช่ราคา
  • routeParams.<param> มีรูปแบบ { value, schemaName, document } ต้องอ่าน .value การอ่านข้อมูลต้องใช้ published_content ไม่ใช่ .content และ registry ต้องใช้คีย์แบบ snake_case ให้ตรงกับชื่อในแผงผู้ดูแล
  • อัปเกรด @types/react และ @types/react-dom เป็น v19 เพราะโครงแอปใช้ v18 ซึ่งทำให้ async server-component blocks ใช้งานร่วมกับ React 19 มีปัญหา

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 price:

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 ใช้เป็นสัญญาณที่เชื่อถือได้สำหรับการดำเนินการหลังการชำระเงิน:

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") {
    // ดำเนินการตามคำสั่งซื้อหรือส่งใบเสร็จ
  }
  return new Response(null, { status: 200 });
}

การแก้ไขโครงแอปที่จำเป็น: src/proxy.ts ของโครงแอปจะส่งทุก /api/* ไปยัง CMS ทำให้ Stripe routes ไม่ทำงาน ต้องปล่อย /api/stripe ให้ผ่านไปจัดการในเครื่องก่อน ส่วน matcher เดิมให้คงไว้เหมือนเดิม

ตรวจสอบด้วย curl -X POST localhost:3000/api/stripe/webhook -d x ซึ่งควรคืนค่า Bad signature

รัน Stripe CLI สำหรับ webhook ในเครื่อง:

stripe login
stripe listen --forward-to localhost:3000/api/stripe/webhook

คัดลอก whsec_... ไปไว้ใน STRIPE_WEBHOOK_SECRET แล้วรีสตาร์ต bun dev ค่า webhook นี้ใช้ได้ต่อเซสชันเท่านั้น ความปลอดภัยมีสองส่วน: checkout จะคำนวณราคาใหม่จาก CMS ด้วย code จึงไม่สามารถเปลี่ยนราคาผ่านตะกร้าที่ถูกแก้ไขได้ และ webhook จะตรวจสอบลายเซ็นกับ body แบบดิบ

5. สร้างเส้นทาง

ไปที่ Admin → Pages → Create page แล้วสร้างสามครั้ง โดยแมปพารามิเตอร์แต่ละตัวกับคอมโพเนนต์ที่ใช้ฟิลด์ 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 เพิ่มคอมโพเนนต์ตามลำดับ กรอกฟิลด์สเกลาร์ด้วยค่าคงที่หรือ 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 เป็นข้อความคงที่ และตั้งหัวข้อเป็นป้ายกำกับคงที่

ข้อควรระวังของ Parametric Page Builder: บนสองเส้นทางแบบ parametric การเพิ่ม UI elements อาจไม่ถูกบันทึก ทำให้บล็อกถูกทิ้งไว้และหน้าแสดงผลว่าง จนกว่าจะได้รับการแก้ไข ให้เชื่อม block_ids ของหน้าโดยตรงผ่าน update_page ใน Profound MCP แล้วเผยแพร่ ส่วน /cart แบบสแตติกจะเชื่อมต่อได้ตามปกติ ด้วยเหตุผลเดียวกัน ProductGrid จะสร้างหัวข้อจากหมวดหมู่ที่ดึงมาแทนการใช้ CEL

7. เรนเดอร์และซื้อสินค้า

เปิด /categories/lighting เพื่อดูตารางสินค้า คลิกสินค้าเพื่อดูรายละเอียดและเพิ่มลงตะกร้า จากนั้นเปิด /cart แล้วกด Pay ระบบจะเปลี่ยนเส้นทางไปยัง Stripe-hosted checkout ใช้บัตรทดสอบ 4242 4242 4242 4242 พร้อมวันหมดอายุและ CVC ใด ๆ ที่ยังไม่หมดอายุ เมื่อชำระเงินเสร็จจะกลับมาที่ /cart?status=success และ stripe listen จะแสดง checkout.session.completed

เฉพาะสินค้าที่มีราคา Stripe เท่านั้นที่ซื้อได้ ดังนั้นให้ซื้อหนึ่งในสินค้าประมาณสามรายการที่กำหนดราคาไว้ในขั้นตอนที่ 6

ตัวเลือก — ทำให้รองรับหลายภาษา แปลแต่ละคอมโพเนนต์ (รองรับทั้ง 35 ภาษาในครั้งเดียว) เพิ่มส่วน /{language}/… ที่แมปกับ System component ชื่อ 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

generated/cms-schemas.ts ถูกละเว้นโดย git ดังนั้นให้กำหนด build command เพื่อสร้างใหม่ เพิ่ม vercel.json:

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

ใน Vercel เลือก Add New → Project แล้ว import store จากนั้นเพิ่ม environment variables ได้แก่ PROFOUND_API_KEY, NEXT_PUBLIC_PROFOUND_WEBSITE_ID, NEXT_PUBLIC_CMS_API_URL, NEXT_PUBLIC_BUNNY_CDN_URL, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET (ค่าของ endpoint ที่ดีพลอยแล้ว) และ NEXT_PUBLIC_SITE_URL (URL สำหรับ production) แล้วดีพลอย

จากนั้นตั้งค่า deployed webhook ซึ่งใช้คนละ secret กับ stripe listen ในเครื่อง ไปที่ Stripe → Developers → Webhooks → + Add endpoint ใส่ https://<prod>/api/stripe/webhook และเลือกเหตุการณ์ checkout.session.completed คัดลอก whsec_… ไปไว้ใน Vercel แล้วดีพลอยใหม่

ตัวแปรสภาพแวดล้อมที่ขาดหายมักทำให้ “ทำงานในเครื่องแต่หน้าเว็บว่างใน production” ซึ่งเป็นข้อผิดพลาดที่พบบ่อยที่สุด ตอนนี้ใช้ test keys ได้ แต่เมื่อพร้อมรับเงินจริงให้เปลี่ยน STRIPE_SECRET_KEY และ webhook secret เป็นค่าจริง

2. ตัวอย่างแบบสดและการแก้ไขในหน้าเดิม

ทั้งสองฟีเจอร์มีมาให้พร้อมกับโครงแอป

  • ตัวอย่างแบบสด: <Refresher> จะอัปเดตหน้าที่กำลังดูตัวอย่างทันทีเมื่อผู้แก้ไขบันทึกข้อมูลในแผงผู้ดูแล โดยไม่ต้องดีพลอยใหม่ ผู้เข้าชมทั่วไปยังเห็นเฉพาะคอนเทนต์ที่เผยแพร่แล้ว
  • การแก้ไขในหน้าเดิม: เติม ?edit_mode=true ใน URL ใด ๆ เพื่อแสดงโอเวอร์เลย์สำหรับแก้ไข ผู้เข้าชมทั่วไปจะเห็นหน้าแบบสะอาด

เพิ่มเส้นทาง preview ที่โครงแอปไม่ได้ใส่มาให้ แผงผู้ดูแลจะโหลด iframe ตัวอย่างที่ /cms-preview_<path> หากไม่มีเส้นทางนี้ ทุก preview จะตอบกลับ 404 ให้เพิ่มหน้า src/app/cms-preview_/[...slug]/page.tsx โดยใช้ ParametricRoutePreviewPage พร้อม registry, API key, website ID, CMS URL, params และ searchParams และเพิ่ม src/app/cms-preview_/page.tsx สำหรับรากของ segment ด้วย

จบการสร้างระบบ

นี่คือร้านค้า Stripe ที่ขับเคลื่อนด้วยคอนเทนต์: มีแคตตาล็อก CMS หน้ารายการและหน้ารายละเอียดจากชุดเส้นทางเดียวกัน และ hosted checkout ที่ใช้งานได้จริง AI ช่วยเติมข้อมูลแคตตาล็อก เชื่อมระบบดีไซน์ และเขียนตัวอ่านแคตตาล็อก คอมโพเนนต์ และตะกร้าแบบ headless ส่วนคุณสร้างคอมโพเนนต์ เชื่อม Stripe prices สร้างสามเส้นทาง เชื่อม CEL และเขียนไฟล์ Stripe สั้น ๆ สามไฟล์ CEL ใช้ผูกองค์ประกอบของหน้า ส่วนคอมโพเนนต์ใช้ดึงข้อมูลแคตตาล็อก และ Stripe ยังคงมีโค้ดขนาดเล็ก — ใช้การเรียก sessions.create หนึ่งครั้งและ webhook ที่เซ็นกำกับหนึ่งรายการ โดยผู้ซื้อชำระเงินบนหน้าเว็บของ Stripe เอง

Continue Reading
Previous‹Deployments