คู่มือแบบลงมือทำ: สร้างร้านค้า 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
/products/{item_code}, /categories/{category_code}) และ /cart แบบสแตติก ทั้งหมดสร้างจากชุดคอมโพเนนต์เดียวกันuseCart) และ Stripe-hosted checkout โดยราคาจะถูกตรวจสอบและดึงจาก Stripe Price ID ทางฝั่งเซิร์ฟเวอร์เสมอcurl -fsSL https://bun.sh/install | bashstripe login) สำหรับ webhook ในเครื่องgh) และบัญชี Vercel ที่เชื่อมต่อกับ GitHubProfound แยก คอนเทนต์ ออกจาก การเรนเดอร์:
category, item) ส่วนคอมโพเนนต์ที่ติดแท็ก UI Element จะสามารถวางบนหน้าได้ (nav, product_grid, …)meta.params.* ใน CEL และ routeParams ใน React)cms-renderer และเพิ่ม Stripe ในรูปแบบ API routes ทั่วไปกฎสำคัญที่กำหนดรูปแบบการสร้างระบบ: CEL ผูกได้เฉพาะฟิลด์ string และ number เท่านั้น ดังนั้นองค์ประกอบแบบสเกลาร์ เช่น แบรนด์ใน nav, footer และหัวข้อ จะผูกด้วย CEL ส่วนข้อมูลที่มีโครงสร้างหรือเป็นคอลเลกชัน เช่น ตารางสินค้า แกลเลอรีรูปภาพ และ rich text จะถูกดึงภายในคอมโพเนนต์ React โดยใช้พารามิเตอร์ของเส้นทาง และ Stripe คือแหล่งข้อมูลราคาหลัก — price ใน CMS ใช้แสดงผลเท่านั้น ราคาที่เรียกเก็บจริงจะถูกดึงจาก Stripe Price ID ทางฝั่งเซิร์ฟเวอร์เสมอ
ผลลัพธ์สุดท้ายของส่วนนี้คือ แคตตาล็อกขนาดเล็กที่เผยแพร่แล้ว แอปที่เชื่อมต่อเพื่ออ่านข้อมูล ติดตั้ง Stripe และมีดีไซน์พร้อมใช้งาน แต่ยังไม่มีการเรนเดอร์ใด ๆ
สมัครที่ Profound (การยืนยันตัวตนผ่าน WorkOS) สร้างเว็บไซต์ชื่อ store จากนั้นคัดลอก website ID (UUID ใน URL ของแผงผู้ดูแล) และ read-tier API key (Deployments → Create API key) แอปจะอ่านข้อมูลเท่านั้น ส่วนการเติมข้อมูลแคตตาล็อกภายหลังจะดำเนินการผ่าน MCP ซึ่งยืนยันตัวตนแยกต่างหาก
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 ฝั่งไคลเอนต์
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
bun run generate-schemas
คำสั่งนี้จะเขียน Zod schemas และ types ลงใน generated/cms-schemas.ts (categorySchema/Category, itemSchema/Item) และใช้ตรวจสอบการเชื่อมต่อด้วย หากข้อมูลรับรองไม่ถูกต้อง ขั้นตอนนี้จะล้มเหลว
ติดตั้งและยืนยันตัวตน 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:
code: lighting): Incandescent Lightbulb (incandescent-lightbulb, $24), Electric Dynamo (electric-dynamo, $890), Electric Pen (electric-pen, $49)code: sound): Tinfoil Phonograph (tinfoil-phonograph, $249), Carbon Microphone (carbon-microphone, $59), Dictaphone (dictaphone, $179)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 ที่มีอยู่แล้ว
เมื่อแคตตาล็อกอยู่ใน CMS แล้ว ให้กำหนด Stripe price จริงให้สินค้าบางรายการ ซึ่งเป็นงานของผู้ค้าและทำได้จากแผงผู้ดูแลสองแห่งโดยไม่ต้องเขียนโค้ด:
price_…)stripePriceId แล้วบันทึกCMS เก็บแคตตาล็อก ส่วน Stripe เก็บราคาที่ใช้เรียกเก็บเงินจริง และการเชื่อมโยงคือสตริงหนึ่งรายการที่ผู้ค้านำไปวาง หากต้องการทำอัตโนมัติ Stripe MCP อย่างเป็นทางการสามารถสร้าง Products และ Prices ให้ได้ จากนั้นนำ ID ที่ได้มาใส่ในลักษณะเดียวกัน
โครงแอปไม่มีสไตล์มาให้ เพิ่มไฟล์ 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
คุณสามารถสร้าง checkout ที่ใช้งานได้โดยไม่ต้องมีรูปภาพ หากต้องการเพิ่มรูป ให้สร้างเอกสาร product_image ต่อรูปหนึ่งภาพ อัปโหลดลงในฟิลด์ image แล้วนำ reference ไปใส่ในอาร์เรย์ images ของสินค้า ใช้รูปสินค้าของคุณเอง หรือสร้างชุดรูปที่มีสไตล์สอดคล้องกันด้วยโมเดลสร้างภาพ
cms-rendererแบบ standalone ไม่มี helper สำหรับ URL รูปภาพ ดังนั้นให้คัดลอกbuildAssetUrlมาไว้ในsrc/lib/image.ts(ประมาณ 40 บรรทัด) เพื่อเติมNEXT_PUBLIC_BUNNY_CDN_URLและนามสกุลไฟล์ คอมโพเนนต์ในส่วนการสร้างจะใช้ฟังก์ชันนี้
สร้างเลเยอร์การเรนเดอร์และ checkout จนจบด้วยการซื้อจริงในโหมดทดสอบ
สร้างคอมโพเนนต์ห้ารายการ ตั้งเป็น 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 ดึงด้วยพารามิเตอร์ของเส้นทาง
bun run generate-schemas
ให้สั่ง 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_contentlistItems(categoryCode?) เรียก cms.documents.list.query ด้วย schemaName: "item", status: "published" และ limit: 100 จากนั้นกรอง active !== false และกรองหมวดหมู่จาก category._ref เมื่อมี categoryCoderesolveImages(refs) แก้ reference ของ item.images ผ่าน cms.documents.get.query และเปลี่ยนฟิลด์ image เป็น URL ด้วย buildAssetUrlsrc/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, … } จึงต้องอ่าน .valueCartSummary แสดงตะกร้าจาก useCart พร้อมปุ่ม Pay และเก็บ formatPrice ไว้ใน src/lib/format.ts เพื่อไม่ให้ client component import catalog.tsAddToCartButton.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 มีปัญหามีไฟล์เซิร์ฟเวอร์สั้น ๆ สามไฟล์ ซึ่งเป็นโค้ดชำระเงินทั้งหมดในแอป โดยใช้ 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 แบบดิบ
ไปที่ Admin → Pages → Create page แล้วสร้างสามครั้ง โดยแมปพารามิเตอร์แต่ละตัวกับคอมโพเนนต์ที่ใช้ฟิลด์ slug คือ code:
/products/{item_code} → item/categories/{category_code} → category/cart — หน้าแบบสแตติก ให้ป้อน /cart ตรง ๆ ไม่ใช่ /{cart}สำหรับแต่ละเส้นทาง ให้ไปที่ 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
เปิด /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
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 เป็นค่าจริง
ทั้งสองฟีเจอร์มีมาให้พร้อมกับโครงแอป
<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 เอง