# الرموز الترويجية

هذه النقاط الأربع تدير الرموز الترويجية للمتجر، أي الرموز التي يكتبها الزبون عند إتمام الطلب ليدفع أقل. وهي تعمل على الرموز نفسها الموجودة في صفحة الرموز الترويجية بلوحة التحكم، المشروحة في [رموز الخصم](https://dzbuild.com/ar/ar/docs/selling/discount-codes.md)، وتستطيع أيضاً تغيير نص الرمز، وهذا ما لا تسمح به لوحة التحكم.

يُخصم الرمز من المجموع الفرعي للمنتجات، ولا يُخصم أبداً من التوصيل. الرمز من نوع `percentage` يأخذ تلك النسبة من المجموع الفرعي. والرمز من نوع `fixed` يطرح مبلغه بالدينار، ولا يطرح أبداً أكثر من المجموع الفرعي. لا يمكن حصر رمز في منتج أو فئة أو زبون، ولا وضع سقف للخصم على سلة كبيرة، ولا منح التوصيل المجاني به.

## قبل أن تبدأ[​](#قبل-أن-تبدأ "رابط مباشر إلى قبل أن تبدأ")

* يجب أن تكون إضافة **الرموز الترويجية** مفعّلة في المتجر (`/dashboard/addons`). القائمة تعمل بدونها وتُخبرك بحالتها في `addon_enabled`. كل كتابة تُرجع `409 addon_inactive` ما دامت الإضافة معطّلة، ولا يستطيع الزبائن استعمال أي رمز عند إتمام الطلب خلال ذلك.
* يحتاج المفتاح صلاحيات الرموز الترويجية. المفاتيح المُنشأة من لوحة التحكم (`/dashboard/api`) تحصل على الاثنتين. الصلاحيات تُجمَّد لحظة إنشاء المفتاح، فالمفتاح الذي لا يملكهما يُرجع `403 forbidden`: أنشئ مفتاحاً جديداً من لوحة التحكم.
* الحقلان `starts_at` و`expires_at` بتوقيت الجزائر، بالشكل `YYYY-MM-DD HH:MM:SS`.

| النطاق         | الوصف                                                                         |
| -------------- | ----------------------------------------------------------------------------- |
| `promos:read`  | الاطلاع على الرموز الترويجية.                                                 |
| `promos:write` | إنشاء الرموز الترويجية وتعديلها وحذفها. هذا يغيّر الأسعار التي يدفعها زبائنك. |

## كائن الرمز الترويجي[​](#كائن-الرمز-الترويجي "رابط مباشر إلى كائن الرمز الترويجي")

| الحقل                      | النوع          | المعنى                                                                                      |
| -------------------------- | -------------- | ------------------------------------------------------------------------------------------- |
| `id`                       | int            | رقم الرمز في المتجر.                                                                        |
| `code`                     | string         | ما يكتبه الزبون. بأحرف كبيرة، `A-Z` و`0-9` و`-` و`_`، وفريد داخل المتجر.                    |
| `discount_type`            | string         | `percentage` أو `fixed`.                                                                    |
| `discount_value`           | number         | النسبة (أكبر من 0 وحتى 100) أو المبلغ بالدينار.                                             |
| `min_order_amount`         | number أو null | المجموع الفرعي للمنتجات الذي يجب أن يبلغه الطلب حتى يُطبَّق الرمز. `null` تعني بلا حد أدنى. |
| `max_uses`                 | int أو null    | عدد الطلبات التي يمكنها استعمال الرمز. `null` تعني بلا حد.                                  |
| `used_count`               | int            | عدد الطلبات التي استعملته. للقراءة فقط.                                                     |
| `is_active`                | bool           | الرمز غير المفعّل يُرفض عند إتمام الطلب.                                                    |
| `starts_at`                | string أو null | قبل هذا الوقت يُرفض الرمز. `null` تعني أنه يعمل فوراً.                                      |
| `expires_at`               | string أو null | بعد هذا الوقت يُرفض الرمز. `null` تعني أنه لا ينتهي أبداً.                                  |
| `created_at`، `updated_at` | string         | `YYYY-MM-DD HH:MM:SS`، بتوقيت الخادم.                                                       |

## `GET /v1/promo-codes`[​](#get-v1promo-codes "رابط مباشر إلى get-v1promo-codes")

رموز المتجر، الأحدث أولاً. لا يوجد نداء يقرأ رمزاً واحداً برقمه: تصفّح هذه القائمة.

**المصادقة:** مفتاح منصة بصلاحية `promos:read`.

### معاملات الاستعلام[​](#معاملات-الاستعلام "رابط مباشر إلى معاملات الاستعلام")

| المعامل     | النوع  | الافتراضي | ملاحظات                                                                                                               |
| ----------- | ------ | --------- | --------------------------------------------------------------------------------------------------------------------- |
| `is_active` | string | لا شيء    | `true` أو `1` أو `yes` أو `on` تُرجع الرموز المفعّلة. أي قيمة أخرى تُرجع الرموز المعطّلة. اتركه للحصول على كل الرموز. |
| `limit`     | int    | 50        | من 1 إلى 200.                                                                                                         |
| `cursor`    | string | لا شيء    | قيمة `next_cursor` من الصفحة السابقة. انظر [الترقيم](https://dzbuild.com/ar/ar/api-docs/pagination.md).               |

### الطلب[​](#الطلب "رابط مباشر إلى الطلب")

```
curl 'https://api.dzbuild.app/v1/promo-codes?is_active=true' \

  -H "Authorization: Bearer $DZ_KEY"
```

### الاستجابة 200[​](#الاستجابة-200 "رابط مباشر إلى الاستجابة 200")

```
{

  "data": {

    "items": [

      {

        "id": 20,

        "code": "WELCOME10",

        "discount_type": "percentage",

        "discount_value": 10,

        "min_order_amount": 3000,

        "max_uses": 100,

        "used_count": 7,

        "is_active": true,

        "starts_at": null,

        "expires_at": "2026-11-30 23:59:00",

        "created_at": "2026-10-06 14:20:11",

        "updated_at": "2026-10-06 14:20:11"

      }

    ],

    "next_cursor": null,

    "has_more": false,

    "addon_enabled": true

  }

}
```

يُبيّن `addon_enabled` إن كانت إضافة **الرموز الترويجية** مفعّلة. عندما تكون قيمته `false` تبقى القائمة تعمل، لكن الكتابة تفشل ولا يستطيع الزبائن استعمال الرموز.

## `POST /v1/promo-codes`[​](#post-v1promo-codes "رابط مباشر إلى post-v1promo-codes")

يُنشئ رمزاً. يعمل الرمز عند إتمام الطلب بمجرد إنشائه، إلا إذا أرسلت `is_active: false` أو `starts_at` بتاريخ لاحق.

**المصادقة:** مفتاح منصة بصلاحية `promos:write`. **يتطلب `Idempotency-Key`.**

### الجسم[​](#الجسم "رابط مباشر إلى الجسم")

| الحقل                   | النوع          | إلزامي | ملاحظات                                                                                                                                                                                               |
| ----------------------- | -------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `code`                  | string         | نعم    | تُحذف المسافات من طرفيه وتتحوّل الأحرف إلى كبيرة، ثم يجب أن يكون من 2 إلى 30 حرفاً من `A-Z` أو `0-9` أو `-` أو `_`. الرمز الموجود من قبل في المتجر يُرجع `409 code_exists`.                           |
| `discount_type`         | string         | نعم    | `percentage` أو `fixed`، بأحرف كبيرة أو صغيرة. لا توجد قيمة افتراضية.                                                                                                                                 |
| `discount_value`        | number         | نعم    | أكبر من 0. لا يتجاوز 100 مع `percentage`، والقيمة 100 تحتاج تأكيد التاجر (انظر قسم خصم 100% أسفله). لا حد أعلى مع `fixed`.                                                                            |
| `min_order_amount`      | number أو null | لا     | الحد الأدنى للمجموع الفرعي للمنتجات بالدينار. `0` أو `""` أو `null` تعني بلا حد أدنى.                                                                                                                 |
| `max_uses`              | int أو null    | لا     | 1 أو أكثر. `0` أو `""` أو `null` تعني بلا حد.                                                                                                                                                         |
| `is_active`             | bool           | لا     | الافتراضي `true`. أرسل قيمة JSON منطقية: النص `"false"` مثلاً يُقرأ `true`.                                                                                                                           |
| `starts_at`             | string أو null | لا     | صيغة تاريخ وساعة شائعة، مثل `2026-11-01 08:00` أو نص بصيغة ISO 8601. يُخزَّن بالشكل `YYYY-MM-DD HH:MM:SS` بتوقيت الجزائر، والقيمة التي تحمل فارقاً عن UTC تُحوَّل. `null` أو `""` تعني بلا تاريخ بدء. |
| `expires_at`            | string أو null | لا     | الصيغ نفسها. يجب أن يكون في المستقبل وبعد `starts_at`. `null` أو `""` تعني بلا تاريخ انتهاء.                                                                                                          |
| `confirm_token`         | string         | لا     | لخصم 100% فقط.                                                                                                                                                                                        |
| `confirm_full_discount` | bool           | لا     | لخصم 100% فقط.                                                                                                                                                                                        |

### الطلب[​](#الطلب-1 "رابط مباشر إلى الطلب")

```
curl -X POST 'https://api.dzbuild.app/v1/promo-codes' \

  -H "Authorization: Bearer $DZ_KEY" \

  -H "Content-Type: application/json" \

  -H "Idempotency-Key: promo-welcome10-create" \

  -d '{"code": "welcome10", "discount_type": "percentage", "discount_value": 10, "min_order_amount": 3000, "max_uses": 100, "expires_at": "2026-11-30 23:59"}'
```

### الاستجابة 201[​](#الاستجابة-201 "رابط مباشر إلى الاستجابة 201")

```
{

  "data": {

    "id": 20,

    "code": "WELCOME10",

    "discount_type": "percentage",

    "discount_value": 10,

    "min_order_amount": 3000,

    "max_uses": 100,

    "used_count": 0,

    "is_active": true,

    "starts_at": null,

    "expires_at": "2026-11-30 23:59:00",

    "created_at": "2026-10-06 14:20:11",

    "updated_at": "2026-10-06 14:20:11"

  }

}
```

## `PATCH /v1/promo-codes/{id}`[​](#patch-v1promo-codesid "رابط مباشر إلى patch-v1promo-codesid")

يغيّر الحقول التي ترسلها فقط، بقواعد الإنشاء نفسها. الحقل الذي لا ترسله يحتفظ بقيمته.

**المصادقة:** مفتاح منصة بصلاحية `promos:write`. **يتطلب `Idempotency-Key`.**

* يمكن تغيير `code`. يجب ألا يكون النص الجديد مستعملاً في المتجر، وإلا يُرجع النداء `409 code_exists`.
* يُفحص `discount_type` و`discount_value` معاً. إرسال `discount_type: "percentage"` وحده على رمز `fixed` بـ 500 د.ج يُرجع `422 invalid_discount_value`، لأن 500 أكبر من 100. أرسل الحقلين.
* يجب أن يكون `expires_at` الجديد في المستقبل. الرمز المنتهي الصلاحية يبقى قابلاً للتعديل ما دمت لا ترسل `expires_at`.
* لا يمكن كتابة `used_count`.
* الجسم الفارغ لا يغيّر شيئاً ويُرجع الرمز.
* رمز متجر آخر يُرجع `404`، مثل رمز غير موجود.

### الطلب[​](#الطلب-2 "رابط مباشر إلى الطلب")

```
curl -X PATCH 'https://api.dzbuild.app/v1/promo-codes/20' \

  -H "Authorization: Bearer $DZ_KEY" \

  -H "Content-Type: application/json" \

  -H "Idempotency-Key: promo-20-extend" \

  -d '{"max_uses": 200, "expires_at": "2026-12-31 23:59"}'
```

### الاستجابة 200[​](#الاستجابة-200-1 "رابط مباشر إلى الاستجابة 200")

```
{

  "data": {

    "id": 20,

    "code": "WELCOME10",

    "discount_type": "percentage",

    "discount_value": 10,

    "min_order_amount": 3000,

    "max_uses": 200,

    "used_count": 7,

    "is_active": true,

    "starts_at": null,

    "expires_at": "2026-12-31 23:59:00",

    "created_at": "2026-10-06 14:20:11",

    "updated_at": "2026-10-20 09:05:42"

  }

}
```

## `DELETE /v1/promo-codes/{id}`[​](#delete-v1promo-codesid "رابط مباشر إلى delete-v1promo-codesid")

يحذف الرمز، فلا يستطيع الزبائن استعماله بعدها. الطلبات التي استعملته من قبل تحتفظ بخصمها. لإيقاف رمز مؤقتاً دون أن تفقد عدد استعمالاته، أرسل `is_active: false` عبر `PATCH` بدل الحذف.

**المصادقة:** مفتاح منصة بصلاحية `promos:write`. **يتطلب `Idempotency-Key`.**

### الطلب[​](#الطلب-3 "رابط مباشر إلى الطلب")

```
curl -X DELETE 'https://api.dzbuild.app/v1/promo-codes/20' \

  -H "Authorization: Bearer $DZ_KEY" \

  -H "Idempotency-Key: promo-20-delete"
```

### الاستجابة 200[​](#الاستجابة-200-2 "رابط مباشر إلى الاستجابة 200")

```
{

  "data": {

    "deleted": true,

    "id": 20

  }

}
```

## خصم 100%[​](#خصم-100 "رابط مباشر إلى خصم 100%")

رمز `percentage` بقيمة 100 يجعل المنتجات مجانية لكل زبون يملك الرمز. الإنشاء، أو التعديل الذي يرسل `discount_type` أو `discount_value`، لا يُكتب من النداء الأول إذا كانت النتيجة رمز `percentage` بنسبة 100%:

1. يُرجع النداء `422 confirmation_required` ولا يكتب شيئاً. يحمل الخطأ `confirm_token` (يُستعمل مرة واحدة، صالح `600` ثانية) و`action` و`will_change`، وهو ملخّص تعرضه على التاجر.
2. بعد موافقة التاجر، أعد الجسم نفسه مع إضافة `confirm_token` وبـ `Idempotency-Key` **جديد** (المفتاح الأول مربوط بالجسم الذي لا يحمل رمز التأكيد). رمز التأكيد المنتهي، أو المستعمل من قبل، أو الذي لم يعد يطابق الطلب، يُرجع `422 confirmation_stale` مع رمز تأكيد جديد.

بمفتاح مُنشأ من لوحة التحكم يمكنك تجاوز هذه الجولة بإرسال `confirm_full_discount: true` في النداء الأول. أما DZBuild Copilot فلا يستطيع استعمال هذا الحقل ويمرّ دائماً عبر رمز التأكيد.

```
{

  "error": {

    "code": "confirmation_required",

    "message": "A 100% discount makes every order free ...",

    "confirm_token": "cft_xxxxxxxxxxxxxxxx",

    "confirm_token_expires_in": 600,

    "action": "promo.full_discount:new",

    "will_change": {

      "action": "Create a promo code that makes orders free",

      "code": "FREEGIFT",

      "discount": "100% off the whole order subtotal",

      "reversible": true,

      "note": "Any customer with this code pays 0 for the goods. Orders already placed with it cannot be reversed by deleting the code."

    }

  }

}
```

في التعديل، ينتهي `action` برقم الرمز بدل `new`، ويصبح نص `will_change.action` هو `Change promo code #20 to make orders free`.

```
curl -X POST 'https://api.dzbuild.app/v1/promo-codes' \

  -H "Authorization: Bearer $DZ_KEY" \

  -H "Content-Type: application/json" \

  -H "Idempotency-Key: promo-freegift-approved" \

  -d '{"code": "FREEGIFT", "discount_type": "percentage", "discount_value": 100, "max_uses": 1, "confirm_token": "cft_REPLACE_WITH_TOKEN"}'
```

## سجل التغييرات والتراجع[​](#سجل-التغييرات-والتراجع "رابط مباشر إلى سجل التغييرات والتراجع")

كل إنشاء وتعديل وحذف يتم عبر الواجهة البرمجية يُسجَّل. الرموز التي تُعدَّل من صفحة لوحة التحكم لا تُسجَّل، فلا يمكن التراجع عنها عبر الواجهة البرمجية.

* يعرض `GET /v1/changes?entity=promo_code` تغييرات الرموز الترويجية، الأحدث أولاً، بصلاحية `store:read`. كل عنصر يحمل `id` التغيير، ورقم الرمز في `entity_id`، و`action` (`create` أو `update` أو `delete`) و`summary`.
* يحتاج `POST /v1/changes/{id}/undo` إلى `promos:write` وإلى `Idempotency-Key` وإلى أن تكون الإضافة مفعّلة، مثل أي كتابة.
* التراجع عن تعديل يُرجع القيم السابقة دون أي تأكيد، حتى لو كانت خصماً بـ 100% أو تاريخ انتهاء قد مضى. وإذا حُذف الرمز بعد ذلك، يُرجع التراجع `422 restore_target_missing`.
* التراجع عن حذف يُعيد إنشاء الرمز مع عدد استعمالاته، ويحتفظ برقمه إذا كان هذا الرقم ما يزال شاغراً.
* التراجع عن إنشاء يُرجع `422 nothing_to_restore`: احذف الرمز بدلاً من ذلك.
* لا يستطيع رمز التطبيق المثبّت قراءة التغييرات ولا التراجع عنها: كلاهما يُرجع `403 forbidden`.

```
curl -X POST 'https://api.dzbuild.app/v1/changes/500/undo' \

  -H "Authorization: Bearer $DZ_KEY" \

  -H "Idempotency-Key: undo-500"
```

```
{

  "data": {

    "undone": true,

    "change_id": 500,

    "entity": "promo_code",

    "undo_change_id": 510

  }

}
```

التغيير الذي يُتراجع عنه مرة ثانية يُرجع `409 already_undone`.

## الأخطاء[​](#الأخطاء "رابط مباشر إلى الأخطاء")

| HTTP | الرمز                                         | السبب                                                                                                                                                             |
| ---- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400  | `bad_request`                                 | الرقم في المسار ليس أرقاماً فقط، أو الجسم ليس JSON صالحاً، أو `Idempotency-Key` غائب أو غير صالح.                                                                 |
| 403  | `forbidden`                                   | `Missing scope: promos:read` أو `Missing scope: promos:write`، أو `API access requires an active Enterprise plan` لمفتاح تاجر متجره ليس على خطة Enterprise سارية. |
| 404  | `not_found`                                   | لا يوجد رمز ترويجي بهذا الرقم في المتجر.                                                                                                                          |
| 409  | `addon_inactive`                              | إضافة الرموز الترويجية غير مفعّلة في المتجر. في الكتابة فقط.                                                                                                      |
| 409  | `code_exists`                                 | رمز آخر في المتجر يحمل هذا النص.                                                                                                                                  |
| 422  | `invalid_code`                                | `code` أقصر من حرفين أو أطول من 30 حرفاً، أو يحتوي حرفاً غير `A-Z` و`0-9` و`-` و`_`.                                                                              |
| 422  | `invalid_discount_type`                       | `discount_type` غائب، أو ليس `percentage` ولا `fixed`.                                                                                                            |
| 422  | `invalid_discount_value`                      | `discount_value` غائب، أو ليس رقماً، أو يساوي 0 أو أقل، أو أكبر من 100 مع `percentage`.                                                                           |
| 422  | `invalid_min_order_amount`                    | `min_order_amount` ليس رقماً.                                                                                                                                     |
| 422  | `invalid_max_uses`                            | `max_uses` ليس رقماً، أو أقل من 1.                                                                                                                                |
| 422  | `invalid_starts_at`، `invalid_expires_at`     | تعذّرت قراءة التاريخ.                                                                                                                                             |
| 422  | `expires_at_in_past`                          | قيمة `expires_at` التي أرسلتها ليست في المستقبل.                                                                                                                  |
| 422  | `invalid_date_window`                         | `expires_at` ليس بعد `starts_at`.                                                                                                                                 |
| 422  | `confirmation_required`، `confirmation_stale` | خصم 100% ينتظر موافقة التاجر. انظر القسم أعلاه.                                                                                                                   |
| 422  | `idempotency_key_reuse`                       | استُعمل `Idempotency-Key` نفسه مع جسم أو مسار مختلف.                                                                                                              |
| 500  | `server_error`                                | فشلت الكتابة. أعد المحاولة بـ `Idempotency-Key` نفسه.                                                                                                             |

استجابة `4xx` تُحفظ مع `Idempotency-Key` الخاص بها 24 ساعة، وتُعاد لكل إعادة محاولة بالجسم نفسه. بعد تفعيل الإضافة أو تصحيح الجسم، أرسل النداء بمفتاح جديد. الأخطاء المشتركة بين كل النقاط، مثل `401` و`402` و`429`، موجودة في [الأخطاء](https://dzbuild.com/ar/ar/api-docs/errors.md)، وقواعد إعادة المحاولة في [Idempotency](https://dzbuild.com/ar/ar/api-docs/idempotency.md).

## حدود معروفة[​](#حدود-معروفة "رابط مباشر إلى حدود معروفة")

* **قد تتجاوز الاستعمالات `max_uses`.** عندما يُتمّ عدة زبائن طلباتهم بالرمز نفسه في اللحظة نفسها، قد يتجاوز `used_count` قيمة `max_uses`.
* **لا يوجد `webhook`.** إنشاء رمز ترويجي أو تعديله أو حذفه لا يُرسل أي حدث `webhook`.
