في Direct API، يكون نموذج البطاقة جزءًا من موقعك، ويرسل خادمك بيانات البطاقة إلى Payniko. ولا يُعاد توجيه متصفح العميل إلى Payniko إلا لمصادقة 3-D Secure ولأرقام BIN من نوع hosted. استخدم Direct API عندما يتعيّن أن يكون نموذج البطاقة على صفحتك.
خطوات التكامل
فحص BIN
بعد أن يُدخل العميل ستة أرقام، استدعِ GET /api/payments/bin/{bin6}. ويُحدد payment_mode ما إذا كان يجب جمع بيانات البطاقة كاملة في نموذجك أو توجيه العميل إلى صفحة الدفع.
إنشاء الدفعة
استدعِ POST /api/payments/direct مع بيانات البطاقة، أو مع bin6 فقط لرقم BIN من نوع hosted.
إتمام مصادقة 3-D Secure
إذا كانت الحالة requires_action، فأعِد توجيه المتصفح إلى next_action.url. ثم يعود المتصفح إلى success_url أو fail_url الخاص بك مع معلمات موقّعة.
تأكيد النتيجة
انتظر إشعار Webhook payment.succeeded أو payment.failed، أو استدعِ GET /api/payments/{payment_id}. ولا تنفّذ طلب الشراء إلا عند الحالة succeeded.
مسار الدفع
Your server Payniko Payer's browser
1 GET /api/payments/bin/{bin6} ───► payment_mode: direct | hosted
2 POST /api/payments/direct ─────► 200 requires_action + next_action.url
(or processing / succeeded / failed)
3 redirect to next_action.url ──────────────────────────────────► 3-D Secure page
───────────────────────────► success_url / fail_url
4 webhook ◄─────────────────────── payment.succeeded / payment.failed
GET /api/payments/{payment_id} ──► current status
المتطلبات المسبقة
يُفعَّل Direct API لكل حساب على حدة. ودون هذه الصلاحية، تُرفض الطلبات بالرمز 4030055502.
قبل إنشاء الدفعة، يُبيّن فحص BIN ما إذا كان يمكن الدفع بالبطاقة في نموذجك (direct) أو يجب إتمام الدفع على صفحة الدفع في Payniko (hosted). ولا يترتب على الفحص أي خصم.
المعلمات
الحقل
النوع
إلزامي
الوصف
مثال
bin6
string · path
إلزامي
الأرقام الستة الأولى من البطاقة.
540061
amount
string · query
إلزامي
المبلغ بمنزلتين عشريتين، على سبيل المثال 100.00. ويُقارن بحدود حسابك.
100.00
currency
string · query
اختياري
USD أو EUR أو AED أو TRY. والقيمة الافتراضية هي العملة الرئيسية للنظام. استخدم العملة نفسها عند إنشاء الدفعة.
USD
تُرفض معلمات الاستعلام الأخرى. ولا تدخل سلسلة الاستعلام في التوقيع.
./payniko.sh GET '/api/payments/bin/540061?amount=100.00¤cy=USD'
true للبطاقة المحلية؛ وfalse في غير ذلك أو إذا كان ذلك غير معروف.
payment_mode
string | null
direct: اجمع بيانات البطاقة كاملة. hosted: وجّه العميل إلى صفحة الدفع. أي قيمة أخرى، بما في ذلك null: لا تُنشئ دفعة.
directhosted
التعامل مع الاستجابة
direct: اجمع بيانات البطاقة كاملة وأنشئ الدفعة باستخدام card.
hosted: أنشئ الدفعة باستخدام bin6 فقط. ويُدخل العميل بيانات البطاقة على صفحة الدفع في Payniko.
أي قيمة أخرى لـ payment_mode، أو خطأ بطاقة 409 (0055230، 0055231، 0055233): لا تُنشئ دفعة، واطلب من العميل استخدام بطاقة أخرى برسالة محايدة، إذ لا يُعد ذلك رفضًا من البنك. ويشير 4220055235 إلى مشكلة في المبلغ. وعند تلقي 429 أو 5xx، كرّر الفحص نفسه لاحقًا.
مثال
import {api} from './payniko.mjs';
let mode = null;
try {
const lookup = await api('GET', '/api/payments/bin/411111?amount=100.00¤cy=USD');
mode = lookup.data.payment_mode;
} catch (error) {
// No answer, 429 or 5xx: temporary. Try the same lookup again later.
if (error.status === undefined || error.status === 429 || error.status >= 500) throw error;
// Other 4xx: error.problem.code says why, for example 0055233.
}
if (mode !== 'direct' && mode !== 'hosted') {
// Also null: do not create a payment. Ask for another card with a neutral message.
}
يُعيد دائمًا 200 مع الدفعة الحالية، بما في ذلك عند إعادة إرسال الطلب نفسه. ورفض البطاقة ليس خطأ HTTP؛ إذ تكون الاستجابة 200 مع status: failed.
متن الطلب
الحقل
النوع
إلزامي
الوصف
مثال
merchant_order_id
string
إلزامي
مرجع طلب الشراء لديك: من 1 إلى 64 حرفًا من A-Z a-z 0-9 . _ -. دفعة واحدة لكل مرجع في كل بيئة، ولا يُفرَّق بين الأحرف الكبيرة والصغيرة عند التحقق من التفرد.
ORDER-2026-002
amount
string
إلزامي
المبلغ بمنزلتين عشريتين، على سبيل المثال "100.00"، ضمن حدود حسابك.
"100.00"
currency
string
اختياري
القيمة الافتراضية هي العملة الرئيسية للنظام. استخدم العملة نفسها المستخدمة في فحص BIN.
USDEURAEDTRY
USD
webhook_url
string · uri
إلزامي
عنوان HTTPS عام على المنفذ 443 يستقبل webhooks هذه الدفعة، بحد أقصى 500 حرف. لا تُقبل العناوين المحلية، حتى في sandbox؛ ويجب أن يُحلّ اسم المضيف إلى عنوان IP عام وقت الطلب، وإلا 4229000008.
https://merchant.example/webhooks/payments
success_url
string · uri
إلزامي
وجهة إعادة توجيه المتصفح عند نجاح الدفعة. HTTPS، بحد أقصى 2048 حرفًا، ودون معلومات المستخدم (user info) أو المقطع (fragment). وتقبل البيئة التجريبية أيضًا http://localhost و127.0.0.1 و[::1].
https://merchant.example/payment/success
fail_url
string · uri
إلزامي
وجهة إعادة توجيه المتصفح في جميع الحالات الأخرى. وتسري عليها القواعد نفسها المطبقة على success_url.
https://merchant.example/payment/failure
customer
object
إلزامي
بيانات الاتصال الخاصة بالدافع؛ تُستخدم لنتيجة الدفع والإيصال وخطوات التحقق لدى البنك. يُقبل email وphone فقط؛ وأي حقل آخر داخل customer يُرفض بـ 4221000001.
{ … }
customer.email
string
إلزامي
عنوان البريد الإلكتروني الحقيقي للدافع: RFC 5322، بحد أقصى 254 حرفًا، مع نطاق يحتوي على نقطة. يُحفظ بأحرف صغيرة.
رقم الهاتف الحقيقي للدافع بالصيغة الدولية (+ رمز الدولة، E.164)، مثل +37061234567. قاعدة خاصة بتركيا: +90 متبوعًا بالرقم الوطني المكوّن من 10 أرقام (5xx جوال، 2xx و3xx و4xx ثابت، 8xx خاص). الرقم المرسل بدون رمز الدولة يُعامل كرقم تركي (+90).
+37061234567
card
object
مشروط
بيانات البطاقة التي أدخلها العميل (راجع الجدول أدناه). أرسل card أو bin6، وليس كليهما.
{ … }
bin6
string
مشروط
لرقم BIN من نوع hosted فقط: الأرقام الستة الأولى. ويُدخل العميل بيانات البطاقة على صفحة الدفع. أما الطلب الخاص برقم BIN من نوع direct مع bin6 فقط فيُعيد 4090055230.
540061
card.number
string
إلزامي
13–19 رقمًا بدون مسافات، مع رقم تحقق صالح (15 رقمًا لبطاقات American Express).
4355090000044242
card.holder_name
string
إلزامي
الاسم المدوّن على البطاقة، من 2 إلى 120 حرفًا.
JONAS KAZLAUSKAS
card.expiry_month
string
إلزامي
رقمان، من 01 إلى 12.
12
card.expiry_year
string
إلزامي
رقمان، مثل 30. يجب ألا تكون البطاقة منتهية الصلاحية، ويمكن أن تكون السنة متقدمة بما لا يزيد على 30 عامًا.
30
card.cvv
string
إلزامي
3 أرقام بالضبط، أو 4 أرقام لبطاقات American Express. لا تخزّنه ولا تسجّله أبدًا.
123
جميع قيم البطاقة نصوص. وتُرفض الحقول غير المعروفة داخل card.
مع BIN من نوع hosted، لا تطلب صفحة الدفع من الدافع عنوان البريد الإلكتروني ورقم الهاتف مرة أخرى. حفاظًا على الخصوصية، لا يُعاد customer في كائن الدفع ولا في الـ webhooks.
الحقل
السبب
الرسالة في errors
customer.email
صيغة غير صالحة أو أطول من 254 حرفًا.
customer.email must be a valid e-mail address (max 254 characters).
customer.email
عنوان نائب أو نموذجي أو مؤقت.
customer.email must be the payer's real address; placeholder, example or disposable addresses (example.com, test@…, mailinator…) are not accepted.
customer.phone
ليس رقمًا صالحًا بالصيغة الدولية.
customer.phone must be a valid phone number in international format (e.g. +37061234567).
customer.phone
نمط أرقام مكررة أو متسلسلة.
customer.phone must be the payer's real number; repeated or sequential digit patterns (5555555555, 1234567890) are not accepted.
customer.phone
رقم تركي ليس بصيغة +90 متبوعًا بعشرة أرقام تبدأ بـ 2 أو 3 أو 4 أو 5 أو 8.
customer.phone for Türkiye must be +90 followed by a 10-digit number starting with 2, 3, 4, 5 or 8 (e.g. +905321234567).
HTTP 422 · 1000001
{
"type": "https://payniko.com/problems/1000001",
"title": "Validation failed",
"status": 422,
"detail": "The submitted fields are not valid.",
"code": "1000001",
"correlation_id": "4c7a1e9d-2b3f-4d85-9a6e-0f1b2c3d4e5f",
"errors": {
"customer.email": [
"customer.email must be the payer's real address; placeholder, example or disposable addresses (example.com, test@…, mailinator…) are not accepted."
],
"customer.phone": [
"customer.phone must be the payer's real number; repeated or sequential digit patterns (5555555555, 1234567890) are not accepted."
]
}
}
إذا رُفض الطلب بسبب customer، فاعرض السبب للدافع بوضوح (مثلًا: أدخل عنوان بريدك الإلكتروني أو رقم هاتفك الخاص) واسمح له بتصحيح الحقل قبل إنشاء الدفع مرة أخرى. تعيد Direct API الرسائل في errors مترجمة؛ ويعرض الجدول النص الإنجليزي.
تطبّق صفحات روابط الدفع في Payniko القواعد نفسها على عنوان البريد الإلكتروني ورقم الهاتف اللذين يدخلهما الدافع هناك، وتعرض له سبب الرفض.
أعِد توجيه المتصفح إلى next_action.url (لمصادقة 3-D Secure، أو لصفحة الدفع في حالة BIN من نوع hosted).
processing
انتظر إشعار Webhook ولا تُنشئ دفعة جديدة.
succeeded
نفّذ طلب الشراء.
failed
اعرض failure.message واطلب من العميل استخدام بطاقة أخرى.
قد تستغرق المكالمة عدة ثوانٍ بينما تتواصل Payniko مع البنك. استخدم مهلة لا تقل عن 60 ثانية، وبعد انتهاء المهلة أعد إرسال الطلب بالـ Idempotency-Key نفسه والمتن نفسه. وبينما لا يزال الطلب الأول قيد التنفيذ (حتى 3 دقائق)، تعيد إعادة المحاولة 4090055503 مع metadata.retry_class: retry_same_request: انتظر بضع ثوانٍ وأعد إرسال الطلب نفسه.
لرقم BIN من نوع hosted، يُتجاهل أي card مُرسل ويُكمل العميل الدفع على صفحة الدفع.
إذا كانت هناك دفعة قائمة لقيمة merchant_order_id، أو أُرسل Idempotency-Key نفسه مع متن مختلف، يُرفض الطلب بالرمز 4090055503.
تُرفض بيانات البطاقة غير الصالحة بالرمز 4090055234. ويحدد metadata.developer_detail الحقل المعني: card_number_invalid أو card_expiry_invalid أو card_cvv_invalid. واطلب من العميل إعادة إدخال بيانات البطاقة.
تُرفض بيانات الاتصال المفقودة أو غير الصالحة بـ 4221000001؛ ويحدد errors الحقل customer أو customer.email أو customer.phone.
3. مصادقة 3-D Secure والمتصفح
عندما تكون الحالة requires_action، يجب أن يفتح متصفح العميل next_action.url، حيث ينفّذ البنك مصادقة 3-D Secure أو يُكمل العميل الدفع على صفحة الدفع.
أعِد توجيه الصفحة كاملة إلى next_action.url أو افتحه في علامة تبويب جديدة؛ والطريقتان مدعومتان دائمًا.
لا تطلب الصفحة بيانات البطاقة مجددًا؛ بل تجمع فقط بيانات المتصفح التي يحتاجها البنك.
استخدم دائمًا أحدث next_action.url قبل next_action.expires_at الخاص به (نهاية خطوة 3-D Secure). ولا تُعِد فتح رابط سابق مطلقًا.
بعد ذلك يُعاد توجيه المتصفح إلى success_url (عند succeeded فقط) أو إلى fail_url مع معلمات موقّعة (راجع رابط العودة). ثم استعلم عن الدفعة.
التضمين في الإطارات
لا يمكن عرض صفحة 3-D Secure داخل إطار؛ أعِد توجيه الصفحة كاملة أو افتح علامة تبويب جديدة. ولا يجوز تضمين سوى صفحة الدفع الخاصة برقم BIN من نوع hosted في إطار، وذلك مع المفاتيح الحية فقط، وعلى الأصل (origin) المطابق تمامًا لـ success_url الخاص بالدفعة. أما صفحة الدفع في Hosted API فلا يمكن تضمينها في إطار مطلقًا.
تتضمن صفحة الدفع الخاصة برقم BIN من نوع hosted شاشة الدفع الآمنة وشاشة 3-D Secure الخاصتين بمزوّد خدمة الدفع، ويمكن عرضها كإطار iframe داخل نموذج الدفع الخاص بك.
إذا ضمّنت صفحة الدفع الخاصة برقم BIN من نوع hosted في إطار، فإن الرسائل التي ترسلها إلى نافذتك ليست سوى إشارة لمراجعة حالة الدفعة. اقبلها فقط من أصل (origin) next_action.url ومن إطارك، وتجاهل محتواها، واجعل خادمك يستعلم عن الدفعة.
المتصفح
// Only if you show the payment page of a hosted BIN in a frame on the exact origin of success_url.
// nextActionUrl: next_action.url from your server.
const pageOrigin = new URL(nextActionUrl).origin;
window.addEventListener('message', (event) => {
if (event.origin !== pageOrigin || event.source !== frame.contentWindow) return;
// A wake-up call only: ignore event.data. Ask your server to read the payment.
fetch('/checkout/ORDER-2026-002/refresh', {method: 'POST', credentials: 'same-origin'});
});
كائن الدفعة
تُعيد استدعاءات الإنشاء والاستعلام هذا الكائن داخل data، ويتضمنه كل إشعار Webhook خاص بالدفع. وبنيته متطابقة في Hosted API وDirect API.
عملة المبلغ؛ وهي العملة الرئيسية للنظام إذا لم تُحدَّد عملة.
environment
string
بيئة المفتاح الذي أنشأ الدفعة.
sandboxlive
next_action
object | null
الإجراء التالي المطلوب من العميل. ولا يُعيَّن إلا ما دامت قيمة status هي requires_action.
next_action.type
string
دائمًا redirect.
next_action.url
string · uri
افتح هذا الرابط في متصفح العميل تمامًا كما أُعيد. ولا تُنشئه أو تعدّله مطلقًا.
next_action.expires_at
string · date-time
الموعد النهائي لاستخدام الرابط. في Direct API، يمثل نهاية خطوة 3-D Secure وقد يسبق expires_at الخاص بالدفعة؛ وفي Hosted API، يمثل الموعد النهائي للدفعة. وبعد انقضائه، تنتقل الدفعة إلى حالتها التالية وتصبح قيمة next_action هي null.
سبب الفشل. ولا يُعيَّن إلا عندما تكون قيمة status هي failed.
failure.code
string
رمز السبب لاستخدامه في منطق تطبيقك؛ راجع القائمة أدناه.
failure.message
string
رسالة قصيرة مناسبة لعرضها على العميل كنص عادي.
created_at
string · date-time
وقت إنشاء الدفعة.
expires_at
string · date-time
الموعد النهائي لإتمام الدفعة.
completed_at
string · date-time | null
وقت وصول الدفعة إلى حالة نهائية.
قد تكون قيمة next_action هي null لفترة وجيزة حتى في الحالة requires_action؛ وفي هذه الحالة استعلم عن الدفعة مجددًا. ولا يُعاد مطلقًا رقم البطاقة الكامل أو رمز CVV أو تاريخ انتهاء الصلاحية أو اسم حامل البطاقة. وقد تكون قيمتا card.network وcard.bank هي null عندما تكونان غير معروفتين.
بيانات customer المرسلة عند إنشاء الدفع ليست جزءًا من هذا الكائن ولا تظهر في الـ webhooks.
يجب أن يفتح المتصفح next_action.url لمصادقة 3-D Secure أو لصفحة الدفع الخاصة برقم BIN من نوع hosted.
أعِد توجيه العميل إلى next_action.url.
processing
استجابة البنك قيد الانتظار. وقد تُعاد هذه الحالة مباشرةً في استجابة الإنشاء.
انتظر الـ webhook ولا تنشئ دفعة جديدة. إذا لم يرد البنك أبدًا، فقد تبقى الدفعة في processing حتى التسوية ثم تنتهي بـ failed أو expired.
succeeded
تم تحصيل الأموال.
نفّذ طلب الشراء.
failed
لم تكتمل الدفعة. ويتضمن failure السبب.
اعرض failure.message. ولإعادة المحاولة، أنشئ دفعة جديدة بقيمة merchant_order_id جديدة.
expired
لم يُكمل العميل الدفع قبل expires_at.
إذا كان العميل لا يزال يرغب في الدفع، فأنشئ دفعة جديدة.
cancelled
أُلغيت الدفعة.
لا تنفّذ طلب الشراء.
السيناريو
الحالة
الحدث
الموافقة بعد 3-D Secure
requires_action → succeeded
payment.succeeded
الرفض
failed
payment.failed
استجابة البنك قيد الانتظار
processing
لا شيء حتى صدور النتيجة النهائية
لا استجابة من البنك؛ وانتهت مهلة المحاولة
processing → failed / expired
payment.failed
لم يبدأ العميل الدفع وانقضى الموعد النهائي (إذا انصرف العميل عن الدفع في منتصفه، فقد تنتهي الدفعة بالحالة failed)
expired
payment.failed
تعامل مع أي حالة غير مدرجة هنا على أنها processing، واستعلم عن الدفعة مجددًا.
رموز الفشل
تتضمن الدفعة في الحالة failed الحقلين failure.code وfailure.message. استخدم الرمز في منطق تطبيقك، واعرض الرسالة على العميل كنص عادي.
الرمز
المعنى
payment_declined
رفض البنك الدفعة.
do_not_honor
رفض البنك الدفعة دون ذكر السبب.
insufficient_funds
رصيد البطاقة غير كافٍ.
card_limit_reached
تم بلوغ أحد حدود البطاقة.
card_details_invalid
بيانات البطاقة غير صالحة.
card_expiry_invalid
تاريخ انتهاء الصلاحية غير صالح أو أن البطاقة منتهية الصلاحية.
card_security_code_invalid
رمز CVV غير صالح.
card_not_active
البطاقة محظورة أو غير مفعّلة.
card_brand_not_supported
شبكة هذه البطاقة غير مدعومة.
three_ds_failed
فشلت مصادقة 3-D Secure أو لم تكتمل.
payment_not_permitted
لا يُسمح باستخدام البطاقة لهذا النوع من المدفوعات.
amount_not_allowed
المبلغ غير مسموح به لهذه البطاقة.
currency_not_supported
العملة غير مدعومة لهذه البطاقة.
payment_details_rejected
رفض البنك بيانات الدفع.
duplicate_payment
صنّف البنك الدفعة على أنها مكررة.
payment_timed_out
لم تكتمل الدفعة في الوقت المحدد.
bank_unavailable
تعذّر الاتصال بالبنك.
bank_requested_retry
طلب البنك إعادة المحاولة في وقت لاحق.
service_unavailable
تعذّرت معالجة الدفعة في الوقت الحالي.
قد تُضاف رموز جديدة؛ لذا تعامل مع أي رمز غير معروف على أنه payment_declined. وقد تتضمن failure.message النص الصادر عن البنك نفسه؛ وإلا فهي باللغة الإنجليزية في إشعارات Webhook، وتتبع Accept-Language في طلبات GET.
رابط العودة
عند عودة العميل، يُعاد توجيه المتصفح إلى success_url فقط إذا كانت حالة الدفعة succeeded، وإلى fail_url في جميع الحالات الأخرى. وتُضاف إلى الرابط معلمات موقّعة. تحقّق منها لعرض الصفحة المناسبة، ولا تنفّذ طلب الشراء إلا بعد أن يؤكد إشعار Webhook أو طلب GET الحالة succeeded.
Direct API: إذا لم تكن النتيجة نهائية بعد عندما يعود الدافع من 3-D Secure، فلا يُعاد توجيه المتصفح إلى fail_url؛ بل يبقى الدافع في صفحة نتيجة محايدة من Payniko. اعتمد على الـ webhook أو على GET لمعرفة النتيجة.
الحقل
الوصف
payment_id
معرّف الدفعة .
merchant_order_id
مرجع طلب الشراء لديك. وهذه القيمة غير موقّعة: استخدمها للعثور على طلب الشراء فقط، واعتمد في المطابقة على payment_id.
status
حالة الدفعة وقت إعادة التوجيه، بالقيم نفسها كما في GET. دائمًا succeeded على success_url. على fail_url، عادةً failed أو expired أو cancelled؛ ومع Hosted API قد تكون requires_action أو processing إذا غادر الدافع قبل وصول الدفعة إلى حالة نهائية، مثلًا عبر "العودة إلى المتجر".
failure_code
لا يظهر إلا عندما تكون قيمة status هي failed؛ وهو مطابق لـ failure.code.
ts
وقت Unix الذي أُنشئ فيه رابط العودة. وقد يفتح العميل الرابط لاحقًا، لذا فإن قِدم ts وحده لا يدل على التزوير.
key_version
يحدد المفتاح الذي وقّع الرابط: 1 لأول مفتاح أُنشئ في تلك البيئة، و2 للمفتاح التالي، وهكذا. ولا تتغير القيمة مطلقًا للدفعة الواحدة.
sig
قيمة HMAC-SHA256 بالنظام الست عشري وبأحرف صغيرة، مكونة من 64 حرفًا.
السلسلة المراد توقيعها
ts + "." + payment_id + "." + status + "." + failure_code // failure_code empty: keep the last dot
اختر المفتاح السري المرتبط بالمفتاح المحدد في key_version. وهو المفتاح الذي أنشأ الدفعة، حتى إذا استُبدلت المفاتيح منذ ذلك الحين.
كوّن السلسلة من القيم بعد فك ترميز URL. وإذا لم يكن failure_code موجودًا، فأبقِ على النقطة الأخيرة.
احسب HMAC-SHA256 وقارنه بـ sig باستخدام مقارنة ثابتة الزمن. وتأكّد من أن payment_id يخص إحدى دفعاتك.
استعلم عن الدفعة باستخدام GET /api/payments/{payment_id} (أو انتظر إشعار Webhook) قبل تنفيذ طلب الشراء.
يُحتفظ بمعلمات الاستعلام الخاصة بك. وإذا كان الرابط يتضمن مسبقًا معلمة تحمل أحد الأسماء المذكورة أعلاه، فإنها تُستبدل.
إذا تعذّر توقيع الرابط بسبب خطأ مؤقت، فلن يتضمن الرابط أيًا من هذه المعلمات. اعتبر النتيجة غير معروفة واستعلم عن الدفعة.
// Node.js 20+: check the signed parameters on success_url / fail_url.
import {createHmac, timingSafeEqual} from 'node:crypto';
// query: URLSearchParams of the return URL.
// secretFor(keyVersion): the API secret of that key, or undefined if you do not know it.
export function verifyReturn(query, secretFor) {
const sig = query.get('sig') ?? '';
const secret = secretFor(query.get('key_version'));
if (!secret || !/^[0-9a-f]{64}$/.test(sig)) return false;
const signed = [query.get('ts') ?? '', query.get('payment_id') ?? '',
query.get('status') ?? '', query.get('failure_code') ?? ''].join('.');
const expected = createHmac('sha256', secret).update(signed, 'utf8').digest();
return timingSafeEqual(expected, Buffer.from(sig, 'hex'));
}
// Even a valid return is not proof of payment: read GET /api/payments/{payment_id} before you deliver.
<?php
// PHP 8.1+: check the signed parameters on success_url / fail_url.
// $secretFor(string $keyVersion): the API secret of that key, or null if you do not know it.
function payniko_verify_return(array $query, callable $secretFor): bool
{
$sig = (string) ($query['sig'] ?? '');
$secret = $secretFor((string) ($query['key_version'] ?? ''));
if (!$secret || !preg_match('/^[0-9a-f]{64}$/', $sig)) return false;
$signed = implode('.', [(string) ($query['ts'] ?? ''), (string) ($query['payment_id'] ?? ''),
(string) ($query['status'] ?? ''), (string) ($query['failure_code'] ?? '')]);
return hash_equals(hash_hmac('sha256', $signed, $secret), $sig);
}
// $ok = payniko_verify_return($_GET, fn (string $v) => $secretsByKeyVersion[$v] ?? null);
// Even a valid return is not proof of payment: read GET /api/payments/{payment_id} before you deliver.
# Python 3.9+: check the signed parameters on success_url / fail_url.
import hashlib, hmac, re
def verify_return(query, secret_for) -> bool:
# query: dict-like (for example Flask request.args); secret_for(key_version) -> secret or None
sig = query.get("sig", "")
secret = secret_for(query.get("key_version"))
if not secret or not re.fullmatch(r"[0-9a-f]{64}", sig):
return False
signed = ".".join([query.get("ts", ""), query.get("payment_id", ""),
query.get("status", ""), query.get("failure_code", "")])
expected = hmac.new(secret.encode(), signed.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)
# Even a valid return is not proof of payment: read GET /api/payments/{payment_id} before you deliver.
روابط العودة · مثال
https://merchant.example/payment/success?payment_id=LUVEI-PAYN-654321&merchant_order_id=ORDER-2026-001&status=succeeded&ts=1793001234&key_version=1&sig=200e8a5d4a7a03ead362a2c076408f9d8764f8f636f58bd4c17d78a50d59eb8a
string to sign: 1793001234.LUVEI-PAYN-654321.succeeded.
https://merchant.example/payment/failure?payment_id=LUVEI-PAYN-654321&merchant_order_id=ORDER-2026-001&status=failed&failure_code=insufficient_funds&ts=1793001234&key_version=1&sig=1cd0a104ec4ec4f5b2933d48d14c0eb33d949dd7f27936a067ab8f68b413b70d
string to sign: 1793001234.LUVEI-PAYN-654321.failed.insufficient_funds
secret used in this example (fake): example_secret_not_real
GET/api/payments/{payment_id}
الاستعلام عن دفعة
يُعيد { "data": Payment } مع الحالة الحالية. ولا يؤدي الاستعلام عن الدفعة إلى تعديلها مطلقًا، لذا يمكنك استدعاء نقطة النهاية هذه كلما لزم الأمر: بعد العودة، أو عندما يبدو إشعار Webhook غير متسق، أو بعد انتهاء المهلة.
المعلمات
الحقل
النوع
إلزامي
الوصف
payment_id
string · path
إلزامي
قيمة payment_id المُعادة عند إنشاء الدفعة. ويمكن لأي مفتاح من البيئة نفسها الاستعلام عنها.
./payniko.sh GET '/api/payments/3c2d7a4e-5b1f-4e8a-9d06-7f2b1c9e4a35'
تحقّق من كل إشعار Webhook: يجب أن تساوي قيمة HMAC-SHA256 لـ X-Timestamp + "." + raw body، المحسوبة بالمفتاح السري المرتبط بالمفتاح الذي أنشأ الدفعة، قيمةَ X-Signature، وأن يكون X-Timestamp ضمن 300 ثانية. احفظ كل event_id مرة واحدة، وأرسل استجابة 2xx دون تأخير. وللاطلاع على أمثلة الشيفرة وسلوك إعادة المحاولة، راجع إشعارات Webhook.
الأخطاء
الرمز
HTTP
المعنى
الإجراء الموصى به
1000001
422
الطلب غير صالح: حقل غير معروف، أو صيغة غير صالحة، أو سلسلة استعلام في طلب POST، أو معلمة استعلام غير مدعومة، أو متن ليس كائن JSON. ويتضمن errors الحقول المعنية.
صحّح الحقول المذكورة.
1000009
422
الترويسة Idempotency-Key مفقودة أو غير صالحة.
أرسل مع كل طلب POST مفتاحًا يتكون من 8 إلى 128 حرفًا من A-Z a-z 0-9 _ -.
0055502
401 / 403
رُفض الوصول. 401: الترويسة X-Api-Key أو X-Merchant-Code مفقودة، أو إحدى الترويسات مكررة أو غير صحيحة الصيغة، أو المفتاح غير معروف أو مُلغى، أو رمز التاجر غير مطابق، أو أُرسلت X-Environment بقيمة لا تطابق المفتاح. 403: الحساب أو صلاحية الوصول إلى Direct API غير مفعّلة.
تحقّق من الترويسات ومن مفتاح API. لا تُعِد المحاولة تلقائيًا.
0055503
409
تعارض في Direct API: توجد دفعة مسبقًا لقيمة merchant_order_id هذه، أو استُخدم Idempotency-Key مع متن مختلف أو لدفعة عبر Hosted API، أو أن الطلب الأصلي لا يزال قيد التنفيذ.
استعلم عن الدفعة باستخدام GET. ولا تُعِد إرسال سوى طلب مطابق تمامًا.
0055210
422
رقم BIN لا يتكون من ستة أرقام.
أرسل الأرقام الستة الأولى من رقم البطاقة.
0055234
409
بيانات البطاقة غير صالحة: رقم البطاقة أو تاريخ الانتهاء أو CVV. يحدد metadata.developer_detail الحقل: card_number_invalid أو card_expiry_invalid أو card_cvv_invalid.
اطلب من العميل إعادة إدخال بيانات البطاقة.
0055233
409
رقم BIN غير معروف.
اطلب من العميل استخدام بطاقة أخرى. وهذا ليس رفضًا من البنك.
0055230
409
لا يمكن استخدام هذه البطاقة لهذه الدفعة.
اطلب من العميل استخدام بطاقة أخرى. وهذا ليس رفضًا من البنك.
0055231
409
هذه البطاقة غير مقبولة لهذه الدفعة.
اطلب من العميل استخدام بطاقة أخرى.
0055221
409
تم بلوغ أحد حدود هذه البطاقة.
استخدم بطاقة أخرى أو أعِد المحاولة لاحقًا.
0055235
422
المبلغ غير مقبول (Direct API وفحص BIN). ويتضمن amount_limit القيمتين min وmax عند توفرهما.
عدّل المبلغ.
0055001
409
حسابك غير جاهز لهذه الدفعة. مع مفتاح sandbox، يعني هذا أيضًا أن محاكاة sandbox غير متاحة مؤقتًا (developer_detail: sandbox_unavailable)، وهو مكافئ لـ 9000013.
تواصل مع فريق الدعم.
1000002
404
غير موجود: المسار غير معروف، أو منتج API هذا غير مفعّل على المنصة. أما API المعطّل لحسابك فيعيد 403 بدلًا من ذلك (9000040 في Hosted API و0055502 في Direct API).
تحقق من المسار؛ وتواصل مع الدعم إذا كان ينبغي أن يكون API متاحًا.
1000011
503
الخدمة غير متاحة مؤقتًا.
أعِد إرسال الطلب نفسه. واستعلم عن الموارد القائمة قبل إنشاء موارد جديدة.
تتضمن أخطاء Direct API أيضًا metadata.retry_class (no_retry، retry_same_request، retry_after، read_order). ولا يعني إغلاق نافذة المتصفح أو انتهاء المهلة من جانبك أن الدفعة قد فشلت؛ لذا استعلم عن الدفعة. وللاطلاع على القائمة الكاملة، راجع رموز الأخطاء.
الاختبار ببطاقات البيئة التجريبية
مع المفتاح التجريبي، يستخدم فحص BIN والدفعة بيانات تجريبية ثابتة: 435509 و400000 رقما BIN من نوع direct، و540061 رقم BIN من نوع hosted. استخدم أي تاريخ انتهاء صلاحية مستقبلي وأي رمز CVV من 3 أرقام. وللمزيد من التفاصيل، راجع البيئة التجريبية والبطاقات التجريبية.
رقم البطاقة
النتيجة
4000000000000010
موافقة دون 3-D Secure. وفي Direct API، تُعيد استجابة الإنشاء الحالة succeeded مباشرةً.
4000000000000002 · 4355090000044242
خطوة 3-D Secure تجريبية، تليها الحالة succeeded.
4000000000000051 · 4355090000040000
مرفوضة قبل 3-D Secure (do_not_honor). Hosted API: قد تبقى الدفعة في requires_action نحو 5 دقائق وترسل payment.updated قبل أن تنتهي بـ failed. Direct API: تعيد استجابة الإنشاء failed مباشرة.
4000000000000093 · 4355090000005151
خطوة 3-D Secure تجريبية، تليها الحالة failed مع insufficient_funds.
4000000000000044
لا استجابة من البنك ولا 3-D Secure؛ وتبقى الدفعة في الحالة processing.
4355090000089999
Hosted API: processing بدون خطوة 3-D Secure. Direct API: خطوة 3-D Secure تجريبية ثم processing.
مع Direct API، تعيد بطاقة الاختبار المرفوضة failed مباشرة في استجابة الإنشاء؛ ولا يظهر requires_action المؤقت إلا في صفحة الدفع الخاصة بـ BIN من نوع hosted. اعتمد في قراراتك على الـ webhook أو على GET.