authentication اپلیکیشن پذیرنده
سوپر اپلیکیشن مهراد
این سند نحوهٔ دریافت و استفاده از access token سرور-به-سرور را توضیح میدهد. این token برای تأیید پرداخت و مدیریت سفارشها از سمت سرور پذیرنده استفاده میشود و به هیچ کاربر خاصی وابسته نیست.
کاربرد این روش
عملیات حساس سفارش — تأیید پرداخت، ثبت ارسال و تحویل، اصلاح و لغو — باید از سرور پذیرنده انجام شود و نباید به token کاربری که در حال خرید است وابسته باشد. برای این کار یک اپلیکیشن پذیرنده تعریف میشود که با client_id و client_secret خود token دریافت میکند.
تفاوت آن با ورود یکپارچه: token SSO هویت یک کاربر را حمل میکند و عمر ۱۲ ساعته دارد، در حالی که token اپلیکیشن پذیرنده هویت کسبوکار پذیرنده را حمل میکند و ۲ ساعت اعتبار دارد.
endpointهای قابل استفاده با این token
| endpoint | کاربرد |
|---|---|
POST /gateway/order/payment/verify | تأیید پرداخت |
GET /gateway/order/payment/{authority}/status | استعلام وضعیت پرداخت |
POST /gateway/order/{order_id}/shipped | ثبت ارسال |
POST /gateway/order/{order_id}/deliver | ثبت تحویل |
POST /gateway/order/{order_id}/update | اصلاح کاهشی سفارش |
POST /gateway/order/{order_id}/cancel | لغو سفارش |
GET /gateway/order/{order_id} | دریافت جزئیات و تاریخچهٔ سفارش |
POST /gateway/order/draft/request | ایجاد خرید حضوری |
شرح کامل این endpointها در مستندات سفارش و مستندات خرید حضوری آمده است.
ایجاد پرداخت (
POST /gateway/order/payment/request) با این token کار نمیکند؛ آن endpoint به Access Token کاربر از SSO و scopeorderنیاز دارد.
پیشنیازها
| مورد | توضیح |
|---|---|
client_id | ID اپلیکیشن پذیرنده |
client_secret | کلید محرمانه؛ تنها یکبار هنگام ساخت یا بازتولید نمایش داده میشود |
| لیست سفید آیپی (اختیاری) | در صورت تنظیم، requestها فقط از همان آیپیها پذیرفته میشوند |
این مقادیر هنگام راهاندازی در اختیار پذیرنده قرار میگیرد. client_secret به صورت بازگشتناپذیر نگهداری میشود و قابل بازیابی نیست؛ در صورت مفقود شدن باید کلید جدیدی تولید شود.
مرحله ۱ — دریافت token
/gateway/merchant-app/tokenAuthentication: این request بدون header Authorization ارسال میشود؛ اعتبارسنجی با client_id و client_secret در body انجام میشود.
ورودی
client_ idstringالزامیclient_ secretstringالزامی{
"client_id": "8f3c2a1b-4d5e-4f7a-8b9c-0d1e2f3a4b5c",
"client_secret": "YOUR_CLIENT_SECRET"
}Response
{
"success": true,
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 7200
},
"meta": null
}مقدار expires_in برابر ۷۲۰۰ ثانیه (۲ ساعت) است. این مقدار با مدت اعتبار token SSO (۴۳۲۰۰ ثانیه) متفاوت است.
مرحله ۲ — استفاده از token
token در header Authorization و با پیشوند Bearer ارسال میشود:
POST /gateway/order/payment/verify
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
{
"authority": "8cdb1c8c-bd3c-4f0f-9c8b-9c72c1f2e41c",
"amount": 11300000
}ID پذیرنده از خود token استخراج میشود؛ نیازی به ارسال آن در body نیست.
مدیریت چرخهٔ عمر token
- token باید در حافظهٔ سرور نگهداری و تا پیش از انقضا از همان استفاده شود؛ نباید برای هر request، token جدید دریافت شود.
- token باید کمی زودتر از انقضا (مثلاً ۵ دقیقه) تازه شود تا در میانهٔ ارسال requestها منقضی نشود.
- اگر response با کد ۴۰۱ برگردانده شود، یکبار token تازه دریافت و همان request دوباره ارسال میشود. اگر باز هم کد ۴۰۱ برگردانده شود، اعتبارنامهها باید بررسی شوند و نباید حلقهٔ تلاش مجدد ایجاد شود.
- در پیادهسازی چندنخی یا چندپردازشی، باید از دریافت همزمان token توسط چند request جلوگیری شود؛ دریافت token باید پشت یک قفل (lock) قرار گیرد.
import threading
import time
import requests
BASE_URL = "https://api.mehrad.app"
class MerchantAppClient:
def __init__(self, client_id: str, client_secret: str):
self._client_id = client_id
self._client_secret = client_secret
self._token = None
self._expires_at = 0.0
self._lock = threading.Lock()
def _fetch_token(self) -> str:
response = requests.post(
f"{BASE_URL}/gateway/merchant-app/token",
json={"client_id": self._client_id, "client_secret": self._client_secret},
timeout=10,
)
response.raise_for_status()
data = response.json()["data"]
self._token = data["access_token"]
# refresh 5 minutes before the real expiry
self._expires_at = time.time() + data["expires_in"] - 300
return self._token
def access_token(self) -> str:
with self._lock:
if not self._token or time.time() >= self._expires_at:
return self._fetch_token()
return self._token
def verify_payment(self, authority: str, amount: int) -> dict:
response = requests.post(
f"{BASE_URL}/gateway/order/payment/verify",
headers={"Authorization": f"Bearer {self.access_token()}"},
json={"authority": authority, "amount": amount},
timeout=10,
)
response.raise_for_status()
return response.json()["data"]const BASE_URL = "https://api.mehrad.app";
let cached = { token: null, expiresAt: 0 };
async function getAccessToken(clientId, clientSecret) {
if (cached.token && Date.now() < cached.expiresAt) return cached.token;
const res = await fetch(`${BASE_URL}/gateway/merchant-app/token`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ client_id: clientId, client_secret: clientSecret }),
});
if (!res.ok) throw new Error(`token request failed: ${res.status}`);
const { data } = await res.json();
cached = {
token: data.access_token,
// refresh 5 minutes before the real expiry
expiresAt: Date.now() + (data.expires_in - 300) * 1000,
};
return cached.token;
}لیست سفید آیپی
اگر برای اپلیکیشن پذیرنده لیست سفید آیپی تنظیم شده باشد، هم دریافت token و هم requestهای بعدی فقط از همان آدرسها پذیرفته میشوند و در غیر این صورت response آن با کد ۴۰۳ و خطای MERCHANT_APP_IP_ADDRESS_NOT_ALLOWED برمیگردد. در تأیید پرداخت و استعلام وضعیت، بهجای آن response با کد ۴۰۱ و خطای HTTP_EXCEPTION برگردانده میشود. پیش از تغییر زیرساخت یا افزودن سرور جدید، لازم است آدرسهای جدید اعلام شود.
فهرست خطاها
اگر request با خطا مواجه شود، response آن کدی غیر از ۲xx دارد و success در آن false است. کد خطا در فیلد error.code و متن قابل نمایش در error.message برگردانده میشود. ساختار کامل خطا در بخش مقدمه آمده است.
دریافت token
/gateway/merchant-app/token| کد خطا | HTTP | توضیح و اقدام پیشنهادی |
|---|---|---|
| VALIDATION_ERROR | 422 | client_id یا client_secret ارسال نشده، یا client_id قالب UUID ندارد. |
| MERCHANT_APP_INVALID_CREDENTIALS | 401 | client_id وجود ندارد یا client_secret نادرست است. |
| MERCHANT_APP_NOT_ACTIVE | 400 | اپلیکیشن غیرفعال است؛ فعالسازی آن نیازمند هماهنگی با مهراد است. |
| MERCHANT_APP_IP_ADDRESS_NOT_ALLOWED | 403 | آیپی request در لیست سفید نیست. |
{
"success": false,
"error": {
"code": "MERCHANT_APP_INVALID_CREDENTIALS",
"message": "اطلاعات کلاینت خارجی (client_id / client_secret) نامعتبر است.",
"details": null
}
}استفاده از token
| کد خطا | HTTP | توضیح و اقدام پیشنهادی |
|---|---|---|
| HTTP_EXCEPTION | 401 | header Authorization ارسال نشده یا قالب Bearer <token> ندارد (message برابر Not authenticated). در تأیید پرداخت و استعلام وضعیت، هر مشکل token — نبود header، token نامعتبر یا منقضی، اپلیکیشن غیرفعال یا آیپی خارج از لیست سفید — با همین کد و message برابر Unauthorized برگردانده میشود. |
| MERCHANT_APP_TOKEN_EXPIRED_OR_INVALID | 401 | token نامعتبر یا منقضی است، یا اپلیکیشن غیرفعال شده است؛ token تازه باید دریافت شود. |
| MERCHANT_APP_IP_ADDRESS_NOT_ALLOWED | 403 | آیپی request در لیست سفید نیست. |
{
"success": false,
"error": {
"code": "MERCHANT_APP_TOKEN_EXPIRED_OR_INVALID",
"message": "توکن کلاینت خارجی نامعتبر یا منقضی شده است.",
"details": null
}
}خطاهای مربوط به خود سفارشها (مانند ORDER_ALREADY_PAID یا ORDER_AMOUNT_NOT_MATCHED) در مستندات سفارش فهرست شدهاند.
نکات امنیتی
client_secretو token فقط باید روی سرور نگهداری شوند و نباید در کد سمت مرورگر، اپلیکیشن موبایل یا مخزن کد قرار گیرند.- token نباید در لاگها ثبت شود.
- ارتباط باید فقط روی HTTPS برقرار شود.
- برای محیط تست،
https://dev-api.mehrad.appو اعتبارنامههای همان محیط استفاده میشود.
چکلیست پیادهسازی
- دریافت و نگهداری امن
client_idوclient_secret - پیادهسازی دریافت token از
POST /gateway/merchant-app/token - نگهداری token در حافظه و تازهسازی پیش از انقضای ۲ ساعته
- افزودن header
Authorization: Bearerبه requestهای محافظتشده - مدیریت response با کد ۴۰۱ با یکبار تازهسازی token و تکرار request
- هماهنگی لیست سفید آیپی برای سرورهای عملیاتی
- جداسازی اعتبارنامههای محیط تست و عملیاتی