# الدفع المستضاف (/ar/integrate/integration-patterns/hosted-checkout)

دمج من ناحية السيرفر بس. أنشئ جلسة دفع، حوّل العميل لصفحة XPay المستضافة، وأكّد بـ webhook.

الدفع المستضاف هو أبسط دمج فيه أي كود. السيرفر بتاعك بينشئ جلسة دفع، ويحوّل العميل لصفحة الدفع المستضافة بتاعة XPay، والـ webhook بتاعك بيستلم `checkout.session.completed` لما الدفعة تنزل. ومش بتكتب أي كود واجهة.

اختار النمط ده لما تكون عايز دمج من ناحية الـ backend بس، ومش هتمانع إن العميل يتحوّل بعيد عن موقعك للتلت ثواني اللي بياخدها الدفع.

<Callout type="info">
  الدليل ده بيعرض الحد الأدنى من المحتوى المطلوب علشان تبدأ جلسة بنمط التحويل. لمجموعة الحقول
  الكاملة اللي تقدر تظبطها على `POST /checkout/sessions` (البنود، وتحصيل بيانات العميل، والعلامة
  التجارية، وقيود وسائل الدفع، والرسوم، والبيانات الوصفية، إلخ)، شوف مرجع [جلسة
  الدفع](/integrate/checkout-session/overview).
</Callout>

## إزاي بيشتغل [#إزاي-بيشتغل]

1. **السيرفر بتاعك** بيستدعي `POST /checkout/sessions` بالبنود ومكان توديه العميل بعد الدفع. XPay بيرجّع كائن جلسة فيه `url`.
2. **السيرفر بتاعك** بيحوّل متصفّح العميل للـ `url` ده.
3. **العميل** بيوصل `https://checkout.xpay.app/c/cs_test_...`، ويدخّل بطاقته، ويكمّل الـ 3D Secure لو لازم، والدفعة بتشتغل.
4. **XPay** بيحوّل العميل تاني للـ URL اللي ظبطته في `afterCompletion.redirect.url`.
5. **XPay** بيبعت webhook بـ `checkout.session.completed` لنقطة النهاية بتاعتك علشان السيرفر يأكّد وينفّذ الطلب.

تجربة العميل عبارة عن تحويل من 5 لـ 15 ثانية لصفحة XPay المستضافة ورجوع. السيرفر بتاعك هو الحاجة الوحيدة اللي بتحتفظ بالحالة عبر العملية كلها.

## نفّذه [#نفّذه]

### 1. خُد مفتاح API للاختبار [#1-خُد-مفتاح-api-للاختبار]

افتح لوحة التحكم، وروح **المطورين ← مفاتيح API**، وانسخ مفتاح سرّي بيبدأ بـ `sk_test_`. خلّيه على السيرفر بتاعك. عمرك ما تحطّ مفتاح سرّي في كود الواجهة ولا تعمله commit في repo.

هتستخدم نفس مسار الكود بـ `sk_live_*` بمجرد ما حسابك يتعتمد للدفعات الفعلية.

### 2. أنشئ جلسة دفع على السيرفر بتاعك [#2-أنشئ-جلسة-دفع-على-السيرفر-بتاعك]

الحقل الوحيد اللي لازم تبعته هو `afterCompletion`، اللي بيقول لـ XPay يحوّل العميل لفين بعد الدفع. عمليًا هتبعت كمان `lineItems` علشان XPay يعرف بيتم خصم إيه.

<Tabs items="[&#x22;cURL&#x22;, &#x22;Node.js&#x22;, &#x22;Python&#x22;]">
  <Tab value="cURL">
    ```bash
    curl -X POST https://api.xpay.app/checkout/sessions \
      -H "Authorization: Bearer sk_test_..." \
      -H "Content-Type: application/json" \
      -d '{
        "afterCompletion": {
          "type": "redirect",
          "redirect": { "url": "https://yourshop.example/order/{CHECKOUT_SESSION_ID}" }
        },
        "lineItems": [
          {
            "priceData": {
              "currency": "EGP",
              "unitAmount": 149900,
              "productData": { "name": "Test product" }
            },
            "quantity": 1
          }
        ]
      }'
    ```
  </Tab>

  <Tab value="Node.js">
    ```typescript
    const res = await fetch("https://api.xpay.app/checkout/sessions", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.XPAY_SECRET_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        afterCompletion: {
          type: "redirect",
          redirect: { url: "https://yourshop.example/order/{CHECKOUT_SESSION_ID}" },
        },
        lineItems: [
          {
            priceData: {
              currency: "EGP",
              unitAmount: 149900, // 1,499.00 EGP, in minor units
              productData: { name: "Test product" },
            },
            quantity: 1,
          },
        ],
      }),
    });

    const session = await res.json();
    ```
  </Tab>

  <Tab value="Python">
    ```python
    import os, requests

    res = requests.post(
        "https://api.xpay.app/checkout/sessions",
        headers={
            "Authorization": f"Bearer {os.environ['XPAY_SECRET_KEY']}",
            "Content-Type": "application/json",
        },
        json={
            "afterCompletion": {
                "type": "redirect",
                "redirect": {"url": "https://yourshop.example/order/{CHECKOUT_SESSION_ID}"},
            },
            "lineItems": [
                {
                    "priceData": {
                        "currency": "EGP",
                        "unitAmount": 149900,  # 1,499.00 EGP, in minor units
                        "productData": {"name": "Test product"},
                    },
                    "quantity": 1,
                }
            ],
        },
    )
    session = res.json()
    ```
  </Tab>
</Tabs>

الرد فيه الحقول اللي هتحتاجها بعد كده:

```json
{
  "id": "cs_test_AbC123...",
  "object": "checkout.session",
  "status": "open",
  "url": "https://checkout.xpay.app/c/cs_test_AbC123...",
  "amountTotal": 149900,
  "currency": "EGP",
  "afterCompletion": { "type": "redirect", "redirect": { "url": "..." } }
}
```

كام ملاحظة:

* **`unitAmount` بالوحدات الصغرى.** `149900` معناها 1,499.00 EGP. ابعت العدد الصحيح الكامل دايمًا، مش رقم عشري.
* **`productData.name` مطلوب** لما تستخدم `priceData` علشان تنشئ <ApiLink href="/api-reference/objects/price">Price</ApiLink> فوري. لو سبق وأنشأت منتجات في لوحة التحكم، تقدر تبعت `price: "price_..."` بدالها وتتخطى `priceData`. المرجع الكامل: [البنود والتسعير](/integrate/checkout-session/line-items-and-pricing).
* **`{CHECKOUT_SESSION_ID}` في `redirect.url`** بيتستبدل من ناحية السيرفر بـ `id` بتاع الجلسة، فصفحة الرجوع بتاعتك تقدر تقرا معرّف الجلسة من المسار من غير ما تمرّره أنت بالحالة.
* **الجلسة بتنتهي بعد 24 ساعة** لو العميل ما دفعش أبدًا. تقدر كمان تنهيها يدويًا بـ `POST /checkout/sessions/:id/expire`.
* **وضع الاختبار بيتحدّد بمفتاح الـ API**، مش بالـ URL. `sk_test_*` بينتج جلسات `cs_test_*`؛ و`sk_live_*` بينتج جلسات `cs_live_*`. اسم مضيف الـ URL المستضاف واحد في الحالتين.

### 3. حوّل العميل [#3-حوّل-العميل]

ابعت HTTP 302 لحقل `url` بتاع الرد.

<Tabs items="[&#x22;Node.js (Express)&#x22;, &#x22;Python (Flask)&#x22;]">
  <Tab value="Node.js (Express)">
    ```typescript
    app.post("/start-checkout", async (req, res) => {
      const session = await createCheckoutSession(req.body); // your code from step 2
      res.redirect(303, session.url);
    });
    ```
  </Tab>

  <Tab value="Python (Flask)">
    ```python
    from flask import redirect

    @app.post("/start-checkout")
    def start_checkout():
        session = create_checkout_session(request.json)  # your code from step 2
        return redirect(session["url"], code=303)
    ```
  </Tab>
</Tabs>

استخدم `303 See Other` لو بتحوّل من طلب `POST`، وغير كده `302 Found` تمام.

### 4. تعامل مع رجوع العميل [#4-تعامل-مع-رجوع-العميل]

بعد ما العميل يدفع، XPay بيحوّله لـ `afterCompletion.redirect.url` من غير أي بارامترات استعلام إضافية. صفحتك على الـ URL ده موجّهة للعميل بحتة: رسالة شكر، أو ملخص طلب، أو أي حاجة عايزها.

**ما تعتمدش على التحويل وحده لتأكيد الدفع.** العميل ممكن يقفل التاب قبل التحويل أو يضرب الـ URL بالغلط. مصدر الحقيقة الوحيد لنجاح الدفع هو webhook `checkout.session.completed` اللي السيرفر بتاعك بيستلمه.

لو عايز كمان تعرض الطلب الفعلي على صفحة الرجوع، تقدر تقراه تاني بـ `GET /checkout/sessions/:id` باستخدام معرّف الجلسة اللي خزّنته قبل التحويل.

## أكّد بـ webhook [#أكّد-بـ-webhook]

XPay بيبعت `checkout.session.completed` بمجرد ما الدفعة تنجح. الـ `data.object` جوّه الحدث هو نفس جلسة الدفع اللي هتجيبها من `POST /checkout/sessions` أو `GET /checkout/sessions/:id`، فكود التنفيذ بتاعك ممكن يبقى دالة واحدة بتقرا جلسة دفع.

لكل حقل في الجلسة، شوف <ApiLink href="/api-reference/objects/checkout-session">Checkout Session</ApiLink>. الحد الأدنى اللي معالِجك محتاج يعمله:

1. تحقّق من ترويسة `XPay-Signature`.
2. فُكّ محتوى الـ JSON.
3. لو نوع الحدث `checkout.session.completed` وحالة الجلسة `status` هي `complete`، نفّذ الطلب.

### تحقّق من التوقيع [#تحقّق-من-التوقيع]

XPay بيوقّع كل webhook بـ HMAC-SHA256 باستخدام السرّ بتاع التوقيع اللي خدته لما أنشأت نقطة نهاية الـ webhook (بيبدأ بـ `whsec_`). الترويسة شكلها كده:

```
XPay-Signature: t=1730000000,v1=a1b2c3d4...
```

الحمولة الموقَّعة هي الـ timestamp، وبعده `.` حرفية، والمحتوى الخام للـ JSON. علشان تتحقّق:

<Tabs items="[&#x22;Node.js&#x22;, &#x22;Python&#x22;]">
  <Tab value="Node.js">
    ```typescript
    import crypto from "node:crypto";

    function verifyWebhook(rawBody: string, header: string, secret: string) {
      const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("="))); // { t: "1730000000", v1: "a1b2c3..." }

      const timestamp = parts.t;
      const signature = parts.v1;

      // Reject events older than 5 minutes (replay protection)
      if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
        throw new Error("Webhook timestamp out of tolerance");
      }

      const expected = crypto
        .createHmac("sha256", secret)
        .update(`${timestamp}.${rawBody}`)
        .digest("hex");

      if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
        throw new Error("Bad webhook signature");
      }
    }
    ```
  </Tab>

  <Tab value="Python">
    ```python
    import hmac, hashlib, time

    def verify_webhook(raw_body: bytes, header: str, secret: str):
        parts = dict(kv.split("=") for kv in header.split(","))
        timestamp = parts["t"]
        signature = parts["v1"]

        # Reject events older than 5 minutes (replay protection)
        if abs(time.time() - int(timestamp)) > 300:
            raise ValueError("Webhook timestamp out of tolerance")

        signed = f"{timestamp}.".encode() + raw_body
        expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()

        if not hmac.compare_digest(signature, expected):
            raise ValueError("Bad webhook signature")
    ```
  </Tab>
</Tabs>

اقرا محتوى الطلب كنص خام (أو bytes)، مش كـ JSON مفكوك. إعادة تسلسل الـ JSON مش هتنتج نفس الـ bytes اللي XPay وقّعها.

### اقرا الحمولة [#اقرا-الحمولة]

جوّه `checkout.session.completed`، الـ `data.object` هو <ApiLink href="/api-reference/objects/checkout-session">Checkout Session</ApiLink> كاملة. الحقول اللي بتهمّك عادةً:

| الحقل                          | يعني إيه                                                                                                                                                   |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data.object.id`               | معرّف جلسة الدفع (`cs_test_...` / `cs_live_...`). استخدمه كمفتاح عدم تكرار للتنفيذ.                                                                        |
| `data.object.status`           | `complete` عند نجاح الدفعة.                                                                                                                                |
| `data.object.amountTotal`      | الإجمالي المخصوم، بالوحدات الصغرى.                                                                                                                         |
| `data.object.currency`         | كود ISO من تلات حروف، مثلًا `EGP`.                                                                                                                         |
| `data.object.customer`         | سجل <ApiLink href="/api-reference/objects/customer">Customer</ApiLink> (لو بعتّ `customerId` أو XPay أنشأ واحد من الفورم).                                 |
| `data.object.paymentIntent.id` | معرّف <ApiLink href="/api-reference/objects/payment-intent">Payment Intent</ApiLink> (`pi_*`). خزّنه لو هتعمل [مبالغ مستردة](/integrate/refunds) من الكود. |
| `data.object.lineItems`        | اللي اتشري، بالأسعار وبيانات المنتج.                                                                                                                       |
| `data.object.metadata`         | أي حاجة أرفقتها وقت إنشاء الجلسة.                                                                                                                          |

للصورة الأوسع عن إزاي جلسة الدفع، وPayment Intent، وCharge، والعميل بيتركّبوا مع بعض، وأنهي معرّفات تحتفظ بيها في سجل الطلب بتاعك، شوف [نموذج الكائنات](/integrate/object-model).

معالِج قصير بـ Node.js / Express:

```typescript
app.post("/webhooks/xpay", express.raw({ type: "application/json" }), (req, res) => {
  try {
    verifyWebhook(
      req.body.toString("utf8"),
      req.header("XPay-Signature")!,
      process.env.XPAY_WEBHOOK_SECRET!,
    );
  } catch {
    return res.status(400).send("invalid signature");
  }

  const event = JSON.parse(req.body.toString("utf8"));

  if (event.type === "checkout.session.completed" && event.data.object.status === "complete") {
    fulfillOrder(event.data.object); // idempotent on session.id
  }

  res.status(200).send();
});
```

تسليمات الـ webhook بتتبع جدول إعادة المحاولة ده عند الردود المش 2xx أو انتهاء المهلة (30 ثانية): فورًا، وبعد دقيقة، و5 دقايق، و30 دقيقة، وساعتين. XPay بيستسلم بعد 5 محاولات.

## اختبره [#اختبره]

في وضع الاختبار، كل جلسة دفع بتنشئها بترجّع معرّف `cs_test_*` وURL دفع مستضاف بيستخدم معالِج XPay التجريبي (sandbox). استخدم بطاقة النجاح `5123 4500 0000 0008` بتاريخ انتهاء `01/39` علشان تشغّل المسار الناجح. لقائمة بطاقات الاختبار الكاملة ومصفوفة تاريخ الانتهاء للنتيجة، شوف [وضع الاختبار وبطاقات الاختبار](/get-started/test-mode).

علشان تجرّب معالِج الـ webhook بتاعك محليًا قبل النشر، شوف [تطوير الـ webhook محليًا](/integrate/webhooks/local-development).

## قائمة التحقق قبل الإنتاج [#قائمة-التحقق-قبل-الإنتاج]

قبل ما تحوّل للحساب الفعلي:

* **بدّل المفاتيح.** استبدل `sk_test_*` بـ `sk_live_*` في بيئة السيرفر بتاعك. رابط الـ API مش بيتغيّر.
* **اظبط نقطة نهاية webhook فعلية.** وضع الاختبار والحساب الفعلي ليهم نقاط نهاية webhook منفصلة. كل واحدة بياخد السرّ بتاع التوقيع `whsec_*` الخاص بيها.
* **استخدم URL رجوع حقيقي.** لازم `afterCompletion.redirect.url` يكون URL بـ `https://` على نطاق أنت متحكّم فيه. ما تستخدمش `localhost`.
* **امنع تكرار تسليمات الـ webhook على `event.id`.** XPay بيعيد محاولة تسليمات الـ webhook عند الردود المش 2xx أو انتهاء المهلة، فـ `checkout.session.completed` لنفس الجلسة ممكن يوصل أكتر من مرة. تتبّع معرّفات الأحداث اللي عالجتها بالفعل وتخطّى المكرّر.
* **ابعت `Idempotency-Key` على `POST /checkout/sessions`.** لو الطلب وقعت مهلته، إعادة المحاولة بنفس المفتاح بترجّع الجلسة الأصلية بدل ما تفتح تانية لنفس السلة. شوف [عدم التكرار](/integrate/idempotency).
* **فكّر في `cancelUrl` للدفعات الفاشلة.** ظبطه على الجلسة لو عايز تتعامل مع حالات الفشل (الرفض / رفض 3DS / انتهاء مهلة وسيلة الدفع المحلية) على صفحتك بدل صفحة الإعادة المستضافة بتاعة XPay. السلوك الكامل في [After completion → cancelUrl](/integrate/checkout-session/after-completion#cancelurl-where-to-send-the-customer-on-payment-failure).
* **تحقّق من التواقيع في الإنتاج كمان.** ما تعطّلش التحقق من التوقيع "علشان تعمل debug". استخدم أداة tunneling للـ webhook مع وضع الاختبار بدالها.

## رايح فين بعد كده [#رايح-فين-بعد-كده]

<Cards>
  <Card icon="<Network />" title="نموذج الكائنات" href="/integrate/object-model">
    إزاي جلسة الدفع، وPayment Intent، وCharge، والعميل بيرتبطوا، وأنهي معرّفات تحتفظ بيها في سجل
    الطلب بتاعك.
  </Card>

  <Card icon="<RotateCcw />" title="المبالغ المستردة" href="/integrate/refunds">
    اعكس دفعة ناجحة باستخدام الـ `pi_*` اللي خزّنته من الـ webhook.
  </Card>

  <Card icon="<Webhook />" title="إعداد الـ Webhooks" href="/integrate/webhooks/setting-up-an-endpoint">
    أضِف نقطة نهاية webhook وخُد السرّ بتاع التوقيع `whsec_*`.
  </Card>

  <Card icon="<ShieldCheck />" title="التحقق من التواقيع" href="/integrate/webhooks/verifying-signatures">
    طريقة التحقق من التوقيع بتفصيل أكتر، زائد الحماية من إعادة التشغيل.
  </Card>

  <Card icon="<FlaskConical />" title="وضع الاختبار وبطاقات الاختبار" href="/get-started/test-mode">
    قائمة بطاقات الاختبار الكاملة والنتائج اللي تقدر تحاكيها بتغيير تاريخ الانتهاء.
  </Card>

  <Card icon="<GitBranch />" title="عايز نمط مختلف؟" href="/get-started/choose-your-integration">
    قارن الدفع المستضاف بـ Drop-in، وElements، وروابط الدفع.
  </Card>

  <Card icon="<SquareCode />" title="مثال HTML عادي" href="https://github.com/xpayeg/xpay-examples/tree/main/vanilla-html">
    `hosted-redirect.html`: إنشاء الجلسة من السيرفر وإعادة التوجيه، من غير SDK على الصفحة. ملف واحد
    جاهز للتشغيل زائد سيرفر Express صغيّر.
  </Card>

  <Card icon="<Atom />" title="متجر مثال Next.js" href="https://github.com/xpayeg/xpay-examples/tree/main/nextjs-react">
    مسار `/checkout?ui=hosted`: إنشاء الجلسة، وإعادة التوجيه، ومستقبِل webhooks متحقّق من التوقيع.
  </Card>
</Cards>