جولة عملية: ابنِ واجهة متجر Stripe مدفوعة بالمحتوى على Profound CMS — كتالوج يعدّله التاجر دون كود، مساران بارامتريان، سلة عديمة الرأس، وCheckout مستضاف من Stripe.
المتجر المنتهي أثناء الحركة — تصفح فئة، افتح منتجًا، أضف إلى السلة، وأكمل الدفع.
جولة عملية تبني متجرًا مدفوعًا بالمحتوى على Profound CMS: كتالوج منتجات (فئات + عناصر) مصمم في الـCMS، صفحات عرض وتفاصيل من مجموعة واحدة من المسارات، وخروج مستضاف من Stripe مُرسل كمكوّن عديم الرأس.
الهيكل الأساسي مكتوب يدويًا باستخدام Next.js بالإضافة إلى لوحة تحكم Profound. يقوم Claude Code (عبر Profound MCP) بالأعمال الثقيلة في ثلاث مهام — تعبئة الكتالوج، ربط نظام التصميم، وكتابة مكوّنات المتجر (بما في ذلك السلة عديمة الرأس). ثلاثة أجزاء: الإعداد، البناء، الإنتاج.
المدفوعات في سطر واحد. نستخدم Checkout المستضاف من Stripe: يدفع المتسوق على صفحة Stripe، وليس صفحتك. تطبيقك يقوم بأمرين فقط على الخادم — إنشاء جلسة Checkout والتحقق من Webhook واحد. لا حقول بطاقات، لا Stripe Elements، ولا أعباء PCI.
/products/{item_code}، /categories/{category_code}) بالإضافة إلى /cart ثابتة، كلها من مجموعة واحدة من المكوّنات.useCart) وCheckout مستضاف من Stripe، مع تحديد السعر دائمًا على الخادم من خلال Stripe Price ID.curl -fsSL https://bun.sh/install | bashstripe login) من أجل Webhooks المحلية.gh) وحساب Vercel مرتبط بـGitHub.يقوم Profound بفصل المحتوى عن التصيير:
category، item); واحد موسوم بـUI Element يمكن وضعه في صفحة (nav، product_grid، …).meta.params.* في CEL، وrouteParams في React).cms-renderer; وتُضاف Stripe كمسارات API عادية.القاعدة الوحيدة التي تشكّل البناء: CEL يرتبط بـحقول string/number فقط. لذا يتم ربط الإطار البسيط (علامة التنقل، التذييل، العناوين) بـCEL، بينما أي شيء غني أو مجموعة (شبكة منتجات، معرض صور، نص غني) يتم جلبه داخل مكوّن React بواسطة معامل المسار. وStripe هي مصدر الحقيقة للتسعير — سعر الـCMS للعرض فقط؛ يتم تحصيل الرسوم دائمًا على الخادم من Stripe Price ID.
النقطة النهائية: كتالوج صغير منشور، التطبيق موصول لقراءته، Stripe مُثبّت، التصميم في مكانه — لا شيء معروض بعد.
سجّل في Profound (مصادقة WorkOS). أنشئ موقعًا باسم store، ثم انسخ معرّف الموقع (UUID في رابط لوحة التحكم) ومفتاح API بطبقة قراءة (Deployments → Create API key). التطبيق يقرأ فقط؛ تعبئة الكتالوج لاحقًا تتم عبر MCP، الذي يصادق بشكل منفصل.
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 الخادم — التابع الوحيد للمدفوعات الذي يحتاجه الـCheckout المستضاف.
أضف قيمك إلى .env.local:
# CMS
PROFOUND_API_KEY=<مفتاح القراءة الخاص بك>
NEXT_PUBLIC_PROFOUND_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 — يظهر النموذج المبدئي.
يعيد الـCheckout المستضاف توجيه المتصفح إلى عنوان Stripe، لذا المفتاح السرّي للخادم هو كل ما يحتاجه Stripe — لا مفتاح قابل للنشر، ولا عميل Stripe SDK.
category، product_image، وitemأنشئ ثلاثة Custom Components (Components → Create new component) — مصدر البيانات، لذا بدون علامة UI Element. اضبط كل واحد على Active.
لا يحتوي الـCMS على حقل "مصفوفة صور"، لذا المعرض عبارة عن مصفوفة مراجع لمكوّن صغير product_image. أنشئ category وproduct_image (واجعلها Active) قبل item — حقل المرجع يستهدف فقط المكوّنات المفعّلة.
category — name (نص)، code (نص، Route Slug)، description (نص غني)، heroImage (صورة)product_image — image (صورة)item — name (نص)، code (نص، Route Slug)، description (نص غني)، images (مصفوفة مراجع → product_image)، price (رقم، بالسنت — للعرض فقط)، currency (قائمة، usd)، stripePriceId (نص)، category (مرجع → category)، active (قيمة منطقية)اترك جميع الحقول اختيارية. يقوم النظام بتحويل أسماء الحقول إلى أحرف سفلية ("Stripe Price Id" → stripe_price_id) — هذا ما يعتمد عليه كودك، لذا اقرأ الأسماء الحقيقية من generate-schemas لاحقًا. سمّينا المقبض القابل للتوجيه code (وليس slug): فهو Route Slug وأيضًا المفتاح لاستعلام documents.getByCode نظيف لاحقًا.
مكوّن item — code كـRoute Slug، وimages كمراجع إلى product_image، بالإضافة إلى stripePriceId ومرجع category.
bun run generate-schemas
يكتب مخططات وأنواع Zod إلى 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 — ثلاث فئات وهذه المنتجات، مع
descriptionقصيرة دقيقة زمنيًا لكل منها، وpriceبالسنت، وcurrency: "usd"، وactive: true:
- Lighting & Power (
code: lighting): مصباح متوهج (incandescent-lightbulb, $24)، داينامو كهربائي (electric-dynamo, $890)، قلم كهربائي (electric-pen, $49)- Sound Recording (
code: sound): فونوغراف القصدير (tinfoil-phonograph, $249)، ميكروفون الكربون (carbon-microphone, $59)، الدكتافون (dictaphone, $179)- Motion Pictures (
code: motion): الكينتوسكوب (kinetoscope, $399)، كاميرا الكينتوغراف (kinetograph, $549)تحتاج كل فئة إلى
nameوcodeصغير؛ ويحتاج كل عنصر إلىname، وcodeصغير، وdescription، وprice(بالسنت)، وcurrency، وactive. احفظه فيdata/catalog.jsonوتحقق من صحته مقابل مكوّناتناcategoryوitem. ثم استخدم Profound MCP لإنشاء كل مستند منشور: أنشئ الفئات أولًا، احفظ معرّفاتها، ثم أنشئ العناصر مع تعيينcategoryإلى مرجع —{ "_type": "reference", "_ref": "<category-id>", "_schema": "category" }. اتركstripePriceIdفارغًا الآن. نفّذ العناصر بالتوازي.
يقوم Claude بكتابة data/catalog.json، والتحقق منه، وإرسال استدعاءات create_document المتوازية (status: "published"). ازرع الفئات قبل العناصر حتى تشير المراجع إلى معرّفات موجودة.
الفئات الثلاث المزروعة، منشورة وبث مباشر.
المنتجات الثمانية المزروعة، كل منها مرتبط بفئة.
الكتالوج موجود في الـCMS؛ الآن أعطِ بعض المنتجات سعرًا حقيقيًا في Stripe — مهمة التاجر، تتم في لوحتين إداريتين، دون كود:
price_…).stripePriceId، ثم احفظ.يحتفظ الـCMS بالكتالوج؛ يحتفظ Stripe بالسعر الرسمي؛ الرابط هو سلسلة واحدة يلصقها التاجر. (تفضل أتمتتها؟ يمكن لـStripe MCP الرسمي إنشاء المنتجات/الأسعار نيابةً عنك — ألصق المعرّفات المعادة بنفس الطريقة.)
يأتي الهيكل بدون تنسيق. ضع ملف DESIGN.md (كتلة @theme خاصة بتايلويند v4 + الرموز) في جذر المشروع — إحدى ملفاتك، أو نزّل واحدًا من refero.design.
ثم اطلب من Claude، مع حصر المهمة في التنسيق فقط:
اقرأ ملف التصميم الذي أضفته للتو. اضبط Tailwind إذا لزم الأمر، ثم اربط السمة والخطوط حتى يعمل التنسيق. استخدم
next/fontللخطوط — لا تحمّلها من Google وقت التشغيل. فقط التنسيق — لا تُنشئ أي صفحات أو مكوّنات الآن.
تحقّق من أن src/app/globals.css يحتوي على @import "tailwindcss"; + كتلة @theme وأن localhost:3000 يعرض الرموز. أبقِ الطلب محددًا (إذا كان مفتوحًا، قد ينشئ الوكيل صفحة كاملة)، وحمّل الخطوط عبر next/font، وليس استيرادًا وقت التشغيل من Google.
اختياري — يمكنك الوصول إلى Checkout عامل دون صور. لإضافتها: أنشئ مستند product_image لكل صورة (حمّلها في حقل image)، ثم اربط تلك الوثائق من مصفوفة images الخاصة بالمنتج. أحضر صور المنتجات بنفسك، أو أنشئ مجموعة متناسقة عبر نموذج صور (اطلب من Claude صياغة مطالبات متناسقة بناءً على DESIGN.md وثبّت قيمة Midjourney --sref واحدة حتى تتطابق كل اللقطات).
لا يحتوي
cms-rendererالمستقل على أداة مساعدة لعناوين الصور، لذا استورد دالةbuildAssetUrlإلىsrc/lib/image.ts(~40 سطرًا) — تضيف بادئةNEXT_PUBLIC_BUNNY_CDN_URLوتلحق الامتداد. المكوّنات في خطوة البناء 3 تستخدمها.
ابنِ طبقة التصيير وCheckout، وانتهِ بعملية شراء حقيقية في وضع الاختبار.
خمسة مكوّنات، كل واحد Active وموسوم UI Element (الإعدادات → Tags)، بدون Route Slug:
nav → brand · product_grid → heading · product_detail → heading ·
cart_summary → heading · footer → text (كلها نص)علامة UI Element هي ما يجعل المكوّن يظهر في قائمة Add UI Element في منشئ الصفحات — كون المكوّن Active وحده لا يكفي. كل حقل عبارة عن قيمة قياسية (النوع الذي يرتبط به CEL)؛ بيانات الكتالوج الفعلية ليست حقلًا هنا — ProductGrid/ProductDetail تجلبها بواسطة معامل المسار (الخطوة 3).
bun run generate-schemas
طلب واحد يبني أداة القراءة، المكوّنات الخمسة، السلة، والسجل:
ابنِ متجرنا في
src/، باستخدامcms-rendererSDK الخاص بـProfound.
src/lib/catalog.ts— قارئ CMS يعمل على الخادم. أنشئ عميلًا بواسطة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فاحتفظ بالعناصر التي يساوي مرجع فئتهاdocument.id. صدّرresolveImages(refs)الذي يحل كل مرجع فيitem.imagesعبرcms.documents.get.query({ websiteId, id: ref._ref })ويحويل حقل الصورة إلى عنوان URL باستخدامbuildAssetUrlالمستورد (الجزء 1 الخطوة 8).
src/components/— خمسة مكوّنات عناصر واجهة مسجلة في سجل المسار الجامع بنفس أسماء المكوّنات، بالحروف السفلية لتطابق لوحة التحكم:{ nav, product_grid, product_detail, cart_summary, footer }. يقرأNavوFooterحقلهما القياسي من الخاصيةcontent(مُtyped بـBlockComponentProps<T>منcms-renderer/lib/types).ProductGridوProductDetailهما مكوّنان خادميان غير متزامنين يقرآنrouteParamsويجلبان البيانات منcatalog.ts:routeParams.<param>هو{ value, … }— استخدم.value، لذا يستدعيProductGridدالةlistItems(routeParams.category_code?.value)(البطاقات تصل إلى/products/{code}) ويستدعيProductDetailدالةgetItemByCode(routeParams.item_code?.value)(معرض عبرresolveImages، وصف نصي غني، سعر، زر إضافة إلى السلة). يعرضCartSummaryالسلة منuseCartمع زر الدفع. احتفظ بـ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()ترسل طلب POST{ lines: [{ code, quantity }] }(أكواد وكميات فقط — لا أسعار أبدًا) إلى/api/stripe/checkout، ثم تعيد التوجيه إلىurlالمعادة.نسّق كل شيء باستخدام نظام التصميم لدينا، كمكوّناتنا الخاصة — لا تنسخ تخطيط الموقع الأصلي.
ثلاث نقاط يجب معرفتها بعد التنفيذ:
content ({ content }: BlockComponentProps<T>) — إذا قمت بتفكيك الحقول كخصائص عليا فلن يظهر شيء. بيانات الكتالوج تأتي من routeParams + جلب catalog.ts، لأن CEL لا يستطيع ربط القوائم أو المعارض. السلة تحمل أكواد العناصر، وليس الأسعار.routeParams.<param> عبارة عن { value, schemaName, document } — استخدم .value. عمليات القراءة تعيد
published_content، وليس .content. مفاتيح السجل بحروف سفلية لتطابق لوحة التحكم.@types/react و@types/react-dom إلى الإصدار 19 — الهيكل يأتي بالإصدار 18، الذي يعطّل المكوّنات الخادمية غير المتزامنة مع 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:
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(); // جسم خام — مطلوب للتحقق من التوقيع
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 لا تعمل. دعها تمر أولًا: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 للـWebhooks المحلية:
stripe login
stripe listen --forward-to localhost:3000/api/stripe/webhook
# انسخ whsec_... إلى STRIPE_WEBHOOK_SECRET، ثم أعد تشغيل bun dev
يعتمد whsec_… على الجلسة. يحافظ على الأمان قاعدتان: Checkout يعيد حساب السعر من الـCMS عبر code (السلة المتلاعب بها لا يمكنها تغييره)، وWebhook يتحقق من التوقيع مقابل الجسم الخام.
لوحة التحكم → Pages → Create page، ثلاث مرات. اربط كل معامل بمكوّنه (حقل 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 على سلاسل ثابتة؛ والعناوين على تسميات ثابتة.
منشئ الصفحات على مسار الفئة — عنصر product_grid محدد، وعنوانه مرتبط عبر CEL.
منشئ الصفحات على مسار المنتج — عنصر product_detail على ربط منتج.
ملاحظة منشئ الصفحات البارامترية: على المسارين البارامترين، إضافة عناصر الواجهة لا تُحفظ (تتيتم الكتل وتعرض الصفحة فارغة). حتى يتم إصلاحه، صِل
block_idsلتلك الصفحات مباشرة عبر Profound MCP باستخدامupdate_page، ثم انشر. (الصفحة الثابتة/cartترتبط طبيعيًا.) وللسبب نفسه، يستخرجProductGridعنوانه من الفئة التي يجلبها بدلًا من CEL.
/categories/lighting → شبكة المنتجات. اضغط على منتج → تفاصيل + زر إضافة إلى السلة. /cart → زر الدفع./cart?status=success، ويعرض stripe listen حدث checkout.session.completed.صفحة منتج معروضة — معرض، سعر، وزر إضافة إلى السلة.
السلة — عناصر السطر وزر دفع واحد من Stripe.
يمكن شراء العناصر التي لها أسعار فقط — اشترِ أحد العناصر الثلاثة التي سعّرتها في الخطوة 6.
اختياري — دعم اللغات. ترجم كل مكوّن (جميع اللغات الـ35 دفعة واحدة)، أضف مقطع
/{language}/…مرتبطًا بـSystem component المدمجlanguage، وبدّل الحقول المرتبطة بـCEL إلىdocuments.translated. راجع دليل دليل المطار، الجزء الثاني الخطوة 7.
git init && git add -A && git commit -m "Stripe storefront"
gh repo create store --private --source=. --push # --public أيضًا خيار متاح
أمر البناء: الملف
generated/cms-schemas.tsمُستبعد من git، لذا ثبّت البناء ليعيد توليده — أضفvercel.json:{ "$schema": "https://openapi.vercel.sh/vercel.json", "buildCommand": "bun run generate-schemas && next build" }
في Vercel: Add New → Project، استورد store، وأضف متغيرات البيئة — 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 (رابطك الإنتاجي). انشر.
ثم اربط Webhook النشر (سر stripe listen المحلي كان محليًا فقط): Stripe → Developers → Webhooks → + Add endpoint → https://<prod>/api/stripe/webhook، الحدث checkout.session.completed. انسخ whsec_… إلى Vercel وأعد النشر.
غياب متغيرات البيئة = "يعمل محليًا، فارغ في الإنتاج" — أكثر مشكلة شائعة في النشر. ننشر هنا بمفاتيح اختبار؛ استبدل STRIPE_SECRET_KEY وسر الـWebhook بالقيم الحية عندما تكون جاهزًا لقبول المدفوعات الحقيقية.
كلاهما يأتيان مع الهيكل.
<Refresher> بتحديث الصفحة التي يعاينها المحرر عند حفظه في لوحة التحكم — بدون إعادة نشر. (إنها معاينة للمحرر؛ الزوار يرون المحتوى المنشور في إعادة التحقق المعتادة.)?edit_mode=true لأي عنوان URL لعرض تراكبات التحرير. الزائرون العموميون يرون الصفحة النظيفة.أضف مسار المعاينة الذي يغفله الهيكل. حمّل لوحة التحكم إطار المعاينة عند
/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; // 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، صفحات عرض + تفاصيل من مجموعة مسارات واحدة، وCheckout عامل مستضاف. قام الذكاء الاصطناعي بزرع الكتالوج، وربط التصميم، وكتب قارئ الكتالوج + المكوّنات + السلة عديمة الرأس؛ قمت أنت بالمكوّنات، وروابط أسعار Stripe، والمسارات الثلاثة، وتنسيق CEL، وثلاثة ملفات Stripe قصيرة. CEL يربط الإطار؛ المكوّنات تجلب الكتالوج. وظلّت Stripe بسيطة — استدعاء sessions.create واحد وWebhook موقع، مع دفع المتسوق على صفحة Stripe نفسها.