# @xpayeg/react (/ar/sdk/sdk-react)

الـ React SDK. XPayProvider، والـ hooks، والمكوّنات المبنية فوق @xpayeg/sdk.

الـ React SDK بتاع XPay غلاف رفيع حوالين `@xpayeg/sdk` بيكشف provider، وhooks، ومكوّن للـ payment element. الـ provider بينشئ نسخة `Elements` من `clientSecret`، والـ hooks بيكشفوا حالة الجلسة الحيّة ودوال الإجراءات، والمكوّنات بتركّب واجهة الدفع.

```sh
pnpm add @xpayeg/sdk @xpayeg/react
# Both packages are required. @xpayeg/react peer-depends on @xpayeg/sdk, react ^18 || ^19, react-dom ^18 || ^19.
```

الصفحة دي مرجع لكل تصدير. للشروحات، شوف [Drop-in](/integrate/integration-patterns/drop-in) و[Elements](/integrate/integration-patterns/elements) تحت Integrate.

<Card icon="<Atom />" title="شوفه شغّال: متجر مثال Next.js" href="https://github.com/xpayeg/xpay-examples/tree/main/nextjs-react">
  متجر شغّال مبني بالـ SDK ده: الدفع المستضاف، وdrop-in، وinline، وElements، زائد مستقبِل webhooks
  متحقّق من التوقيع. اعمل clone، وحطّ مفاتيح الاختبار، وشغّل.
</Card>

## البداية السريعة [#البداية-السريعة]

النمط المنصوح بيه هو `loadXPay` على مستوى الموديول (أو في ملف محمّل صغيّر)، وبعدين تنشئ جلسة دفع على السيرفر بتاعك، وتمسك الـ `clientSecret` اللي رجع في الحالة، وتعرض `<XPayProvider>` بمجرد ما يبقى عندك. وبعدها `useCheckout()` بيقرا الجلسة الحيّة جوّه أي مكوّن ابن.

```tsx title="lib/xpay.ts"
"use client";

import { loadXPay } from "@xpayeg/sdk";

// Module-level: load once, share across all components.
export const xpayPromise = loadXPay(process.env.NEXT_PUBLIC_XPAY_PUBLISHABLE_KEY!);
```

```tsx title="app/checkout/page.tsx"
"use client";

import { useState } from "react";
import { xpayPromise } from "@/lib/xpay";
import { PaymentElement, useCheckout, XPayProvider } from "@xpayeg/react";

export default function CheckoutPage() {
  const [clientSecret, setClientSecret] = useState<string | null>(null);
  const [creating, setCreating] = useState(false);

  const startCheckout = async () => {
    setCreating(true);
    const res = await fetch("/api/create-checkout", { method: "POST" });
    const { clientSecret } = await res.json();
    setClientSecret(clientSecret);
    setCreating(false);
  };

  if (!clientSecret) {
    return (
      <button onClick={startCheckout} disabled={creating}>
        {creating ? "Starting…" : "Pay now"}
      </button>
    );
  }

  return (
    <XPayProvider xpay={xpayPromise} options={{ clientSecret }}>
      <CheckoutForm />
    </XPayProvider>
  );
}

function CheckoutForm() {
  const state = useCheckout();

  if (state.type === "loading") return <p>Loading…</p>;
  if (state.type === "error") return <p>{state.error.message}</p>;

  const { checkout } = state;
  if (checkout.status.type === "expired") return <p>This checkout has expired.</p>;
  if (checkout.status.type === "complete") return <p>Payment complete. Thank you.</p>;

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    const result = await checkout.confirm({
      customerDetails: { email: "customer@example.com" },
    });
    if (result.type === "error") {
      alert(result.error.message);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <PaymentElement />
      <button type="submit" disabled={!checkout.canConfirm}>
        Pay {checkout.currency} {(checkout.amountTotal / 100).toFixed(2)}
      </button>
    </form>
  );
}
```

الـ `clientSecret` بيعيش في الحالة، فـ `XPayProvider` بيتركّب مرة واحدة بس و`/api/create-checkout` بيتنادى مرة واحدة بالظبط لكل عملية دفع. لو بتنشئ الجلسة بدري في مسارك (ضغطة على صفحة منتج، أو server component، أو route param)، مرّر النص اللي رجع بأي طريقة تحبها: props، أو search params، أو context. الـ provider محتاج النص بس.

`useCheckout()` بيتحدّث تفاعليًا بعد كل كود خصم، أو تغيير كمية، أو إعادة حساب رسوم. مفيش حاجة لـ `onChange` callback في React؛ المكوّن بيعيد العرض بآخر بيانات `checkout` من نفسه.

## التصديرات بنظرة سريعة [#التصديرات-بنظرة-سريعة]

| التصدير                                      | النوع     | يعني إيه                                                                                  |
| -------------------------------------------- | --------- | ----------------------------------------------------------------------------------------- |
| `XPayProvider`                               | component | بيغلّف واجهة الدفع بتاعتك وبيوفّر السياق لكل المكوّنات الأبناء.                           |
| `useCheckout`                                | hook      | بيرجّع حالة الـ disjoint union وكائن الجلسة-زائد-الإجراءات المدموج.                       |
| `useXPay`                                    | hook      | بيرجّع الـ `XPayInstance` الأساسي (أو `null`).                                            |
| `useElements`                                | hook      | بيرجّع نسخة الـ `Elements` الأساسية (أو `null`).                                          |
| `useConfirmPayment`                          | hook      | hook راحة بيكشف `confirmPayment` من الـ checkout النشط.                                   |
| `PaymentElement`                             | component | بيركّب مختار وسيلة الدفع مع فورم البطاقة.                                                 |
| `CheckoutButton`                             | component | زرار جاهز بيفتح الـ drop-in checkout modal عند الضغط.                                     |
| `Checkout`                                   | type      | `CheckoutSession & CheckoutActions`. الشكل المدموج اللي `useCheckout` بيرجّعه عند النجاح. |
| `UseCheckoutResult`                          | type      | الـ disjoint union اللي بيرجّعه `useCheckout`.                                            |
| `PaymentElementProps`, `CheckoutButtonProps` | type      | أنواع props المكوّنات.                                                                    |

الـ package بيعيد تصدير كام نوع أساسي من `@xpayeg/sdk` للراحة: `CheckoutSession`، و`CheckoutActions`، و`PaymentMethodInfo`، و`Appearance`، و`ActionResult`، و`XPayError`، و`ConfirmPaymentOptions`، و`PaymentElementChangeEvent`، و`ElementsOptions`. استورد الأنواع الإضافية مباشرة من `@xpayeg/sdk` لما تحتاجها.

## `<XPayProvider>` [#xpayprovider]

بيغلّف الجزء من تطبيقك اللي بيشغّل الدفع. بينشئ نسخة `Elements` من `options.clientSecret` وبيوفّر تلات سياقات React: الـ `XPayInstance`، والـ `Elements`، وحالة الـ checkout الواعية بحالة التحميل.

```tsx
<XPayProvider xpay={xpayPromise} options={{ clientSecret, appearance, locale }}>
  {children}
</XPayProvider>
```

| الـ Prop               | النوع                                                   | الوصف                                                                                        |
| ---------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `xpay`                 | `XPayInstance \| Promise<XPayInstance \| null> \| null` | النسخة من `loadXPay()`. مرّر الـ Promise؛ والـ provider بيستناه على العميل.                  |
| `options.clientSecret` | `string \| Promise<string>`                             | الـ client secret بتاع جلسة الدفع. مطلوب لإنشاء نسخة Elements وتحميل الجلسة.                 |
| `options.appearance`   | `Appearance`                                            | اختياري. تجاوزات العلامة التجارية المدموجة مع العلامة من ناحية السيرفر للجلسة.               |
| `options.locale`       | `"en" \| "ar"`                                          | اختياري. الافتراضي `"en"`.                                                                   |
| `children`             | `ReactNode`                                             | الشجرة الفرعية اللي بتستدعي `useCheckout`، و`useXPay`، و`useElements`، و`useConfirmPayment`. |

الـ provider بيتعامل مع الـ SSR بسلاسة: `loadXPay` بيرجّع `null` على السيرفر، والـ provider بيستنى العميل علشان يحمّل الـ SDK قبل ما ينشئ نسخة الـ Elements.

## `useCheckout()` [#usecheckout]

الـ hook الأساسي. بيرجّع disjoint union؛ ضيّق حسب `state.type` قبل ما تقرا بيانات الجلسة أو تستدعي الإجراءات.

```ts
type UseCheckoutResult =
  | { type: "loading" }
  | { type: "error"; error: { message: string } }
  | { type: "success"; checkout: Checkout };

type Checkout = CheckoutSession & CheckoutActions;
```

```tsx
const state = useCheckout();
if (state.type === "loading") return <Spinner />;
if (state.type === "error") return <p>{state.error.message}</p>;
const { checkout } = state;
```

بعد التضييق لـ `success`، الـ `checkout` بيحمل كل حقل جلسة زائد كل دالة إجراء.

### حقول الجلسة [#حقول-الجلسة]

حقول بيانات الجلسة على الـ `checkout` جاية مباشرة من `CheckoutSession`. شوف [مرجع `@xpayeg/sdk`](/sdk/sdk-js#checkoutsession-data-type) للمخطط الكامل. أكتر الحقول استخدامًا:

| الحقل            | النوع                  | الوصف                                                                   |
| ---------------- | ---------------------- | ----------------------------------------------------------------------- |
| `id`             | `string`               | معرّف الجلسة.                                                           |
| `amountTotal`    | `number`               | الإجمالي بالوحدات الصغرى.                                               |
| `amountSubtotal` | `number`               | المجموع الفرعي بالوحدات الصغرى.                                         |
| `currency`       | `string`               | كود العملة.                                                             |
| `merchantName`   | `string`               | اسم نشاطك التجاري.                                                      |
| `livemode`       | `boolean`              | الحساب الفعلي مقابل وضع الاختبار.                                       |
| `status`         | `SessionStatus`        | `{type:"open"} \| {type:"expired"} \| {type:"complete", paymentStatus}` |
| `canConfirm`     | `boolean`              | إذا كانت الجلسة جاهزة للتأكيد.                                          |
| `paymentMethods` | `PaymentMethodInfo[]`  | الوسائل المتاحة على الجلسة دي.                                          |
| `lineItems`      | `CheckoutLineItem[]`   | البنود.                                                                 |
| `totalDetails`   | `CheckoutTotalDetails` | تفصيل المجموع الفرعي، والضريبة، والشحن، والخصم، والرسوم.                |
| `discounts`      | `CheckoutDiscount[]`   | أكواد الخصم المطبَّقة.                                                  |

### دوال الإجراءات [#دوال-الإجراءات]

دوال الإجراءات جاية من `CheckoutActions`. كلها بترجّع `Promise<ActionResult>` (tagged union من `success` أو `error`) إلا حيث ما يُذكر.

| الدالة                                                       | الوصف                                                           |
| ------------------------------------------------------------ | --------------------------------------------------------------- |
| `confirm(options?: Omit<ConfirmPaymentOptions, "elements">)` | بتأكّد الدفعة. بتتولّى تحدّيات 3DS وتحويلات BNPL داخليًا.       |
| `applyPromotionCode(code: string)`                           | بتطبّق كود خصم؛ والجلسة بتعيد العرض بالإجمالي الجديد.           |
| `removePromotionCode()`                                      | بتشيل الكود المطبَّق.                                           |
| `updateLineItemQuantity({ lineItem, quantity })`             | بتحدّث كمية بند.                                                |
| `submit()`                                                   | بتتحقّق من الحقول. بترجّع `{ error?, selectedPaymentMethod? }`. |
| `fetchUpdates()`                                             | بتعيد جلب الجلسة من السيرفر.                                    |
| `changeAppearance(appearance: Appearance)`                   | بتحدّث العلامة التجارية في وقت التشغيل؛ بترجّع `void`.          |
| `on(event, handler)`                                         | تستنى `"change"`، أو `"error"`، أو حدث مخصّص. بترجّع `void`.    |
| `getElements()`                                              | بترجّع نسخة الـ `Elements` الأساسية للوصول منخفض المستوى.       |

الـ hook بيستنى أحداث الـ `change` والـ `loaderror` الأساسية بنفسه، فلما أكواد الخصم بتتطبّق، أو البنود بتتحدّث، أو الرسوم بتتعاد حسابها، المكوّن بيعيد العرض بآخر بيانات من غير ما تربط effect.

### أكواد الخصم والكميات (مثال) [#أكواد-الخصم-والكميات-مثال]

```tsx
function LineItems() {
  const state = useCheckout();
  if (state.type !== "success") return null;
  const { checkout } = state;

  return (
    <ul>
      {checkout.lineItems?.map((item) => (
        <li key={item.id}>
          <span>{item.description}</span>
          <button
            onClick={() =>
              checkout.updateLineItemQuantity({
                lineItem: item.id,
                quantity: item.quantity + 1,
              })
            }
          >
            +
          </button>
          <button
            onClick={() =>
              checkout.updateLineItemQuantity({
                lineItem: item.id,
                quantity: Math.max(1, item.quantity - 1),
              })
            }
          >
            -
          </button>
        </li>
      ))}
    </ul>
  );
}

function PromoInput() {
  const state = useCheckout();
  const [code, setCode] = useState("");
  const [error, setError] = useState("");

  if (state.type !== "success") return null;
  const { checkout } = state;

  const apply = async () => {
    setError("");
    const result = await checkout.applyPromotionCode(code);
    if (result.type === "error") setError(result.error.message);
  };

  return (
    <div>
      <input value={code} onChange={(e) => setCode(e.target.value)} />
      <button onClick={apply}>Apply</button>
      <button onClick={() => checkout.removePromotionCode()}>Remove</button>
      {error && <p style={{ color: "red" }}>{error}</p>}
    </div>
  );
}
```

### استنى الأخطاء غير المطلوبة [#استنى-الأخطاء-غير-المطلوبة]

أحداث الـ `change` بتشغّل حالة React تلقائيًا. حدث الـ `error` بيغطي الأخطاء اللي بتطلق بره أي إجراء تاجر (مثلًا جلسة انتهت أثناء إعادة حساب رسوم، أو فشل في اكتشاف الـ BIN):

```tsx
useEffect(() => {
  if (state.type !== "success") return;
  state.checkout.on("error", (err) => {
    console.error("[XPay] unsolicited error:", err.code, err.message);
  });
}, [state]);
```

## `useXPay()` و`useElements()` [#usexpay-وuseelements]

hooks منخفضة المستوى بترجّع النسخ الأساسية. مفيدة لما تحتاج تستدعي دوال مش مكشوفة من خلال `useCheckout` (مثلًا تركيب element إضافي برمجيًا).

```ts
function useXPay(): XPayInstance | null;
function useElements(): Elements | null;
```

الاتنين بيرجّعوا `null` أثناء تحميل الـ SDK أو قبل ما يتوفّر `clientSecret`. والاتنين لازم يتنادوا جوّه `<XPayProvider>`.

## `useConfirmPayment()` [#useconfirmpayment]

hook راحة حوالين `useCheckout`. بيرجّع نفس دالة الـ `confirm` زائد علَم `isConfirming`. مفيد لما عايز API صغيّر لمكوّن طرفي محتاج بس يقدّم:

```tsx
const { confirmPayment, isConfirming } = useConfirmPayment();
```

لو الـ checkout لسه مش جاهز، `confirmPayment()` بيرجّع `{ type: "error", error: { message: "Checkout not ready" } }` فورًا بدل ما يرمي خطأ.

## `<PaymentElement />` [#paymentelement-]

بيعرض مختار وسيلة الدفع وفورم البطاقة. لازم يكون جوّه `<XPayProvider>` بجلسة.

```tsx
<PaymentElement
  onChange={(e) => setReady(e.complete)}
  onReady={() => console.log("element ready")}
/>
```

| الـ Prop        | النوع                                        | الوصف                                                |
| --------------- | -------------------------------------------- | ---------------------------------------------------- |
| `onReady`       | `() => void`                                 | اختياري. بيطلق لما iframe الـ element يتهيّأ.        |
| `onChange`      | `(event: PaymentElementChangeEvent) => void` | اختياري. بيطلق عند اختيار وسيلة الدفع وتغيّر الحقول. |
| `onLoaderStart` | `() => void`                                 | اختياري. بيطلق بشكل متزامن لما الـ iframe يتعمل.     |
| `onLoadError`   | `(event: ElementsLoadErrorEvent) => void`    | اختياري. بيطلق لما الـ element يفشل في التحميل.      |
| `className`     | `string`                                     | اختياري. CSS class للحاوية `<div>`.                  |
| `id`            | `string`                                     | اختياري. ID للحاوية `<div>`.                         |

المكوّن بيعرض `<div>` واحد وبيركّب الـ `PaymentElement` الأساسي (من `@xpayeg/sdk`) فيه. عند التفكيك، الـ element بيتفكّك تلقائيًا.

## `<CheckoutButton />` [#checkoutbutton-]

زرار جاهز بيفتح الـ drop-in checkout modal عند الضغط. بيغلّف `xpay.checkout({ mode: "modal" })` علشان ما تضطرش تربطه بنفسك.

```tsx
<CheckoutButton
  clientSecret="cs_test_..."
  checkoutOptions={{
    onComplete: (result) => router.push(`/orders/${result.paymentIntentId}`),
    onClose: () => console.log("Customer closed checkout"),
  }}
>
  Pay Now
</CheckoutButton>
```

| الـ Prop          | النوع                                             | الوصف                                                                                                                |
| ----------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `clientSecret`    | `string`                                          | مطلوب. الـ client secret بتاع جلسة الدفع.                                                                            |
| `children`        | `ReactNode`                                       | لافتة الزرار. الافتراضي `"Pay"`.                                                                                     |
| `checkoutOptions` | `Omit<CheckoutOptions, "clientSecret" \| "mode">` | اختياري. الـ callbacks (`onComplete`، و`onClose`، و`onReady`، و`onConfirmed`، و`onError`)، و`appearance`، و`locale`. |
| `className`       | `string`                                          | اختياري. CSS class للزرار.                                                                                           |
| `disabled`        | `boolean`                                         | اختياري. بيعطّل الزرار.                                                                                              |

الزرار بيتعطّل تلقائيًا كمان لما الـ SDK لسه مش متحمّل (`useXPay()` بيرجّع `null`). لازم يكون جوّه `<XPayProvider>`.

## أنماط شائعة [#أنماط-شائعة]

### مظهر واعٍ بالثيم [#مظهر-واعٍ-بالثيم]

زامن وضع لون XPay مع ثيم تطبيقك باستخدام `changeAppearance` في وقت التشغيل:

```tsx
function ThemeAwareCheckout() {
  const state = useCheckout();
  const { theme } = useTheme();

  useEffect(() => {
    if (state.type === "success") {
      state.checkout.changeAppearance({ colorMode: theme as "light" | "dark" });
    }
  }, [theme, state]);

  return <PaymentElement />;
}
```

### تحقّق مسبق قبل التأكيد [#تحقّق-مسبق-قبل-التأكيد]

`submit()` بيتحقّق من كل الحقول قبل `confirm()`. مفيد لما عايز تحكم خطوة التأكيد ورا خطوة واجهة تانية (نافذة تأكيد، أو خانة شروط، إلخ):

```tsx
const handlePay = async () => {
  const { error, selectedPaymentMethod } = await checkout.submit();
  if (error) return setError(error.message);

  const ok = await showConfirmDialog(selectedPaymentMethod);
  if (!ok) return;

  const result = await checkout.confirm();
  if (result.type === "error") setError(result.error.message);
};
```

### التحويل بعد النجاح [#التحويل-بعد-النجاح]

افتراضيًا `confirm()` بيرجّع النتيجة لكودك. مرّر `redirect: "always"` علشان توجّه العميل لـ `afterCompletion.redirect.url` بتاعك بعد النجاح:

```tsx
await checkout.confirm({
  customerDetails: { email },
  redirect: "always",
});
// On success, the page navigates away. Any code below only runs on error.
```

حدّد العنوان على الجلسة في `afterCompletion.redirect.url`. XPay بتوجّه ليه زي ما هو من غير ما تضيف عليه حاجة. وحطّ `{CHECKOUT_SESSION_ID}` جوّه الرابط لو صفحة الرجوع بتاعتك محتاجة تعرف الجلسة دي أنهي واحدة.

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

<Cards>
  <Card icon="<Code />" title="@xpayeg/sdk" href="/sdk/sdk-js">
    الـ JavaScript SDK الأساسي. مرجع لـ `loadXPay`، و`XPayInstance`، و`Elements`، وdrop-in checkout، وكل نوع.
  </Card>

  <Card icon="<Sparkles />" title="نمط Drop-in" href="/integrate/integration-patterns/drop-in">
    شرح للـ modal/inline checkout عن طريق `<CheckoutButton />` أو `xpay.checkout()`.
  </Card>

  <Card icon="<Sparkles />" title="نمط Elements" href="/integrate/integration-patterns/elements">
    شرح لـ `<XPayProvider>` + `<PaymentElement />` + `useCheckout`.
  </Card>
</Cards>