Coupons API
برنامج القسائم هو الوصفة التي تُصدر منها الرسالة رموزاً شخصية ({{ coupons.<slug>.code }}). تُنشئ Joryio كل رمز في متجر WooCommerce أو Shopify أو Magento أو PrestaShop المتصل (أو تُنشئه وتتتبعه وحدها عندما لا يكون للبرنامج متجر) وتتعرّف على الطلب الذي استخدمه. سلوك البرامج موصوف في رموز القسائم.
جميع نقاط النهاية في هذه الصفحة نسبية إلى عنوان URL الأساسي: https://api-eu1.joryio.com/api - راجع نظرة عامة على API.
الصلاحيات
| الصلاحية | تسمح بـ |
|---|---|
campaigns:read | عرض البرامج وقراءتها والتقرير والمتاجر المتصلة |
campaigns:write | إنشاء البرامج، وتغيير البرامج التي لم تُصدر رموزاً بعد، وحذفها، وإصدار رمز |
settings:write | مطلوبة إضافةً لتغيير برنامج فعّال - أي برنامج أصدر رموزاً حقيقية |
users:read | عرض الرموز المُصدَرة (الرموز تخص أشخاصاً) |
كائن البرنامج
{
"id": "6c0f3c1e-8a52-4a8e-9a39-2a1d7f0b9a11",
"name": "Welcome 10%",
"slug": "welcome10",
"description": "10% off the first order",
"integrationId": "0b8d6f2a-5f43-4e0a-b9a3-7a8b2d1c4e55",
"platform": "woocommerce",
"storeName": "My Store",
"source": "personal",
"status": "active",
"rules": {
"discountType": "percent",
"amount": 10,
"currency": null,
"minimumSubtotal": 50,
"minimumQuantity": null,
"scope": { "kind": "all" },
"excludeSaleItems": false,
"usageLimit": 1,
"oncePerCustomer": true,
"combinable": false,
"expiry": { "mode": "relative", "amount": 14, "unit": "days", "endOfDay": true }
},
"rulesVersion": 1,
"codeFormat": { "length": 10, "charset": "unambiguous", "prefix": "WELCOME-", "suffix": "", "separatorEvery": null },
"fallback": { "policy": "skip", "sharedCode": null },
"reuseOpenCode": true,
"lockToPerson": false,
"live": true,
"sampleCode": "WELCOME-PREVIEW",
"storeError": null,
"stats": { "issued": 1240, "redeemed": 311, "revenue": 18420.5, "currency": "EUR" },
"createdAt": "2026-10-04T08:00:00.000Z",
"updatedAt": "2026-10-04T08:00:00.000Z"
}
| الحقل | القيم |
|---|---|
integrationId | متجر متصل، أو null: تُنشئ Joryio الرموز وتتتبعها دون متجر |
status | draft، active، paused، archived - البرنامج active وحده يُصدر رموزاً |
rules.discountType | percent، fixed_cart، fixed_product (غير متاح في PrestaShop: 400)، free_shipping |
rules.scope | { "kind": "all" }، أو products (WooCommerce، Shopify، PrestaShop) / categories (WooCommerce، Magento، PrestaShop) / collections (Shopify) مع ids - معرّفات المتجر نفسه؛ products في Magento يُعيد 400 |
rules.minimumQuantity | Shopify فقط |
rules.excludeSaleItems | WooCommerce فقط |
rules.usageLimit | الاستخدامات لكل رمز، الافتراضي 1؛ null = بلا حد |
rules.expiry | { "mode": "none" }، { "mode": "fixed", "date": "2026-12-31T23:59:59Z" }، أو { "mode": "relative", "amount": 14, "unit": "days" | "hours", "endOfDay": true } |
codeFormat.charset | unambiguous (أحرف وأرقام بلا أحرف متشابهة، الافتراضي)، letters، digits؛ length 6-20 |
fallback.policy | skip (الافتراضي)، shared_code (يحتاج sharedCode)، send_without |
lockToPerson | WooCommerce وMagento وPrestaShop (حساب العميل الخاص بالشخص) |
live | true بمجرد أن يُصدر البرنامج رمزاً حقيقياً (ليس رمز اختبار) |
rulesVersion | يزداد عند تغيير قواعد برنامج فعّال؛ تحتفظ الرموز المُصدرة سابقاً بالقواعد التي أُنشئت بها |
البرامج
عرض البرامج
GET /coupons/programs?status=active&includeArchived=false
تُعيد مصفوفة من كائنات البرامج. تُستبعد البرامج المؤرشفة ما لم يكن includeArchived=true.
إنشاء برنامج
POST /coupons/programs
جسم الطلب هو كائن البرنامج دون الحقول المخصصة للقراءة فقط (id، platform، storeName، rulesVersion، stock، live، sampleCode، storeError، stats، التواريخ). slug هو [a-z0-9_]، فريد في مساحة العمل. يمكن أن يكون status إما draft (الافتراضي) أو active.
{
"name": "Welcome 10%",
"slug": "welcome10",
"integrationId": "0b8d6f2a-5f43-4e0a-b9a3-7a8b2d1c4e55",
"rules": { "discountType": "percent", "amount": 10, "scope": { "kind": "all" }, "usageLimit": 1, "oncePerCustomer": true, "combinable": false, "expiry": { "mode": "relative", "amount": 14, "unit": "days" } },
"codeFormat": { "length": 10, "charset": "unambiguous", "prefix": "WELCOME-" },
"fallback": { "policy": "skip" },
"reuseOpenCode": true,
"lockToPerson": false
}
قراءة برنامج
GET /coupons/programs/{id}
تحديث برنامج
PATCH /coupons/programs/{id}
أي حقل من جسم الإنشاء، أو status (draft، active، paused، archived). تغيير برنامج فعّال يحتاج إلى settings:write بالإضافة إلى campaigns:write.
حذف برنامج
DELETE /coupons/programs/{id}
204 لبرنامج لم يُصدر رمزاً حقيقياً قط. البرنامج الفعّال يُجيب 409 - أرشفه بدلاً من ذلك (PATCH مع "status": "archived").
تقرير البرنامج
GET /coupons/programs/{id}/report
{
"programId": "6c0f3c1e-8a52-4a8e-9a39-2a1d7f0b9a11",
"totals": { "issued": 1240, "redeemed": 311, "redemptionRate": 0.2508, "revenue": 18420.5, "discount": 1842.05, "currency": "EUR", "expired": 402, "failed": 3, "redeemedBySomeoneElse": 12 },
"bySource": [
{ "kind": "journey", "id": "a1b2c3d4-0000-4000-8000-000000000001", "name": "Welcome journey", "issued": 980, "redeemed": 270, "revenue": 15900 }
],
"daily": [{ "date": "2026-10-04", "issued": 52, "redeemed": 14 }]
}
الإيرادات والخصم كلاهما بعملة التقارير في مساحة العمل (currency): يُحوَّل الطلب المدفوع بعملة أخرى بسعر الصرف الخاص بذلك الطلب، وهو نفس السعر الذي حُوِّلت به إيراداته. لا تُحتسب رموز الاختبار.
إصدار رمز
POST /coupons/programs/{id}/issue
يمنح رمزاً من برنامج نشط لجهة اتصال واحدة - للشركاء الذين يعرضون الرمز في قناتهم الخاصة. حدّد جهة الاتصال بـواحد مما يلي:
| الحقل | الوصف |
|---|---|
contactId | المعرّف الداخلي لجهة الاتصال في Joryio |
externalId | معرّف المستخدم لديك |
email | البريد الإلكتروني لجهة الاتصال |
issueKey | مفتاح idempotency اختياري: issueKey نفسه يُعيد دائماً الرمز نفسه |
{ "externalId": "customer-123", "issueKey": "order-confirmation-9876" }
الاستجابة:
{
"issued": true,
"reused": false,
"applyUrl": "https://shop.example.com/?jry_coupon=WELCOME-7KQ4M2XH9P",
"issue": {
"id": "1f6a...", "programId": "6c0f...", "programSlug": "welcome10", "programName": "Welcome 10%",
"contactId": "66a1f2c3d4e5f6a7b8c9d0e1", "code": "WELCOME-7KQ4M2XH9P", "status": "issued", "isTest": false,
"sourceKind": "api", "campaignId": null, "canvasId": null, "nodeId": null, "channel": null,
"issuedAt": "2026-10-04T09:00:00.000Z", "expiresAt": "2026-10-18T23:59:59.000Z",
"redeemedAt": null, "redeemedOrderId": null, "redeemedRevenue": null, "failureReason": null
}
}
عند تفعيل reuseOpenCode في البرنامج، تحصل جهة الاتصال التي لا يزال لديها رمز مفتوح من البرنامج على ذلك الرمز ("reused": true). وعندما يتعذّر إصدار رمز تكون الإجابة { "issued": false, "reason": "..." } - مثل program_paused وstore_error وstore_permission_missing (لم يوافق متجر Shopify على write_discounts بعد) وstore_reconnect_required (أُضيف WooCommerce إلى موقع WordPress بعد ربطه - افصل إضافة Joryio ثم اربطها مجددًا).
الرموز المُصدَرة
GET /coupons/issues?contactId=66a1f2c3d4e5f6a7b8c9d0e1&programId=&status=&limit=50&offset=0
تُعيد { "items": [issue, ...], "total": 3 }، الأحدث أولاً. status هي pending أو issued أو failed أو redeemed أو expired أو revoked. limit حتى 200.
المتاجر المتصلة
GET /coupons/stores
[
{
"integrationId": "0b8d6f2a-5f43-4e0a-b9a3-7a8b2d1c4e55",
"platform": "shopify",
"storeName": "My Shop",
"storeUrl": "https://my-shop.myshopify.com",
"status": "connected",
"capabilities": { "lockToPerson": false, "perCodeExpiry": false, "percentCap": false, "scopes": ["all", "products", "collections"] },
"missingPermission": "write_discounts"
}
]
يحدد missingPermission ما يجب أن يمنحه التاجر قبل إمكانية إنشاء الرموز (Shopify: افتح Integrations → Shopify → Approve new permissions).
مصادر الرموز
يحدد الحقل source في البرنامج من أين تأتي رموزه. يُضبط عند الإنشاء (الافتراضي personal) ولا يمكن تغييره بعد أن يصبح في البرنامج رموز. راجع مصادر الرموز.
source | الرموز | sourceConfig |
|---|---|---|
personal | رمز واحد لكل شخص، يُنشأ عندما تحتاجه رسالة | {} |
bank | تُنشأ في المتجر مسبقاً وتوزَّع رمزاً لكل شخص. يحتاج إلى integrationId. | { "bankTargetSize": 5000 } - عدد الرموز غير المستخدمة التي تبقى جاهزة (0 = عند الطلب وقبل الحملات فقط) |
uploaded | ترفعها أنت (رفع الرموز) وتوزَّع رمزاً لكل شخص؛ لا تُنشأ أبداً في متجر. integrationId اختياري؛ lockToPerson غير مسموح. | {} |
shared | رمز واحد للجميع، يُسجَّل لكل إصدار | { "sharedCode": "SUMMER10", "createInStore": true } - يحتاج createInStore إلى integrationId؛ وfalse يسجّل فقط رمزاً موجوداً أصلاً |
وللبرامج أيضاً:
| الحقل | القيم |
|---|---|
lowStock | { "threshold": 500, "percent": null } - لـ bank وuploaded: تنبيه عندما يبقى أقل من threshold رمزاً غير مستخدم، أو أقل من percent (1-99) % من كل الرموز المُحمَّلة. null يوقف القاعدة. |
stock | للقراءة فقط. { "available": 1830, "total": 5000, "level": "ok" } لـ bank وuploaded (level: ok أو low أو empty)؛ وnull لبقية المصادر. |
في برنامج uploaded، وفي برنامج shared مع createInStore: false، تصف rules فقط القيمة التي يعرضها {{ coupons.<slug>.value }} - ولا يراها المتجر أبداً.
{
"name": "Partner coffee voucher",
"slug": "coffee",
"source": "uploaded",
"integrationId": null,
"lowStock": { "threshold": 200 },
"rules": { "discountType": "fixed_cart", "amount": 3, "currency": "EUR", "scope": { "kind": "all" }, "usageLimit": 1, "oncePerCustomer": true, "combinable": false, "expiry": { "mode": "none" } },
"fallback": { "policy": "send_without" }
}
المخزون
GET /coupons/programs/{id}/stock
{
"programId": "6c0f3c1e-8a52-4a8e-9a39-2a1d7f0b9a11",
"source": "bank",
"tracked": true,
"available": 1830,
"total": 5000,
"used": 3170,
"level": "ok",
"lowStock": { "threshold": 500, "percent": null },
"bank": { "targetSize": 5000, "filling": false, "lastRunAt": "2026-10-04T09:15:00.000Z", "lastAdded": 250, "lastError": null }
}
تكون tracked بقيمة false (والأعداد 0) في برامج personal وshared. يظهر bank في بنوك الرموز فقط؛ وتكون filling بقيمة true أثناء تشغيل الإكمال.
رفع الرموز
POST /coupons/programs/{id}/codes
POST /coupons/programs/{id}/codes?dryRun=true
يضيف رموزاً إلى برنامج uploaded. أرسل إما ملف CSV كـ multipart/form-data (الحقل file) وإما JSON { "text": "...", "fileName": "optional.csv" } مع الرموز ملصقة. كل رفع يُضاف؛ وتبقى الرموز السابقة. dryRun=true يتحقق ويعيد التقرير نفسه دون حفظ أي شيء - أي المعاينة.
{ "text": "code,expires_at,value\nCOFFEE-001,2026-12-31,Free coffee\nCOFFEE-002,,Free coffee" }
| العمود | إلزامي | الصيغة |
|---|---|---|
code | نعم | من 3 إلى 64 حرفاً: أحرف وأرقام و- _ . # / +؛ تُقارن دون تمييز حالة الأحرف |
expires_at | لا | YYYY-MM-DD (نهاية ذلك اليوم، UTC) أو تاريخ ووقت ISO؛ يجب أن يكون في المستقبل |
value | لا | حتى 100 حرف؛ يُعرض كـ {{ coupons.<slug>.value }} لهذا الرمز |
صف العناوين اختياري (من دونه تُقرأ الأعمدة بهذا الترتيب)؛ وتُكتشف الفواصل (الفاصلة، الفاصلة المنقوطة، علامة الجدولة) من السطر الأول. الحدود: 100,000 صف و10 MB لكل رفع، و2,000,000 رمز لكل برنامج.
الاستجابة (CouponUploadReport):
{
"uploadId": "9d2e4b10-1c3f-4b7a-8a55-0e6f2c9d1a77",
"dryRun": false,
"totalRows": 3,
"accepted": 2,
"rejected": 1,
"duplicates": 1,
"truncated": false,
"maxRows": 100000,
"hasHeader": true,
"preview": [{ "line": 2, "code": "COFFEE-001", "expiresAt": "2026-12-31T23:59:59.000Z", "value": "Free coffee" }],
"rejectedRows": [{ "line": 4, "code": "COFFEE-001", "reason": "duplicate_in_file", "message": "The same code appears earlier in this file." }]
}
تكون uploadId بقيمة null في التشغيل التجريبي (dry run). يحوي preview أول 10 صفوف مقبولة، وrejectedRows أول 500 صف مرفوض. تكون truncated بقيمة true عندما يحوي الملف صفوفاً أكثر من maxRows (فلا يُقرأ الباقي).
reason | المعنى |
|---|---|
missing_code | لا رمز في الصف |
invalid_code | ليس من 3 إلى 64 من الأحرف والأرقام و- _ . # / + |
invalid_expiry | تاريخ الانتهاء ليس تاريخاً |
expired | تاريخ الانتهاء قد مضى |
value_too_long | القيمة أطول من 100 حرف |
duplicate_in_file | الرمز نفسه ظهر سابقاً في الملف |
already_in_program | الرمز موجود أصلاً في البرنامج |
already_exists | الرمز في برنامج آخر في مساحة العمل |
over_limit | البرنامج ممتلئ |
البرنامج ذو المصدر المختلف يجيب بـ 400.
عمليات الرفع السابقة
GET /coupons/programs/{id}/uploads
آخر 100 عملية رفع، من الأحدث: { "id", "kind", "fileName", "totalRows", "accepted", "rejected", "duplicates", "rejectedRows", "createdBy", "createdAt" }.
توليد رموز البنك
POST /coupons/programs/{id}/bank/generate
{ "count": 5000 }
ينشئ count (من 1 إلى 100,000) رمزاً جديداً في المتجر لبرنامج bank. يجري العمل في الخلفية: الإجابة فوراً 202 Accepted مع { "started": true, "requested": 5000 }؛ تابعه عبر المخزون (bank.filling، bank.lastAdded، bank.lastError). الطلب الثاني أثناء تشغيل إكمال يجيب بـ 400. يضيف Shopify الرموز إلى خصم البرنامج، وينشئ WooCommerce قسيمة لكل رمز، ويولّدها Magento ضمن قاعدة سعر السلة الخاصة بالبرنامج وفق صيغة رمزه، وينشئ PrestaShop قاعدة سلة (cart rule) لكل رمز.
قبل إرسال حملة، يُكمَل كل بنك يستخدمه محتواها تلقائياً ليغطي الجمهور. تحظر مراجعة الإطلاق (Launch Review) الحملة التي يكون جمهورها أكبر من رموز بنك أو قائمة (coupon_stock_short) وتحذّر عندما يترك الإرسال رموزاً أقل من حد تنبيه انخفاض المخزون (coupon_stock_low).
الأخطاء
| الحالة | متى |
|---|---|
400 | التحقق - مثل lockToPerson في برنامج Shopify (يحدد Shopify من يستخدم الخصم على مستوى الخصم لا الرمز)، أو slug مستخدم أو ليس [a-z0-9_]، أو shared_code دون sharedCode، أو تاريخ انتهاء في الماضي |
403 | صلاحية ناقصة - بما فيها settings:write عند تغيير برنامج فعّال |
404 | البرنامج ليس في مساحة العمل هذه |
409 | حذف برنامج فعّال |