المطورون/التكامل

المنتجات والطلبات

يشرح هذا الدليل عملية الشراء كاملة: تتحقق من رصيدك، وتختار المنتج، وتعرف سعره، ثم تطلبه وتستلم الأكواد.

ذات صلة: المصادقة · القواعد العامة · Webhook

#الحساب والرصيد

#الحساب

يعرض GET/api/v1/account بيانات شركتك، ويبيّن ما إذا كان الوصول إلى API مفعّلًا.

JSON
{
  "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 المبلغ المتاح لك للشراء.

JSON
{
  "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 قائمة المنتجات المتاحة لك. مثال:

HTTP
GET /api/v1/skus?search=steam&region=US&limit=50&offset=0

عوامل التصفية (كلها اختيارية):

عامل التصفيةمثالما يطابقه
searchsteamرقم المنتج أو اسمه أو علامته التجارية
brandSteamاسم العلامة التجارية (لا فرق بين الأحرف الكبيرة والصغيرة)
regionUSرمز الدولة أو اسمها
verticalgift_cardفئة المنتج
product_typepin_codeطريقة التسليم

الصفحات: أرسل limit (الافتراضي 100، والحد الأقصى 500) وoffset. كرّر الطلب مع زيادة offset حتى تصل قيمته إلى count.

JSON
{
  "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.
availabilityavailable أو unavailable. لا تطلب إلا المنتجات available.
denomination_typefixed أو range. راجع القيمة الثابتة والقيمة المرنة.
face_currencyالعملة المطبوعة على البطاقة، وقد تختلف عن عملة محفظتك.
min_quantity، max_quantityعدد الوحدات المسموح به في بند الطلب الواحد.
product_typepin_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 إلى بيانات الحساب، ويذكرها المنتج هكذا:

JSON
"required_input_schema": [
  {"key": "player_id", "label": "Player ID", "required": true},
  {"key": "server", "label": "Server", "required": false}
]

أرسل القيم في الحقل inputs داخل بند الطلب، مستخدمًا كل key:

JSON
"inputs": {"player_id": "123456789", "server": "EU"}
  • كل حقل إلزامي ما لم يُذكر فيه "required": false.
  • إذا نقصت قيمة إلزامية، يُرفض الطلب بخطأ في items.
  • هذه القيم بيانات شخصية لعميلك، فاحمِها (راجع الأمان).

#عرض السعر

يخبرك عرض السعر بالسعر الحالي لكمية معيّنة.

HTTP
GET /api/v1/skus/S000456/quote?quantity=2
GET /api/v1/skus/S000789/quote?quantity=1&amount=25.00
JSON
{
  "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 منتجًا أو أكثر، ويدفع قيمته من محفظتك. ويجب توقيع هذا الاستدعاء.

JSON
{
  "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:

JSON
{
  "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رقم طلبك مستخدم من قبل لطلب مختلفراجع إعادة المحاولة بأمان

مثال على تغيّر السعر:

JSON
{
  "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 على الطلب المكرر قبل التحقق من الرصيد والسعر. لذلك تُرجع الطلب الأول دائمًا، حتى لو تغيّر السعر بعده.

#خطوات إعادة المحاولة

إذا لم تحصل على رد واضح، فأرسل الطلب نفسه مرة أخرى ببساطة.

Text
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} الطلب وحالته وأكواده.

JSON
{
  "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.

#حالة الطلب والأكواد

#الحالة

Text
accepted ──► processing ──► succeeded
                  │
                  ├──► partially_succeeded   (سُلّمت بعض البنود ولم يُسلَّم بعضها)
                  │
                  └──► failed ──► refunded   (أُعيد المبلغ إلى محفظتك)
الحالةهل اكتمل؟ما الذي تفعله
acceptedلاانتظر. خُصم المبلغ من المحفظة، ولم يبدأ التسليم بعد.
processingلاانتظر. لا ترسل الطلب مرة أخرى.
succeededنعماقرأ الأكواد وسلّمها لعميلك.
partially_succeededنعمسلّم ما وصل. يُسترد الباقي لاحقًا.
failedليس بعدانتظر refunded. الفشل لا يعني أن المبلغ أُعيد بعد.
refundedنعمعاد المبلغ إلى محفظتك.

إذا لم تستخدم Webhook، فاستعلم عن الطلب بهذا الترتيب: بعد 5 ثوانٍ، ثم 10 ثوانٍ، ثم 30، ثم 60، ثم كل 5 دقائق. والتزم بحد الطلبات. تكتمل معظم الطلبات خلال ثوانٍ، لكن بعضها يحتاج إلى مراجعة يدوية وقد يستغرق ساعات.

#الأكواد

كل وحدة مُسلَّمة هي عنصر واحد داخل items[].deliveries:

JSON
{
  "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 ومعرّف الطلب أو الاستدعاء.