رفتن به محتوا

API نمایندگی

API نمایندگی راهی است که یک شریک تجاری با آن مشتریان خودش را روی نسین از سامانه‌های خودش اداره می‌کند — وب‌سایتش، پنل صورت‌حسابش، یا یک نصب WHMCS — بدون این‌که کسی وارد داشبورد شود.

این محصولی جدا از REST API مشتری است. مخاطب متفاوت، اعتبارنامه‌ی متفاوت، پیشوند متفاوت. یک کلید API پنل (nsin_…) نمی‌تواند آن را صدا بزند، و توکن نماینده هم نمی‌تواند API مشتری را صدا بزند.

نسین ارائه‌دهنده است، نه نماینده. «نماینده‌ی شماره ۱» وجود ندارد. مشتری مستقیم نسین reseller_id = null دارد؛ مشتریان شما شناسه‌ی شما را دارند.

یک توکن بسازید و آن را به‌عنوان اعتبارنامه‌ی bearer بفرستید:

Terminal window
curl -H "Authorization: Bearer nsin_live_xxxxxxxxxxxx_yyyy…" \
https://api.nsin.cloud/reseller/v1/ping

GET /reseller/v1/ping بررسی ارزان زنده‌بودن و درستی اعتبارنامه است. TestConnection در WHMCS به همین نگاشت می‌شود.

ساختار توکن ماشین چنین است:

nsin_live_<key_id>_<secret>
└ عمومی ┘ └ محرمانه ┘

key_id به‌صورت متن ساده ذخیره و ایندکس می‌شود و نمایش و ثبت آن در لاگ بی‌خطر است — نسین با همان توکن را پیدا می‌کند. اما بخش محرمانه فقط به‌صورت هش bcrypt ذخیره می‌شود و دقیقاً یک بار، هنگام ساخت، نمایش داده می‌شود. هیچ چیزی در سامانه نمی‌تواند بعداً آن را بازیابی کند؛ اگر گمش کردید، توکن را باطل کنید و یکی دیگر بسازید.

دو نوع اعتبارنامه در همان هدر Authorization: Bearer می‌آیند و میان‌افزار با پیشوند آن‌ها را از هم تشخیص می‌دهد:

اعتبارنامهاستفاده‌کنندهنحوه‌ی بررسی
nsin_live_…کلاینت‌های ماشینی — WHMCS، سایت خودتانkey_id جست‌وجو و بخش محرمانه با هش bcrypt آن بررسی می‌شود
هر چیز دیگرداشبورد نمایندگیمثل JWT نشست تفسیر می‌شود

در هر دو حالت هندلر به یک هویت واحد می‌رسد، و هر هندلر پیش از دست‌زدن به هر ردیفی بررسی می‌کند که مشتری یا دامنه‌ای که نام برده‌اید مال شماست.

شما کیف پول خودتان را به ارزش اسمی از طریق درگاه زرین‌پال شارژ می‌کنید — این بخش جزو این API نیست. هر چیزی که مشتریانتان می‌خرند در نهایت از همان موجودی و با قیمت رسمی نسین پرداخت می‌شود. هیچ‌وقت تخفیف نمایندگی در لحظه‌ی خرید وجود ندارد.

کیف پول مشتری شما واقعی است، اما او هرگز نمی‌تواند آن را ببیند یا به آن دست بزند: هر نقطه‌ی پایانی کیف پول، فاکتور، قیمت و تیکت برای کاربری که reseller_id دارد 403 برمی‌گرداند.

هر فیلد مالی عدد صحیح و بر حسب ریال است (*_rials). برای نمایش به تومان بر ۱۰ تقسیم کنید. هرگز عدد اعشاری نفرستید.

خرید پلن، کیف پول مشتری را خودکار شارژ می‌کند

Section titled “خرید پلن، کیف پول مشتری را خودکار شارژ می‌کند”

لازم نیست پیش از خرید پلن، کیف پول مشتری را شارژ کنید. ساخت یا تغییر سرویس حساب می‌کند که کیف پول مشتری چقدر کم دارد، دقیقاً همان مقدار را از کیف پول شما به او منتقل می‌کند و بعد از او کسر می‌کند — همه در یک تراکنش پایگاه داده. این انتقال در هر دو دفتر با عنوان reseller_autofund ثبت می‌شود.

POST /reseller/v1/clients/{clientId}/credit همچنان وجود دارد و همچنان مفید است — برای گذاشتن موجودی اولیه روی یک حساب، یا شارژ خارج از فرآیند خرید. فقط پیش‌نیاز نیست؛ شارژ قبلی صرفاً باعث می‌شود انتقال خودکار کاری نکند.

برخلاف خرید، credit هرگز شما را به منفی نمی‌برد: نمی‌توانید بیش از موجودی فعلی خود منتقل کنید.

آن 402 که واقعاً می‌بینید

Section titled “آن 402 که واقعاً می‌بینید”

وقتی خریدی به‌خاطر پول شکست می‌خورد، کیف پولی که کم آورده مال شماست، نه مشتری:

{ "error": "This purchase would take your wallet past your credit limit. Top up, or ask NSIN to raise the limit.",
"code": "credit_limit_reached" }

کیف پول خودتان را از طریق زرین‌پال شارژ کنید، یا از نسین بخواهید سقف اعتبارتان را بالا ببرد. شارژکردن مشتری کمکی نمی‌کند — و خودش هم 402 می‌گیرد.

نسین می‌تواند به شما سقف اعتبار منفی بدهد. این مقدار به‌صورت پیش‌فرض صفر است، یعنی به‌طور پیش‌فرض هر خریدی که توانش را نداشته باشید دقیقاً همین خطا را می‌دهد. اگر سقف اعتبار داشته باشید و بعد از پایان مهلت همچنان منفی بمانید، همه‌ی دامنه‌های مجموعه‌ی شما — مال خودتان و مشتریانتان — تعلیق می‌شوند.

مشتریان شما سقف دارند، نه کنتور

Section titled “مشتریان شما سقف دارند، نه کنتور”

مشتری یک نماینده پرداخت به‌ازای مصرف ندارد. ترافیک بیش از حجم پلن به هیچ‌کس صورت‌حساب نمی‌شود: ردیف مصرف با مبلغ صفر ثبت می‌شود و هیچ کیف پولی کسر نمی‌شود. وقتی به سقف پلن برسد، به‌جای صورت‌حساب تعلیق می‌شود.

پس موجودی مشتری هرگز به‌خاطر ترافیک منفی نمی‌شود، و نباید روی درآمد مصرف مازاد حساب کنید — چنین چیزی وجود ندارد. پلن بالاتر را بفروشید.

هر نقطه‌ی پایانی که پول جابه‌جا می‌کند، هدر Idempotency-Key را الزامی می‌داند.

Terminal window
curl -X POST https://api.nsin.cloud/reseller/v1/clients/7/credit \
-H "Authorization: Bearer $NSIN_RESELLER_TOKEN" \
-H "Idempotency-Key: 5f9c1b7e-3a2d-4c11-9d40-6f2f0a1b8e33" \
-H "Content-Type: application/json" \
-d '{"amount_rials": 5000000}'

کلید هر رشته‌ی یکتای تولیدشده توسط شماست؛ یک UUID کافی است. کلیدهای بلندتر از ۱۹۱ کاراکتر رد می‌شوند. تکرار یک کلید همان پاسخ اولیه را برمی‌گرداند و عملیات را دوباره انجام نمی‌دهد.

این صرفاً یک ادب اختیاری نیست. قطع‌شدن ارتباط هنگام فراخوانی credit سرور شما را در وضعیتی می‌گذارد که نمی‌داند پول جابه‌جا شده یا نه، و تلاش دوباره‌ی ساده‌لوحانه دو بار اعتبار می‌دهد — و دقیقاً معنای «تایم‌اوت» همین است.

چهار ویژگی که باید رویشان حساب کنید:

  • تکرار همیشگی است، نه پنجره‌ای. هیچ انقضایی وجود ندارد: کلیدی که یک سال پیش استفاده کرده‌اید امروز هم همان پاسخ اولیه را برمی‌گرداند. برای هر عملیات منطقی یک کلید تازه بسازید و هرگز آن‌ها را زمان‌بندی‌شده بازیافت نکنید.
  • تکرار برچسب دارد. پاسخ تکراری هدر Idempotent-Replay: true را همراه دارد. این تنها راه تشخیص تکرار از اجرای تازه است — وضعیت و بدنه عمداً یکسان‌اند.
  • عملیات ناموفق چیزی ثبت نمی‌کند، پس با همان کلید آزادانه قابل تکرار است. اگر خریدی به‌خاطر کم‌بودن موجودی شما برگشت خورد، شارژ کنید و با همان کلید دوباره بفرستید.
  • استفاده از یک کلید برای درخواستی متفاوت 422 است، نه تکرار. کلید در برابر نقطه‌ی پایانی و هش بدنه‌ی درخواست بررسی می‌شود. پاسخ‌دادن به درخواستی متفاوت با پاسخ ذخیره‌شده یعنی به شما بگوییم انتقال دوم موفق بوده، در حالی که اصلاً اجرا نشده است.

کلیدها به شما محدودند، پس دو شریک تجاری با UUID یکسان هرگز تداخل نمی‌کنند.

به‌صورت پیش‌فرض ۳۰۰ درخواست در دقیقه به‌ازای هر اعتبارنامه.

سهمیه روی اعتبارنامه حساب می‌شود، نه روی نماینده — یک یکپارچه‌سازی از کنترل خارج‌شده نباید شما را از داشبورد خودتان بیرون بیندازد، و باطل‌کردن همان توکن برای متوقف‌کردنش کافی است.

با عبور از سهمیه، پاسخ 429 است:

{ "error": "Rate limit exceeded. Slow down and retry shortly.", "code": "rate_limited" }

عقب‌نشینی کنید و دوباره تلاش کنید؛ اگر هدر Retry-After بود رعایتش کنید. اگر در حال درون‌ریزی انبوه هستید، به‌جای حلقه‌زدن روی تک‌تک رکوردها از نقاط پایانی دسته‌ای (records/import و records/scan-import) استفاده کنید — پخش‌کردن درخواست روی زونی با تعداد واقعی رکورد این محدودیت را فعال می‌کند.

نقاط پایانی فهرست، page (از ۱) و per_page (بین ۱ تا ۱۰۰، پیش‌فرض ۲۵) می‌گیرند و ردیف‌ها را زیر data به‌همراه یک شیء meta برمی‌گردانند:

{
"data": [ ],
"meta": { "page": 1, "per_page": 25, "total": 137 }
}

بدنه‌ی هر خطا به شکل {"error": "پیام قابل خواندن"} است، گاهی همراه با یک "code" ماشین‌خوان.

کدمعنی
400ورودی نامعتبر یا بدشکل، از جمله نبودِ Idempotency-Key روی نقطه‌ی پایانی مالی.
401اعتبارنامه نبود، بدشکل بود، منقضی یا باطل شده بود.
402موجودی کیف پول شما کفاف نمی‌دهد — "code": "credit_limit_reached". کیف پول خودتان را شارژ کنید؛ شارژ مشتری کمکی نمی‌کند.
403احراز هویت شدید، اما هدف مال شما نیست — یا اصلاً نماینده نیستید.
404چنین منبعی وجود ندارد — و منبعی که متعلق به نماینده‌ی دیگری است هم عمداً همین پاسخ را می‌گیرد.
409منبع در وضعیتی است که این عملیات را نمی‌پذیرد. رایج‌ترین مورد "code": "domain_disabled" است.
422یک Idempotency-Key با بدنه‌ی درخواستِ متفاوت دوباره استفاده شده است.
429محدودیت نرخ — "code": "rate_limited".
503انبار آمار در دسترس نیست — عمداً خطا برمی‌گردد نه صفحه‌ای پر از صفر، چون صفحه‌ی صفر از «ترافیک مشتریانتان قطع شده» قابل تشخیص نیست.

در زبان رو به نماینده، حساب یا دامنه غیرفعال و فعال می‌شود — هرگز «معلق». دامنه‌ی غیرفعال فقط‌خواندنی است: پیکربندی‌اش را نگه می‌دارد و همه‌ی بخش‌هایش را می‌توانید بخوانید، اما نوشتن تا زمان فعال‌شدن دوباره 409 می‌گیرد.

این عمداً 409 است و نه 403. شما کاملاً حق این تغییر را دارید؛ دامنه صرفاً در وضعیتی است که نمی‌تواند آن را بپذیرد، و گفتن «دسترسی ندارید» به شریک تجاری او را به نتیجه‌گیری کاملاً اشتباهی می‌رساند.

مشتری را نمی‌توانید حذف کنید

Section titled “مشتری را نمی‌توانید حذف کنید”

مسیری برایش وجود ندارد — نه مسیری محافظت‌شده، اصلاً هیچ مسیری. به‌جایش حساب را غیرفعال کنید.

هر مسیر این API با یک ماتریس ایزولاسیون مستأجرها آزموده می‌شود: با اعتبارنامه‌ی یک نماینده روی شناسه‌های نماینده‌ی دیگر صدا زده می‌شود و باید 403 یا 404 بدهد، بدون هیچ داده‌ای از طرف مقابل در بدنه.

چیزی که مشتریان شما هرگز دریافت نمی‌کنند

Section titled “چیزی که مشتریان شما هرگز دریافت نمی‌کنند”

همه‌ی پیامک‌ها و ایمیل‌های تجاری درباره‌ی مشتری شما — قطعی دامنه، انقضای پلن، منفی‌شدن موجودی — به شما فرستاده می‌شود، نه به او.

دو مورد عمداً مستثنا هستند و همچنان به مشتری می‌روند: رمز یک‌بارمصرف ورود و بازنشانی رمز عبور. هر دو بازیابی حساب‌اند و مسدودکردنشان مشتری شما را برای همیشه از پنل بیرون می‌گذارد.

پیشوند /reseller/v1 است. تغییرات افزایشی — نقطه‌ی پایانی جدید، فیلد اختیاری جدید، عضو جدید در enum یک پاسخ — داخل همان v1 منتشر می‌شوند و به‌عنوان تغییر شکننده اعلام نمی‌شوند. حذف یک فیلد، سخت‌گیرانه‌ترکردن یک نوع، یا تغییر یک کد وضعیت به معنای /reseller/v2 خواهد بود.

کلاینت خود را طوری بنویسید که فیلدهای ناشناخته‌ی پاسخ را نادیده بگیرد.