دليل ربط Udeyou OTP بلغة Node.js ================================ الرابط الأساسي https://udeyou.com/api/v1 التوثيق https://udeyou.com/docs الصيغة JSON في الطلب والرد (Content-Type: application/json) المصادقة Authorization: Bearer أو X-API-Key: هذه النسخة العربية، والنسخة الإنجليزية هي المرجع الأساسي. الكود هو نفسه المعروض في https://udeyou.com/docs ومُختبر على الواجهة. 1) قبل البدء 1) أنشئ حسابًا: https://udeyou.com/register تبدأ الحسابات الجديدة تجربة مجانية لمدة شهرين: 2000 رسالة في الشهر الأول ومثلها في الشهر الثاني. 2) أنشئ مفتاح API من لوحة التحكم: https://udeyou.com/dashboard#keys يظهر المفتاح مرة واحدة فقط، فاحفظه في متغير البيئة UDEYOU_API_KEY على خادمك. 3) أنواع المفاتيح udy_test_... لا يرسل بريدًا ولا يخصم رصيدًا، ويعمل حتى برصيد صفر. يعود الرمز في الرد ضمن الحقل code مع "test_mode": true. للتطوير والاختبار الآلي. udy_live_... يرسل البريد فعليًا ويخصم رصيدًا واحدًا لكل رمز، ويُعاد الرصيد تلقائيًا إذا تعذّر الإرسال. كل مفتاح يرى رموز نوعه فقط. 4) أبقِ المفتاح على خادمك، ولا تضعه في JavaScript المتصفح أو تطبيق الجوال أو مستودع الكود. 2) المتطلبات والتشغيل Node.js 18 أو أحدث (fetch مدمج)، دون حزم إضافية. احفظ الملف أدناه باسم udeyou.mjs ثم شغّل: UDEYOU_API_KEY=udy_test_... node udeyou.mjs user@example.com مع المفتاح التجريبي يكتمل التحقق تلقائيًا، ومع المفتاح الحي يطلب المثال إدخال الرمز الذي وصل إلى البريد. 3) كيف يعمل المثال - الدوال sendOtp(to, options) و verifyOtp(otpId, code) و getBalance() تعيد الكائن data من الرد. - أي فشل يرفع UdeyouError ومعه status و code و retryAfter و attemptsRemaining و fields. انتهاء المهلة وأخطاء الاتصال تأتي بالحالة 0 والكود network_error. - في خادم Express أو Next.js احفظ otp_id في جلسة المستخدم، واعتمد data.to من رد التحقق بريدًا موثّقًا. 4) المثال الكامل ----- BEGIN udeyou.mjs ----- // Udeyou OTP - Node.js 18+ (built-in fetch, no dependencies) // Usage: UDEYOU_API_KEY=udy_test_... node udeyou.mjs user@example.com import { createInterface } from 'node:readline/promises'; const BASE_URL = (process.env.UDEYOU_BASE_URL ?? 'https://udeyou.com/api/v1').replace(/\/+$/, ''); const API_KEY = process.env.UDEYOU_API_KEY; class UdeyouError extends Error { constructor(status, error = {}) { super(error.message ?? `HTTP ${status}`); this.status = status; this.code = error.code ?? `http_${status}`; this.retryAfter = error.retry_after; // 429 responses this.attemptsRemaining = error.attempts_remaining; // invalid_code this.fields = error.fields; // validation_error } } async function udeyou(method, path, body) { let res; try { res = await fetch(BASE_URL + path, { method, headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, body: body ? JSON.stringify(body) : undefined, signal: AbortSignal.timeout(20_000), }); } catch (err) { // Timeout, DNS or connection failure throw new UdeyouError(0, { code: 'network_error', message: err.message }); } const json = await res.json().catch(() => ({})); if (!res.ok || json.success !== true) throw new UdeyouError(res.status, json.error ?? {}); return json.data; } const sendOtp = (to, options = {}) => udeyou('POST', '/otp/send', { to, ...options }); const verifyOtp = (otpId, code) => udeyou('POST', '/otp/verify', { otp_id: otpId, code }); const getBalance = () => udeyou('GET', '/balance'); // 1) Send the code const to = process.argv[2] ?? 'user@example.com'; try { const sent = await sendOtp(to, { lang: 'ar' }); console.log(`Sent ${sent.otp_id} to ${sent.to} (expires in ${sent.expires_in} s)`); // 2) Verify the code the user typed. Test keys (udy_test_) return it in the response. let code = sent.code; if (!code) { const rl = createInterface({ input: process.stdin, output: process.stdout }); code = (await rl.question('Code: ')).trim(); rl.close(); } const result = await verifyOtp(sent.otp_id, code); console.log(`Verified ${result.to} at ${result.verified_at}`); } catch (err) { if (!(err instanceof UdeyouError)) throw err; console.error(`Error ${err.status} ${err.code}: ${err.message}`); if (err.retryAfter) console.error(`Retry after ${err.retryAfter} s`); if (err.attemptsRemaining !== undefined) console.error(`Attempts remaining: ${err.attemptsRemaining}`); if (err.fields) console.error(err.fields); process.exitCode = 1; } ----- END udeyou.mjs ----- 5) مرجع الواجهة POST /otp/send -> 201 Created to نص إلزامي بريد المستلم، حتى 190 حرفًا. sender_name نص اختياري اسم المرسل في صندوق الوارد، من 2 إلى 60 حرفًا. الافتراضي اسم حسابك. lang نص اختياري ar (الافتراضي) أو en: لغة الرسالة الافتراضية والتذييل. code_length رقم اختياري من 4 إلى 10 خانات. الافتراضي 6. expires_in رقم اختياري الصلاحية بالثواني من 60 إلى 3600. الافتراضي 300. code نص اختياري رمز تحدده بنفسك من 4 إلى 10 أحرف أو أرقام، وإلا تولّد Udeyou رمزًا رقميًا. template نص اختياري نص عادي حتى 1000 حرف ويجب أن يحتوي {{code}}، ويمكن استخدام {{sender_name}} و {{minutes}}. subject نص اختياري حتى 120 حرفًا ويقبل المتغيرات نفسها. reply_to نص اختياري بريد يستقبل ردود العميل. أرسل code_length و expires_in كأرقام في JSON، فالقيمة النصية "6" تُرفض بالخطأ 422. إرسال رمز جديد إلى البريد نفسه يُلغي الرموز السابقة. وتنتهي كل رسالة بسطر يوضح أنها أُرسلت نيابةً عن sender_name عبر Udeyou OTP. data: otp_id و to و status ("sent") و mode ("live" أو "test") و expires_in و expires_at (ISO 8601 بتوقيت UTC) و credits_remaining، ومع المفتاح التجريبي test_mode و code (تعامل مع الرمز كنص فقد يبدأ بصفر). POST /otp/verify -> 200 OK code نص إلزامي الرمز الذي أدخله العميل (4 إلى 10 أحرف أو أرقام). otp_id نص موصى به المعرّف المعاد من /otp/send. to نص بديل بريد المستلم بدل otp_id، ويُتحقق من آخر رمز أُرسل إليه. data: verified (true) و otp_id و to و verified_at. اعتمد data.to بريدًا موثّقًا. الرمز يُستخدم مرة واحدة، وينتهي بعد expires_in ثانية، ويُقفل بعد 5 محاولات خاطئة. GET /balance -> 200 OK data: company و mode و credits. GET /health -> 200 OK (دون مفتاح) {"success": true, "status": "ok"} النجاح: {"success": true, "data": {...}} الفشل: {"success": false, "error": {"code": "...", "message": "...", ...}} 6) أكواد الأخطاء (اعتمد على error.code، أما message فنص توضيحي بالإنجليزية) 400 invalid_json جسم الطلب ليس كائن JSON. 400 invalid_code الرمز خاطئ، و attempts_remaining = المحاولات المتبقية. 401 invalid_api_key المفتاح مفقود أو بصيغة خاطئة أو غير معروف أو ملغى. 402 insufficient_credits مفتاح حي والرصيد صفر. 403 account_suspended الحساب موقوف. 404 otp_not_found لا يوجد رمز مطابق لـ otp_id أو to ضمن نوع المفتاح. 404 not_found المسار غير موجود. 405 method_not_allowed طريقة HTTP غير مدعومة لهذا المسار. 409 otp_already_used الرمز استُخدم مسبقًا. 410 otp_expired انتهت الصلاحية، أو أُرسل رمز أحدث إلى البريد نفسه. 422 validation_error حقول غير صالحة (التفاصيل في error.fields عند /otp/send). 429 rate_limited أكثر من 60 طلب إرسال في الدقيقة (retry_after). 429 resend_too_soon أقل من 60 ثانية منذ آخر رمز لهذا البريد (retry_after). 429 recipient_limit 5 رموز لهذا البريد خلال الساعة الأخيرة (retry_after). 429 too_many_attempts استُنفدت المحاولات (5)، اطلب رمزًا جديدًا. 500 server_error خطأ غير متوقع في الخادم. 502 delivery_failed تعذّر تسليم البريد، وأُعيد الرصيد. 7) الحدود (لكل حساب، ويتشاركها المفتاح الحي والتجريبي) - طلبات الإرسال: 60 في الدقيقة (rate_limited و retry_after 60). - الفاصل بين رمزين للبريد نفسه: 60 ثانية (resend_too_soon و retry_after 60). - الرموز للبريد نفسه: 5 في الساعة (recipient_limit و retry_after 3600). - المحاولات الخاطئة: 5 لكل رمز (too_many_attempts، ثم اطلب رمزًا جديدًا). - صلاحية الرمز: 300 ثانية افتراضيًا، ومن 60 إلى 3600 عبر expires_in (otp_expired). طلبات الإرسال وحدها تدخل في حد الدقيقة. وقيمة retry_after هي طول النافذة بالثواني وليست الوقت المتبقي: انتظرها ثم أعد المحاولة. 8) أفضل الممارسات - احفظ المفتاح في متغير بيئة على الخادم فقط. - المفتاح التجريبي للتطوير والاختبار الآلي، والحي للإنتاج فقط. - احفظ otp_id في جلسة المستخدم على الخادم وتحقق به. - اعتمد data.to من رد التحقق، لا البريد المرسل من التطبيق. - تعامل مع الرمز كنص، فقد يبدأ بصفر. - اعرض attempts_remaining، وعطّل زر إعادة الإرسال 60 ثانية بعد كل إرسال. - اضبط مهلة لكل طلب، ولا تعرض تفاصيل أخطاء الشبكة للمستخدم. - ألغِ أي مفتاح يُشتبه في تسرّبه من لوحة التحكم ثم أنشئ مفتاحًا جديدًا. - لا تسجّل الرموز أو المفاتيح في السجلات. الدعم: https://udeyou.com/contact (أرفق otp_id وكود الخطأ إن وُجد)