# المبالغ المستردة (/ar/integrate/refunds)

اعكس دفعة ناجحة بالكامل أو جزئيًا. POST واحد مقابل الـ Payment Intent أو الـ Charge، وGET اختياري علشان تقراه.

الـ Refund بيعكس فلوس من دفعة ناجحة ويرجّعها للعميل. تقدر ترد المبلغ كامل أو أي جزء منه، أكتر من مرة، لحد ما الدفعة ما يبقاش فيها حاجة تتردّ. سطح الـ API صغير: `POST /refunds` واحد للإصدار، و`GET /refunds/:id` واحد للقراءة، و`GET /refunds` واحد للسرد.

الصفحة دي بتغطّي مسار المطوّر: إمتى تستخدم الـ API، وتبعت إيه، وبيرجع إيه، وأنهي أحداث تستمع لها. علشان مسار الإصدار من لوحة التحكم اللي التجار بيستخدموه يوم بيوم، شوف [المبالغ المستردة](/features/transactions/refunds) تحت المميزات.

<Callout type="info">
  `POST /refunds&#x60; بياخد &#x2A;*`paymentIntentId`*&#x2A; (مقبض المعاملة اللي خزّنته على الطلب) أو
  &#x2A;*`chargeId`** (charge معيّن). قدّم واحد بالظبط. مسار `paymentIntentId` هو الأساسي
  واللي معظم عمليات الدمج المفروض تستخدمه؛ بيشتغل مباشرة مع الـ `pi_*` اللي بتمسكه أصلًا
  من `checkout.session.completed`.
</Callout>

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

1. **السيرفر بتاعك** معاه الـ `pi_*` بتاع الدفعة، اللي مسكته من webhook `checkout.session.completed` (أو قريته عند الطلب من `GET /checkout/sessions/:id`).
2. **السيرفر بتاعك** بينادي `POST /refunds` بالـ `paymentIntentId`. واختياريًا ضيف `amount` (للمبالغ المستردة الجزئية)، أو `reason`، أو `description`، أو `metadata`.
3. **XPay** بيعالج الاسترداد عن طريق وسيلة الدفع الأصلية.
4. **XPay** بيبعت webhook لـ `refund.created` وواحد لـ `charge.refunded` علشان السيرفر بتاعك يعلّم الطلب على إنه مسترد ويبلّغ العميل.

الـ Balance Transaction الخاص بالـ Refund هو اللي بيأثّر على رصيدك في XPay. والقيمة بإشارتها (سالبة للمبالغ المستردة) هي اللي بتتسوّى مقابل تسوياتك.

## ابنيها [#ابنيها]

### 1. أنشئ الاسترداد [#1-أنشئ-الاسترداد]

الحقل الوحيد المطلوب على `POST /refunds` هو واحد من `paymentIntentId` أو `chargeId`. سيب `amount` علشان ترد كامل المبلغ المتبقّي القابل للاسترداد.

<Tabs items="[&#x22;cURL&#x22;, &#x22;Node.js&#x22;, &#x22;Python&#x22;]">
  <Tab value="cURL">
    ```bash
    curl -X POST https://api.xpay.app/refunds \
      -H "Authorization: Bearer sk_test_..." \
      -H "Content-Type: application/json" \
      -d '{
        "paymentIntentId": "pi_test_xyz789",
        "amount": 50000,
        "reason": "requested_by_customer",
        "metadata": { "order_id": "ord_42" }
      }'
    ```
  </Tab>

  <Tab value="Node.js">
    ```typescript
    const res = await fetch("https://api.xpay.app/refunds", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.XPAY_SECRET_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        paymentIntentId: "pi_test_xyz789",
        amount: 50000, // 500.00 EGP, in minor units
        reason: "requested_by_customer",
        metadata: { order_id: "ord_42" },
      }),
    });

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

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

    res = requests.post(
        "https://api.xpay.app/refunds",
        headers={
            "Authorization": f"Bearer {os.environ['XPAY_SECRET_KEY']}",
            "Content-Type": "application/json",
        },
        json={
            "paymentIntentId": "pi_test_xyz789",
            "amount": 50000,
            "reason": "requested_by_customer",
            "metadata": {"order_id": "ord_42"},
        },
        timeout=10,
    )

    refund = res.json()
    ```
  </Tab>
</Tabs>

### 2. استرداد charge معيّن (بديل) [#2-استرداد-charge-معيّن-بديل]

لو بتشتغل أصلًا على مستوى الـ Charge (مثلًا، بتبني تنفيذ الطلب على webhooks `charge.succeeded`)، مرّر `chargeId` بدالها. نفس المحتوى في الباقي؛ و`chargeId` و`paymentIntentId` بيستبعدوا بعض.

```bash
curl -X POST https://api.xpay.app/refunds \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "chargeId": "ch_test_AbC123",
    "amount": 50000
  }'
```

لما `paymentIntentId` يترفض بخطأ "صفر أو أكتر من charge ناجح"، ارجع لـ `chargeId` علشان تستهدف الـ charge المعيّن اللي عايز ترده.

### 3. اقرا الرد [#3-اقرا-الرد]

```json
{
  "id": "re_test_AbC123",
  "object": "refund",
  "amount": 50000,
  "currency": "EGP",
  "chargeId": "ch_test_AbC123",
  "paymentIntentId": "pi_test_xyz789",
  "status": "succeeded",
  "reason": "requested_by_customer",
  "balanceTransactionId": "txn_test_def456",
  "createdAt": "2026-05-01T12:00:00.000Z"
}
```

كل استرداد بتعمله عن طريق API التاجر النهارده بيحسم لـ `status: "succeeded"` بشكل متزامن. مخطط كائن الـ <ApiLink href="/api-reference/objects/refund">Refund</ApiLink> بيحجز قيم إضافية (`pending`، `requires_action`، `failed`) للاستخدام مستقبلًا؛ مش هتشوفها في الردود النهارده.

الرد بيحمل `chargeId` و`paymentIntentId` الاتنين علشان تقدر توصّل الاسترداد لأي جنب من نموذج بياناتك. والـ `balanceTransactionId` بيشاور على صف السجل اللي خصم من رصيدك المتاح. وللمبالغ المستردة الجزئية، كذا كائن Refund بيتراكموا مقابل Charge واحد؛ كل واحد ليه `txn_*` خاص بيه.

## الاختيار بين `paymentIntentId` و`chargeId` [#الاختيار-بين-paymentintentid-وchargeid]

الحقلين بيستبعدوا بعض: قدّم واحد بالظبط.

* **استخدم `paymentIntentId` افتراضيًا.** الـ `pi_*` هو مقبض المعاملة لدفعة ناجحة واحدة؛ والسيرفر بيحسمه للـ charge الوحيد الناجح والمتحصَّل على الـ intent ده. ده المسار الصح لكل عملية دمج بتعوز ببساطة ترد "الدفعة".
* **استخدم `chargeId` لما يبقى فيه التباس.** الـ `paymentIntentId` بيترفض لما الـ Payment Intent يبقى فيه صفر أو أكتر من charge ناجح. ده ممكن يحصل في تدفقات حدّية ممكن تبنيها بعدين (تحصيل متعدد، أو تحصيلات جزئية عبر charges منفصلة). الـ `chargeId` بيستهدف دايمًا charge معيّن.

اعمل حسابك لـ `chargeId` بس لما تحتاج مستوى التحديد ده. في الحالة المعتادة "العميل دفع مرة، وعايز تردّله"، الـ `paymentIntentId` أقصر، وأوضح، وبيستخدم المعرّف اللي بتخزّنه أصلًا.

## الاسترداد الجزئي مقابل الكامل [#الاسترداد-الجزئي-مقابل-الكامل]

الـ `amount` بالوحدات الصغرى، زي كل حاجة تانية (`50000` يعني 500.00 EGP).

* **سيب `amount`** علشان ترد أي حاجة لسه قابلة للاسترداد على الـ Charge. في أول استرداد ده بيبقى إجمالي الـ charge. وفي الاستردادات اللي بعديها بيبقى اللي فاضل بعد المبالغ المستردة الجزئية السابقة.
* **ابعت `amount`** علشان ترد جزء معيّن. لازم يكون وحدة صغرى واحدة على الأقل وبحد أقصى المبلغ المتبقّي القابل للاسترداد.
* تقدر تعمل **كذا استرداد جزئي** مقابل نفس الـ Charge طول ما الإجمالي ما يتعداش المبلغ الأصلي. كل استدعاء بيعمل `re_*` خاص بيه وBalance Transaction خاص بيه.
* بمجرد ما المبلغ المسترد التراكمي يساوي إجمالي الـ Charge، محاولات الاسترداد اللي بعد كده بترجّع خطأ.

## السبب والبيانات الوصفية [#السبب-والبيانات-الوصفية]

الـ `reason` هو كود قصير معدود بيوصف ليه رددت. مفيد للتحليلات والدعم. القيم الشائعة:

| القيمة                      | إمتى تستخدمها                                                  |
| --------------------------- | -------------------------------------------------------------- |
| `requested_by_customer`     | العميل طلبه. الافتراضي بتاعك للمبالغ المستردة بالخدمة الذاتية. |
| `duplicate`                 | العميل اتخصم منه مرتين لنفس الطلب.                             |
| `fraudulent`                | الـ charge اتعمل عليه نزاع كاحتيال وأنت بترد بشكل استباقي.     |
| `expired_uncaptured_charge` | charge للتفويض بس انتهى من غير تحصيل. نادرًا بيتعرض يدويًا.    |

الـ `description` بياخد لحد 500 حرف كملاحظة حرة على الـ Refund. بتتخزّن على الكائن وبتظهر في الخط الزمني للاسترداد في لوحة التحكم، مفيدة لسجلاتك أنت ولموظفي الدعم اللي بيراجعوا الاسترداد بعدين.

الـ `metadata` هي حقيبة مفاتيح وقيم خاصة بيك، XPay مش بيفهمها، مفيدة للربط المرجعي مع نظام الطلبات بتاعك.

## استمع لأحداث الاسترداد [#استمع-لأحداث-الاسترداد]

للمبالغ المستردة المعمولة من لوحة التحكم، أو علشان تتأكد إن الاستردادات غير المتزامنة اكتملت، استمع لـ webhooks الاسترداد. حدثين بيتطلقوا على كل استرداد ناجح:

| الحدث             | إمتى                                                                        |
| ----------------- | --------------------------------------------------------------------------- |
| `refund.created`  | كائن Refund جديد اتعمل. بيتطلق للمبالغ المستردة من الـ API ومن لوحة التحكم. |
| `charge.refunded` | الـ Charge اتطبّق عليه استرداد. مفيد لما تبني تنفيذ الطلب على الـ Charges.  |

الـ `data.object` على `refund.created` بيبقى <ApiLink href="/api-reference/objects/refund">Refund</ApiLink> كامل، مطابق لـ `GET /refunds/:id`. وعلى `charge.refunded` بيبقى <ApiLink href="/api-reference/objects/charge">Charge</ApiLink> كامل مع الـ Refund الجديد متضمَّن جوّه `refunds[]`.

علشان وصفة التحقق من التوقيع والحماية من إعادة الإرسال، شوف [Webhooks ← التحقق من التوقيعات](/integrate/webhooks/verifying-signatures).

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

* **ابعت `Idempotency-Key` على `POST /refunds`.** الإعادة بعد مهلة شبكة منتهية بترجّع وقتها الاسترداد الأصلي بدل ما تعمل واحد تاني. شوف [عدم التكرار](/integrate/idempotency).
* **تنفيذ بلا تكرار.** تتبّع أنهي معرّفات Refund اتعاملت معاها قبل كده في قاعدة بياناتك. الـ `refund.created` ممكن يتسلّم تاني.
* **ما تعملش الإصدار من التطبيق اللي بيواجه العميل.** المبالغ المستردة بتستخدم مفتاح سري (`sk_test_*` / `sk_live_*`). أصدرها من السيرفر بتاعك، مش من كود الواجهة.
* **قرّر مين يقدر يعمل المبالغ المستردة.** حدّد صلاحيات الاسترداد على مفتاح الـ API اللي بتستخدمه في مسار الكود ده. وللمبالغ المستردة من لوحة التحكم، قيّد صلاحية **Refunds** على صلاحيات الفريق.
* **اضبط التوقعات بخصوص فترة البنك.** الاسترداد الناجح بيأثّر على سجل XPay على طول، بس المبالغ المستردة على البطاقة بتاخد من 7 لـ 14 يوم علشان تظهر في كشف حساب العميل. قول للعميل في إيميل الاسترداد بتاعك علشان ما يفتكرش إنه فشل.
* **سوّي مقابل الـ Balance Transactions.** الـ `balanceTransactionId` على كل استرداد هو اللي خصم من رصيدك. استخدمه للمحاسبة، مش الـ `amount` بتاع الـ Refund لوحده.

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

<Cards>
  <Card icon="<Network />" title="نموذج الكائنات" href="/integrate/object-model">
    إزاي Payment Intents وCharges وRefunds بتتركّب مع بعض، وأنهي معرّفات تخزّن.
  </Card>

  <Card icon="<Webhook />" title="إعداد نقطة نهاية webhook" href="/integrate/webhooks/setting-up-an-endpoint">
    استقبل `refund.created` و`charge.refunded` وتحقق من توقيعاتهم.
  </Card>

  <Card icon="<Receipt />" title="المبالغ المستردة في لوحة التحكم" href="/features/transactions/refunds">
    المسار اللي بيواجه التاجر لاسترداد لمرة واحدة من غير كتابة كود.
  </Card>
</Cards>