تخطّ إلى المحتوى الرئيسي

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 الرموز وتتتبعها دون متجر
statusdraft، active، paused، archived - البرنامج active وحده يُصدر رموزاً
rules.discountTypepercent، 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.minimumQuantityShopify فقط
rules.excludeSaleItemsWooCommerce فقط
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.charsetunambiguous (أحرف وأرقام بلا أحرف متشابهة، الافتراضي)، letters، digits؛ length 6-20
fallback.policyskip (الافتراضي)، shared_code (يحتاج sharedCode)، send_without
lockToPersonWooCommerce وMagento وPrestaShop (حساب العميل الخاص بالشخص)
livetrue بمجرد أن يُصدر البرنامج رمزاً حقيقياً (ليس رمز اختبار)
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حذف برنامج فعّال