المطورون/البداية

المصادقة

يحمل كل استدعاء لـ API رقم التاجر (Merchant ID) ومفتاح API الخاص بك. أما إرسال الطلب (POST/orders) فيحمل كذلك توقيعًا، حتى لا يستطيع أحد تعديله أو تكراره.

ذات صلة: المنتجات والطلبات · الأمان · البداية

#بيانات الاعتماد

البيانمثالهل هو سري؟
Merchant IDM00000001لا. يعرّف بك فقط.
مفتاح APIcvb2b_...نعم. هو كلمة مرورك ومفتاح التوقيع في الوقت نفسه.
  • تمنحك CardV رقم Merchant ID عند الموافقة على حسابك.
  • ينشئ صاحب الحساب (Owner) مفتاح API من البوابة (راجع مفاتيح API).
  • لكل من Live وSandbox مفاتيح مختلفة، ولا يعمل مفتاح إحدى البيئتين في الأخرى.
  • لا تعمل الواجهة إلا بعد موافقة CardV على شركتك. عندها يُظهر GET/account القيمة "api_access_enabled": true.

#الترويسات

أرسل هاتين الترويستين مع كل استدعاء:

HTTP
X-Merchant-Id: M00000001
X-Api-Key: cvb2b_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

ومع POST/orders أرسل أيضًا هذه الثلاث:

HTTP
X-Timestamp: 1790000000
X-Nonce: 0123456789abcdef0123456789abcdef
X-Signature: fd55fa31a3b6817d5368903dbb4bad4458381e024c17e688580bac96577acfed
  • X-Timestamp هو الوقت الحالي بتوقيت Unix بالثواني. يجب ألا يبعد عن ساعة CardV أكثر من 5 دقائق (300 ثانية).
  • X-Nonce قيمة عشوائية لا تستخدمها أكثر من مرة. استخدم 32 حرفًا عشوائيًا بالنظام الست عشري (hex).
  • X-Signature مشروح في توقيع الطلب، ويجب أن يكون hex بأحرف صغيرة.

استدعاءات GET لا تحتاج إلى توقيع.

#توقيع الطلب

  1. حوّل طلبك إلى نص JSON مرة واحدة فقط. ستوقّع هذه البايتات نفسها وترسلها كما هي.

  2. احسب بصمة المحتوى: BODY_HASH = قيمة SHA-256 للمحتوى، بصيغة hex بأحرف صغيرة.

  3. اجمع هذه الأسطر الخمسة مع فاصل سطر (\n) بينها، دون فاصل سطر في النهاية:

    Text
    POST
    /api/v1/orders
    <X-Timestamp>
    <X-Nonce>
    <BODY_HASH>
  4. وقّع النص: X-Signature = قيمة HMAC-SHA256 لهذا النص، باستخدام مفتاح API كاملًا كمفتاح. اكتب النتيجة بصيغة hex بأحرف صغيرة.

الخطأ الأكثر شيوعًا هو توقيع نسخة من JSON ثم إرسال نسخة أخرى. فمثلًا {"a":1} و{"a": 1} بايتات مختلفة. تأكد من أن مكتبة HTTP لديك لا تغيّر المحتوى بعد توقيعه.

قيم اختبار. تحقق من برنامجك بهذه القيم. المفتاح هنا وهمي.

Text
API key      cvb2b_TEST_ONLY_not_a_real_key_0123456789abcdef
Method       POST
Path         /api/v1/orders
X-Timestamp  1790000000
X-Nonce      0123456789abcdef0123456789abcdef

المحتوى (108 بايت، في سطر واحد، دون فاصل سطر في النهاية):

JSON
{"external_order_id":"TEST-0001","items":[{"sku_id":"S000001","quantity":1,"expected_unit_price":"9.2500"}]}

النتائج المتوقعة:

Text
BODY_HASH    b0545ae25d54b219f27d8bd90e4dcbf26cf0d491f53982da67b8eef0a1a59960
X-Signature  fd55fa31a3b6817d5368903dbb4bad4458381e024c17e688580bac96577acfed

#أمثلة برمجية

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

cURL (bash + OpenSSL)

Shell
BASE_URL="https://sandbox.cardv.net"
REQ_PATH="/api/v1/orders"      # do not call this variable PATH
BODY='{"external_order_id":"SHOP-10001","items":'
BODY+='[{"sku_id":"S000001","quantity":1,"expected_unit_price":"9.2500"}]}'
TS=$(date +%s)
NONCE=$(openssl rand -hex 16)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | sed 's/^.*= //')
SIG=$(printf 'POST\n%s\n%s\n%s\n%s' "$REQ_PATH" "$TS" "$NONCE" "$BODY_HASH" \
  | openssl dgst -sha256 -hmac "$CARDV_API_KEY" -hex | sed 's/^.*= //')

curl -sS -X POST "$BASE_URL$REQ_PATH" \
  -H "Content-Type: application/json" \
  -H "X-Merchant-Id: $CARDV_MERCHANT_ID" \
  -H "X-Api-Key: $CARDV_API_KEY" \
  -H "X-Timestamp: $TS" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIG" \
  --data-raw "$BODY"

استخدم --data-raw وليس -d @file، لأن -d يحذف فواصل الأسطر فلا تطابق البايتات المرسلة ما وقّعته.

Python (requests)

Python
import hashlib, hmac, json, os, secrets, time
import requests

BASE_URL = "https://sandbox.cardv.net"
MERCHANT_ID = os.environ["CARDV_MERCHANT_ID"]
API_KEY = os.environ["CARDV_API_KEY"]
AUTH = {"X-Merchant-Id": MERCHANT_ID, "X-Api-Key": API_KEY}


def sign(path: str, body: bytes, ts: str, nonce: str) -> str:
    body_hash = hashlib.sha256(body).hexdigest()
    text = "\n".join(["POST", path, ts, nonce, body_hash])
    return hmac.new(API_KEY.encode(), text.encode(), hashlib.sha256).hexdigest()


def cardv_get(path: str, params=None) -> requests.Response:
    return requests.get(BASE_URL + path, params=params, headers=AUTH, timeout=30)


def cardv_post(path: str, payload: dict) -> requests.Response:
    body = json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode()
    ts, nonce = str(int(time.time())), secrets.token_hex(16)
    headers = {
        **AUTH,
        "Content-Type": "application/json",
        "X-Timestamp": ts,
        "X-Nonce": nonce,
        "X-Signature": sign(path, body, ts, nonce),
    }
    return requests.post(BASE_URL + path, data=body, headers=headers, timeout=30)


resp = cardv_post("/api/v1/orders", {
    "external_order_id": "SHOP-10001",
    "items": [{"sku_id": "S000001", "quantity": 1, "expected_unit_price": "9.2500"}],
})
print(resp.status_code, resp.json())

order_id = resp.json()["order"]["order_id"]
print(cardv_get(f"/api/v1/orders/{order_id}").json()["status"])

Node.js 18+ (built-in fetch)

Node.js
import crypto from "node:crypto";

const BASE_URL = "https://sandbox.cardv.net";
const { CARDV_MERCHANT_ID, CARDV_API_KEY } = process.env;
const AUTH = { "X-Merchant-Id": CARDV_MERCHANT_ID, "X-Api-Key": CARDV_API_KEY };

function sign(path, body, timestamp, nonce) {
  const bodyHash = crypto.createHash("sha256").update(body).digest("hex");
  const text = ["POST", path, timestamp, nonce, bodyHash].join("\n");
  return crypto.createHmac("sha256", CARDV_API_KEY).update(text, "utf8").digest("hex");
}

async function cardvPost(path, payload) {
  const body = Buffer.from(JSON.stringify(payload), "utf8"); // serialize once
  const timestamp = String(Math.floor(Date.now() / 1000));
  const nonce = crypto.randomBytes(16).toString("hex");
  const headers = {
    ...AUTH,
    "Content-Type": "application/json",
    "X-Timestamp": timestamp,
    "X-Nonce": nonce,
    "X-Signature": sign(path, body, timestamp, nonce),
  };
  const res = await fetch(BASE_URL + path, { method: "POST", headers, body });
  return { status: res.status, body: await res.json() };
}

console.log(await cardvPost("/api/v1/orders", {
  external_order_id: "SHOP-10001",
  items: [{ sku_id: "S000001", quantity: 1, expected_unit_price: "9.2500" }],
}));

PHP (التوقيع فقط)

PHP
<?php
function cardv_sign(
    string $apiKey, string $path, string $body, string $ts, string $nonce
): string {
    $text = implode("\n", ['POST', $path, $ts, $nonce, hash('sha256', $body)]);
    return hash_hmac('sha256', $text, $apiKey); // lowercase hex
}

// Send exactly $body.
$body = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$ts = (string) time();
$nonce = bin2hex(random_bytes(16));
$signature = cardv_sign($apiKey, '/api/v1/orders', $body, $ts, $nonce);

#الأخطاء

تُرجع كل هذه الأخطاء HTTP 403 (وليس 401)، باستثناء تجاوز حد الطلبات الذي يُرجع 429. ويبدو المحتوى هكذا:

JSON
{"detail": "Invalid HMAC signature."}
المشكلةما الذي تفعله
Merchant ID أو مفتاح API مفقود أو غير صحيحراجع الترويستين وتأكد من البيئة.
الوصول إلى API غير مفعّلانتظر موافقة CardV على شركتك.
عنوان IP لخادمك غير مسموح بهأضفه إلى قائمة عناوين IP المسموح بها في البوابة.
نقطة النهاية متاحة في البوابة فقطاستخدم البوابة. سبع نقاط نهاية فقط تعمل بالمفتاح.
التوقيع مفقود أو غير صحيحأصلح شيفرة التوقيع، واختبرها بقيم الاختبار.
الطابع الزمني قديم جدًا أو في المستقبلزامن ساعة خادمك (NTP).
قيمة nonce مستخدمة من قبلاستخدم قيمة nonce عشوائية جديدة مع كل طلب شراء.
طلبات كثيرة جدًا (429)انتظر عدد الثواني المذكور في Retry-After، ثم أعد المحاولة.

مهم:

  • عند إعادة إرسال طلب، أنشئ طابعًا زمنيًا وقيمة nonce وتوقيعًا جديدة. وأبقِ المحتوى كما هو. راجع إعادة المحاولة بأمان.
  • تُعدّ قيمة nonce مستخدمة حتى لو كان التوقيع خاطئًا.
  • تسجّل CardV المحاولات الفاشلة، وتنبّه فريقها إذا كثرت.

#قائمة عناوين IP المسموح بها

تضمن هذه القائمة ألا يستخدم مفتاحك إلا خوادمك. يديرها صاحب الحساب (Owner) من البوابة.

  • القائمة اختيارية. إن لم تضف أي قاعدة، تُقبل الاستدعاءات من أي عنوان IP.
  • بمجرد إضافة قاعدة واحدة، تحصل الاستدعاءات من العناوين الأخرى على HTTP 403.
  • تُكتب القواعد هكذا: 203.0.113.10/32 (IPv4) أو 2001:db8::/48 (IPv6).
  • تنطبق القائمة على استدعاءات مفتاح API فقط، لا على تسجيل الدخول إلى البوابة.
  • أضف كل عناوين خوادمك قبل تفعيلها، بما فيها بوابات NAT والمناطق الاحتياطية.

#مفاتيح API

يستطيع مفتاح API استخدام نقاط النهاية السبع الواردة في نظرة سريعة على الواجهة فقط. ومن بينها إرسال الطلبات وقراءة الأكواد، لذا احمِه كما تحمي كلمة مرور صاحب الحساب.

صاحب الحساب (Owner) وحده يدير المفاتيح، من البوابة عبر Integrations → API keys. كرّر ذلك بشكل منفصل في Live وفي Sandbox.

  1. أنشئ مفتاحًا وسمِّه. لا تعرض البوابة المفتاح في هذه الخطوة.
  2. اعرضه (Reveal). ترسل إليك CardV رمزًا من 6 أرقام بالبريد، صالحًا لمدة 10 دقائق (5 محاولات). أدخله لترى المفتاح كاملًا، واحفظه فورًا. تُسجَّل كل عملية عرض.
  3. عطّل المفتاح عندما لا تعود بحاجة إليه. يسري التعطيل فورًا ولا يمكن التراجع عنه.

لتغيير المفاتيح دون توقف الخدمة:

  1. أنشئ مفتاحًا جديدًا واعرضه.
  2. انشره على كل خوادمك.
  3. تأكد من البوابة أن المفتاح القديم لم يعد مستخدمًا.
  4. عطّل المفتاح القديم.

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

هل لديك سؤال حول التكامل؟ راسلنا على [email protected] مع ذكر Merchant ID ومعرّف الطلب أو الاستدعاء.