profound-logoProfound CMS
⌘K
Admin
Theme
Tài liệuHướng dẫnBlogTriết học
Tài liệuHướng dẫnBlogTriết học

Hướng dẫn

Build & Ship an Airport DirectoryTriển khaiMặt tiền stripe

tính năng CMS

Documentation Site TemplateFeature Template BuilderTrình dịch tính năngOrganizations & Website HeirarchyConnect Profound CMS to your AI clientCài đặt-tích hợpSettings API KeysSettings UsageCài đặt-website
All Systems Operational
Powered Byprofound-logo
Theme

Mặt tiền stripe

Một hướng dẫn thực hành: xây dựng mặt tiền Stripe dựa trên nội dung trên Profound CMS — danh mục mà thương nhân chỉnh sửa không cần code, hai tuyến tham số, giỏ headless và Stripe-hosted checkout.

Cửa hàng hoàn chỉnh đang chuyển động — duyệt một danh mục, mở một sản phẩm, thêm vào giỏ, thanh toán.

Một hướng dẫn thực hành để xây dựng cửa hàng định hướng nội dung trên Profound CMS: một danh mục sản phẩm (danh mục + mặt hàng) được mô hình hóa trong CMS, trang danh sách và trang chi tiết từ một bộ tuyến đường, và thanh toán do Stripe lưu trữ được triển khai dưới dạng thành phần headless.

Xương sống là Next.js viết tay cộng với bảng quản trị Profound. Claude Code (thông qua Profound MCP) gánh phần việc nặng cho ba nhiệm vụ — gieo dữ liệu danh mục, kết nối hệ thống thiết kế, và viết các thành phần mặt tiền cửa hàng (bao gồm cả giỏ headless). Ba phần: Thiết lập, Xây dựng, Sản xuất.

Thanh toán trong một dòng. Chúng ta dùng Stripe-hosted Checkout: người mua trả tiền trên trang Stripe, không phải trang của bạn. Ứng dụng của bạn chỉ làm hai việc phía máy chủ — tạo một phiên Checkout và xác minh một webhook. Không có trường thông tin thẻ, không có Stripe Elements, không gánh nặng PCI.

Bạn sẽ xây dựng gì

  • Một danh mục nhỏ được xuất bản trong CMS — ba danh mục và tám sản phẩm (bản demo "Edison's Inventions") — mỗi mục đều chỉnh sửa được bởi thương nhân mà không cần mã.
  • Hai tuyến đường tham số (/products/{item_code}, /categories/{category_code}) cùng một /cart tĩnh, tất cả dùng chung một bộ thành phần.
  • Một giỏ headless (useCart) và Stripe-hosted checkout, với giá luôn được giải quyết phía máy chủ từ Stripe Price ID.
  • Cửa hàng được triển khai lên Vercel với xem trước trực tiếp và chỉnh sửa tại chỗ cho nhóm.

Điều kiện tiên quyết

  • Bun ≥ 1.3 — curl -fsSL https://bun.sh/install | bash
  • Claude Code với Profound MCP (Phần 1 cài đặt nó).
  • Một tài khoản Profound CMS.
  • Một tài khoản Stripe. Hướng dẫn này chạy ở chế độ thử nghiệm nên không có khoản nào bị trừ tiền thật trong lúc bạn xây dựng — nhưng luồng làm việc giống hệt với khóa live, nên nếu muốn bạn cứ dùng khóa tài khoản thật. (Chế độ thử nghiệm không cần thông tin doanh nghiệp hay ngân hàng.)
  • Stripe CLI (stripe login) để dùng webhook cục bộ.
  • Để triển khai: GitHub CLI (gh) và tài khoản Vercel kết nối với GitHub.

Cách các mảnh ghép kết nối với nhau

Profound tách nội dung khỏi hiển thị:

  • Thành phần định nghĩa hình dạng của nội dung. Một Custom Component với trường Route Slug là có thể định tuyến (category, item); một thành phần được gắn thẻ UI Element thì có thể đặt trên trang (nav, product_grid, …).
  • Tài liệu chính là nội dung (một sản phẩm, một danh mục).
  • UI element là các phần của trang; mỗi trường vô hướng nhận một giá trị tĩnh hoặc biểu thức CEL, được đánh giá khi render.
  • Tuyến đường tham số ánh xạ URL tới một tài liệu + UI element, truyền tham số tuyến đường (meta.params.* trong CEL, routeParams trong React).
  • Ứng dụng Next.js của bạn đọc thông qua cms-renderer; Stripe được thêm như các tuyến API thông thường.

Quy tắc duy nhất định hình toàn bộ quy trình: CEL chỉ liên kết các trường kiểu string/number. Vì vậy phần chrome vô hướng (nav brand, footer, tiêu đề) được liên kết bằng CEL, trong khi bất cứ thứ gì phong phú hoặc là tập hợp (lưới sản phẩm, thư viện ảnh, rich text) thì được fetch bên trong thành phần React bằng tham số tuyến đường. Và Stripe là nguồn chân lý về giá — trường price trong CMS chỉ để hiển thị; khoản thu luôn được tính phía máy chủ từ Stripe Price ID.

Phần 1 — Thiết lập

Trạng thái cuối: một danh mục nhỏ đã xuất bản, ứng dụng được kết nối để đọc dữ liệu, Stripe đã cài đặt, thiết kế đã vào chỗ — chưa render gì cả.

1. Đăng ký và tạo website

Đăng ký tại Profound (WorkOS auth). Tạo website tên store, rồi sao chép ID website (UUID trong URL quản trị) và một API key cấp đọc (Deployments → Create API key). Ứng dụng chỉ đọc; việc gieo dữ liệu danh mục sau này đi qua MCP, được xác thực riêng.

2. Tạo khung ứng dụng, kết nối và thêm Stripe

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

Khung sườn là dự án Next.js App Router được cấu hình sẵn cho Profound (cms-renderer SDK, tuyến bắt tất, script generate-schemas, <Refresher>). Nó không kèm styling. bun add stripe kéo về SDK máy chủ — phụ thuộc thanh toán duy nhất mà hosted checkout cần.

Thêm giá trị vào .env.local:

# CMS
PROFOUND_API_KEY=<khóa đọc của bạn>
NEXT_PUBLIC_PROFOUND_WEBSITE_ID=<id website của bạn>
NEXT_PUBLIC_CMS_API_URL=https://cms.dev.tryprofound.com
NEXT_PUBLIC_BUNNY_CDN_URL=https://cms-profound.b-cdn.net   # phục vụ ảnh lưu trên CMS

# Stripe
STRIPE_SECRET_KEY=sk_test_...            # khóa test ở đây; đổi sang khóa live khi go live
STRIPE_WEBHOOK_SECRET=whsec_...          # điền ở bước 4 của phần Build
NEXT_PUBLIC_SITE_URL=http://localhost:3000

Lấy STRIPE_SECRET_KEY từ Stripe → Developers → API keys. Chúng ta dùng khóa test (sk_test_…) nên quá trình xây dựng không đụng tới tiền thật; đổi sang khóa live khi bạn sẵn sàng nhận thanh toán thật. Chạy bun dev và mở localhost:3000 — starter hiển thị.

Hosted checkout chuyển hướng trình duyệt tới URL Stripe, nên server secret key là tất cả những gì Stripe cần — không cần publishable key, không cần client Stripe SDK.

3. Định nghĩa các thành phần category, product_image, và item

Tạo ba Custom Component (Components → Create new component) — nguồn dữ liệu nên không gắn thẻ UI Element. Bật Active cho từng cái.

CMS không có trường "array of image", nên một gallery là mảng tham chiếu tới một thành phần product_image nhỏ. Tạo category và product_image (và bật Active) trước item — một trường tham chiếu chỉ nhắm tới thành phần đang 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 (mảng tham chiếu → product_image), price (Number, cent — chỉ hiển thị), currency (Select, usd), stripePriceId (Text), category (Reference → category), active (Boolean)

Để tất cả trường ở chế độ tùy chọn. Giao diện quản trị chuyển tên trường sang lower-snake-case ("Stripe Price Id" → stripe_price_id) — đó là tên mà mã của bạn sẽ dùng, nên đọc lại tên thật từ generate-schemas ở bước kế tiếp. Chúng ta đặt handle định tuyến là code (không phải slug): nó vừa là Route Slug vừa là khóa để truy xuất sạch bằng documents.getByCode sau này.

Thành phần item — code làm Route Slug, images là tham chiếu tới product_image, cộng thêm stripePriceId và tham chiếu category.

4. Kéo các thành phần về để tạo kiểu cục bộ

bun run generate-schemas

Sinh các schema + kiểu Zod vào generated/cms-schemas.ts (categorySchema/Category, itemSchema/Item). Đồng thời kiểm tra kết nối — sai thông tin đăng nhập sẽ lỗi tại đây.

5. Gieo dữ liệu danh mục qua Profound MCP

Cài và xác thực MCP một lần:

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

Chạy mcp__Profound__authenticate, hoàn tất luồng WorkOS, rồi nhờ Claude:

Tạo một danh mục thương mại điện tử nhỏ cho cửa hàng tên Edison's Inventions — ba danh mục và các sản phẩm này, với description ngắn sát bối cảnh lịch sử, price tính bằng cent, currency: "usd", và 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)

Mỗi danh mục cần name và code chữ thường đó; mỗi mặt hàng cần name, code chữ thường đó, description, price (cent), currency, và active. Lưu vào data/catalog.json và xác thực với thành phần category và item của chúng ta. Sau đó dùng Profound MCP tạo từng tài liệu ở trạng thái đã xuất bản: tạo danh mục trước, ghi lại ID của chúng, rồi tạo mặt hàng với category là tham chiếu — { "_type": "reference", "_ref": "<category-id>", "_schema": "category" }. Làm song song các mặt hàng.

Claude sẽ viết data/catalog.json, xác thực, và gọi create_document song song (status: "published"). Gieo danh mục trước mặt hàng để tham chiếu trỏ tới ID đã tồn tại.

Ba danh mục đã gieo dữ liệu, trạng thái published và Live.

Tám sản phẩm đã gieo dữ liệu, mỗi sản phẩm liên kết tới một danh mục.

6. Tạo Stripe Price và kết nối vài sản phẩm

Danh mục đã nằm trong CMS; giờ hãy gán giá Stripe thật cho vài sản phẩm — việc của thương nhân, thực hiện trên hai bảng quản trị, không cần viết mã:

  1. Stripe Dashboard → Products → + Add product, đặt giá một lần, sao chép Price ID (price_…).
  2. Làm cho khoảng 3 sản phẩm nổi bật (ví dụ Lightbulb, Phonograph, Kinetoscope).
  3. Profound admin → item → Documents → dán từng Price ID vào stripePriceId, lưu lại.

CMS giữ danh mục; Stripe giữ giá chính thức; mối liên kết là một chuỗi mà thương nhân dán vào. (Muốn tự động hóa? Stripe MCP chính thức có thể tạo Products/Prices cho bạn — dán lại ID trả về theo cách tương tự.)

7. Thả hệ thống thiết kế vào và kết nối với AI

Khung sườn không kèm style. Đặt DESIGN.md (khối @theme của Tailwind v4 + token) vào thư mục gốc dự án — của riêng bạn, hoặc tải từ refero.design. Rồi nhờ Claude, chỉ xoay quanh styling:

Đọc file thiết kế tôi vừa thêm. Thiết lập Tailwind nếu cần, rồi gắn theme và font để styling hoạt động. Dùng next/font cho font — đừng tải từ Google lúc runtime. Chỉ styling thôi — chưa cần dựng trang hay thành phần nào.

Xác nhận src/app/globals.css có @import "tailwindcss"; + khối @theme và localhost:3000 hiển thị token. Giữ prompt gọn (mở rộng quá, agent sẽ dựng nguyên trang chủ), và tải font qua next/font, không import Google runtime.

8. Thêm ảnh sản phẩm (tùy chọn)

Tùy chọn — bạn có thể đạt tới checkout hoạt động mà không cần ảnh. Để thêm ảnh: tạo một tài liệu product_image cho mỗi ảnh (upload vào trường image), rồi tham chiếu chúng từ mảng images của sản phẩm. Tự mang ảnh sản phẩm, hoặc dùng mô hình tạo ảnh (nhờ Claude suy ra prompt hợp thương hiệu từ DESIGN.md và khóa một --sref của Midjourney để mọi ảnh thống nhất).

cms-renderer độc lập không có helper tạo URL ảnh, nên hãy chép buildAssetUrl vào src/lib/image.ts (~40 dòng) — nó thêm tiền tố NEXT_PUBLIC_BUNNY_CDN_URL và thêm phần mở rộng. Các thành phần ở bước 3 Phần Build sẽ dùng nó.

Phần 2 — Xây dựng

Xây lớp hiển thị và quy trình thanh toán, kết thúc bằng một giao dịch test-mode thật.

1. Định nghĩa năm thành phần UI element

Năm thành phần, mỗi thành phần Active và được gắn thẻ UI Element (Settings → Tags), không có Route Slug:

  • nav → brand · product_grid → heading · product_detail → heading · cart_summary → heading · footer → text (đều là Text)

Thẻ UI Element khiến một thành phần xuất hiện trong danh sách Add UI Element của Page Builder — chỉ Active thôi chưa đủ. Mỗi trường là vô hướng (loại CEL liên kết được); dữ liệu danh mục thực sự không ở đây — ProductGrid/ProductDetail fetch theo tham số tuyến đường (bước 3).

2. Sinh lại kiểu dữ liệu

bun run generate-schemas

3. Sinh bộ đọc danh mục, các thành phần và giỏ headless

Một prompt tạo helper đọc dữ liệu, năm thành phần, giỏ và registry:

Xây mặt tiền cửa hàng của chúng ta trong src/, dùng Profound cms-renderer SDK.

src/lib/catalog.ts — bộ đọc CMS phía máy chủ. Tạo client với getCmsClient({ cmsUrl: process.env.NEXT_PUBLIC_CMS_API_URL!, apiKey: process.env.PROFOUND_API_KEY, websiteId: process.env.NEXT_PUBLIC_PROFOUND_WEBSITE_ID! }) từ cms-renderer/lib/cms-api. Export getItemByCode(code) → cms.documents.getByCode.query({ websiteId, schemaName: "item", code }) trả về res.document.published_content. Export listItems(categoryCode?) → cms.documents.list.query({ websiteId, schemaName: "item", status: "published", limit: 100 }), map res.documents sang .published_content, lọc active !== false, và nếu có categoryCode thì giữ các mặt hàng có category._ref bằng document.id của danh mục đó. Export resolveImages(refs) giải từng tham chiếu item.images bằng cms.documents.get.query({ websiteId, id: ref._ref }) và chuyển trường ảnh của nó thành URL bằng buildAssetUrl đã chép (Phần 1 bước 8).

src/components/ — năm thành phần UI element được đăng ký trong registry của tuyến bắt tất theo tên thành phần, snake_case khớp với admin: { nav, product_grid, product_detail, cart_summary, footer }. Nav và Footer đọc trường vô hướng từ prop content (được gõ kiểu BlockComponentProps<T> từ cms-renderer/lib/types). ProductGrid và ProductDetail là server component async đọc routeParams và fetch từ catalog.ts: routeParams.<param> là { value, … } — đọc .value, nên ProductGrid gọi listItems(routeParams.category_code?.value) (thẻ sản phẩm liên kết tới /products/{code}) và ProductDetail gọi getItemByCode(routeParams.item_code?.value) (gallery qua resolveImages, mô tả rich-text, giá, nút Thêm vào giỏ). CartSummary hiển thị giỏ từ useCart với nút Thanh toán. Giữ formatPrice trong file thuần src/lib/format.ts để component phía client không import catalog.ts chỉ dành cho server.

src/components/AddToCartButton.tsx — một nút "use client" nhận { code, name, priceLabel } và gọi useCart().addItem({ code, name, priceLabel, quantity: 1 }). Dùng nó bên trong ProductDetail.

src/lib/useCart.ts — giỏ headless: dòng hàng { code, name, priceLabel, quantity } trong state, lưu vào localStorage, cung cấp addItem/removeItem/updateQty/subtotal và checkout() gửi POST { lines: [{ code, quantity }] } (chỉ mã và số lượng — không bao giờ gửi giá) tới /api/stripe/checkout, rồi chuyển hướng tới url trả về.

Style mọi thứ bằng hệ thống thiết kế của chúng ta, dưới dạng component riêng — đừng sao chép layout của site gốc.

Ba điều cần nhớ sau khi chạy:

  • Chrome vô hướng nằm trong content ({ content }: BlockComponentProps<T>) — nếu bạn bung các trường thành prop cấp cao nhất, block sẽ render trống. Dữ liệu danh mục đến từ routeParams + fetch trong catalog.ts, vì CEL không ràng buộc được danh sách hay gallery. Giỏ giữ code của sản phẩm, không bao giờ giữ giá.
  • routeParams.<param> là { value, schemaName, document } — đọc .value. Giá trị trả về là published_content, không phải .content. Khóa trong registry phải ở dạng snake_case khớp admin.
  • Nâng @types/react/@types/react-dom lên v19 — khung sườn đi kèm v18, gây lỗi cho block server component async với React 19.

4. Viết mã máy chủ Stripe (xương sống)

Ba file server ngắn — toàn bộ mã thanh toán của ứng dụng. Chúng tái sử dụng getItemByCode, nên khoản thu được xác định lại phía máy chủ.

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 — resolve từng mặt hàng từ CMS, thu theo giá Stripe:

import { NextResponse } from "next/server";
import { stripe } from "@/lib/stripe";
import { getItemByCode } from "@/lib/catalog"; // phía server, dùng khóa cấp đọc

export async function POST(req: Request) {
  const { lines } = await req.json();                // [{ code, quantity }] — không nhận giá từ client
  const line_items = await Promise.all(
    lines.map(async ({ code, quantity }: { code: string; quantity: number }) => {
      const item = await getItemByCode(code);          // server lấy từ CMS
      return { price: item!.stripe_price_id, quantity }; // giá từ CMS, không phải 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 tới đây
}

src/app/api/stripe/webhook/route.ts — tín hiệu hoàn tất đáng tin cậy:

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

export async function POST(req: Request) {
  const body = await req.text();                      // phần thân RAW — cần để xác minh chữ ký
  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") {
    // thực hiện hoàn tất đơn hàng: ghi nhận đơn / gửi hóa đơn.
  }
  return new Response(null, { status: 200 });
}

Sửa lỗi bắt buộc của khung sườn: src/proxy.ts hiện chuyển tiếp mọi /api/* tới CMS, nên các tuyến Stripe của bạn không chạy. Cho chúng đi qua trước:

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();                       // xử lý cục bộ
  }
  return cmsProxy(request as unknown as Parameters<typeof cmsProxy>[0]);
};
// giữ nguyên `export const config = { matcher: [...] }` của khung sườn

Kiểm tra: curl -X POST localhost:3000/api/stripe/webhook -d x trả về Bad signature.

Chạy Stripe CLI để nghe webhook cục bộ:

stripe login
stripe listen --forward-to localhost:3000/api/stripe/webhook
# sao chép whsec_... vào STRIPE_WEBHOOK_SECRET, khởi động lại bun dev

whsec_… chỉ dùng cho phiên đó. Hai quy tắc đảm bảo an toàn: checkout xác định lại giá từ CMS qua code (giỏ bị sửa không đổi được giá), và webhook xác minh chữ ký dựa trên phần thân raw.

5. Tạo tuyến đường

Admin → Pages → Create page, ba lần. Ánh xạ từng tham số tới thành phần (trường slug code):

  1. /products/{item_code} → item
  2. /categories/{category_code} → category
  3. /cart — trang tĩnh (nhập nguyên /cart, không phải /{cart})

6. Thêm UI element, gắn CEL, xuất bản

Cho từng tuyến: Page Builder → Add UI Element → Custom → thêm thành phần theo thứ tự, điền giá trị vô hướng (giá trị tĩnh hoặc CEL), Publish.

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

Đặt nav.brand và footer.text thành chuỗi tĩnh; tiêu đề thành nhãn tĩnh.

Page Builder trên tuyến danh mục — element product_grid được chọn, heading của nó gắn bằng CEL.

Page Builder trên tuyến sản phẩm — element product_detail trong binding của một mặt hàng.

Lưu ý Page Builder cho tuyến tham số: trên hai tuyến tham số, việc thêm UI element không được lưu (block bị mồ côi và trang render trắng). Cho tới khi sửa, hãy gắn block_ids của các trang đó trực tiếp qua Profound MCP update_page, rồi publish. (Trang /cart tĩnh gắn bình thường.) Vì lí do tương tự, ProductGrid lấy heading từ danh mục fetch được thay vì CEL.

7. Render và thanh toán

  • /categories/lighting → lưới sản phẩm. Nhấp vào một sản phẩm → trang chi tiết + Thêm vào giỏ. /cart → Thanh toán.
  • Thanh toán chuyển hướng tới Stripe-hosted checkout. Dùng thẻ test 4242 4242 4242 4242, hạn và CVC bất kỳ trong tương lai. Bạn trở lại /cart?status=success, và stripe listen hiển thị checkout.session.completed.

Trang sản phẩm đã render — gallery, giá và nút Thêm vào giỏ.

Giỏ hàng — các dòng sản phẩm và nút Thanh toán bằng Stripe.

Chỉ những sản phẩm đã có giá mới mua được — hãy mua một trong khoảng 3 sản phẩm bạn đã gắn giá ở bước 6.

Tùy chọn — quốc tế hóa. Dịch từng thành phần (cả 35 ngôn ngữ một lúc), thêm segment /{language}/… ánh xạ tới System component language tích hợp sẵn, và chuyển trường gắn CEL sang documents.translated. Xem hướng dẫn thư mục sân bay, Phần 2 bước 7.

Phần 3 — Sản xuất

1. Triển khai: GitHub, rồi Vercel

git init && git add -A && git commit -m "Stripe storefront"
gh repo create store --private --source=. --push   # --public cũng được

Lệnh build: generated/cms-schemas.ts được bỏ qua khỏi git, nên cố định build để sinh lại — thêm vercel.json:

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

Trong Vercel: Add New → Project, import store, rồi bổ sung biến môi trường — PROFOUND_API_KEY, NEXT_PUBLIC_PROFOUND_WEBSITE_ID, NEXT_PUBLIC_CMS_API_URL, NEXT_PUBLIC_BUNNY_CDN_URL, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET (giá trị endpoint triển khai, dưới đây), và NEXT_PUBLIC_SITE_URL (URL prod của bạn). Deploy.

Sau đó gắn webhook triển khai (bí mật stripe listen cục bộ chỉ dùng tại chỗ): Stripe → Developers → Webhooks → + Add endpoint → https://<prod>/api/stripe/webhook, sự kiện checkout.session.completed. Sao chép whsec_… vào Vercel và redeploy.

Thiếu biến môi trường = "chạy được cục bộ, trống trên prod" — lỗi triển khai số 1. Chúng ta triển khai với khóa test; đổi STRIPE_SECRET_KEY và webhook secret sang giá trị live khi bạn sẵn sàng nhận thanh toán thật.

2. Xem trước trực tiếp và chỉnh sửa tại chỗ

Cả hai đều đi kèm khung sườn.

  • Live preview: <Refresher> cập nhật trang bạn đang xem trước khi biên tập viên lưu trong admin — không cần redeploy. (Đây là preview cho biên tập viên; khách truy cập vẫn xem nội dung đã publish với tái xác thực thông thường.)
  • Chỉnh sửa tại chỗ: thêm ?edit_mode=true vào bất kỳ URL nào để bật lớp phủ chỉnh sửa. Khách công khai thấy trang sạch.

Thêm tuyến preview mà khung sườn bỏ sót. Bảng quản trị tải iframe preview tại /cms-preview_<path>; không có tuyến này thì mọi preview 404. Thêm:

// src/app/cms-preview_/[...slug]/page.tsx
import { ParametricRoutePreviewPage } from "cms-renderer/lib/renderer";
import { registry } from "../../registry";   // trích registry của bạn sang module dùng chung
export default async function Page({ params, searchParams }) {
  const { slug } = await params;
 ցconst 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} />;
}

Thêm cả src/app/cms-preview_/page.tsx (tương tự, slug: []) cho root segment.

Vậy là hoàn tất

Một mặt tiền Stripe định hướng nội dung: danh mục CMS, trang danh sách + chi tiết từ một bộ tuyến, và quy trình hosted checkout đang hoạt động. AI đã gieo danh mục, kết nối thiết kế, viết bộ đọc danh mục + thành phần + giỏ headless; bạn làm thành phần, gắn giá Stripe, tạo ba tuyến, gắn chrome bằng CEL và viết ba file Stripe ngắn. CEL gắn chrome; component fetch danh mục. Stripe vẫn gọn — một lệnh sessions.create và một webhook có chữ ký, với người mua thanh toán trên chính trang của Stripe.

Continue Reading
Previous‹Triển khai