المطورون/البداية
المصادقة
يحمل كل استدعاء لـ API رقم التاجر (Merchant ID) ومفتاح API الخاص بك.
أما إرسال الطلب (POST/orders) فيحمل كذلك توقيعًا، حتى لا يستطيع أحد تعديله أو تكراره.
ذات صلة: المنتجات والطلبات · الأمان · البداية
#بيانات الاعتماد
| البيان | مثال | هل هو سري؟ |
|---|---|---|
| Merchant ID | M00000001 | لا. يعرّف بك فقط. |
| مفتاح API | cvb2b_... | نعم. هو كلمة مرورك ومفتاح التوقيع في الوقت نفسه. |
- تمنحك CardV رقم Merchant ID عند الموافقة على حسابك.
- ينشئ صاحب الحساب (Owner) مفتاح API من البوابة (راجع مفاتيح API).
- لكل من Live وSandbox مفاتيح مختلفة، ولا يعمل مفتاح إحدى البيئتين في الأخرى.
- لا تعمل الواجهة إلا بعد موافقة CardV على شركتك.
عندها يُظهر
GET/accountالقيمة"api_access_enabled": true.
#الترويسات
أرسل هاتين الترويستين مع كل استدعاء:
X-Merchant-Id: M00000001
X-Api-Key: cvb2b_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxومع POST/orders أرسل أيضًا هذه الثلاث:
X-Timestamp: 1790000000
X-Nonce: 0123456789abcdef0123456789abcdef
X-Signature: fd55fa31a3b6817d5368903dbb4bad4458381e024c17e688580bac96577acfedX-Timestampهو الوقت الحالي بتوقيت Unix بالثواني. يجب ألا يبعد عن ساعة CardV أكثر من 5 دقائق (300 ثانية).X-Nonceقيمة عشوائية لا تستخدمها أكثر من مرة. استخدم 32 حرفًا عشوائيًا بالنظام الست عشري (hex).X-Signatureمشروح في توقيع الطلب، ويجب أن يكون hex بأحرف صغيرة.
استدعاءات GET لا تحتاج إلى توقيع.
#توقيع الطلب
حوّل طلبك إلى نص JSON مرة واحدة فقط. ستوقّع هذه البايتات نفسها وترسلها كما هي.
احسب بصمة المحتوى:
BODY_HASH= قيمة SHA-256 للمحتوى، بصيغة hex بأحرف صغيرة.اجمع هذه الأسطر الخمسة مع فاصل سطر (
\n) بينها، دون فاصل سطر في النهاية:POST /api/v1/orders <X-Timestamp> <X-Nonce> <BODY_HASH>وقّع النص:
X-Signature= قيمة HMAC-SHA256 لهذا النص، باستخدام مفتاح API كاملًا كمفتاح. اكتب النتيجة بصيغة hex بأحرف صغيرة.
الخطأ الأكثر شيوعًا هو توقيع نسخة من JSON ثم إرسال نسخة أخرى.
فمثلًا {"a":1} و{"a": 1} بايتات مختلفة.
تأكد من أن مكتبة HTTP لديك لا تغيّر المحتوى بعد توقيعه.
قيم اختبار. تحقق من برنامجك بهذه القيم. المفتاح هنا وهمي.
API key cvb2b_TEST_ONLY_not_a_real_key_0123456789abcdef
Method POST
Path /api/v1/orders
X-Timestamp 1790000000
X-Nonce 0123456789abcdef0123456789abcdefالمحتوى (108 بايت، في سطر واحد، دون فاصل سطر في النهاية):
{"external_order_id":"TEST-0001","items":[{"sku_id":"S000001","quantity":1,"expected_unit_price":"9.2500"}]}النتائج المتوقعة:
BODY_HASH b0545ae25d54b219f27d8bd90e4dcbf26cf0d491f53982da67b8eef0a1a59960
X-Signature fd55fa31a3b6817d5368903dbb4bad4458381e024c17e688580bac96577acfed#أمثلة برمجية
تعطي كل الأمثلة نتيجة قيم الاختبار نفسها. حمّل المفتاح من مخزن الأسرار، لا من الشيفرة.
cURL (bash + OpenSSL)
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)
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)
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
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. ويبدو المحتوى هكذا:
{"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.
- أنشئ مفتاحًا وسمِّه. لا تعرض البوابة المفتاح في هذه الخطوة.
- اعرضه (Reveal). ترسل إليك CardV رمزًا من 6 أرقام بالبريد، صالحًا لمدة 10 دقائق (5 محاولات). أدخله لترى المفتاح كاملًا، واحفظه فورًا. تُسجَّل كل عملية عرض.
- عطّل المفتاح عندما لا تعود بحاجة إليه. يسري التعطيل فورًا ولا يمكن التراجع عنه.
لتغيير المفاتيح دون توقف الخدمة:
- أنشئ مفتاحًا جديدًا واعرضه.
- انشره على كل خوادمك.
- تأكد من البوابة أن المفتاح القديم لم يعد مستخدمًا.
- عطّل المفتاح القديم.
غيّر المفاتيح مرة واحدة سنويًا على الأقل، وكلما غادر شخص كان يستطيع الوصول إليها. وإذا احتمل تسرّب مفتاح، فعطّله أولًا ثم ابحث في الأمر.
هل لديك سؤال حول التكامل؟ راسلنا على [email protected] مع ذكر Merchant ID ومعرّف الطلب أو الاستدعاء.