انتقل إلى المحتوى
التوثيق

Direct API

في Direct API، يكون نموذج البطاقة جزءًا من موقعك، ويرسل خادمك بيانات البطاقة إلى Payniko. ولا يُعاد توجيه متصفح العميل إلى Payniko إلا لمصادقة 3-D Secure ولأرقام BIN من نوع hosted. استخدم Direct API عندما يتعيّن أن يكون نموذج البطاقة على صفحتك.

خطوات التكامل

  1. فحص BIN

    بعد أن يُدخل العميل ستة أرقام، استدعِ GET /api/payments/bin/{bin6}. ويُحدد payment_mode ما إذا كان يجب جمع بيانات البطاقة كاملة في نموذجك أو توجيه العميل إلى صفحة الدفع.

  2. إنشاء الدفعة

    استدعِ POST /api/payments/direct مع بيانات البطاقة، أو مع bin6 فقط لرقم BIN من نوع hosted.

  3. إتمام مصادقة 3-D Secure

    إذا كانت الحالة requires_action، فأعِد توجيه المتصفح إلى next_action.url. ثم يعود المتصفح إلى success_url أو fail_url الخاص بك مع معلمات موقّعة.

  4. تأكيد النتيجة

    انتظر إشعار 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 لكل حساب على حدة. ودون هذه الصلاحية، تُرفض الطلبات بالرمز 403 0055502.
  • المفاتيح والترويسات والتوقيع مطابقة لما في Hosted API؛ راجع المصادقة. وتستخدم الأمثلة الدوال المساعدة للطلبات.
GET/api/payments/bin/{bin6}

1. فحص BIN

قبل إنشاء الدفعة، يُبيّن فحص BIN ما إذا كان يمكن الدفع بالبطاقة في نموذجك (direct) أو يجب إتمام الدفع على صفحة الدفع في Payniko (hosted). ولا يترتب على الفحص أي خصم.

المعلمات

الحقلالنوعإلزاميالوصفمثال
bin6string · pathإلزاميالأرقام الستة الأولى من البطاقة.540061
amountstring · queryإلزاميالمبلغ بمنزلتين عشريتين، على سبيل المثال 100.00. ويُقارن بحدود حسابك.100.00
currencystring · queryاختياريUSD أو EUR أو AED أو TRY. والقيمة الافتراضية هي العملة الرئيسية للنظام. استخدم العملة نفسها عند إنشاء الدفعة.USD

تُرفض معلمات الاستعلام الأخرى. ولا تدخل سلسلة الاستعلام في التوقيع.

./payniko.sh GET '/api/payments/bin/540061?amount=100.00&currency=USD'
import {api} from './payniko.mjs';

const lookup = await api('GET', '/api/payments/bin/540061?amount=100.00&currency=USD');
console.log(lookup);
<?php
require __DIR__ . '/payniko.php';

$lookup = payniko_api('GET', '/api/payments/bin/540061?amount=100.00&currency=USD');
print_r($lookup);
from payniko import api

lookup = api("GET", "/api/payments/bin/540061?amount=100.00&currency=USD")
print(lookup)

الاستجابة · مثال

{
  "data": {
    "bin6": "540061",
    "bank": "Test Bank",
    "network": "mastercard",
    "card_type": "credit",
    "card_program": null,
    "country": "TR",
    "domestic": true,
    "payment_mode": "hosted"
  }
}

الاستجابة · 200

يُعيد { "data": ... } مع الحقول التالية:

الحقلالنوعالوصف
bin6stringالأرقام الستة المُرسلة.
bankstring | nullالبنك المُصدِر، إن كان معروفًا.
networkstring | nullشبكة البطاقة، إن كانت معروفة.
visamastercardamextroy
card_typestring | nullنوع البطاقة، إن كان معروفًا.
creditdebitprepaid
card_programstring | nullبرنامج البطاقة. والقيمة حاليًا دائمًا null.
countrystring | nullبلد الإصدار (ISO 3166-1 alpha-2)، إن كان معروفًا.
domesticbooleantrue للبطاقة المحلية؛ وfalse في غير ذلك أو إذا كان ذلك غير معروف.
payment_modestring | nulldirect: اجمع بيانات البطاقة كاملة. hosted: وجّه العميل إلى صفحة الدفع. أي قيمة أخرى، بما في ذلك null: لا تُنشئ دفعة.
directhosted

التعامل مع الاستجابة

  • direct: اجمع بيانات البطاقة كاملة وأنشئ الدفعة باستخدام card.
  • hosted: أنشئ الدفعة باستخدام bin6 فقط. ويُدخل العميل بيانات البطاقة على صفحة الدفع في Payniko.
  • أي قيمة أخرى لـ payment_mode، أو خطأ بطاقة 409 (0055230، 0055231، 0055233): لا تُنشئ دفعة، واطلب من العميل استخدام بطاقة أخرى برسالة محايدة، إذ لا يُعد ذلك رفضًا من البنك. ويشير 422 0055235 إلى مشكلة في المبلغ. وعند تلقي 429 أو 5xx، كرّر الفحص نفسه لاحقًا.

مثال

import {api} from './payniko.mjs';

let mode = null;
try {
  const lookup = await api('GET', '/api/payments/bin/411111?amount=100.00&currency=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.
}

الأخطاء المحتملة

POST/api/payments/directيتطلب Idempotency-Key

2. إنشاء الدفعة

يُعيد دائمًا 200 مع الدفعة الحالية، بما في ذلك عند إعادة إرسال الطلب نفسه. ورفض البطاقة ليس خطأ HTTP؛ إذ تكون الاستجابة 200 مع status: failed.

متن الطلب

الحقلالنوعإلزاميالوصفمثال
merchant_order_idstringإلزاميمرجع طلب الشراء لديك: من 1 إلى 64 حرفًا من A-Z a-z 0-9 . _ -. دفعة واحدة لكل مرجع في كل بيئة، ولا يُفرَّق بين الأحرف الكبيرة والصغيرة عند التحقق من التفرد.ORDER-2026-002
amountstringإلزاميالمبلغ بمنزلتين عشريتين، على سبيل المثال "100.00"، ضمن حدود حسابك."100.00"
currencystringاختياريالقيمة الافتراضية هي العملة الرئيسية للنظام. استخدم العملة نفسها المستخدمة في فحص BIN.
USDEURAEDTRY
USD
webhook_urlstring · uriإلزاميعنوان HTTPS عام على المنفذ 443 يستقبل webhooks هذه الدفعة، بحد أقصى 500 حرف. لا تُقبل العناوين المحلية، حتى في sandbox؛ ويجب أن يُحلّ اسم المضيف إلى عنوان IP عام وقت الطلب، وإلا 422 9000008.https://merchant.example/webhooks/payments
success_urlstring · uriإلزاميوجهة إعادة توجيه المتصفح عند نجاح الدفعة. HTTPS، بحد أقصى 2048 حرفًا، ودون معلومات المستخدم (user info) أو المقطع (fragment). وتقبل البيئة التجريبية أيضًا http://localhost و127.0.0.1 و[::1].https://merchant.example/payment/success
fail_urlstring · uriإلزاميوجهة إعادة توجيه المتصفح في جميع الحالات الأخرى. وتسري عليها القواعد نفسها المطبقة على success_url.https://merchant.example/payment/failure
customerobjectإلزاميبيانات الاتصال الخاصة بالدافع؛ تُستخدم لنتيجة الدفع والإيصال وخطوات التحقق لدى البنك. يُقبل email وphone فقط؛ وأي حقل آخر داخل customer يُرفض بـ 422 1000001.{ … }
customer.emailstringإلزاميعنوان البريد الإلكتروني الحقيقي للدافع: RFC 5322، بحد أقصى 254 حرفًا، مع نطاق يحتوي على نقطة. يُحفظ بأحرف صغيرة.[email protected]
customer.phonestringإلزاميرقم الهاتف الحقيقي للدافع بالصيغة الدولية (+ رمز الدولة، E.164)، مثل +37061234567. قاعدة خاصة بتركيا: +90 متبوعًا بالرقم الوطني المكوّن من 10 أرقام (5xx جوال، 2xx و3xx و4xx ثابت، 8xx خاص). الرقم المرسل بدون رمز الدولة يُعامل كرقم تركي (+90).+37061234567
cardobjectمشروطبيانات البطاقة التي أدخلها العميل (راجع الجدول أدناه). أرسل card أو bin6، وليس كليهما.{ … }
bin6stringمشروطلرقم BIN من نوع hosted فقط: الأرقام الستة الأولى. ويُدخل العميل بيانات البطاقة على صفحة الدفع. أما الطلب الخاص برقم BIN من نوع direct مع bin6 فقط فيُعيد 409 0055230.540061
card.numberstringإلزامي13–19 رقمًا بدون مسافات، مع رقم تحقق صالح (15 رقمًا لبطاقات American Express).4355090000044242
card.holder_namestringإلزاميالاسم المدوّن على البطاقة، من 2 إلى 120 حرفًا.JONAS KAZLAUSKAS
card.expiry_monthstringإلزاميرقمان، من 01 إلى 12.12
card.expiry_yearstringإلزاميرقمان، مثل 30. يجب ألا تكون البطاقة منتهية الصلاحية، ويمكن أن تكون السنة متقدمة بما لا يزيد على 30 عامًا.30
card.cvvstringإلزامي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 القواعد نفسها على عنوان البريد الإلكتروني ورقم الهاتف اللذين يدخلهما الدافع هناك، وتعرض له سبب الرفض.

مع بيانات البطاقة · مثال

{
  "merchant_order_id": "ORDER-2026-002",
  "amount": "100.00",
  "currency": "USD",
  "webhook_url": "https://merchant.example/webhooks/payments",
  "success_url": "https://merchant.example/payment/success",
  "fail_url": "https://merchant.example/payment/failure",
  "customer": {
    "email": "[email protected]",
    "phone": "+37061234567"
  },
  "card": {
    "number": "4355090000044242",
    "holder_name": "JONAS KAZLAUSKAS",
    "expiry_month": "12",
    "expiry_year": "30",
    "cvv": "123"
  }
}
KEY="$(openssl rand -hex 16)"  # store with the order before sending; reuse it after network errors
./payniko.sh POST '/api/payments/direct' '{
  "merchant_order_id": "ORDER-2026-002",
  "amount": "100.00",
  "currency": "USD",
  "webhook_url": "https://merchant.example/webhooks/payments",
  "success_url": "https://merchant.example/payment/success",
  "fail_url": "https://merchant.example/payment/failure",
  "customer": {
    "email": "[email protected]",
    "phone": "+37061234567"
  },
  "card": {
    "number": "4355090000044242",
    "holder_name": "JONAS KAZLAUSKAS",
    "expiry_month": "12",
    "expiry_year": "30",
    "cvv": "123"
  }
}' "$KEY"
import {api, randomUUID} from './payniko.mjs';

const key = randomUUID(); // Store this key with the order before sending; reuse it after network errors.
const payment = await api('POST', '/api/payments/direct', {
  "merchant_order_id": "ORDER-2026-002",
  "amount": "100.00",
  "currency": "USD",
  "webhook_url": "https://merchant.example/webhooks/payments",
  "success_url": "https://merchant.example/payment/success",
  "fail_url": "https://merchant.example/payment/failure",
  "customer": {
    "email": "[email protected]",
    "phone": "+37061234567"
  },
  "card": {
    "number": "4355090000044242",
    "holder_name": "JONAS KAZLAUSKAS",
    "expiry_month": "12",
    "expiry_year": "30",
    "cvv": "123"
  }
}, key);
console.log(payment);
<?php
require __DIR__ . '/payniko.php';

$key = bin2hex(random_bytes(16)); // store with the order; reuse it after network errors
$payment = payniko_api('POST', '/api/payments/direct', [
    "merchant_order_id" => 'ORDER-2026-002',
    "amount" => '100.00',
    "currency" => 'USD',
    "webhook_url" => 'https://merchant.example/webhooks/payments',
    "success_url" => 'https://merchant.example/payment/success',
    "fail_url" => 'https://merchant.example/payment/failure',
    "customer" => [
        "email" => '[email protected]',
        "phone" => '+37061234567',
    ],
    "card" => [
        "number" => '4355090000044242',
        "holder_name" => 'JONAS KAZLAUSKAS',
        "expiry_month" => '12',
        "expiry_year" => '30',
        "cvv" => '123',
    ],
], $key);
print_r($payment);
from payniko import api
import uuid

key = str(uuid.uuid4())  # store with the order; reuse it after network errors
payment = api("POST", "/api/payments/direct", {
    "merchant_order_id": "ORDER-2026-002",
    "amount": "100.00",
    "currency": "USD",
    "webhook_url": "https://merchant.example/webhooks/payments",
    "success_url": "https://merchant.example/payment/success",
    "fail_url": "https://merchant.example/payment/failure",
    "customer": {
        "email": "[email protected]",
        "phone": "+37061234567"
    },
    "card": {
        "number": "4355090000044242",
        "holder_name": "JONAS KAZLAUSKAS",
        "expiry_month": "12",
        "expiry_year": "30",
        "cvv": "123"
    }
}, key)
print(payment)

الاستجابة · مثال

{
  "data": {
    "payment_id": "3c2d7a4e-5b1f-4e8a-9d06-7f2b1c9e4a35",
    "merchant_order_id": "ORDER-2026-002",
    "flow": "direct",
    "status": "requires_action",
    "amount": "100.00",
    "currency": "USD",
    "environment": "sandbox",
    "next_action": {
      "type": "redirect",
      "url": "https://payniko.com/example-authentication-step",
      "expires_at": "2026-11-01T12:30:00.000Z"
    },
    "card": {
      "bin6": "435509",
      "last_four": "4242",
      "network": "visa",
      "bank": "Test Bank"
    },
    "failure": null,
    "created_at": "2026-11-01T12:00:00.000Z",
    "expires_at": "2026-11-01T12:30:00.000Z",
    "completed_at": null
  }
}

مع bin6 لرقم BIN من نوع hosted · مثال

{
  "merchant_order_id": "ORDER-2026-003",
  "amount": "100.00",
  "currency": "USD",
  "webhook_url": "https://merchant.example/webhooks/payments",
  "success_url": "https://merchant.example/payment/success",
  "fail_url": "https://merchant.example/payment/failure",
  "customer": {
    "email": "[email protected]",
    "phone": "+37061234567"
  },
  "bin6": "540061"
}

تفسير الاستجابة

الحالةالإجراء الموصى به
requires_actionأعِد توجيه المتصفح إلى next_action.url (لمصادقة 3-D Secure، أو لصفحة الدفع في حالة BIN من نوع hosted).
processingانتظر إشعار Webhook ولا تُنشئ دفعة جديدة.
succeededنفّذ طلب الشراء.
failedاعرض failure.message واطلب من العميل استخدام بطاقة أخرى.
  • قد تستغرق المكالمة عدة ثوانٍ بينما تتواصل Payniko مع البنك. استخدم مهلة لا تقل عن 60 ثانية، وبعد انتهاء المهلة أعد إرسال الطلب بالـ Idempotency-Key نفسه والمتن نفسه. وبينما لا يزال الطلب الأول قيد التنفيذ (حتى 3 دقائق)، تعيد إعادة المحاولة 409 0055503 مع metadata.retry_class: retry_same_request: انتظر بضع ثوانٍ وأعد إرسال الطلب نفسه.
  • لرقم BIN من نوع hosted، يُتجاهل أي card مُرسل ويُكمل العميل الدفع على صفحة الدفع.
  • إذا كانت هناك دفعة قائمة لقيمة merchant_order_id، أو أُرسل Idempotency-Key نفسه مع متن مختلف، يُرفض الطلب بالرمز 409 0055503.
  • تُرفض بيانات البطاقة غير الصالحة بالرمز 409 0055234. ويحدد metadata.developer_detail الحقل المعني: card_number_invalid أو card_expiry_invalid أو card_cvv_invalid. واطلب من العميل إعادة إدخال بيانات البطاقة.
  • تُرفض بيانات الاتصال المفقودة أو غير الصالحة بـ 422 1000001؛ ويحدد 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.

الحقلالنوعالوصف
payment_idstring · LUVEI-ABCD-123456معرّف الدفعة في Payniko . احفظه مع طلب الشراء.
merchant_order_idstringمرجع طلب الشراء لديك، كما أُرسل تمامًا.
flowstringhosted لـ Hosted API، وdirect لـ Direct API.
hosteddirect
statusstringالحالة الحالية للدفعة؛ راجع جدول الحالات أدناه.
requires_actionprocessingsucceededfailedexpiredcancelled
amountstringالمبلغ بمنزلتين عشريتين.
currencystringعملة المبلغ؛ وهي العملة الرئيسية للنظام إذا لم تُحدَّد عملة.
environmentstringبيئة المفتاح الذي أنشأ الدفعة.
sandboxlive
next_actionobject | nullالإجراء التالي المطلوب من العميل. ولا يُعيَّن إلا ما دامت قيمة status هي requires_action.
next_action.typestringدائمًا redirect.
next_action.urlstring · uriافتح هذا الرابط في متصفح العميل تمامًا كما أُعيد. ولا تُنشئه أو تعدّله مطلقًا.
next_action.expires_atstring · date-timeالموعد النهائي لاستخدام الرابط. في Direct API، يمثل نهاية خطوة 3-D Secure وقد يسبق expires_at الخاص بالدفعة؛ وفي Hosted API، يمثل الموعد النهائي للدفعة. وبعد انقضائه، تنتقل الدفعة إلى حالتها التالية وتصبح قيمة next_action هي null.
cardobject | nullبيانات البطاقة المقنّعة، بمجرد معرفتها.
card.bin6string | nullالأرقام الستة الأولى.
card.last_fourstring | nullالأرقام الأربعة الأخيرة.
card.networkstring | nullشبكة البطاقة.
visamastercardamextroyunionpaydiscoverjcbdinersmaestro
card.bankstring | nullالبنك المُصدِر، إن كان معروفًا.
failureobject | nullسبب الفشل. ولا يُعيَّن إلا عندما تكون قيمة status هي failed.
failure.codestringرمز السبب لاستخدامه في منطق تطبيقك؛ راجع القائمة أدناه.
failure.messagestringرسالة قصيرة مناسبة لعرضها على العميل كنص عادي.
created_atstring · date-timeوقت إنشاء الدفعة.
expires_atstring · date-timeالموعد النهائي لإتمام الدفعة.
completed_atstring · date-time | nullوقت وصول الدفعة إلى حالة نهائية.

قد تكون قيمة next_action هي null لفترة وجيزة حتى في الحالة requires_action؛ وفي هذه الحالة استعلم عن الدفعة مجددًا. ولا يُعاد مطلقًا رقم البطاقة الكامل أو رمز CVV أو تاريخ انتهاء الصلاحية أو اسم حامل البطاقة. وقد تكون قيمتا card.network وcard.bank هي null عندما تكونان غير معروفتين.

بيانات customer المرسلة عند إنشاء الدفع ليست جزءًا من هذا الكائن ولا تظهر في الـ webhooks.

دفعة · مثال

{
  "data": {
    "payment_id": "3c2d7a4e-5b1f-4e8a-9d06-7f2b1c9e4a35",
    "merchant_order_id": "ORDER-2026-002",
    "flow": "direct",
    "status": "requires_action",
    "amount": "100.00",
    "currency": "USD",
    "environment": "sandbox",
    "next_action": {
      "type": "redirect",
      "url": "https://payniko.com/example-authentication-step",
      "expires_at": "2026-11-01T12:30:00.000Z"
    },
    "card": {
      "bin6": "435509",
      "last_four": "4242",
      "network": "visa",
      "bank": "Test Bank"
    },
    "failure": null,
    "created_at": "2026-11-01T12:00:00.000Z",
    "expires_at": "2026-11-01T12:30:00.000Z",
    "completed_at": null
  }
}

حالات دفعات Direct API

الحالةالمعنىالإجراء الموصى به
requires_actionيجب أن يفتح المتصفح 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 Securerequires_action → succeededpayment.succeeded
الرفضfailedpayment.failed
استجابة البنك قيد الانتظارprocessingلا شيء حتى صدور النتيجة النهائية
لا استجابة من البنك؛ وانتهت مهلة المحاولةprocessing → failed / expiredpayment.failed
لم يبدأ العميل الدفع وانقضى الموعد النهائي (إذا انصرف العميل عن الدفع في منتصفه، فقد تنتهي الدفعة بالحالة failed)expiredpayment.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
  1. اختر المفتاح السري المرتبط بالمفتاح المحدد في key_version. وهو المفتاح الذي أنشأ الدفعة، حتى إذا استُبدلت المفاتيح منذ ذلك الحين.

  2. كوّن السلسلة من القيم بعد فك ترميز URL. وإذا لم يكن failure_code موجودًا، فأبقِ على النقطة الأخيرة.

  3. احسب HMAC-SHA256 وقارنه بـ sig باستخدام مقارنة ثابتة الزمن. وتأكّد من أن payment_id يخص إحدى دفعاتك.

  4. استعلم عن الدفعة باستخدام 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_idstring · pathإلزاميقيمة payment_id المُعادة عند إنشاء الدفعة. ويمكن لأي مفتاح من البيئة نفسها الاستعلام عنها.
./payniko.sh GET '/api/payments/3c2d7a4e-5b1f-4e8a-9d06-7f2b1c9e4a35'
import {api} from './payniko.mjs';

const payment = await api('GET', '/api/payments/3c2d7a4e-5b1f-4e8a-9d06-7f2b1c9e4a35');
console.log(payment);
<?php
require __DIR__ . '/payniko.php';

$payment = payniko_api('GET', '/api/payments/3c2d7a4e-5b1f-4e8a-9d06-7f2b1c9e4a35');
print_r($payment);
from payniko import api

payment = api("GET", "/api/payments/3c2d7a4e-5b1f-4e8a-9d06-7f2b1c9e4a35")
print(payment)

الأخطاء المحتملة

إشعارات Webhook الخاصة بالدفع

تُرسل الأحداث التالية إلى webhook_url الخاص بالدفعة. ويُرسل كل حدث دفع مرة واحدة على الأكثر لكل دفعة.

الحدثالمعنى
payment.succeededحالة الدفعة succeeded. نفّذ طلب الشراء.
payment.failedانتهت الدفعة دون نجاح: failed أو expired أو cancelled. راجع data.status.
payment.updatedلا يزال مطلوبًا اتخاذ إجراء من العميل (requires_action)، على سبيل المثال بعد رفض مؤقت. وهذه ليست نتيجة نهائية.
refund.* · payment.voided · dispute.*الأحداث اللاحقة لهذه الدفعة (الاسترداد والإبطال والنزاعات). راجع إشعارات Webhook.

payment.succeeded

{
  "event_id": "e0743f4c-a297-4bf0-8085-ceb7d9d5e181",
  "event_type": "payment.succeeded",
  "environment": "sandbox",
  "occurred_at": "2026-11-01T12:02:00.000Z",
  "data": {
    "payment_id": "LUVEI-PAYN-123456",
    "merchant_order_id": "ORDER-2026-001",
    "flow": "hosted",
    "status": "succeeded",
    "amount": "100.00",
    "currency": "USD",
    "environment": "sandbox",
    "next_action": null,
    "card": {
      "bin6": "400000",
      "last_four": "0002",
      "network": "visa",
      "bank": null
    },
    "failure": null,
    "created_at": "2026-11-01T12:00:00.000Z",
    "expires_at": "2026-11-02T12:00:00.000Z",
    "completed_at": "2026-11-01T12:02:00.000Z"
  }
}

تحقّق من كل إشعار Webhook: يجب أن تساوي قيمة HMAC-SHA256 لـ X-Timestamp + "." + raw body، المحسوبة بالمفتاح السري المرتبط بالمفتاح الذي أنشأ الدفعة، قيمةَ X-Signature، وأن يكون X-Timestamp ضمن 300 ثانية. احفظ كل event_id مرة واحدة، وأرسل استجابة 2xx دون تأخير. وللاطلاع على أمثلة الشيفرة وسلوك إعادة المحاولة، راجع إشعارات Webhook.

الأخطاء

الرمزHTTPالمعنىالإجراء الموصى به
1000001422الطلب غير صالح: حقل غير معروف، أو صيغة غير صالحة، أو سلسلة استعلام في طلب POST، أو معلمة استعلام غير مدعومة، أو متن ليس كائن JSON. ويتضمن errors الحقول المعنية.صحّح الحقول المذكورة.
1000009422الترويسة Idempotency-Key مفقودة أو غير صالحة.أرسل مع كل طلب POST مفتاحًا يتكون من 8 إلى 128 حرفًا من A-Z a-z 0-9 _ -.
0055502401 / 403رُفض الوصول. 401: الترويسة X-Api-Key أو X-Merchant-Code مفقودة، أو إحدى الترويسات مكررة أو غير صحيحة الصيغة، أو المفتاح غير معروف أو مُلغى، أو رمز التاجر غير مطابق، أو أُرسلت X-Environment بقيمة لا تطابق المفتاح. 403: الحساب أو صلاحية الوصول إلى Direct API غير مفعّلة.تحقّق من الترويسات ومن مفتاح API. لا تُعِد المحاولة تلقائيًا.
0055503409تعارض في Direct API: توجد دفعة مسبقًا لقيمة merchant_order_id هذه، أو استُخدم Idempotency-Key مع متن مختلف أو لدفعة عبر Hosted API، أو أن الطلب الأصلي لا يزال قيد التنفيذ.استعلم عن الدفعة باستخدام GET. ولا تُعِد إرسال سوى طلب مطابق تمامًا.
0055210422رقم BIN لا يتكون من ستة أرقام.أرسل الأرقام الستة الأولى من رقم البطاقة.
0055234409بيانات البطاقة غير صالحة: رقم البطاقة أو تاريخ الانتهاء أو CVV. يحدد metadata.developer_detail الحقل: card_number_invalid أو card_expiry_invalid أو card_cvv_invalid.اطلب من العميل إعادة إدخال بيانات البطاقة.
0055233409رقم BIN غير معروف.اطلب من العميل استخدام بطاقة أخرى. وهذا ليس رفضًا من البنك.
0055230409لا يمكن استخدام هذه البطاقة لهذه الدفعة.اطلب من العميل استخدام بطاقة أخرى. وهذا ليس رفضًا من البنك.
0055231409هذه البطاقة غير مقبولة لهذه الدفعة.اطلب من العميل استخدام بطاقة أخرى.
0055221409تم بلوغ أحد حدود هذه البطاقة.استخدم بطاقة أخرى أو أعِد المحاولة لاحقًا.
0055235422المبلغ غير مقبول (Direct API وفحص BIN). ويتضمن amount_limit القيمتين min وmax عند توفرهما.عدّل المبلغ.
0055001409حسابك غير جاهز لهذه الدفعة. مع مفتاح sandbox، يعني هذا أيضًا أن محاكاة sandbox غير متاحة مؤقتًا (developer_detail: sandbox_unavailable)، وهو مكافئ لـ 9000013.تواصل مع فريق الدعم.
1000002404غير موجود: المسار غير معروف، أو منتج API هذا غير مفعّل على المنصة. أما API المعطّل لحسابك فيعيد 403 بدلًا من ذلك (9000040 في Hosted API و0055502 في Direct API).تحقق من المسار؛ وتواصل مع الدعم إذا كان ينبغي أن يكون API متاحًا.
1000011503الخدمة غير متاحة مؤقتًا.أعِد إرسال الطلب نفسه. واستعلم عن الموارد القائمة قبل إنشاء موارد جديدة.

تتضمن أخطاء 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.
4355090000089999Hosted API: processing بدون خطوة 3-D Secure. Direct API: خطوة 3-D Secure تجريبية ثم processing.

مع Direct API، تعيد بطاقة الاختبار المرفوضة failed مباشرة في استجابة الإنشاء؛ ولا يظهر requires_action المؤقت إلا في صفحة الدفع الخاصة بـ BIN من نوع hosted. اعتمد في قراراتك على الـ webhook أو على GET.

مثال متكامل

خادم Node.js مبسّط يغطي فحص BIN، وإنشاء الدفعة، وإعادة التوجيه إلى 3-D Secure، ورابط العودة، وإشعار Webhook. ويستخدم الدالة المساعدة للطلبات ودوال التحقق الواردة في إشعارات Webhook ورابط العودة.

server.mjs

// server.mjs: Direct API from start to end (Node.js 20+, no dependencies).
import http from 'node:http';
import {randomUUID} from 'node:crypto';
import {api} from './payniko.mjs';                  // request helper (Authentication)
import {verifyWebhook} from './verify-webhook.mjs'; // webhook check (Webhooks)
import {verifyReturn} from './verify-return.mjs';   // return URL check (Return URL)

const site = 'https://merchant.example';
const orders = new Map();      // use your database
const seenEvents = new Set();  // use your database
const readBody = async (req) => { const parts = []; for await (const p of req) parts.push(p); return Buffer.concat(parts); };

const reply = (res, data) => {
  res.writeHead(200, {'Content-Type': 'application/json'});
  res.end(JSON.stringify(data.status === 'requires_action' && data.next_action
    ? {redirect: data.next_action.url} : {status: data.status, failure: data.failure}));
};

http.createServer(async (req, res) => {
  const url = new URL(req.url, site);
  // 1. BIN check, 2. create the payment. Your own HTTPS form posts the card here.
  if (req.method === 'POST' && url.pathname === '/pay') {
    const form = JSON.parse((await readBody(req)).toString('utf8'));
    const bin6 = form.number.slice(0, 6);
    let mode = null;
    try {
      mode = (await api('GET', `/api/payments/bin/${bin6}?amount=100.00&currency=USD`)).data.payment_mode;
    } catch (error) {
      if (error.status === undefined || error.status >= 500 || error.status === 429) throw error;
    }
    if (mode !== 'direct' && mode !== 'hosted') { res.writeHead(422).end('Please use another card.'); return; }
    const orderId = 'ORDER-' + Date.now();
    const key = randomUUID();
    orders.set(orderId, {key, status: 'new'});
    const body = {
      merchant_order_id: orderId, amount: '100.00', currency: 'USD',
      customer: {email: form.email, phone: form.phone},
      webhook_url: `${site}/webhooks/payniko`,
      success_url: `${site}/payment/success`, fail_url: `${site}/payment/failure`,
    };
    if (mode === 'direct') {
      body.card = {number: form.number, holder_name: form.holder_name,
        expiry_month: form.expiry_month, expiry_year: form.expiry_year, cvv: form.cvv};
    } else {
      body.bin6 = bin6;                               // payer enters the card on the payment page
    }
    const {data} = await api('POST', '/api/payments/direct', body, key);
    Object.assign(orders.get(orderId), {paymentId: data.payment_id, status: data.status});
    reply(res, data);                                 // 3. browser goes to next_action.url
    return;
  }
  // Return URL: show a page, but trust only the webhook or a GET.
  if (req.method === 'GET' && (url.pathname === '/payment/success' || url.pathname === '/payment/failure')) {
    const signed = verifyReturn(url.searchParams, () => process.env.API_SECRET);
    let status = 'unknown';
    if (signed) status = (await api('GET', `/api/payments/${url.searchParams.get('payment_id')}`)).data.status;
    res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
    res.end(status === 'succeeded' ? 'Thank you, payment received.' : 'Payment not completed.');
    return;
  }
  // Webhook: verify, store event_id once, answer 2xx fast.
  if (req.method === 'POST' && url.pathname === '/webhooks/payniko') {
    const raw = await readBody(req);
    if (!verifyWebhook(raw, req.headers, process.env.API_SECRET)) { res.writeHead(401).end(); return; }
    const event = JSON.parse(raw.toString('utf8'));
    if (!seenEvents.has(event.event_id)) {          // use a unique index in your database
      seenEvents.add(event.event_id);
      const order = orders.get(event.data.merchant_order_id);
      if (order && event.event_type.startsWith('payment.')) order.status = event.data.status;
    }
    res.writeHead(204).end();
    return;
  }
  res.writeHead(404).end();
}).listen(8080);