profound-logoProfound CMS
⌘K
Admin
Theme
Docsبرنامج تعليميBlogPhilosophy
Docsبرنامج تعليميBlogPhilosophy

دروس تعليمية

دليل المطاراتعمليات النشرBuild & Ship a Stripe Storefront

Feature

Documentation Site Templateمنشئ قالب الميزةTranslation ServiceOrganizations & Website HeirarchyConnect Profound CMS to your AI clientإعدادات التكاملاتSettings API KeysSettings UsageSettings Websites
All Systems Operational
Powered Byprofound-logo
Theme

Build & Ship a Stripe Storefront

جولة عملية: ابنِ واجهة متجر Stripe مدفوعة بالمحتوى على Profound CMS — كتالوج يعدّله التاجر دون كود، مساران بارامتريان، سلة عديمة الرأس، وCheckout مستضاف من Stripe.

المتجر المنتهي أثناء الحركة — تصفح فئة، افتح منتجًا، أضف إلى السلة، وأكمل الدفع.

جولة عملية تبني متجرًا مدفوعًا بالمحتوى على Profound CMS: كتالوج منتجات (فئات + عناصر) مصمم في الـCMS، صفحات عرض وتفاصيل من مجموعة واحدة من المسارات، وخروج مستضاف من Stripe مُرسل كمكوّن عديم الرأس.

الهيكل الأساسي مكتوب يدويًا باستخدام Next.js بالإضافة إلى لوحة تحكم Profound. يقوم Claude Code (عبر Profound MCP) بالأعمال الثقيلة في ثلاث مهام — تعبئة الكتالوج، ربط نظام التصميم، وكتابة مكوّنات المتجر (بما في ذلك السلة عديمة الرأس). ثلاثة أجزاء: الإعداد، البناء، الإنتاج.

المدفوعات في سطر واحد. نستخدم Checkout المستضاف من Stripe: يدفع المتسوق على صفحة Stripe، وليس صفحتك. تطبيقك يقوم بأمرين فقط على الخادم — إنشاء جلسة Checkout والتحقق من Webhook واحد. لا حقول بطاقات، لا Stripe Elements، ولا أعباء PCI.

ما الذي ستبنيه

  • كتالوج صغير منشور في الـCMS — ثلاث فئات وثمانية منتجات (عرض "اختراعات إديسون") — كل منها قابل للتحرير من قبل تاجر دون الحاجة إلى كود.
  • مساران بارامتريان (/products/{item_code}، /categories/{category_code}) بالإضافة إلى /cart ثابتة، كلها من مجموعة واحدة من المكوّنات.
  • سلة عديمة الرأس (useCart) وCheckout مستضاف من Stripe، مع تحديد السعر دائمًا على الخادم من خلال Stripe Price ID.
  • المتجر منشور على Vercel مع معاينة حية وتحرير في المكان للفريق.

المتطلبات المسبقة

  • Bun ‎≥ 1.3 — curl -fsSL https://bun.sh/install | bash
  • Claude Code مع Profound MCP (يتم تثبيته في الجزء الأول).
  • حساب Profound CMS.
  • حساب Stripe. يعمل هذا الدليل في وضع الاختبار حتى لا تُخصم أموال حقيقية أثناء البناء — لكن التدفق مطابق مع المفاتيح الحية، لذا استخدم مفاتيح حسابك الحقيقي إذا رغبت. (وضع الاختبار لا يحتاج إلى بيانات عمل أو بنك.)
  • Stripe CLI (stripe login) من أجل Webhooks المحلية.
  • من أجل النشر: GitHub CLI (gh) وحساب Vercel مرتبط بـGitHub.

كيف تتكامل الأجزاء معًا

يقوم Profound بفصل المحتوى عن التصيير:

  • المكوّنات تحدد شكل المحتوى. الـCustom Component مع حقل Route Slug يمكن توجيهه (category، item); واحد موسوم بـUI Element يمكن وضعه في صفحة (nav، product_grid، …).
  • المستندات هي المحتوى (منتج، فئة).
  • عناصر الواجهة هي مقاطع الصفحة؛ كل حقل قياسي يأخذ قيمة ثابتة أو تعبير CEL، يُقيّم عند التصيير.
  • المسارات البارامترية تربط عنوان URL بمستند + عناصر واجهة، وتُمرّر معاملات المسار (meta.params.* في CEL، وrouteParams في React).
  • يقرأ تطبيق Next.js ذلك عبر cms-renderer; وتُضاف Stripe كمسارات API عادية.

القاعدة الوحيدة التي تشكّل البناء: CEL يرتبط بـحقول string/number فقط. لذا يتم ربط الإطار البسيط (علامة التنقل، التذييل، العناوين) بـCEL، بينما أي شيء غني أو مجموعة (شبكة منتجات، معرض صور، نص غني) يتم جلبه داخل مكوّن React بواسطة معامل المسار. وStripe هي مصدر الحقيقة للتسعير — سعر الـCMS للعرض فقط؛ يتم تحصيل الرسوم دائمًا على الخادم من Stripe Price ID.

الجزء الأول — الإعداد

النقطة النهائية: كتالوج صغير منشور، التطبيق موصول لقراءته، Stripe مُثبّت، التصميم في مكانه — لا شيء معروض بعد.

1. سجّل وأنشئ الموقع

سجّل في Profound (مصادقة WorkOS). أنشئ موقعًا باسم store، ثم انسخ معرّف الموقع (UUID في رابط لوحة التحكم) ومفتاح API بطبقة قراءة (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، مسار شامل، سكربت 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.

3. عرّف مكوّنات 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.

4. اسحب المكوّنات إلى الأنواع المحلية

bun run generate-schemas

يكتب مخططات وأنواع Zod إلى 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 — ثلاث فئات وهذه المنتجات، مع 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"). ازرع الفئات قبل العناصر حتى تشير المراجع إلى معرّفات موجودة.

الفئات الثلاث المزروعة، منشورة وبث مباشر.

المنتجات الثمانية المزروعة، كل منها مرتبط بفئة.

6. أنشئ أسعار Stripe ووصل بعض المنتجات

الكتالوج موجود في الـCMS؛ الآن أعطِ بعض المنتجات سعرًا حقيقيًا في Stripe — مهمة التاجر، تتم في لوحتين إداريتين، دون كود:

  1. لوحة Stripe → Products → + Add product، اضبط سعرًا لمرة واحدة، انسخ Price ID (price_…).
  2. افعل ذلك لثلاثة منتجات رئيسية تقريبًا (مثل المصباح، الفونوغراف، الكينتوسكوب).
  3. لوحة Profound → item → Documents → ألصق كل Price ID في stripePriceId، ثم احفظ.

يحتفظ الـCMS بالكتالوج؛ يحتفظ Stripe بالسعر الرسمي؛ الرابط هو سلسلة واحدة يلصقها التاجر. (تفضل أتمتتها؟ يمكن لـStripe MCP الرسمي إنشاء المنتجات/الأسعار نيابةً عنك — ألصق المعرّفات المعادة بنفس الطريقة.)

7. أضف نظام التصميم ووصلّه بالذكاء الاصطناعي

يأتي الهيكل بدون تنسيق. ضع ملف DESIGN.md (كتلة @theme خاصة بتايلويند v4 + الرموز) في جذر المشروع — إحدى ملفاتك، أو نزّل واحدًا من refero.design. ثم اطلب من Claude، مع حصر المهمة في التنسيق فقط:

اقرأ ملف التصميم الذي أضفته للتو. اضبط Tailwind إذا لزم الأمر، ثم اربط السمة والخطوط حتى يعمل التنسيق. استخدم next/font للخطوط — لا تحمّلها من Google وقت التشغيل. فقط التنسيق — لا تُنشئ أي صفحات أو مكوّنات الآن.

تحقّق من أن src/app/globals.css يحتوي على @import "tailwindcss"; + كتلة @theme وأن localhost:3000 يعرض الرموز. أبقِ الطلب محددًا (إذا كان مفتوحًا، قد ينشئ الوكيل صفحة كاملة)، وحمّل الخطوط عبر next/font، وليس استيرادًا وقت التشغيل من Google.

8. أضف صور المنتجات (اختياري)

اختياري — يمكنك الوصول إلى 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، وانتهِ بعملية شراء حقيقية في وضع الاختبار.

1. عرّف مكوّنات عناصر الواجهة الخمسة

خمسة مكوّنات، كل واحد 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).

2. أعد توليد الأنواع

bun run generate-schemas

3. أنشئ قارئ الكتالوج، المكوّنات، والسلة عديمة الرأس

طلب واحد يبني أداة القراءة، المكوّنات الخمسة، السلة، والسجل:

ابنِ متجرنا في src/، باستخدام cms-renderer SDK الخاص بـ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.

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:

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 يتحقق من التوقيع مقابل الجسم الخام.

5. أنشئ المسارات

لوحة التحكم → Pages → Create page، ثلاث مرات. اربط كل معامل بمكوّنه (حقل code):

  1. /products/{item_code} → item
  2. /categories/{category_code} → category
  3. /cart — صفحة ثابتة (أدخل /cart حرفيًا، وليس /{cart})

6. أضف عناصر الواجهة، اربط 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 على سلاسل ثابتة؛ والعناوين على تسميات ثابتة.

منشئ الصفحات على مسار الفئة — عنصر product_grid محدد، وعنوانه مرتبط عبر CEL.

منشئ الصفحات على مسار المنتج — عنصر product_detail على ربط منتج.

ملاحظة منشئ الصفحات البارامترية: على المسارين البارامترين، إضافة عناصر الواجهة لا تُحفظ (تتيتم الكتل وتعرض الصفحة فارغة). حتى يتم إصلاحه، صِل block_ids لتلك الصفحات مباشرة عبر Profound MCP باستخدام update_page، ثم انشر. (الصفحة الثابتة /cart ترتبط طبيعيًا.) وللسبب نفسه، يستخرج ProductGrid عنوانه من الفئة التي يجلبها بدلًا من CEL.

7. اعرض واشترِ

  • /categories/lighting → شبكة المنتجات. اضغط على منتج → تفاصيل + زر إضافة إلى السلة. /cart → زر الدفع.
  • الدفع يعيد التوجيه إلى Checkout المستضاف من Stripe. استخدم بطاقة الاختبار 4242 4242 4242 4242، أي تاريخ انتهاء مستقبلي/CVC. ستعود إلى /cart?status=success، ويعرض stripe listen حدث checkout.session.completed.

صفحة منتج معروضة — معرض، سعر، وزر إضافة إلى السلة.

السلة — عناصر السطر وزر دفع واحد من Stripe.

يمكن شراء العناصر التي لها أسعار فقط — اشترِ أحد العناصر الثلاثة التي سعّرتها في الخطوة 6.

اختياري — دعم اللغات. ترجم كل مكوّن (جميع اللغات الـ35 دفعة واحدة)، أضف مقطع /{language}/… مرتبطًا بـSystem component المدمج language، وبدّل الحقول المرتبطة بـCEL إلى documents.translated. راجع دليل دليل المطار، الجزء الثاني الخطوة 7.

الجزء الثالث — الإنتاج

1. انشره: GitHub، ثم Vercel

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 بالقيم الحية عندما تكون جاهزًا لقبول المدفوعات الحقيقية.

2. المعاينة الحية والتحرير داخل الصفحة

كلاهما يأتيان مع الهيكل.

  • المعاينة الحية: يقوم <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 نفسها.

Continue Reading
Previous‹عمليات النشر