المطورون/التكامل
المنتجات والطلبات
يشرح هذا الدليل عملية الشراء كاملة: تتحقق من رصيدك، وتختار المنتج، وتعرف سعره، ثم تطلبه وتستلم الأكواد.
ذات صلة: المصادقة · القواعد العامة · Webhook
#الحساب والرصيد
#الحساب
يعرض GET/api/v1/account بيانات شركتك، ويبيّن ما إذا كان الوصول إلى API مفعّلًا.
{
"merchant_id": "M00000001",
"name": "Acme Shop",
"legal_name": "Acme Shop Ltd",
"tier": "standard",
"billing_email": "[email protected]",
"status": "active",
"kyb_status": "approved",
"api_access_enabled": true,
"default_currency": "USD"
}default_currencyهي عملة محفظتك، وكل الأسعار التي تدفعها بهذه العملة.- تتحول قيمة
api_access_enabledإلىtrueبعد موافقة CardV على شركتك.
#الرصيد
يعرض GET/api/v1/balance المبلغ المتاح لك للشراء.
{
"currency": "USD",
"balance": "1520.4000",
"reserved_amount": "0.0000",
"available_balance": "1520.4000",
"low_balance_threshold": "200.0000",
"low_balance_notified_at": null,
"is_active": true
}available_balanceهو ما يمكنك إنفاقه الآن، ويساويbalanceناقصreserved_amount.- يُرفض أي طلب تتجاوز قيمته
available_balance، ولا يُخصم أي مبلغ. low_balance_thresholdهو الحد الذي يُرسل عنده بريد تنبيه انخفاض الرصيد، وتضبطه من البوابة.- لشحن المحفظة، استخدم البوابة.
#المنتجات (SKU)
الـ SKU منتج واحد يمكنك شراؤه، مثل "Steam Wallet 10 USD".
ورقمه يشبه S000456، وتستخدم هذا الرقم لمعرفة السعر ولإرسال الطلب.
يعرض GET/api/v1/skus قائمة المنتجات المتاحة لك. مثال:
GET /api/v1/skus?search=steam®ion=US&limit=50&offset=0عوامل التصفية (كلها اختيارية):
| عامل التصفية | مثال | ما يطابقه |
|---|---|---|
search | steam | رقم المنتج أو اسمه أو علامته التجارية |
brand | Steam | اسم العلامة التجارية (لا فرق بين الأحرف الكبيرة والصغيرة) |
region | US | رمز الدولة أو اسمها |
vertical | gift_card | فئة المنتج |
product_type | pin_code | طريقة التسليم |
الصفحات: أرسل limit (الافتراضي 100، والحد الأقصى 500) وoffset.
كرّر الطلب مع زيادة offset حتى تصل قيمته إلى count.
{
"count": 7,
"limit": 1,
"results": [
{
"sku_id": "S000456",
"product_id": "P000123",
"name": "Steam Wallet 10 USD",
"product_name": "Steam Wallet US",
"brand": "Steam",
"region": "US",
"vertical": "gift_card",
"product_type": "pin_code",
"denomination_type": "fixed",
"denomination_value": "10.0000",
"face_currency": "USD",
"merchant_price": "9.2500",
"settlement_currency": "USD",
"availability": "available",
"min_quantity": 1,
"max_quantity": 100,
"required_input_schema": [],
"...": "more fields"
}
],
"filter_options": {"brands": [], "regions": [], "verticals": []}
}يُرجع GET/api/v1/skus/{sku_id} منتجًا واحدًا بالحقول نفسها.
أهم الحقول:
| الحقل | المعنى |
|---|---|
sku_id | الرقم الذي تستخدمه لمعرفة السعر وللطلب. |
merchant_price | سعرك للوحدة الواحدة، بعملة settlement_currency. |
availability | available أو unavailable. لا تطلب إلا المنتجات available. |
denomination_type | fixed أو range. راجع القيمة الثابتة والقيمة المرنة. |
face_currency | العملة المطبوعة على البطاقة، وقد تختلف عن عملة محفظتك. |
min_quantity، max_quantity | عدد الوحدات المسموح به في بند الطلب الواحد. |
product_type | pin_code (تستلم كودًا) أو direct_charge (نشحن حساب العميل مباشرة). |
required_input_schema | البيانات التي يجب إرسالها في الشحن المباشر. |
brand_logo_url، image_url | صور تستضيفها CardV، أو "". |
description، redemption_instructions، terms | نصوص يمكنك عرضها على عملائك. |
نصائح:
- لا تظهر لك إلا المنتجات النشطة والمتاحة لحسابك. أما غيرها فيُرجع 404.
- حدّث قائمة المنتجات كل 5 إلى 15 دقيقة، واطلب عرض السعر دائمًا قبل الطلب مباشرة.
- يعرض
filter_optionsالعلامات التجارية والمناطق وفئات المنتجات التي يمكنك التصفية بها.
#القيمة الثابتة والقيمة المرنة
لمعظم المنتجات قيمة اسمية ثابتة، مثل 10 USD. ولبعضها قيمة مرنة ضمن نطاق: يختار عميلك المبلغ، مثلًا من 5 إلى 500 USD.
| النوع | عند طلب عرض السعر | عند إرسال الطلب |
|---|---|---|
fixed | أرسل quantity | لا ترسل amount |
range | أرسل quantity وamount | أرسل amount |
في المنتج ذي القيمة المرنة، يجب أن يقع amount بين min_face_value وmax_face_value،
ويكون بعملة face_currency.
#الشحن المباشر
بعض المنتجات تشحن حساب عميلك مباشرة، مثل حساب في لعبة. وفي هذه الحالة تحتاج CardV إلى بيانات الحساب، ويذكرها المنتج هكذا:
"required_input_schema": [
{"key": "player_id", "label": "Player ID", "required": true},
{"key": "server", "label": "Server", "required": false}
]أرسل القيم في الحقل inputs داخل بند الطلب، مستخدمًا كل key:
"inputs": {"player_id": "123456789", "server": "EU"}- كل حقل إلزامي ما لم يُذكر فيه
"required": false. - إذا نقصت قيمة إلزامية، يُرفض الطلب بخطأ في
items. - هذه القيم بيانات شخصية لعميلك، فاحمِها (راجع الأمان).
#عرض السعر
يخبرك عرض السعر بالسعر الحالي لكمية معيّنة.
GET /api/v1/skus/S000456/quote?quantity=2
GET /api/v1/skus/S000789/quote?quantity=1&amount=25.00{
"sku_id": "S000456",
"settlement_currency": "USD",
"merchant_price": "9.2500",
"quantity": 2,
"total_price": "18.5000",
"min_quantity": 1,
"max_quantity": 100,
"availability": "available"
}- عرض السعر لا يثبّت السعر، فالأسعار قد تتغيّر في أي وقت.
- لتحمي نفسك، أرسل قيمة
merchant_priceفي الحقلexpected_unit_priceعند الطلب. فإذا تغيّر السعر، ترفض CardV الطلب ولا تخصم شيئًا. - إذا كانت الكمية أو المبلغ خارج النطاق، تحصل على HTTP 400 مع خطأ في
quantityأوamount.
#إرسال طلب
يشتري POST/api/v1/orders منتجًا أو أكثر، ويدفع قيمته من محفظتك.
ويجب توقيع هذا الاستدعاء.
{
"external_order_id": "SHOP-20260929-10001",
"items": [
{"sku_id": "S000456", "quantity": 2, "expected_unit_price": "9.2500"},
{"sku_id": "S000789", "amount": "25.00", "expected_unit_price": "23.7500"},
{
"sku_id": "S000900",
"expected_unit_price": "4.9000",
"inputs": {"player_id": "123456789"}
}
]
}| الحقل | إلزامي؟ | المعنى |
|---|---|---|
external_order_id | نعم | رقم طلبك، من 1 إلى 120 حرفًا، ولا يتكرر. |
items | نعم | بند واحد أو أكثر. |
items[].sku_id | نعم | المنتج الذي تشتريه. |
items[].quantity | لا | عدد الوحدات. الافتراضي 1. |
items[].amount | للمنتجات ذات القيمة المرنة | القيمة الاسمية التي تشتريها. |
items[].expected_unit_price | موصى به | قيمة merchant_price من عرض السعر. أرسله دائمًا. |
items[].inputs | للشحن المباشر | بيانات الحساب في حالة الشحن المباشر. |
عندما تقبل CardV الطلب، تخصم المبلغ الإجمالي كاملًا من محفظتك فورًا. ثم يبدأ التسليم في الخلفية.
يكون الرد HTTP 201:
{
"idempotent_replay": false,
"order": {
"order_id": "O-00001234",
"external_order_id": "SHOP-20260929-10001",
"status": "accepted",
"total_amount": "46.2000",
"...": "more fields"
}
}احفظ order.order_id. هذا الرد لا يحتوي على الأكواد أبدًا، بل تقرؤها لاحقًا (قراءة الطلب).
#الطلبات المرفوضة
الطلب المرفوض يُرجع HTTP 400 ولا يُخصم أي مبلغ. ومفتاح الخطأ يوضح السبب:
| المفتاح | السبب | ما الذي تفعله |
|---|---|---|
items | تغيّر السعر، أو المنتج غير متاح، أو المبلغ غير صحيح، أو نقصت بيانات | اطلب عرض سعر جديدًا، وأصلح المشكلة، ثم أعد الإرسال |
balance | رصيد محفظتك لا يكفي | اشحن محفظتك من البوابة |
risk | تجاوزت الحد الأقصى للطلب الواحد أو الحد اليومي | تواصل مع CardV |
external_order_id | رقم طلبك مستخدم من قبل لطلب مختلف | راجع إعادة المحاولة بأمان |
مثال على تغيّر السعر:
{
"items": "SKU S000456 price changed from 9.2500 to 9.4100 USD; refresh quote and confirm again."
}تحدد فئة حسابك الحد الأقصى للطلب الواحد، وللإنفاق اليومي، ولعدد الطلبات اليومية. تُصفَّر الحدود اليومية عند 00:00 UTC. اسأل CardV عن حدودك.
#إعادة المحاولة بأمان
رقم طلبك (external_order_id) يحميك من الشراء مرتين.
إذا أرسلت الطلب نفسه مرة أخرى برقم الطلب نفسه، لا تخصم CardV المبلغ مرة ثانية،
بل تُرجع الطلب الموجود لديها.
| ما ترسله | ما تحصل عليه |
|---|---|
| رقم طلب جديد | HTTP 201. طلب جديد، ويُخصم المبلغ من محفظتك. |
| رقم الطلب نفسه والطلب نفسه | HTTP 200 و"idempotent_replay": true. الطلب الموجود، دون أي خصم. |
| رقم الطلب نفسه لكن بطلب مختلف | HTTP 400 على external_order_id. لا يحدث شيء. |
"الطلب نفسه" يعني البنود نفسها، بالترتيب نفسه، وبالمنتج والكمية والمبلغ والبيانات نفسها.
وإذا أرسلت expected_unit_price، فيجب أن يطابق سعر الطلب الأول.
تتعرّف CardV على الطلب المكرر قبل التحقق من الرصيد والسعر. لذلك تُرجع الطلب الأول دائمًا، حتى لو تغيّر السعر بعده.
#خطوات إعادة المحاولة
إذا لم تحصل على رد واضح، فأرسل الطلب نفسه مرة أخرى ببساطة.
POST /orders برقم الطلب R
├─ 201 أو 200 → احفظ order_id. انتهى.
├─ 400 items / balance / risk → لم يُنشأ أي طلب.
│ أصلح السبب وأعد الإرسال. يمكنك استخدام R نفسه.
├─ 400 external_order_id → الرقم R يخص طلبًا آخر. توقّف وتحقّق.
├─ 403 خطأ في التوقيع → وقّع من جديد وأرسل المحتوى نفسه.
├─ 429 → انتظر مدة Retry-After، ثم وقّع من جديد وأرسل المحتوى نفسه.
└─ انتهاء المهلة أو 5xx أو انقطاع الاتصال
→ أرسل المحتوى نفسه مرة أخرى بالرقم R نفسه.
ستحصل على 201 (المحاولة الأولى لم تصل) أو 200 (وصلت).القواعد:
- لا تنشئ رقم طلب جديدًا لمجرد أن الرد لم يصلك. فإذا كان الطلب الأول قد وصل، سيؤدي الرقم الجديد إلى شراء كل شيء مرتين.
- كل إعادة إرسال تحتاج إلى طابع زمني وقيمة nonce وتوقيع جديدة، مع بقاء المحتوى كما هو.
#قراءة الطلب
يُرجع GET/api/v1/orders/{order_id} الطلب وحالته وأكواده.
{
"order_id": "O-00001234",
"external_order_id": "SHOP-20260929-10001",
"status": "succeeded",
"currency": "USD",
"total_amount": "18.5000",
"created_at": "2026-09-29T08:15:30.123456Z",
"updated_at": "2026-09-29T08:15:41.004211Z",
"items": [
{
"sku_id": "S000456",
"product_name": "Steam Wallet US",
"quantity": 2,
"unit_price": "9.2500",
"total_price": "18.5000",
"delivery_count": 2,
"deliveries": [{"...": "see Codes below"}]
}
],
"...": "more fields"
}total_amountهو المبلغ الذي خُصم منك عند قبول الطلب.items[].unit_priceهو السعر المثبّت لهذا الطلب.- يحتوي
items[].deliveriesعلى الأكواد كاملة، فتعامل مع الرد كمعلومة سرية. invoice_urlوdelivery_file_urlمساران للفاتورة ولملف الأكواد بصيغة CSV. وكلاهما خاص بالبوابة، ومع مفتاح API يُرجعان HTTP 403.- يُرجع الرد أيضًا
id(رقم قديم، لا تستخدمه)، وevents(سجل للعرض فقط)، ومدى تقدّم التسليم في كل بند. يمكنك تجاهل هذه الحقول. - رقم طلب غير معروف يُرجع HTTP 404.
#حالة الطلب والأكواد
#الحالة
accepted ──► processing ──► succeeded
│
├──► partially_succeeded (سُلّمت بعض البنود ولم يُسلَّم بعضها)
│
└──► failed ──► refunded (أُعيد المبلغ إلى محفظتك)| الحالة | هل اكتمل؟ | ما الذي تفعله |
|---|---|---|
accepted | لا | انتظر. خُصم المبلغ من المحفظة، ولم يبدأ التسليم بعد. |
processing | لا | انتظر. لا ترسل الطلب مرة أخرى. |
succeeded | نعم | اقرأ الأكواد وسلّمها لعميلك. |
partially_succeeded | نعم | سلّم ما وصل. يُسترد الباقي لاحقًا. |
failed | ليس بعد | انتظر refunded. الفشل لا يعني أن المبلغ أُعيد بعد. |
refunded | نعم | عاد المبلغ إلى محفظتك. |
إذا لم تستخدم Webhook، فاستعلم عن الطلب بهذا الترتيب: بعد 5 ثوانٍ، ثم 10 ثوانٍ، ثم 30، ثم 60، ثم كل 5 دقائق. والتزم بحد الطلبات. تكتمل معظم الطلبات خلال ثوانٍ، لكن بعضها يحتاج إلى مراجعة يدوية وقد يستغرق ساعات.
#الأكواد
كل وحدة مُسلَّمة هي عنصر واحد داخل items[].deliveries:
{
"status": "stored",
"delivery_type": "card_pin",
"display_fields": [
{"key": "card_number", "label": "Card number", "value": "X1234", "kind": "secret"},
{"key": "pin_code", "label": "PIN", "value": "9876", "kind": "secret"}
],
"redeem_url": "",
"expiry_date": "2027-09-29",
"instructions": "Redeem at ...",
"is_masked": false
}- اعرض على عميلك محتوى
display_fields: لكل عنصر فيهlabelوvalue. واعرض كذلكredeem_urlوexpiry_dateوinstructionsإذا لم تكن فارغة. - قيمة
kindهيsecretللأكواد وأرقام PIN، وreferenceلبيانات مثل الأرقام التسلسلية. - يوضح
delivery_typeنوع ما استلمته:codeأوcard_pinأوlinkأوcode_linkأوqr. وقد تظهر أنواع جديدة، لذا اعتمد دائمًا علىdisplay_fieldsفي العرض. - في التسليم من نوع
link، يكونredeem_urlنفسه هو الكود، فاحفظه سرًا. - لا تسلّم عميلك أبدًا وحدة حالتها
voided. - منتجات الشحن المباشر لا تُسلَّم فيها أكواد عادةً، والحالة
succeededتعني أن الحساب شُحن. - تظهر بعض القيم، مثل
card_numberوpin_code، في حقول منفصلة أيضًا، وقد تكون فارغة. - رسائل Webhook لا تحتوي على الأكواد أبدًا. اقرأ الطلب بعد وصول الرسالة.
هل لديك سؤال حول التكامل؟ راسلنا على [email protected] مع ذكر Merchant ID ومعرّف الطلب أو الاستدعاء.