الواجهة البرمجية

واجهة واحدة تشتري بها الأكواد من متجرك مباشرة. الطلب يحمل رمز الفئة والكمية فقط — السعر يُحسب على خادمنا من سعر حسابك المتّفق عليه، ولا يُقرأ من الطلب مهما أُرسل فيه.

عنوان الخدمة

https://mena-pin.com/v1

البداية في ثلاث خطوات

  1. 01

    أنشئ حساباً واحصل على مفتاح

    من لوحة التحكم ← الواجهة البرمجية. يُعرض المفتاح مرة واحدة فقط عند إصداره، فاحفظه في مكان آمن.

  2. 02

    اقرأ الكتالوج

    استعرض الفئات المعروضة وسعرك أنت لكل فئة، ثم خزّن رموز الفئات (SKU) التي تبيعها.

  3. 03

    أنشئ طلباً

    أرسل رمز الفئة والكمية مع ترويسة منع التكرار. يُخصم المبلغ ويرجع الطلب قيد التنفيذ حتى يصدر الكود.

أول طلب لك

curl https://mena-pin.com/v1/balance \
  -H "Authorization: Bearer mp_live_YOUR_KEY"

المصادقة

كل طلب يحمل مفتاحك في ترويسة Authorization. المفاتيح تبدأ بـ mp_live_، وكلّها تخصم من رصيدك الحقيقي وتسلّم أكواداً حقيقية — لا توجد بيئة اختبار منفصلة.

الترويسة

Authorization: Bearer mp_live_YOUR_KEY

نحن نحتفظ ببصمة المفتاح فقط، لا بالمفتاح نفسه — ولهذا لا يمكن استعادته بعد إصداره. إن تسرّب مفتاحك، أصدر مفتاحاً جديداً من اللوحة: يتوقّف السابق فوراً.

منع تكرار الشراء

الشبكة تنقطع، والعميل يضغط مرتين، والوظائف تُعاد. أرسل مع كل طلب إنشاء ترويسة Idempotency-Key بقيمة فريدة من عندك. إعادة الإرسال بالمفتاح نفسه تُعيد لك الطلب الأول ذاته — لا طلباً ثانياً، ولا خصماً ثانياً، ولا كوداً ثانياً.

نفس المفتاح مرتين = طلب واحد

Idempotency-Key: order-invoice-84213

كيف تُمثَّل المبالغ

كل مبلغ يعود ككائن يحمل ثلاثة حقول. استخدم minor_units في حساباتك — وهو عدد صحيح بأصغر وحدة للعملة — واعرض amount للمستخدم. لا تُجرِ حسابات على القيمة العشرية.

{
  "amount": "0.83",
  "minor_units": 83,
  "currency": "USD"
}

حدود الاستخدام

السقف محسوب لكل مفتاح على حدة، وتُعيده الترويستان X-RateLimit-Limit و X-RateLimit-Remaining مع كل رد. تجاوز السقف يعيد 429.

السقف الافتراضي 60 طلباً في الدقيقة لكل مفتاح. إن اتُّفق على سقف أعلى لحسابك فهو المطبَّق، ويظهر في X-RateLimit-Limit مع أول رد.

نقاط النهاية

GET /v1/catalog

الفئات المتاحة، بسعرك أنت، مقسّمة على صفحات، ويمكن ضبط per_page حتى 100.

الطلب

curl "https://mena-pin.com/v1/catalog?per_page=25" \
  -H "Authorization: Bearer mp_live_YOUR_KEY"

الرد

{
  "data": [
    {
      "slug": "pubg-mobile",
      "name": "ببجي موبايل",
      "category": "games",
      "region": "global",
      "packages": [
        {
          "sku": "PUBG_MOBILE_60_UC",
          "name": "60 UC",
          "pricing": "per_item",
          "pricing_unit": 1,
          "price":      { "amount": "0.83", "minor_units": 83, "currency": "USD" },
          "list_price": { "amount": "0.89", "minor_units": 89, "currency": "USD" }
        }
      ]
    }
  ],
  "meta": { "current_page": 1, "per_page": 25, "total": 12 }
}

price هو ما ستدفعه أنت، وlist_price سعر القائمة قبل خصم حسابك. الفارق بينهما هو ربحك قبل أن تبيع.

GET /v1/balance

رصيد محفظتك الحالي — وهو ما تُخصم منه طلباتك.

الرد

{
  "data": {
    "balance": { "amount": "500.00", "minor_units": 50000, "currency": "USD" },
    "currency": "USD"
  }
}

فئتان مختلفتان: per_item و per_unit.

الفئة العادية تُباع بالحبّة: price هو ثمن واحدة، وquantity عددها.

أمّا فئة per_unit فتُباع بالعدّاد — عملات، دقائق، رصيد — والوحدة الواحدة قد تساوي أجزاءً من السنت، أي أقلّ من أصغر مبلغ يمكن التعبير عنه. لذلك يُذكر price لكل pricing_unit وحدة، بينما quantity في الطلب يعني عدد الوحدات لا عدد الكتل.

ولحساب ثمن أي كمية استخدم price_per_unit — وهو السعر الدقيق للوحدة كنصّ عشري. والكمية مقيّدة بـ min_quantity وmax_quantity وquantity_step؛ وما خرج عنها يُرفض بـ 422 invalid_quantity دون إنشاء طلب.

ولا يحمل الرد عدد المتوفّر: الفئة المعروضة هنا قابلة للطلب، وغير القابلة للطلب لا تُعرض أصلاً — فلا رفّ ليُعدّ ولا ليَفرغ.

POST /v1/orders

ينشئ طلباً. الجسم يحمل رمز الفئة والكمية، وplayer_id لمنتجات الشحن المباشر.

الطلب

curl -X POST https://mena-pin.com/v1/orders \
  -H "Authorization: Bearer mp_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-invoice-84213" \
  -d '{"sku":"PUBG_MOBILE_60_UC","quantity":1,"player_id":"5512345"}'

الرد · 201

{
  "data": {
    "number": "01kzhxexgmfqka0q1zmtr0f292",
    "status": "pending",
    "quantity": 1,
    "pricing": "per_item",
    "pricing_unit": 1,
    "unit_price": { "amount": "0.83", "minor_units": 83, "currency": "USD" },
    "total":      { "amount": "0.83", "minor_units": 83, "currency": "USD" },
    "discount_basis_points": 650,
    "package": {
      "sku": "PUBG_MOBILE_60_UC",
      "name": "60 UC",
      "product": { "slug": "pubg-mobile", "name": "ببجي موبايل" }
    },
    "placed_by": { "name": "سامر", "email": "samer@example.com" },
    "delivered_at": null,
    "created_at":   "2026-08-09T00:06:16+00:00",
    "codes": []
  }
}

الطلب يرجع pending دائماً.

يُقبل الطلب قبل أن يُنفَّذ، فلا شيء يُسلَّم داخل الرد نفسه ولا يبقى اتصالك معلّقاً: يُخصم المبلغ ويرجع الطلب pending بقائمة codes فارغة. والتنفيذ آلي ويستغرق ثوانٍ: يصلك order.delivered على الويبهوك عند اكتماله — واقرأ الكود من GET /v1/orders/{number} — أو order.refunded إن تعذّر تنفيذه ورُدّ المبلغ كاملاً. لا يُخصم منك مقابل طلب لم يُسلَّم.

الكمية لهذه الفئات تعني عدد الوحدات (مثلاً عدد العملات)، ولها حدّ أدنى وأعلى وخطوة. كمية خارج الحدود تُرفض بـ 422 invalid_quantity ولا يُنشأ طلب.

وplayer_id مطلوب لفئات الشحن المباشر — وهي التي تُضاف إلى حساب داخل اللعبة أو الخدمة لا التي تُسلَّم كوداً. الطلب بدونه يُرفض بـ 422 player_id_required قبل أي خصم، ولا يُنشأ طلب. وراجع القيمة قبل إرسالها: ما وصل إلى حساب لا يُسحب منه، ولا يُغني عنه استرداد.

وإن بقي الطلب pending أطول من المعتاد.

فهو قيد المراجعة، ولا يُقرَّر عنه شيء بالتخمين: لا يُسلَّم كود، ولا يُردّ مبلغ، ولا يُعاد تنفيذ الطلب. تصلك نتيجته على الويبهوك حين تُحسم — order.delivered أو order.refunded — وتراها على GET /v1/orders/{number} في أي وقت. ولن تُحاسَب على الطلب مرتين ولن يصدر له كودان مهما طال انتظاره.

فلا تُعِد إرسال الطلب لتستعجله. إعادة الإرسال بمفتاح Idempotency-Key نفسه تعيد إليك الطلب الأول كما هو، وإرساله بمفتاح جديد ينشئ طلباً ثانياً ويخصم ثمنه.

لا تُرسل سعراً.

أي حقل سعر في الجسم يُتجاهل تماماً. السعر يُحسب على الخادم من سعر حسابك لحظة الطلب، فلا يمكن لعميلك — ولا لك بالخطأ — تنفيذ طلب بسعر غير سعرك.

GET /v1/orders/{number}

طلب واحد بأكواده. الطلبات التي لا تخصّ حسابك تعيد 404، لا 403 — لا نؤكّد وجود أرقام طلبات غيرك.

الطلب

curl https://mena-pin.com/v1/orders/01kzhxexgmfqka0q1zmtr0f292 \
  -H "Authorization: Bearer mp_live_YOUR_KEY"
GET /v1/orders

طلباتك من الأحدث إلى الأقدم، مقسّمة على صفحات. هذه القائمة لا تحمل الأكواد إطلاقاً — اقرأ الطلب المفرد للحصول عليها.

الطلب

curl "https://mena-pin.com/v1/orders?per_page=50" \
  -H "Authorization: Bearer mp_live_YOUR_KEY"

الاسترداد

الاسترداد آلي بالكامل، ولا واجهة له ولا طلب يُقدَّم من أجله. الطلب الذي يتعذّر تنفيذه يُردّ مبلغه كاملاً إلى محفظتك من تلقاء النظام، وتصير حالته refunded، ويصلك حدث order.refunded على الويبهوك إن كنت مشتركاً فيه، وتظهر الحركة في كشف محفظتك قيداً واحداً بتاريخه ورقم الطلب. فلا يبقى مخصوماً منك مقابل طلب لم يُسلَّم، ولا يُطلب منك أن تطالب به.

ولا يوجد استرداد بطلب منك.

ما سُلِّم لا يُسترجع: الكود الذي صدر يبقى صالحاً للاستعمال أياً كانت حالة الطلب عندنا، والشحن الذي وصل إلى حساب لا يمكن سحبه منه. ولذلك ليس في الواجهة البرمجية ولا في لوحة التحكم زرّ ولا مسار لطلب استرداد، والحالة الوحيدة التي يكون فيها ردّ المبلغ صحيحاً بلا لبس — أن يتعثّر التنفيذ — هي الحالة التي يردّ فيها النظام المبلغ وحده.

والطلب الذي لم تُحسم نتيجته بعد لا يُردّ ولا يُعاد تنفيذه، على ما سبق في POST /v1/orders أعلاه: تُحسم نتيجته بجواب، ثم يصلك order.delivered أو order.refunded. ولن تُحاسَب على الطلب مرتين في أي حال.

إشعارات الأحداث

بدل أن تسأل الواجهة البرمجية باستمرار عن جديد، نُرسل نحن طلب POST موقّعاً إلى عنوان على خادمك عند وقوع حدث يخصّك. تُدار العناوين من لوحة التحكم، ولكل عنوان مفتاح توقيع خاص به.

الحدث متى يُرسل
order.delivered يُرسل عند تسليم الأكواد بنجاح. لا يحتوي على الأكواد نفسها.
order.refunded يُرسل عند استرداد قيمة طلب إلى محفظتك.
wallet.credited يُرسل عند إيداع مبلغ في محفظتك بعد اعتماد طلب الشحن.

شكل الحمولة

id ثابت عبر كل محاولات إرسال الحدث نفسه — خزّنه عندك وتجاهل أي حدث سبق أن عالجته. data يحمل الطلب بالشكل نفسه الذي تعيده الواجهة البرمجية.

جسم الطلب الوارد إليك

{
  "id": "01K2H8QF3M4N5P6Q7R8S9T0V1W",
  "event": "order.delivered",
  "created_at": "2026-08-10T09:14:22+00:00",
  "data": {
    "number": "01K2H8Q...",
    "status": "delivered",
    "quantity": 1,
    "total": { "amount": "9.00", "minor_units": 900, "currency": "USD" },
    "package": { "sku": "PUBG-660", "name": "660 UC" },
    "delivered_at": "2026-08-10T09:14:22+00:00"
  }
}

الأكواد لا تُرسل في الإشعار إطلاقاً. الإشعار يذهب إلى عنوان تختاره أنت ويمرّ بما بين يديك من وسائط وسجلات. الكود هو البضاعة نفسها، فيُقرأ من GET /v1/orders/{number} عبر اتصال موثَّق بمفتاحك.

التحقّق من التوقيع

كل طلب يحمل ترويسة X-MenaPin-Signature بالشكل t=<unix>,v1=<hex>. القيمة الموقَّعة هي "{t}.{raw_body}" بخوارزمية HMAC-SHA256 ومفتاح نقطتك. تحقّق من الجسم الخام قبل أي تحليل أو إعادة ترتيب، وارفض طابعاً زمنياً أقدم من 300 ثانية.

البادئة القديمة ما زالت تُرسل. حمل المنتج لفترة اسماً آخر، وكانت الحقول الأربعة تصل خلالها بالبادئة X-Doabletech-. تُرسل البادئتان معاً بالقيم نفسها تماماً — لم يتغيّر شيء على السلك — فما كتبته في أيّ من الوقتين يعمل كما هو. البادئة X-MenaPin- هي المعتمدة، والأخرى مهجورة وستُزال لاحقاً — انقل تحقّقك إليها في وقتك.

PHP

$payload   = file_get_contents('php://input');
$header    = $_SERVER['HTTP_X_MENAPIN_SIGNATURE'] ?? '';
$secret    = getenv('WEBHOOK_SECRET');

parse_str(strtr($header, ',', '&'), $parts);
$timestamp = (int) ($parts['t'] ?? 0);
$expected  = hash_hmac('sha256', $timestamp . '.' . $payload, $secret);

if (! hash_equals($expected, $parts['v1'] ?? '')) {
    http_response_code(400);   // bad signature
    exit;
}

if (abs(time() - $timestamp) > 300) {
    http_response_code(400);   // too old
    exit;
}

$event = json_decode($payload, true);
// handle $event['id'] exactly once, then:
http_response_code(200);

الردّ وإعادة المحاولة

  • ردّ بـ 2xx فور استلامك، وأجّل المعالجة الطويلة. مهلتنا 10 ثوانٍ.
  • عند التعذّر نعيد المحاولة حتى 5 مرات بفواصل 10 ثانية، 1 دقيقة، 5 دقيقة، 30 دقيقة.
  • ردّ 4xx يعني الرفض الواعي فلا نكرّر — ما عدا 408 و429.
  • بعد 15 إخفاقاً متتالياً تُوقَف النقطة ونُرسل لك تنبيهاً. الأحداث الفائتة لا تُعاد تلقائياً بعد إعادة التفعيل.
  • العنوان يجب أن يكون https وعلى شبكة عامة — لا نرسل إلى عنوان داخلي.

الأخطاء

كل خطأ يعود بالشكل نفسه، مع رمز ثابت في code يمكن لبرنامجك التفرّع عليه، ورسالة عربية صالحة للعرض.

{
  "error": {
    "code": "insufficient_balance",
    "message": "الرصيد لا يكفي لإتمام هذه العملية."
  }
}
الحالة الرمز متى يحدث
401 unauthenticated المفتاح مفقود أو غير معروف أو مُلغى.
403 account_suspended الحساب موقوف من طرفنا. لا يمكن فعل شيء جديد به — لا طلب، ولا يعمل مفتاح ولا هاتف — حتى تُعاد تفعيله؛ والطلبات المقدَّمة قبل ذلك تُسلَّم أو تُسترد كالمعتاد.
402 insufficient_balance رصيد المحفظة لا يغطي الطلب. لم يُخصم شيء.
403 email_unverified بريد الحساب لم يُؤكَّد بعد، ولا يتحرّك مال قبل تأكيده.
403 forbidden المفتاح صالح لكنّه لا يملك هذه الصلاحية — راجع صلاحيات المفتاح في اللوحة.
404 not_found رمز فئة غير معروف، أو رقم طلب لا يخصّ حسابك.
409 package_unavailable الفئة موقوفة عن البيع، أو غير قابلة للتنفيذ حالياً. لم يُخصم شيء.
409 idempotency_conflict مفتاح منع تكرار سبق استعماله لطلب مختلف (فئة أو كمية أخرى).
422 validation_failed الجسم ناقص أو غير صالح — التفاصيل في fields.
422 invalid_quantity الكمية خارج الحدّ الأدنى أو الأعلى أو خطوة الفئة. لم يُنشأ طلب.
422 player_id_required الفئة تُشحن إلى حساب ولم يصل player_id. لم يُنشأ طلب ولم يُخصم شيء.
429 rate_limited تجاوزت سقف الطلبات في الدقيقة لحسابك.

وقد يعود إلى جانب ما سبق 400 bad_request لجسم غير صالح بنيوياً، و405 method_not_allowed لطريقة لا يقبلها المسار. وكلاهما خطأ في بناء الطلب نفسه لا في حالة حسابك.

لا يبقى مخصوماً منك مقابل طلب لم يُسلَّم. الخصم يقع مع إنشاء الطلب في عملية واحدة تحت قفل، وإن تعذّر تنفيذ الطلب رُدّ المبلغ كاملاً إلى محفظتك وسُجِّل الردّ في كشفها كحركة واحدة.

تحتاج مفتاحاً؟

افتح حساباً، وأكّد بريدك الإلكتروني، ثم أصدر مفتاحك من اللوحة. لا يوجد وضع اختبار: المفتاح ينفّذ طلبات حقيقية من رصيدك.

افتح حساباً