مستندات API ایراندرگاه
ایجاد و تأیید تراکنشهای پرداخت آنلاین با یک Bearer Token، ساختار پاسخ یکسان، و نمونهکد آماده در ۶ زبان برنامهنویسی.
مقدمه
به API درگاه پرداخت ایراندرگاه خوش آمدید! این API با استاندارد snake_case برای تمامی فیلدهای ورودی و خروجی، امکان ایجاد تراکنشهای پرداخت آنلاین و تأیید آنها را فراهم میکند.
شما میتوانید از نمونه کدهای موجود در سمت چپ صفحه استفاده کنید. کدهای نمونه در زبانهای مختلف برنامهنویسی ارائه شدهاند!
ویژگیهای جدید
- ✅ توکن واحد: احراز هویت با یک Bearer Token (با پیشوند
idg_live_/idg_test_) - ✅ چرخش امن توکن: ۲۴ ساعت پنجرهی grace خودکار پس از هر rotation تا عملیات بدون قطعی انجام شود
- ✅ ابطال اضطراری: امکان باطل کردن فوری توکن از پنل کاربری در صورت نشت
- ✅ استاندارد نامگذاری: تمامی فیلدها با نامگذاری
snake_case(مانندorder_id,callback_url,ref_id) - ✅ ساختار پاسخ یکسان: همه پاسخها شامل
success,data,message,status_codeوtimestamp - ✅ Idempotency-Key: جلوگیری از تراکنشهای تکراری با هدر
- ✅ مدیریت خطای پیشرفته: ساختار خطا با جزئیات و دستهبندی شده
- ✅ جلوگیری از Race Condition: قفل یکتا بودن
order_idدر سطح دیتابیس
احراز هویت
ایران درگاه برای احراز هویت از یک توکن واحد استفاده میکند که برای ترمینال شما صادر و در اختیار شما قرار میگیرد.
فرمت توکن
توکنها با پیشوندِ بامعنی صادر میشوند تا محیط عملیاتی و آزمایشی از روی نگاه قابل تشخیص باشند:
| پیشوند | محیط |
|---|---|
idg_live_... |
محیط عملیاتی (Production) |
idg_test_... |
محیط آزمایشی (Sandbox) |
پس از صدور، توکن شکل کلی idg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx دارد (حدود ۵۰ کاراکتر).
ارسال توکن
توکن را بهصورت مستقیم در هدر Authorization با پیشوند Bearer بفرستید:
curl "https://ipg.irandargah.com/v2/payments" \
-H "Authorization: Bearer idg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
<?php
$headers = [
'Authorization: Bearer idg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'Content-Type: application/json',
];
const headers = {
Authorization: "Bearer idg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Content-Type": "application/json",
};
req.Header.Set("Authorization", "Bearer idg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx")
req.Header.Set("Content-Type", "application/json")
headers = {
'Authorization': 'Bearer idg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
}
client.DefaultRequestHeaders.Add("Authorization", "Bearer idg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx");
دریافت توکن
صدور و چرخش توکن فقط پس از فعال شدن درگاه انجام میشود. برای دریافت توکن جدید:
- وارد پنل کاربری شوید
- به بخش «درگاهها» بروید
- در صورت نیاز به توکن جدید، با پشتیبانی تماس بگیرید — توکن یکبار در پیام پاسخ ارسال میشود
چرخش توکن (Rotation)
زمانی که توکن چرخش پیدا میکند (rotation)، توکن قبلی بهصورت خودکار به مدت ۲۴ ساعت همچنان معتبر میماند. این پنجره برای انتقال آرام بدون قطعی طراحی شده است:
| لحظه | حالت |
|---|---|
T + 0 |
توکن جدید صادر میشود؛ توکن قدیمی هنوز معتبر است |
T + [0, 24h) |
هر دو توکن کار میکنند؛ زمان مناسب برای انتقال تدریجی |
T + 24h |
توکن قدیمی بهطور کامل باطل میشود — فقط توکن جدید کار میکند |
در طول این ۲۴ ساعت، هر درخواستی که با توکن قدیمی ارسال شود، در پاسخ خود این هدرهای هشدار را دریافت میکند:
| هدر | توضیح |
|---|---|
X-Token-Deprecated |
مقدار true — یعنی این توکن در حال انقضاست |
X-Token-Deprecated-Expires-At |
زمان دقیق انقضای توکن قدیمی (ISO 8601) |
اگر در سرور خود این هدرها را رصد کنید، میتوانید پیش از انقضا متوجه شوید کدام instance هنوز به توکن قدیمی متصل است.
ابطال اضطراری (Emergency Revoke)
اگر مشکوک شدید توکن لو رفته (مثلاً در گیتهاب کامیت کردهاید یا در گفتگوی تلگرام دیده شده):
- وارد پنل کاربری شوید
- ترمینال موردنظر را باز کنید ← دکمهی «مدیریت کلید امضا»
- در پایین صفحه، بخش قرمز «ناحیهی خطر» ← دکمهی «باطل کن — توکن لو رفته» را بزنید
پس از ابطال، با پشتیبانی تماس بگیرید تا توکن جدید برایتان صادر شود.
محیط آزمایشی (Sandbox)
آدرس پایه محیط آزمایشی
https://sandbox.irandargah.com
محیط آزمایشی برای تست API اختیار شماست. این محیط دقیقاً مشابه محیط عملیاتی است با تفاوتهای زیر:
تفاوتها با محیط عملیاتی
| ویژگی | محیط عملیاتی | محیط آزمایشی |
|---|---|---|
| آدرس پایه | ipg.irandargah.com | sandbox.irandargah.com |
| توکن احراز هویت | توکن واقعی | توکن تست (از پنل سندباکس) |
| تراکنشها | تراکنش واقعی با بانک | تراکنش شبیهسازی شده |
| کارتهای بانکی | کارتهای واقعی | کارتهای تستی (جدول زیر) |
| هزینه تراکنش | کارمزد واقعی محاسبه میشود | بدون هزینه |
ساختار آدرسها
محیط آزمایشی در زیردامنه جداگانه قرار دارد و ساختار کاملاً مشابه محیط عملیاتی دارد:
| آدرس | محیط عملیاتی | محیط آزمایشی |
|---|---|---|
| آدرس پایه | https://ipg.irandargah.com |
https://sandbox.irandargah.com |
| ایجاد تراکنش | https://ipg.irandargah.com/v2/payments |
https://sandbox.irandargah.com/v2/payments |
| تائید تراکنش | https://ipg.irandargah.com/v2/verifications |
https://sandbox.irandargah.com/v2/verifications |
کارتهای تستی
برای تست در محیط آزمایشی، از کارتهای زیر استفاده کنید:
| شماره کارت | نتیجه | CVV2 | تاریخ انقضا | توضیحات | نوع خطا |
|---|---|---|---|---|---|
6037997123456789 |
✅ | 123 | 05/10 | تراکنش موفق | |
6219861012345678 |
❌ | 456 | 05/10 | پرداخت ناموفق | failed |
5041721098765432 |
❌ | 789 | 05/10 | موجودی ناکافی | insufficient |
6273811122334455 |
❌ | 321 | 05/10 | انقضای زمان تراکنش | timeout |
نحوه استفاده
تمام آدرسهای API بدون تغییر در محیط آزمایشی قابل استفاده هستند، فقط:
- آدرس پایه را تغییر دهید
- از توکن تست استفاده کنید
- از کارتهای تستی استفاده کنید
مثال: ایجاد پرداخت در محیط آزمایشی
تنها تفاوت محیط آزمایشی با محیط عملیاتی در آدرس پایه و توکن است — همان درخواست و همان پاسخ. کافی است این دو را در زمان بیلد روی هر محیطی که میخواهید ست کنید:
# Production
BASE_URL="https://ipg.irandargah.com"; TOKEN="YOUR_PRODUCTION_TOKEN"
# یا Sandbox
BASE_URL="https://sandbox.irandargah.com"; TOKEN="YOUR_SANDBOX_TOKEN"
curl -X POST "$BASE_URL/v2/payments" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"amount": 100000,
"order_id": "ORDER-12345",
"callback_url": "https://yoursite.com/callback"
}'
<?php
$environment = 'sandbox'; // 'production' یا 'sandbox'
$config = [
'production' => [
'base_url' => 'https://ipg.irandargah.com',
'token' => 'YOUR_PRODUCTION_TOKEN',
],
'sandbox' => [
'base_url' => 'https://sandbox.irandargah.com',
'token' => 'YOUR_SANDBOX_TOKEN',
],
];
$baseUrl = $config[$environment]['base_url'];
$token = $config[$environment]['token'];
$paymentData = [
'amount' => 100000,
'order_id' => 'ORDER-' . time(),
'callback_url' => 'https://yoursite.com/callback',
'description' => 'خرید تستی',
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "{$baseUrl}/v2/payments");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer {$token}",
"Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($paymentData, JSON_UNESCAPED_UNICODE));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
if ($result['success']) {
$gatewayUrl = $result['data']['transaction']['gateway_url'];
header('Location: ' . $gatewayUrl);
}
?>
const environment = "sandbox"; // یا "production"
const config = {
production: {
base_url: "https://ipg.irandargah.com",
token: "YOUR_PRODUCTION_TOKEN",
},
sandbox: {
base_url: "https://sandbox.irandargah.com",
token: "YOUR_SANDBOX_TOKEN",
},
};
const { base_url, token } = config[environment];
const response = await fetch(`${base_url}/v2/payments`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: 100000,
order_id: `ORDER-${Date.now()}`,
callback_url: "https://yoursite.com/callback",
description: "خرید تستی",
}),
});
const result = await response.json();
if (result.success) {
window.location.href = result.data.transaction.gateway_url;
}
type EnvConfig struct{ BaseURL, Token string }
config := map[string]EnvConfig{
"production": {"https://ipg.irandargah.com", "YOUR_PRODUCTION_TOKEN"},
"sandbox": {"https://sandbox.irandargah.com", "YOUR_SANDBOX_TOKEN"},
}
env := config["sandbox"] // یا "production"
payload, _ := json.Marshal(map[string]interface{}{
"amount": 100000,
"order_id": fmt.Sprintf("ORDER-%d", time.Now().Unix()),
"callback_url": "https://yoursite.com/callback",
})
req, _ := http.NewRequest("POST", env.BaseURL + "/v2/payments", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer " + env.Token)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
import requests, time
config = {
'production': {'base_url': 'https://ipg.irandargah.com', 'token': 'YOUR_PRODUCTION_TOKEN'},
'sandbox': {'base_url': 'https://sandbox.irandargah.com', 'token': 'YOUR_SANDBOX_TOKEN'},
}
env = config['sandbox'] # یا 'production'
response = requests.post(
f"{env['base_url']}/v2/payments",
headers={
'Authorization': f"Bearer {env['token']}",
'Content-Type': 'application/json',
},
json={
'amount': 100000,
'order_id': f"ORDER-{int(time.time())}",
'callback_url': 'https://yoursite.com/callback',
'description': 'خرید تستی',
}
)
result = response.json()
if result['success']:
gateway_url = result['data']['transaction']['gateway_url']
var config = new Dictionary<string, (string BaseUrl, string Token)> {
["production"] = ("https://ipg.irandargah.com", "YOUR_PRODUCTION_TOKEN"),
["sandbox"] = ("https://sandbox.irandargah.com", "YOUR_SANDBOX_TOKEN"),
};
var env = config["sandbox"]; // یا "production"
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {env.Token}");
var body = new {
amount = 100000,
order_id = $"ORDER-{DateTimeOffset.Now.ToUnixTimeSeconds()}",
callback_url = "https://yoursite.com/callback",
description = "خرید تستی",
};
var content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json");
var response = await client.PostAsync($"{env.BaseUrl}/v2/payments", content);
مثال: تأیید پرداخت در محیط آزمایشی
BASE_URL="https://sandbox.irandargah.com"
TOKEN="YOUR_SANDBOX_TOKEN"
curl -X POST "$BASE_URL/v2/verifications" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"authority": "2025100121424146HC",
"amount": 100000
}'
<?php
$baseUrl = 'https://sandbox.irandargah.com';
$token = 'YOUR_SANDBOX_TOKEN';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "{$baseUrl}/v2/verifications");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer {$token}",
"Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'authority' => '2025100121424146HC',
'amount' => 100000,
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
const baseUrl = "https://sandbox.irandargah.com";
const token = "YOUR_SANDBOX_TOKEN";
const response = await fetch(`${baseUrl}/v2/verifications`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
authority: "2025100121424146HC",
amount: 100000,
}),
});
baseURL := "https://sandbox.irandargah.com"
token := "YOUR_SANDBOX_TOKEN"
payload, _ := json.Marshal(map[string]interface{}{
"authority": "2025100121424146HC",
"amount": 100000,
})
req, _ := http.NewRequest("POST", baseURL + "/v2/verifications", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer " + token)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
base_url = 'https://sandbox.irandargah.com'
token = 'YOUR_SANDBOX_TOKEN'
response = requests.post(
f'{base_url}/v2/verifications',
headers={
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json',
},
json={
'authority': '2025100121424146HC',
'amount': 100000,
}
)
var baseUrl = "https://sandbox.irandargah.com";
var token = "YOUR_SANDBOX_TOKEN";
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
var body = new { authority = "2025100121424146HC", amount = 100000 };
var content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json");
var response = await client.PostAsync($"{baseUrl}/v2/verifications", content);
دریافت توکن تست
برای دریافت توکن تست:
- به پنل مدیریت محیط آزمایشی مراجعه کنید:
https://sandbox.irandargah.com/dashboard - از بخش
API Tokensیک توکن جدید ایجاد کنید - توکن را در کدهای تست خود استفاده کنید
تست فرآیند کامل
1. ایجاد تراکنش
curl -X POST "https://sandbox.irandargah.com/v2/payments" \
-H "Authorization: Bearer YOUR_SANDBOX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"amount": 100000,
"order_id": "TEST-001",
"callback_url": "https://yoursite.com/callback"
}'
پاسخ موفق:
{
"success": true,
"data": {
"transaction": {
"authority": "20250107145623ABCDEF",
"gateway_url": "https://sandbox.irandargah.com/startpay/20250107145623ABCDEF",
"expires_at": "1404-10-17 15:11:23"
},
"meta": {
"request_id": "sandbox_678abc123",
"processing_time_ms": 45,
"environment": "sandbox",
"test_mode": true
}
},
"message": "تراکنش با موفقیت ایجاد شد",
"status_code": 100,
"timestamp": "1404-10-17 14:56:23"
}
2. هدایت به درگاه
از gateway_url دریافتی در پاسخ استفاده کنید.
3. پرداخت با کارت تستی
- شماره کارت:
6037997123456789 - CVV2:
123 - تاریخ انقضا:
05/10 - رمز دوم (
OTP): هر عددی (در محیط آزمایشی قبول میشود)
4. دریافت Callback
پارامترهای بازگشتی:
https://yoursite.com/callback?
authority=2025100121424146HC&
status_code=201&
message=پرداخت+در+انتظار+تایید+است&
amount=100000&
order_id=TEST-001&
ref_id=123456789&
card_pan=603799******6789
5. تأیید تراکنش
curl -X POST "https://sandbox.irandargah.com/v2/verifications" \
-H "Authorization: Bearer YOUR_SANDBOX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"authority": "2025100121424146HC",
"amount": 100000
}'
محدودیتهای محیط آزمایشی
| محدودیت | مقدار |
|---|---|
| تعداد تراکنش روزانه | نامحدود |
| مبلغ حداکثر | ۱۰,۰۰۰,۰۰۰ ریال |
| نرخ درخواست | ۱۰ درخواست در ثانیه |
| زمان انقضای توکن | ۳۰ روز |
فرآیند پرداخت
فرآیند پرداخت در ایراندرگاه شامل مراحل زیر است:
- ایجاد تراکنش: درخواست پرداخت با هدر اجباری
Idempotency-Key(کلید Idempotency) به آدرس/v2/paymentsارسال میشود - هدایت کاربر: کاربر با استفاده از
authorityدریافتی به صفحه پرداخت هدایت میشود - پرداخت: کاربر عملیات پرداخت را در صفحه بانک انجام میدهد
- بازگشت: کاربر به
callback_urlشما برگردانده میشود (پارامترهای بازگشتی امضا نشدهاند و فقط اطلاعاتیاند) - تأیید: ابتدا
authorityبازگشتی باauthorityذخیرهشدهی سفارش مقایسه میشود، سپس درخواست تأیید با مبلغ ذخیرهشدهی سفارش به آدرس/v2/verificationsارسال میشود (در همهی حالتها، حتی باdirect_verify)
کلید Idempotency
دریافت کلید Idempotency
curl -X GET "https://ipg.irandargah.com/v2/idempotency-key"
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://ipg.irandargah.com/v2/idempotency-key");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
$idempotencyKey = $result['idempotency_key'];
?>
fetch("https://ipg.irandargah.com/v2/idempotency-key")
.then((response) => response.json())
.then((data) => {
const idempotencyKey = data.idempotency_key;
console.log("Idempotency Key:", idempotencyKey);
});
resp, err := http.Get("https://ipg.irandargah.com/v2/idempotency-key")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
idempotencyKey := result["idempotency_key"].(string)
import requests
response = requests.get('https://ipg.irandargah.com/v2/idempotency-key')
data = response.json()
idempotency_key = data['idempotency_key']
using var client = new HttpClient();
var response = await client.GetAsync("https://ipg.irandargah.com/v2/idempotency-key");
var json = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<dynamic>(json);
string idempotencyKey = result.idempotency_key;
پاسخ موفق:
{
"success": true,
"idempotency_key": "txn_1a2b3c4d5e6f7g8h9i0j",
"expires_in": 86400,
"usage": "Include this key in the Idempotency-Key header for your payment request"
}
این آدرس برای دریافت یک کلید منحصر به فرد Idempotency استفاده میشود که برای جلوگیری از تراکنشهای تکراری ضروری است.
آدرس درخواست
https://ipg.irandargah.com/v2/idempotency-key
پارامترهای پاسخ
| پارامتر | نوع | توضیح |
|---|---|---|
success |
boolean | وضعیت موفقیت درخواست |
idempotency_key |
string | کلید منحصر به فرد با پیشوند txn_ |
expires_in |
integer | مدت زمان اعتبار کلید به ثانیه (۸۶۴۰۰ = ۲۴ ساعت) |
usage |
string | راهنمای استفاده از کلید |
پرداخت
ایجاد تراکنش پرداخت
curl -X POST "https://ipg.irandargah.com/v2/payments" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-key-123" \
-d '{
"amount": 100000,
"order_id": "ORDER-12345",
"callback_url": "https://merchant.com/callback",
"description": "خرید محصول آزمایشی",
"mobile": "09123456789"
}'
<?php
$data = [
'amount' => 100000,
'order_id' => 'ORDER-12345',
'callback_url' => 'https://merchant.com/callback',
'description' => 'خرید محصول آزمایشی',
'mobile' => '09123456789'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://ipg.irandargah.com/v2/payments");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer YOUR_API_TOKEN",
"Content-Type: application/json",
"Idempotency-Key: unique-key-123"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$result = json_decode($response, true);
?>
const payment = {
amount: 100000,
order_id: "ORDER-12345",
callback_url: "https://merchant.com/callback",
description: "خرید محصول آزمایشی",
mobile: "09123456789",
};
const response = await fetch("https://ipg.irandargah.com/v2/payments", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
"Idempotency-Key": "unique-key-123",
},
body: JSON.stringify(payment),
});
const data = await response.json();
console.log(data);
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
type PaymentRequest struct {
Amount int `json:"amount"`
OrderID string `json:"order_id"`
CallbackURL string `json:"callback_url"`
Description string `json:"description"`
Mobile string `json:"mobile"`
}
func main() {
payment := PaymentRequest{
Amount: 100000,
OrderID: "ORDER-12345",
CallbackURL: "https://merchant.com/callback",
Description: "خرید محصول آزمایشی",
Mobile: "09123456789",
}
jsonData, _ := json.Marshal(payment)
req, _ := http.NewRequest("POST", "https://ipg.irandargah.com/v2/payments", bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "unique-key-123")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
}
import requests
import json
payment_data = {
'amount': 100000,
'order_id': 'ORDER-12345',
'callback_url': 'https://merchant.com/callback',
'description': 'خرید محصول آزمایشی',
'mobile': '09123456789'
}
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
'Idempotency-Key': 'unique-key-123'
}
response = requests.post(
'https://ipg.irandargah.com/v2/payments',
headers=headers,
json=payment_data
)
result = response.json()
print(result)
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
public class PaymentRequest
{
[JsonProperty("amount")]
public int Amount { get; set; }
[JsonProperty("order_id")]
public string OrderId { get; set; }
[JsonProperty("callback_url")]
public string CallbackUrl { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("mobile")]
public string Mobile { get; set; }
}
public async Task<string> CreatePaymentAsync()
{
var payment = new PaymentRequest
{
Amount = 100000,
OrderId = "ORDER-12345",
CallbackUrl = "https://merchant.com/callback",
Description = "خرید محصول آزمایشی",
Mobile = "09123456789"
};
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_TOKEN");
client.DefaultRequestHeaders.Add("Idempotency-Key", "unique-key-123");
var json = JsonConvert.SerializeObject(payment);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://ipg.irandargah.com/v2/payments", content);
return await response.Content.ReadAsStringAsync();
}
پاسخ موفق:
{
"success": true,
"data": {
"transaction": {
"authority": "2025100121424146HC",
"gateway_url": "https://ipg.irandargah.com/startpay/2025100121424146HC",
"expires_at": "1404-06-28 10:50:00"
},
"meta": {
"idempotency_key": "unique-key-123",
"request_id": "65a4c7e8f1234",
"processing_time_ms": 45
}
},
"message": "تراکنش با موفقیت ایجاد شد",
"status_code": 200,
"timestamp": "1404-06-28 10:30:00"
}
پاسخ ناموفق (خطای اعتبارسنجی ورودی —
HTTP 422):
{
"success": false,
"error": {
"message": "خطا در اعتبارسنجی ورودی",
"code": -2,
"type": "validation_error",
"details": {
"validation": {
"amount": ["حداقل مبلغ رعایت نشده است"],
"order_id": ["شماره سفارش الزامی است"]
}
}
},
"status_code": -2,
"timestamp": "1404-06-28 10:30:00",
"meta": {
"request_id": "65a4c7e8f1234"
}
}
این آدرس برای ایجاد یک تراکنش پرداخت جدید استفاده میشود.
آدرس درخواست
https://ipg.irandargah.com/v2/payments
هدرهای ارسالی
| هدر | مقدار | توضیحات |
|---|---|---|
Authorization |
Bearer YOUR_API_TOKEN | توکن شما (اجباری) |
Content-Type |
application/json | نوع محتوای درخواست (اجباری) |
Idempotency-Key |
unique-string | کلید منحصر به فرد برای جلوگیری از درخواستهای تکراری (اجباری) |
پارامترهای ارسالی (snake_case)
| پارامتر | نوع | اجباری | توضیح |
|---|---|---|---|
amount |
integer | ✅ | مبلغ تراکنش به ریال — حداقل ۱۰۰٬۰۰۰ (۱۰٬۰۰۰ تومان) و حداکثر ۴٬۰۰۰٬۰۰۰٬۰۰۰ (۴۰۰ میلیون تومان) |
order_id |
string | ✅ | شناسه سفارش در سیستم شما — حداکثر ۵۰ کاراکتر، فقط A-Z, a-z, 0-9, -, _ (بدون فاصله یا فارسی) |
callback_url |
string | ✅ | آدرس بازگشت پس از پرداخت — حداکثر ۵۰۰ کاراکتر، باید حتما با https:// شروع شود |
mobile |
string | ❌ | شماره موبایل خریدار — فرمتهای پذیرفتهشده: 09xxxxxxxxx, 989xxxxxxxxx, +989xxxxxxxxx, 00989xxxxxxxxx, 9xxxxxxxxx |
description |
string | ❌ | توضیحات تراکنش — حداکثر ۲۵۵ کاراکتر، بدون کاراکترهای < و > (جلوگیری از HTML) |
card_number |
string | ❌ | شماره کارت پیشفرض — فرمتها: کامل (۱۶ رقم) یا ماسکشده (۶ رقم + ****** + ۴ رقم). در صورت ارسال، خریدار فقط با این کارت میتواند پرداخت کند |
action |
string | ❌ | متد HTTP برای فراخوانی callback — مقادیر POST یا GET (پیشفرض: POST) |
direct_verify |
boolean | ❌ | تأیید خودکار تراکنش توسط ایراندرگاه (پیشفرض: false). در حالتی که true باشد، callback با status_code=100 بهجای 201 ارسال میشود؛ با این حال مقادیر callback امضا نشدهاند و پیش از تحویل کالا باید همچنان با API تأیید (/v2/verifications) پرداخت را تأیید کنید |
دریافت اطلاعات پرداخت
curl "https://ipg.irandargah.com/v2/payments/2025100121424146HC" \
-H "Authorization: Bearer YOUR_API_TOKEN"
<?php
$authority = '2025100121424146HC';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://ipg.irandargah.com/v2/payments/{$authority}");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer YOUR_API_TOKEN"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
const authority = "2025100121424146HC";
const response = await fetch(
`https://ipg.irandargah.com/v2/payments/${authority}`,
{
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
},
},
);
const data = await response.json();
authority := "2025100121424146HC"
url := fmt.Sprintf("https://ipg.irandargah.com/v2/payments/%s", authority)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
client := &http.Client{}
resp, err := client.Do(req)
authority = '2025100121424146HC'
response = requests.get(
f'https://ipg.irandargah.com/v2/payments/{authority}',
headers={'Authorization': 'Bearer YOUR_API_TOKEN'}
)
result = response.json()
string authority = "2025100121424146HC";
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_TOKEN");
var response = await client.GetAsync($"https://ipg.irandargah.com/v2/payments/{authority}");
var content = await response.Content.ReadAsStringAsync();
پاسخ موفق:
{
"success": true,
"data": {
"transaction": {
"authority": "2025100121424146HC",
"ref_id": "2025100121424146HC",
"amount": 100000,
"status": "completed",
"created_at": "1404-06-28 10:30:00",
"updated_at": "1404-06-28 10:35:00"
},
"meta": {
"request_id": "65a4c7e8f1234",
"cached": false
}
},
"message": "اطلاعات تراکنش با موفقیت دریافت شد",
"status_code": 200,
"timestamp": "1404-06-28 10:35:00"
}
پاسخ ناموفق (تراکنش یافت نشد —
HTTP 404):
{
"success": false,
"error": {
"message": "تراکنش یافت نشد",
"code": 404,
"type": "client_error",
"details": {
"transaction": ["تراکنش با این شناسه وجود ندارد"]
}
},
"status_code": 404,
"timestamp": "1404-06-28 10:35:00",
"meta": {
"request_id": "65a4c7e8f1234"
}
}
این آدرس برای دریافت اطلاعات یک تراکنش پرداخت استفاده میشود.
آدرس درخواست
https://ipg.irandargah.com/v2/payments/{authority}
پارامترهای ارسالی
| پارامتر | توضیح |
|---|---|
authority |
شناسه یکتای تراکنش |
فیلدهای پاسخ
| فیلد | نوع | توضیح |
|---|---|---|
authority |
string | شناسه یکتای تراکنش |
ref_id |
string | شماره مرجع بانکی (در صورت تأیید موفق) |
amount |
integer | مبلغ تراکنش به ریال |
status |
string | وضعیت متنی — یکی از: pending, completed, failed, timeout, cancelled, unknown |
created_at |
string | زمان ایجاد به خورشیدی |
updated_at |
string | آخرین زمان بهروزرسانی به خورشیدی |
لغو پرداخت
curl -X POST "https://ipg.irandargah.com/v2/payments/2025100121424146HC/cancel" \
-H "Authorization: Bearer YOUR_API_TOKEN"
<?php
$authority = '2025100121424146HC';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://ipg.irandargah.com/v2/payments/{$authority}/cancel");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer YOUR_API_TOKEN"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
const authority = "2025100121424146HC";
const response = await fetch(
`https://ipg.irandargah.com/v2/payments/${authority}/cancel`,
{
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
},
},
);
authority := "2025100121424146HC"
url := fmt.Sprintf("https://ipg.irandargah.com/v2/payments/%s/cancel", authority)
req, _ := http.NewRequest("POST", url, nil)
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
client := &http.Client{}
resp, err := client.Do(req)
authority = '2025100121424146HC'
response = requests.post(
f'https://ipg.irandargah.com/v2/payments/{authority}/cancel',
headers={'Authorization': 'Bearer YOUR_API_TOKEN'}
)
string authority = "2025100121424146HC";
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_TOKEN");
var response = await client.PostAsync($"https://ipg.irandargah.com/v2/payments/{authority}/cancel", null);
پاسخ موفق:
{
"success": true,
"data": {
"transaction": {
"authority": "2025100121424146HC",
"ref_id": null,
"amount": 100000,
"status": "completed",
"created_at": "1404-06-28 10:30:00",
"updated_at": "1404-06-28 10:35:00"
},
"meta": {
"request_id": "65a4c7e8f1234",
"cached": false
}
},
"message": "پرداخت با موفقیت لغو شد",
"status_code": 200,
"timestamp": "1404-06-28 10:35:00"
}
پاسخ ناموفق (تراکنش قابل لغو نیست —
HTTP 400):
{
"success": false,
"error": {
"message": "تراکنش قابل لغو نیست",
"code": 400,
"type": "client_error",
"details": {
"transaction": ["وضعیت تراکنش اجازه لغو را نمیدهد"]
}
},
"status_code": 400,
"timestamp": "1404-06-28 10:35:00",
"meta": {
"request_id": "65a4c7e8f1234"
}
}
این آدرس برای لغو یک تراکنش پرداخت استفاده میشود.
آدرس درخواست
https://ipg.irandargah.com/v2/payments/{authority}/cancel
پارامترهای ارسالی
| پارامتر | توضیح |
|---|---|
authority |
شناسه یکتای تراکنش |
بازگشت از درگاه (Callback)
پارامترهای Callback
پس از انجام پرداخت توسط کاربر در درگاه بانکی، کاربر به آدرس callback_url شما هدایت میشود و پارامترهای زیر برایتان ارسال میگردد.
پارامترهای بازگشتی (snake_case)
| پارامتر | نوع | توضیح |
|---|---|---|
authority |
string | شناسه یکتای تراکنش (باید با authority ذخیرهشدهی سفارش برابر باشد) |
status_code |
integer | کد وضعیت پرداخت (201 یا 100 = ادعای موفقیت، مقادیر منفی = ناموفق) — صرفاً اطلاعاتی |
message |
string | پیام توضیحی وضعیت |
amount |
integer | مبلغ به ریال — فقط نمایشی؛ هرگز برای تأیید استفاده نشود |
order_id |
string | شناسه سفارش شما |
ref_id |
string | شماره مرجع بانکی (فقط نمایشی، با direct_verify=true؛ مرجع معتبر پاسخ تأیید است) |
card_pan |
string | شماره کارت ماسک شده (فقط نمایشی، با direct_verify=true) |
نمونه آدرس Callback (حالت action=GET)
فرمت بازگشت اطلاعات در حالت GET
https://yoursite.com/callback?
authority=2025100121424146HC&
status_code=201&
message=پرداخت+در+انتظار+تایید+است&
amount=100000&
order_id=ORDER-12345&
ref_id=123456789&
card_pan=603799******1234
در حالت پیشفرض (action=POST)، همین پارامترها با همین نامها بهصورت application/x-www-form-urlencoded در بدنه درخواست POST به callback_url ارسال میشوند و آدرس تغییری نمیکند.
مثال پردازش Callback
در نمونهها فرض شده است هنگام ایجاد تراکنش،
authorityوamountسفارش را در رکورد سفارش خودتان ذخیره کردهاید.
<?php
// Laravel - V2 Callback Handler (snake_case)
Route::any('/callback', function (Request $request) {
// پارامترهای callback امضا نشدهاند؛ فقط بهعنوان «ادعا» خوانده میشوند
$authority = (string) $request->input('authority');
$statusCode = (int) $request->input('status_code');
$orderId = (string) $request->input('order_id');
// ۱) سفارش را از رکورد خودتان پیدا کنید
$order = Order::where('order_id', $orderId)->first();
// ۲) authority ذخیرهشدهی سفارش باید با authority بازگشتی برابر باشد
if (!$order || !hash_equals((string) $order->authority, $authority)) {
return view('payment.failed', ['status_code' => -1]);
}
// ۳) سفارش قبلاً پرداخت شده است (idempotent)
if ($order->status === 'paid') {
return view('payment.success', ['ref_id' => $order->ref_id]);
}
// کد 201 و 100 هر دو فقط «ادعای موفقیت» هستند؛ در هر دو حالت تأیید لازم است
if (in_array($statusCode, [201, 100], true)) {
// ۴) تأیید با «مبلغ ذخیرهشدهی سفارش» (نه مبلغ callback)
$response = Http::withToken(env('API_TOKEN'))
->post('https://ipg.irandargah.com/v2/verifications', [
'authority' => $order->authority,
'amount' => (int) $order->amount,
]);
$result = $response->json();
// ۵) مبلغ تأییدشده باید با مبلغ سفارش برابر باشد
if (
($result['success'] ?? false)
&& (int) $result['data']['verification']['amount'] === (int) $order->amount
) {
$ref_id = $result['data']['verification']['ref_id'];
// ۶) تنها اینجا سفارش پرداختشده علامت میخورد
$order->update([
'status' => 'paid',
'ref_id' => $ref_id,
]);
return view('payment.success', ['ref_id' => $ref_id]);
}
}
// پرداخت ناموفق
return view('payment.failed', ['status_code' => $statusCode]);
});
?>
// Node.js/Express - V2 Callback Handler (snake_case)
const crypto = require("crypto");
// مقایسهی زمانثابت دو رشته
const safeEqual = (a, b) => {
const x = Buffer.from(String(a));
const y = Buffer.from(String(b));
return x.length === y.length && crypto.timingSafeEqual(x, y);
};
app.all("/callback", async (req, res) => {
// پارامترها از body یا query string دریافت میشوند (بسته به action)
// امضا نشدهاند؛ فقط بهعنوان «ادعا» خوانده میشوند
const params = { ...req.query, ...req.body };
const { authority, order_id } = params;
const status_code = parseInt(params.status_code);
// ۱) سفارش را از رکورد خودتان پیدا کنید
const order = await Order.findOne({ where: { order_id } });
// ۲) authority ذخیرهشده باید با authority بازگشتی برابر باشد
if (!order || !authority || !safeEqual(order.authority, authority)) {
return res.render("failed", { status_code: -1 });
}
// ۳) سفارش قبلاً پرداخت شده است (idempotent)
if (order.status === "paid") {
return res.render("success", { ref_id: order.ref_id });
}
// کد 201 و 100 هر دو فقط «ادعای موفقیت» هستند؛ در هر دو حالت تأیید لازم است
if (status_code === 201 || status_code === 100) {
// ۴) تأیید با «مبلغ ذخیرهشدهی سفارش» (نه مبلغ callback)
const response = await fetch(
"https://ipg.irandargah.com/v2/verifications",
{
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
authority: order.authority,
amount: Number(order.amount),
}),
},
);
const result = await response.json();
// ۵) مبلغ تأییدشده باید با مبلغ سفارش برابر باشد
if (
result.success &&
Number(result.data.verification.amount) === Number(order.amount)
) {
// ۶) تنها اینجا سفارش پرداختشده علامت میخورد
await order.update({
status: "paid",
ref_id: result.data.verification.ref_id,
});
return res.render("success", {
ref_id: result.data.verification.ref_id,
});
}
}
res.render("failed", { status_code });
});
# Python/Django - V2 Callback Handler (snake_case)
import hmac
from django.shortcuts import render
import requests
def payment_callback(request):
# پارامترهای callback (GET یا POST بسته به action)
# امضا نشدهاند؛ فقط بهعنوان «ادعا» خوانده میشوند
params = request.POST if request.method == 'POST' else request.GET
authority = params.get('authority') or ''
order_id = params.get('order_id')
try:
status_code = int(params.get('status_code', -1))
except ValueError:
status_code = -1
# ۱) سفارش را از رکورد خودتان پیدا کنید
order = Order.objects.filter(order_id=order_id).first()
# ۲) authority ذخیرهشده باید با authority بازگشتی برابر باشد
if order is None or not hmac.compare_digest(
str(order.authority).encode(), authority.encode()
):
return render(request, 'payment/failed.html', {'status_code': -1})
# ۳) سفارش قبلاً پرداخت شده است (idempotent)
if order.status == 'paid':
return render(request, 'payment/success.html', {'ref_id': order.ref_id})
# کد 201 و 100 هر دو فقط «ادعای موفقیت» هستند؛ در هر دو حالت تأیید لازم است
if status_code in (201, 100):
# ۴) تأیید با «مبلغ ذخیرهشدهی سفارش» (نه مبلغ callback)
response = requests.post(
'https://ipg.irandargah.com/v2/verifications',
headers={'Authorization': 'Bearer YOUR_API_TOKEN'},
json={
'authority': order.authority,
'amount': int(order.amount),
},
timeout=15,
)
result = response.json()
# ۵) مبلغ تأییدشده باید با مبلغ سفارش برابر باشد
if (
result.get('success')
and int(result['data']['verification']['amount']) == int(order.amount)
):
ref_id = result['data']['verification']['ref_id']
# ۶) تنها اینجا سفارش پرداختشده علامت میخورد
order.status = 'paid'
order.ref_id = ref_id
order.save(update_fields=['status', 'ref_id'])
return render(request, 'payment/success.html', {'ref_id': ref_id})
return render(request, 'payment/failed.html', {'status_code': status_code})
کدهای وضعیت Callback
| status_code | معنی | اقدام |
|---|---|---|
201 |
ادعای پرداخت موفق، منتظر تأیید API | اتصال authority به سفارش و فراخوانی API تأیید (/verifications) |
100 |
ادعای پرداخت موفق (direct_verify) |
دقیقاً مانند 201: هنوز باید با API تأیید کنید |
-1 |
پرداخت ناموفق یا لغو توسط کاربر | نمایش خطا به کاربر |
تأیید پرداخت
تأیید تراکنش
curl -X POST "https://ipg.irandargah.com/v2/verifications" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: verification-key-123" \
-d '{
"authority": "2025100121424146HC",
"amount": 100000
}'
<?php
$data = [
'authority' => '2025100121424146HC',
'amount' => 100000
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://ipg.irandargah.com/v2/verifications");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer YOUR_API_TOKEN",
"Content-Type: application/json",
"Idempotency-Key: verification-key-123"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
?>
const verification = {
authority: "2025100121424146HC",
amount: 100000,
};
const response = await fetch("https://ipg.irandargah.com/v2/verifications", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
"Idempotency-Key": "verification-key-123",
},
body: JSON.stringify(verification),
});
const data = await response.json();
type VerificationRequest struct {
Authority string `json:"authority"`
Amount int `json:"amount"`
}
func verifyPayment() {
verification := VerificationRequest{
Authority: "2025100121424146HC",
Amount: 100000,
}
jsonData, _ := json.Marshal(verification)
req, _ := http.NewRequest("POST", "https://ipg.irandargah.com/v2/verifications", bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "verification-key-123")
client := &http.Client{}
resp, err := client.Do(req)
}
verification_data = {
'authority': '2025100121424146HC',
'amount': 100000
}
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
'Idempotency-Key': 'verification-key-123'
}
response = requests.post(
'https://ipg.irandargah.com/v2/verifications',
headers=headers,
json=verification_data
)
result = response.json()
public class VerificationRequest
{
[JsonProperty("authority")]
public string Authority { get; set; }
[JsonProperty("amount")]
public int Amount { get; set; }
}
var verification = new VerificationRequest
{
Authority = "2025100121424146HC",
Amount = 100000
};
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_TOKEN");
client.DefaultRequestHeaders.Add("Idempotency-Key", "verification-key-123");
var json = JsonConvert.SerializeObject(verification);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://ipg.irandargah.com/v2/verifications", content);
پاسخ موفق:
{
"success": true,
"data": {
"verification": {
"ref_id": "123456789",
"authority": "2025100121424146HC",
"amount": 100000,
"verified_at": "1404-06-28 10:35:00"
},
"meta": {
"request_id": "req_verify_001",
"processing_time_ms": 85
}
},
"message": "تراکنش با موفقیت تأیید شد",
"status_code": 200,
"timestamp": "1404-06-28 10:35:00"
}
پاسخ ناموفق (شناسه یا مبلغ نادرست —
HTTP 400):
{
"success": false,
"error": {
"message": "شناسه یکتا، شماره سفارش یا مبلغ اشتباه است",
"code": -19,
"type": "business_logic_error"
},
"status_code": -19,
"timestamp": "1404-06-28 10:35:00",
"meta": {
"request_id": "req_verify_001"
}
}
این آدرس پس از بازگشت کاربر از صفحه پرداخت برای تأیید نهایی تراکنش استفاده میشود.
آدرس درخواست
https://ipg.irandargah.com/v2/verifications
پارامترهای ارسالی (snake_case)
| پارامتر | نوع | الزامی | توضیح |
|---|---|---|---|
authority |
string | ✅ | شناسه یکتای تراکنش |
amount |
integer | ✅ | مبلغ تراکنش به ریال (برای اعتبارسنجی) |
order_id |
string | ❌ | شناسه سفارش (اختیاری) |
وضعیت تأیید
curl "https://ipg.irandargah.com/v2/verifications/2025100121424146HC" \
-H "Authorization: Bearer YOUR_API_TOKEN"
<?php
$authority = '2025100121424146HC';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://ipg.irandargah.com/v2/verifications/{$authority}");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer YOUR_API_TOKEN"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
const authority = "2025100121424146HC";
const response = await fetch(
`https://ipg.irandargah.com/v2/verifications/${authority}`,
{
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
},
}
);
const data = await response.json();
authority := "2025100121424146HC"
url := fmt.Sprintf("https://ipg.irandargah.com/v2/verifications/%s", authority)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
client := &http.Client{}
resp, err := client.Do(req)
authority = '2025100121424146HC'
response = requests.get(
f'https://ipg.irandargah.com/v2/verifications/{authority}',
headers={'Authorization': 'Bearer YOUR_API_TOKEN'}
)
result = response.json()
string authority = "2025100121424146HC";
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_TOKEN");
var response = await client.GetAsync($"https://ipg.irandargah.com/v2/verifications/{authority}");
var content = await response.Content.ReadAsStringAsync();
پاسخ موفق:
{
"success": true,
"data": {
"verification": {
"ref_id": "123456789",
"authority": "2025100121424146HC",
"amount": 100000,
"verified_at": "1404-06-28 10:35:00"
},
"meta": {
"request_id": "req_status_001"
}
},
"message": "اطلاعات تراکنش با موفقیت دریافت شد",
"status_code": 200,
"timestamp": "1404-06-28 10:40:00"
}
پاسخ ناموفق (تراکنش یافت نشد —
HTTP 404):
{
"success": false,
"error": {
"message": "تراکنش یافت نشد",
"code": 404,
"type": "client_error",
"details": {
"transaction": ["تراکنش با این شناسه وجود ندارد"]
}
},
"status_code": 404,
"timestamp": "1404-06-28 10:40:00",
"meta": {
"request_id": "req_status_001"
}
}
این آدرس برای بررسی وضعیت تأیید یک تراکنش استفاده میشود.
آدرس درخواست
https://ipg.irandargah.com/v2/verifications/{authority}
پارامترهای ارسالی
| پارامتر | توضیح |
|---|---|
authority |
شناسه یکتای تراکنش |
تلاش مجدد تأیید
curl -X POST "https://ipg.irandargah.com/v2/verifications/2025100121424146HC/retry" \
-H "Authorization: Bearer YOUR_API_TOKEN"
<?php
$authority = '2025100121424146HC';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://ipg.irandargah.com/v2/verifications/{$authority}/retry");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer YOUR_API_TOKEN"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
const authority = "2025100121424146HC";
const response = await fetch(
`https://ipg.irandargah.com/v2/verifications/${authority}/retry`,
{
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
},
}
);
authority := "2025100121424146HC"
url := fmt.Sprintf("https://ipg.irandargah.com/v2/verifications/%s/retry", authority)
req, _ := http.NewRequest("POST", url, nil)
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
client := &http.Client{}
resp, err := client.Do(req)
authority = '2025100121424146HC'
response = requests.post(
f'https://ipg.irandargah.com/v2/verifications/{authority}/retry',
headers={'Authorization': 'Bearer YOUR_API_TOKEN'}
)
string authority = "2025100121424146HC";
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_TOKEN");
var response = await client.PostAsync($"https://ipg.irandargah.com/v2/verifications/{authority}/retry", null);
پاسخ موفق:
{
"success": true,
"data": {
"verification": {
"ref_id": "123456789",
"authority": "2025100121424146HC",
"amount": 100000,
"verified_at": "1404-06-28 10:45:00"
},
"meta": {
"request_id": "req_retry_001",
"processing_time_ms": 120
}
},
"message": "تأیید مجدد با موفقیت انجام شد",
"status_code": 200,
"timestamp": "1404-06-28 10:45:00"
}
پاسخ ناموفق (تراکنش یافت نشد —
HTTP 404):
{
"success": false,
"error": {
"message": "تراکنش یافت نشد",
"code": 404,
"type": "client_error",
"details": {
"transaction": ["تراکنش با این شناسه وجود ندارد"]
}
},
"status_code": 404,
"timestamp": "1404-06-28 10:45:00",
"meta": {
"request_id": "req_retry_001"
}
}
این آدرس برای تلاش مجدد تأیید یک تراکنش ناموفق استفاده میشود.
آدرس درخواست
https://ipg.irandargah.com/v2/verifications/{authority}/retry
پارامترهای ارسالی
| پارامتر | توضیح |
|---|---|
authority |
شناسه یکتای تراکنش |
تراکنشها
فهرست تراکنشها
curl "https://ipg.irandargah.com/v2/transactions?page=1&per_page=10&status=completed&from_date=1404-06-01&to_date=1404-06-31" \
-H "Authorization: Bearer YOUR_API_TOKEN"
<?php
$query = http_build_query([
'page' => 1,
'per_page' => 10,
'status' => 'completed',
'from_date' => '1404-06-01',
'to_date' => '1404-06-31',
]);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://ipg.irandargah.com/v2/transactions?{$query}");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer YOUR_API_TOKEN"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
const params = new URLSearchParams({
page: 1,
per_page: 10,
status: "completed",
from_date: "1404-06-01",
to_date: "1404-06-31",
});
const response = await fetch(
`https://ipg.irandargah.com/v2/transactions?${params}`,
{
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
},
}
);
params := url.Values{}
params.Add("page", "1")
params.Add("per_page", "10")
params.Add("status", "completed")
params.Add("from_date", "1404-06-01")
params.Add("to_date", "1404-06-31")
url := fmt.Sprintf("https://ipg.irandargah.com/v2/transactions?%s", params.Encode())
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
params = {
'page': 1,
'per_page': 10,
'status': 'completed',
'from_date': '1404-06-01',
'to_date': '1404-06-31',
}
response = requests.get(
'https://ipg.irandargah.com/v2/transactions',
headers={'Authorization': 'Bearer YOUR_API_TOKEN'},
params=params
)
var query = "?page=1&per_page=10&status=completed&from_date=1404-06-01&to_date=1404-06-31";
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_TOKEN");
var response = await client.GetAsync($"https://ipg.irandargah.com/v2/transactions{query}");
پاسخ موفق:
{
"success": true,
"data": {
"transactions": [
{
"authority": "2025100121424146HC",
"amount": 100000,
"status": "completed",
"order_id": "ORDER-12345",
"ref_code": "2025100121424146HC",
"created_at": "1404-06-28 10:30:00",
"verified_at": "1404-06-28 10:45:00"
}
],
"pagination": {
"current_page": 1,
"per_page": 10,
"total": 1,
"last_page": 1
}
},
"timestamp": "1404-06-28 10:45:00"
}
پاسخ ناموفق (توکن نامعتبر —
HTTP 401):
{
"success": false,
"error": {
"message": "اطلاعات احراز هویت نامعتبر است",
"code": -51,
"type": "authentication_error",
"details": {
"authentication": ["اطلاعات احراز هویت نامعتبر است"]
}
},
"status_code": -51,
"timestamp": "1404-06-28 10:45:00",
"meta": {
"request_id": "65a4c7e8f1234"
}
}
این آدرس برای دریافت فهرست تراکنشها استفاده میشود.
آدرس درخواست
https://ipg.irandargah.com/v2/transactions
پارامترهای ارسالی
| پارامتر | اجباری | پیشفرض | توضیح |
|---|---|---|---|
from_date |
✅ | — | تاریخ شروع به خورشیدی (YYYY-MM-DD) — نمیتواند تاریخ آینده باشد |
to_date |
✅ | — | تاریخ پایان به خورشیدی (YYYY-MM-DD) — باید بزرگتر یا مساوی from_date باشد |
page |
❌ | 1 | شماره صفحه |
per_page |
❌ | 10 | تعداد آیتم در هر صفحه (حداکثر 100) |
status |
❌ | — | فیلتر بر اساس وضعیت — یکی از: pending, completed, failed, cancelled |
جزئیات تراکنش
curl "https://ipg.irandargah.com/v2/transactions/2025100121424146HC" \
-H "Authorization: Bearer YOUR_API_TOKEN"
<?php
$authority = '2025100121424146HC';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://ipg.irandargah.com/v2/transactions/{$authority}");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer YOUR_API_TOKEN"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
const authority = "2025100121424146HC";
const response = await fetch(
`https://ipg.irandargah.com/v2/transactions/${authority}`,
{
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
},
}
);
authority := "2025100121424146HC"
url := fmt.Sprintf("https://ipg.irandargah.com/v2/transactions/%s", authority)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
authority = '2025100121424146HC'
response = requests.get(
f'https://ipg.irandargah.com/v2/transactions/{authority}',
headers={'Authorization': 'Bearer YOUR_API_TOKEN'}
)
string authority = "2025100121424146HC";
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_TOKEN");
var response = await client.GetAsync($"https://ipg.irandargah.com/v2/transactions/{authority}");
پاسخ موفق:
{
"success": true,
"data": {
"transaction": {
"authority": "2025100121424146HC",
"order_id": "ORDER-12345",
"amount": 100000,
"status": "completed",
"workflow_state": "COMPLETED",
"description": "خرید محصول آزمایشی",
"ref_code": "2025100121424146HC",
"callback_url": "https://merchant.com/callback",
"created_at": "1404-06-28 10:30:00",
"updated_at": "1404-06-28 10:35:00",
"expires_at": "1404-06-28 10:50:00",
"is_expired": false,
"can_be_verified": false,
"verification_status": 200
}
},
"timestamp": "1404-06-28 11:00:00"
}
پاسخ ناموفق (تراکنش یافت نشد —
HTTP 404):
{
"success": false,
"error": {
"message": "تراکنش یافت نشد",
"code": 404,
"type": "client_error",
"details": {
"transaction": ["تراکنش با این شناسه وجود ندارد"]
}
},
"status_code": 404,
"timestamp": "1404-06-28 11:00:00",
"meta": {
"request_id": "65a4c7e8f1234"
}
}
این آدرس برای دریافت جزئیات کامل یک تراکنش استفاده میشود.
آدرس درخواست
https://ipg.irandargah.com/v2/transactions/{authority}
پارامترهای ارسالی
| پارامتر | توضیح |
|---|---|
authority |
شناسه یکتای تراکنش |
فیلدهای پاسخ
| فیلد | نوع | توضیح |
|---|---|---|
authority |
string | شناسه یکتای تراکنش |
order_id |
string | شناسه سفارش در سیستم شما |
amount |
integer | مبلغ تراکنش به ریال |
status |
string | وضعیت متنی — یکی از: pending, completed, failed, timeout, cancelled, unknown |
workflow_state |
string | وضعیت دقیق در ماشین حالت داخلی (برای دیباگ) |
description |
string | توضیحات تراکنش |
ref_code |
string | شماره مرجع بانکی (پس از تأیید موفق) |
callback_url |
string | آدرس callback ثبتشده برای این تراکنش |
created_at |
string | زمان ایجاد به خورشیدی |
updated_at |
string | آخرین زمان بهروزرسانی به خورشیدی |
expires_at |
string | زمان انقضای تراکنش به خورشیدی |
is_expired |
boolean | آیا تراکنش منقضی شده است |
can_be_verified |
boolean | آیا تراکنش قابل تأیید است |
verification_status |
integer | کد وضعیت تأیید (۲۰۰ = تأییدشده) |
وبهوکها
وبهوکها به شما اجازه میدهند بهجای polling، رویدادهای مهم (پرداخت موفق، استرداد، تسویه و …) را بهصورت real-time روی URL خودتان دریافت کنید. ایراندرگاه پس از وقوع هر رویداد، یک درخواست POST به آدرس ثبتشدهی شما ارسال میکند.
ثبت وبهوک
curl -X POST "https://ipg.irandargah.com/v2/webhooks/subscribe" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://merchant.com/webhook",
"events": ["payment.completed", "payment.failed"],
"secret": "your_optional_secret_min_8_chars",
"description": "وبهوک اصلی فروشگاه"
}'
<?php
$data = [
'url' => 'https://merchant.com/webhook',
'events' => ['payment.completed', 'payment.failed'],
'secret' => 'your_optional_secret_min_8_chars', // اختیاری
'description' => 'وبهوک اصلی فروشگاه', // اختیاری
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://ipg.irandargah.com/v2/webhooks/subscribe");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer YOUR_API_TOKEN",
"Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data, JSON_UNESCAPED_UNICODE));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
const webhook = {
url: "https://merchant.com/webhook",
events: ["payment.completed", "payment.failed"],
secret: "your_optional_secret_min_8_chars", // اختیاری
description: "وبهوک اصلی فروشگاه", // اختیاری
};
const response = await fetch(
"https://ipg.irandargah.com/v2/webhooks/subscribe",
{
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify(webhook),
},
);
type WebhookSubscription struct {
URL string `json:"url"`
Events []string `json:"events"`
Secret string `json:"secret,omitempty"`
Description string `json:"description,omitempty"`
}
webhook := WebhookSubscription{
URL: "https://merchant.com/webhook",
Events: []string{"payment.completed", "payment.failed"},
Secret: "your_optional_secret_min_8_chars",
Description: "وبهوک اصلی فروشگاه",
}
jsonData, _ := json.Marshal(webhook)
req, _ := http.NewRequest("POST", "https://ipg.irandargah.com/v2/webhooks/subscribe", bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
req.Header.Set("Content-Type", "application/json")
webhook_data = {
'url': 'https://merchant.com/webhook',
'events': ['payment.completed', 'payment.failed'],
'secret': 'your_optional_secret_min_8_chars', # اختیاری
'description': 'وبهوک اصلی فروشگاه', # اختیاری
}
response = requests.post(
'https://ipg.irandargah.com/v2/webhooks/subscribe',
headers={
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
},
json=webhook_data
)
public class WebhookSubscription
{
public string Url { get; set; }
public List<string> Events { get; set; }
public string Secret { get; set; } // اختیاری
public string Description { get; set; } // اختیاری
}
var webhook = new WebhookSubscription
{
Url = "https://merchant.com/webhook",
Events = new List<string> { "payment.completed", "payment.failed" },
Secret = "your_optional_secret_min_8_chars",
Description = "وبهوک اصلی فروشگاه",
};
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_TOKEN");
var json = JsonConvert.SerializeObject(webhook);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://ipg.irandargah.com/v2/webhooks/subscribe", content);
پاسخ موفق (HTTP 201):
{
"success": true,
"data": {
"webhook": {
"id": "65a4c7e8f1234",
"url": "https://merchant.com/webhook",
"events": ["payment.completed", "payment.failed"],
"secret": "***masked***",
"is_active": true,
"created_at": "1404-06-29 10:30:00"
}
},
"message": "Webhook subscription created successfully",
"timestamp": "1404-06-29 10:30:00"
}
پاسخ ناموفق (خطای اعتبارسنجی —
HTTP 422):
{
"success": false,
"error": {
"message": "خطا در اعتبارسنجی ورودی",
"code": -2,
"type": "validation_error",
"details": {
"validation": {
"url": ["The url field is required."],
"events": ["The events field is required."]
}
}
},
"status_code": -2,
"timestamp": "1404-06-29 10:30:00",
"meta": {
"request_id": "65a4c7e8f1234"
}
}
آدرس درخواست
https://ipg.irandargah.com/v2/webhooks/subscribe
پارامترهای ارسالی
| پارامتر | نوع | اجباری | توضیح |
|---|---|---|---|
url |
string | ✅ | آدرس HTTPS endpoint شما (باید URL معتبر باشد) |
events |
array | ✅ | حداقل یک رویداد از فهرست انواع رویدادها |
secret |
string | ❌ | کلید مخفی برای امضای HMAC — بین ۸ تا ۶۴ کاراکتر. اگر ندهید، سرور یک کلید ۶۴ کاراکتری Hex تولید میکند |
description |
string | ❌ | توضیح کوتاه برای شناسایی این وبهوک — حداکثر ۲۵۵ کاراکتر |
انواع رویدادها
| رویداد | توضیح |
|---|---|
payment.created |
تراکنش پرداخت جدید ایجاد شد |
payment.completed |
پرداخت با موفقیت تکمیل شد |
payment.failed |
پرداخت ناموفق بود |
payment.cancelled |
پرداخت لغو شد |
payment.reversed |
پرداخت معکوس/استرداد شد |
verification.completed |
تأیید پرداخت با موفقیت انجام شد |
verification.failed |
تأیید پرداخت ناموفق بود |
refund.created |
درخواست برگشت وجه ثبت شد |
refund.completed |
برگشت وجه با موفقیت تکمیل شد |
settlement.created |
دسته تسویهحساب ایجاد شد |
settlement.completed |
تسویهحساب با موفقیت انجام شد |
settlement.failed |
تسویهحساب ناموفق بود |
دریافت فهرست رویدادها
curl "https://ipg.irandargah.com/v2/webhooks/events" \
-H "Authorization: Bearer YOUR_API_TOKEN"
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://ipg.irandargah.com/v2/webhooks/events");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer YOUR_API_TOKEN"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$events = json_decode($response, true)['data']['events'];
?>
const response = await fetch("https://ipg.irandargah.com/v2/webhooks/events", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const { data } = await response.json();
console.log(data.events);
req, _ := http.NewRequest("GET", "https://ipg.irandargah.com/v2/webhooks/events", nil)
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil { log.Fatal(err) }
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
response = requests.get(
'https://ipg.irandargah.com/v2/webhooks/events',
headers={'Authorization': 'Bearer YOUR_API_TOKEN'}
)
events = response.json()['data']['events']
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_TOKEN");
var response = await client.GetAsync("https://ipg.irandargah.com/v2/webhooks/events");
var content = await response.Content.ReadAsStringAsync();
پاسخ موفق:
{
"success": true,
"data": {
"events": {
"payment.created": "Triggered when a new payment is initiated",
"payment.completed": "Triggered when a payment is successfully completed",
"...": "..."
},
"webhook_requirements": {
"url": "Must be a valid HTTPS URL",
"response_timeout": "10 seconds",
"retry_attempts": "3 times with exponential backoff",
"expected_response": "HTTP 200-299 status code",
"signature_header": "X-Webhook-Signature (HMAC-SHA256)"
}
},
"timestamp": "1404-06-29 10:30:00"
}
https://ipg.irandargah.com/v2/webhooks/events
این آدرس برای دریافت فهرست بهروز رویدادهای پشتیبانیشده استفاده میشود.
لغو اشتراک
curl -X POST "https://ipg.irandargah.com/v2/webhooks/unsubscribe" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"webhook_id": "65a4c7e8f1234"}'
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://ipg.irandargah.com/v2/webhooks/unsubscribe");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer YOUR_API_TOKEN",
"Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['webhook_id' => '65a4c7e8f1234']));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
const response = await fetch(
"https://ipg.irandargah.com/v2/webhooks/unsubscribe",
{
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ webhook_id: "65a4c7e8f1234" }),
},
);
data := map[string]string{"webhook_id": "65a4c7e8f1234"}
jsonData, _ := json.Marshal(data)
req, _ := http.NewRequest("POST", "https://ipg.irandargah.com/v2/webhooks/unsubscribe", bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
req.Header.Set("Content-Type", "application/json")
response = requests.post(
'https://ipg.irandargah.com/v2/webhooks/unsubscribe',
headers={
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
},
json={'webhook_id': '65a4c7e8f1234'}
)
var data = new { webhook_id = "65a4c7e8f1234" };
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_TOKEN");
var json = JsonConvert.SerializeObject(data);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://ipg.irandargah.com/v2/webhooks/unsubscribe", content);
پاسخ موفق:
{
"success": true,
"data": {
"webhook_id": "65a4c7e8f1234",
"status": "unsubscribed"
},
"message": "Webhook unsubscribed successfully",
"timestamp": "1404-06-29 10:30:00"
}
پاسخ ناموفق (وبهوک یافت نشد —
HTTP 404):
{
"success": false,
"error": {
"message": "وبهوک یافت نشد",
"code": 404,
"type": "client_error",
"details": {
"webhook": ["اشتراک وبهوک با این شناسه وجود ندارد"]
}
},
"status_code": 404,
"timestamp": "1404-06-29 10:30:00",
"meta": {
"request_id": "65a4c7e8f1234"
}
}
این آدرس وبهوک را غیرفعال میکند (is_active = false) — رکورد در دیتابیس باقی میماند ولی دیگر رویدادی به این endpoint ارسال نمیشود.
https://ipg.irandargah.com/v2/webhooks/unsubscribe
پارامترهای ارسالی
| پارامتر | نوع | اجباری | توضیح |
|---|---|---|---|
webhook_id |
string | ✅ | شناسهی وبهوک که در زمان ثبت دریافت کردهاید |
تست وبهوک
curl -X POST "https://ipg.irandargah.com/v2/webhooks/test" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"webhook_id": "65a4c7e8f1234",
"event_type": "payment.completed"
}'
<?php
$data = [
'webhook_id' => '65a4c7e8f1234',
'event_type' => 'payment.completed',
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://ipg.irandargah.com/v2/webhooks/test");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer YOUR_API_TOKEN",
"Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
const response = await fetch("https://ipg.irandargah.com/v2/webhooks/test", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
webhook_id: "65a4c7e8f1234",
event_type: "payment.completed",
}),
});
data := map[string]string{
"webhook_id": "65a4c7e8f1234",
"event_type": "payment.completed",
}
jsonData, _ := json.Marshal(data)
req, _ := http.NewRequest("POST", "https://ipg.irandargah.com/v2/webhooks/test", bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
req.Header.Set("Content-Type", "application/json")
response = requests.post(
'https://ipg.irandargah.com/v2/webhooks/test',
headers={
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
},
json={
'webhook_id': '65a4c7e8f1234',
'event_type': 'payment.completed',
}
)
var data = new {
webhook_id = "65a4c7e8f1234",
event_type = "payment.completed",
};
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_TOKEN");
var json = JsonConvert.SerializeObject(data);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://ipg.irandargah.com/v2/webhooks/test", content);
پاسخ موفق:
{
"success": true,
"data": {
"webhook_id": "65a4c7e8f1234",
"event_type": "payment.completed",
"test_result": {
"status": "success",
"response_code": 200,
"response_time": null,
"error": null
}
},
"message": "Webhook test completed",
"timestamp": "1404-06-29 10:30:00"
}
پاسخ ناموفق (وبهوک یافت نشد —
HTTP 404):
{
"success": false,
"error": {
"message": "وبهوک یافت نشد",
"code": 404,
"type": "client_error",
"details": {
"webhook": ["اشتراک وبهوک با این شناسه وجود ندارد"]
}
},
"status_code": 404,
"timestamp": "1404-06-29 10:30:00",
"meta": {
"request_id": "65a4c7e8f1234"
}
}
این آدرس یک بدنه درخواست نمونه برای رویداد مشخصشده تولید کرده و فوراً به endpoint شما ارسال میکند تا بتوانید پیادهسازی خود را پیش از رفتن به محیط عملیاتی تست کنید.
https://ipg.irandargah.com/v2/webhooks/test
پارامترهای ارسالی
| پارامتر | نوع | اجباری | توضیح |
|---|---|---|---|
webhook_id |
string | ✅ | شناسهی وبهوک |
event_type |
string | ✅ | یکی از: payment.created, payment.completed, payment.failed, payment.cancelled, refund.created, refund.completed, verification.completed |
ساختار پیام وبهوک
پیامی که به endpoint شما ارسال میشود ساختار زیر را دارد:
{
"event": "payment.completed",
"timestamp": "2025-10-01T10:35:00+03:30",
"terminal_id": 4231,
"data": {
"authority": "2025100121424146HC",
"amount": 100000,
"order_id": "ORDER-12345",
"ref_code": "2025100121424146HC",
"pan": "603799******1234",
"verified_at": "1404-06-29 10:35:00"
},
"signature": "a1b2c3d4e5f6..."
}
فیلدهای ثابت بدنه درخواست
| فیلد | نوع | توضیح |
|---|---|---|
event |
string | نوع رویداد (همان مقداری که در زمان اشتراک انتخاب کردهاید) |
timestamp |
string | زمان وقوع رویداد در فرمت ISO 8601 با منطقه زمانی تهران |
terminal_id |
number | شناسهی درگاهی که رویداد از آن آمده (برای وبهوکهای حسابمحورِ چند-درگاهه مفید است) |
data |
object | محتوای رویداد (فیلدها بسته به نوع رویداد متفاوت است — جدول زیر) |
signature |
string | امضای HMAC-SHA256 روی data (فقط اگر secret ست شده باشد) |
فیلدهای data بر اساس نوع رویداد
| رویداد | فیلدهای داخل data |
|---|---|
payment.created |
authority, amount, status, created_at |
payment.completed / verification.completed |
authority, amount, order_id, ref_code, pan, verified_at |
payment.failed / payment.cancelled / verification.failed |
authority, amount, status, message |
refund.created |
refund_code, authority, amount, status, reason, created_at |
refund.completed |
refund_code, authority, amount, status, completed_at |
settlement.created / settlement.completed / settlement.failed |
settlement_code, amount, status, period |
هدرهای وبهوک
هر درخواست وبهوک با هدرهای زیر ارسال میشود:
| هدر | توضیح |
|---|---|
Content-Type |
همیشه application/json |
User-Agent |
IranDargah-Webhook/1.0 |
X-Webhook-Event |
نوع رویداد (مثال: payment.completed) |
X-Webhook-Timestamp |
زمان ارسال (Unix timestamp بر حسب ثانیه) |
X-Webhook-ID |
شناسه یکتای این رویداد (برای deduplication و idempotency) |
X-Webhook-Signature |
امضای HMAC-SHA256 — فقط اگر secret در زمان اشتراک ست شده باشد |
راستیآزمایی امضا
<?php
function verifyWebhookSignature(string $rawBody, string $signature, string $secret): bool {
// فقط فیلد data امضا میشود
$payload = json_decode($rawBody, true);
$dataJson = json_encode($payload['data'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$expected = hash_hmac('sha256', $dataJson, $secret);
return hash_equals($expected, $signature);
}
// استفاده
$body = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
if (! verifyWebhookSignature($body, $signature, 'YOUR_WEBHOOK_SECRET')) {
http_response_code(401);
exit('Invalid signature');
}
?>
const crypto = require("crypto");
function verifyWebhookSignature(rawBody, signature, secret) {
// فقط فیلد data امضا میشود
const payload = JSON.parse(rawBody);
const dataJson = JSON.stringify(payload.data);
const expected = crypto
.createHmac("sha256", secret)
.update(dataJson)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
// در Express
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-webhook-signature"];
if (!verifyWebhookSignature(req.body, signature, "YOUR_WEBHOOK_SECRET")) {
return res.status(401).send("Invalid signature");
}
// ... پردازش webhook
res.sendStatus(200);
});
import hmac, hashlib, json
def verify_webhook_signature(raw_body: bytes, signature: str, secret: str) -> bool:
# فقط فیلد data امضا میشود
payload = json.loads(raw_body)
data_json = json.dumps(payload['data'], ensure_ascii=False, separators=(',', ':'))
expected = hmac.new(secret.encode(), data_json.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
Retry و Timeout
برای اطمینان از دریافت رویدادها، ایراندرگاه ارسال ناموفق را با backoff نمایی تلاش مجدد میکند:
| پارامتر | مقدار |
|---|---|
| Timeout | ۱۰ ثانیه (اگر endpoint شما در این بازه پاسخ ندهد، خطا تلقی میشود) |
| Retries | حداکثر ۳ تلاش |
| Backoff | exponential — ۲ دقیقه، ۴ دقیقه، ۸ دقیقه (سقف ۶۰ دقیقه) |
| موفقیت | پاسخ HTTP با کد 2xx |
| شکست | پاسخ غیر-2xx، timeout، یا عدم پاسخدهی |
پس از ۳ تلاش ناموفق، رویداد به DLQ (Dead Letter Queue) منتقل میشود و میتوانید آن را از پنل مدیریتی مشاهده و در صورت نیاز دوباره retry کنید.
گزارشها
گزارش روزانه
curl "https://ipg.irandargah.com/v2/reports/daily?date=1404-06-28" \
-H "Authorization: Bearer YOUR_API_TOKEN"
<?php
$date = '1404-06-28';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://ipg.irandargah.com/v2/reports/daily?date={$date}");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer YOUR_API_TOKEN"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
const date = "1404-06-28";
const response = await fetch(
`https://ipg.irandargah.com/v2/reports/daily?date=${date}`,
{
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
},
}
);
date := "1404-06-28"
url := fmt.Sprintf("https://ipg.irandargah.com/v2/reports/daily?date=%s", date)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
date = '1404-06-28'
response = requests.get(
f'https://ipg.irandargah.com/v2/reports/daily?date={date}',
headers={'Authorization': 'Bearer YOUR_API_TOKEN'}
)
string date = "1404-06-28";
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_TOKEN");
var response = await client.GetAsync($"https://ipg.irandargah.com/v2/reports/daily?date={date}");
پاسخ موفق:
{
"success": true,
"data": {
"report_date": "1404-06-28",
"summary": {
"total_transactions": 150,
"successful_transactions": 135,
"failed_transactions": 15,
"total_amount": 15000000,
"successful_amount": 13500000,
"success_rate": 90.0,
"total_fees": 270000
},
"hourly_breakdown": [
{ "hour": 10, "transactions": 25, "amount": 2500000 }
],
"payment_methods": [
{ "method": "card", "transactions": 140, "amount": 14000000 }
],
"status_breakdown": [
{ "status": "completed", "count": 135 },
{ "status": "failed", "count": 15 }
]
},
"timestamp": "1404-06-28 23:59:59"
}
پاسخ ناموفق (تاریخ نامعتبر —
HTTP 422):
{
"success": false,
"error": {
"message": "خطا در اعتبارسنجی ورودی",
"code": -2,
"type": "validation_error",
"details": {
"validation": {
"date": ["The date must be a valid Jalali date format (Y-m-d)."]
}
}
},
"status_code": -2,
"timestamp": "1404-06-28 23:59:59",
"meta": {
"request_id": "65a4c7e8f1234"
}
}
این آدرس برای دریافت گزارش روزانه تراکنشها استفاده میشود.
آدرس درخواست
https://ipg.irandargah.com/v2/reports/daily
پارامترهای ارسالی
| پارامتر | الزامی | توضیح |
|---|---|---|
date |
❌ | تاریخ مورد نظر به خورشیدی (YYYY-MM-DD). در صورت ارسال نشدن، امروز درنظر گرفته میشود. نمیتواند تاریخ آینده باشد |
فیلدهای پاسخ
| فیلد | نوع | توضیح |
|---|---|---|
report_date |
string | تاریخ گزارش به خورشیدی |
summary.total_transactions |
int | تعداد کل تراکنشها |
summary.successful_transactions |
int | تعداد تراکنشهای موفق |
summary.failed_transactions |
int | تعداد تراکنشهای ناموفق |
summary.total_amount |
int | جمع کل مبلغ تراکنشها (ریال) |
summary.successful_amount |
int | جمع مبلغ تراکنشهای موفق (ریال) |
summary.success_rate |
float | درصد موفقیت |
summary.total_fees |
int | جمع کارمزد (ریال) |
hourly_breakdown |
array | تفکیک ساعتی |
payment_methods |
array | تفکیک روش پرداخت |
status_breakdown |
array | تفکیک وضعیت |
گزارش ماهانه
curl "https://ipg.irandargah.com/v2/reports/monthly?month=1404-06" \
-H "Authorization: Bearer YOUR_API_TOKEN"
<?php
$month = '1404-06';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://ipg.irandargah.com/v2/reports/monthly?month={$month}");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer YOUR_API_TOKEN"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
const month = "1404-06";
const response = await fetch(
`https://ipg.irandargah.com/v2/reports/monthly?month=${month}`,
{
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
},
}
);
month := "1404-06"
url := fmt.Sprintf("https://ipg.irandargah.com/v2/reports/monthly?month=%s", month)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
month = '1404-06'
response = requests.get(
f'https://ipg.irandargah.com/v2/reports/monthly?month={month}',
headers={'Authorization': 'Bearer YOUR_API_TOKEN'}
)
string month = "1404-06";
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_TOKEN");
var response = await client.GetAsync($"https://ipg.irandargah.com/v2/reports/monthly?month={month}");
پاسخ موفق:
{
"success": true,
"data": {
"report_month": "1404-06",
"period": {
"from": "1404-06-01",
"to": "1404-06-31"
},
"summary": {
"total_transactions": 4500,
"successful_transactions": 4050,
"failed_transactions": 450,
"total_amount": 450000000,
"success_rate": 90.0
},
"daily_breakdown": [
{ "date": "1404-06-28", "transactions": 150, "amount": 15000000 }
],
"payment_methods": [
{ "method": "card", "transactions": 4200, "amount": 420000000 }
],
"psp_breakdown": [
{ "psp": "mellat", "transactions": 2500, "amount": 250000000 },
{ "psp": "saderat", "transactions": 1550, "amount": 200000000 }
]
},
"timestamp": "1404-07-01 00:00:00"
}
پاسخ ناموفق (ماه نامعتبر —
HTTP 422):
{
"success": false,
"error": {
"message": "خطا در اعتبارسنجی ورودی",
"code": -2,
"type": "validation_error",
"details": {
"validation": {
"month": ["The month must be a valid Jalali month format (Y-m)."]
}
}
},
"status_code": -2,
"timestamp": "1404-07-01 00:00:00",
"meta": {
"request_id": "65a4c7e8f1234"
}
}
آدرس درخواست
https://ipg.irandargah.com/v2/reports/monthly
پارامترهای ارسالی
| پارامتر | الزامی | توضیح |
|---|---|---|
month |
❌ | ماه مورد نظر به خورشیدی (YYYY-MM). در صورت ارسال نشدن، ماه جاری درنظر گرفته میشود. نمیتواند ماه آینده باشد |
فیلدهای پاسخ
| فیلد | نوع | توضیح |
|---|---|---|
report_month |
string | ماه گزارش به خورشیدی |
period.from / period.to |
string | بازهی تاریخی گزارش به خورشیدی |
summary.* |
object | خلاصهی آماری (مشابه گزارش روزانه) |
daily_breakdown |
array | تفکیک روزانه |
payment_methods |
array | تفکیک روش پرداخت |
psp_breakdown |
array | تفکیک بهازای ارائهدهندهی خدمات پرداخت (PSP) |
کدهای خطا
ایراندرگاه از کدهای وضعیت HTTP استاندارد و همچنین یک کد وضعیت سفارشی (status_code) در بدنهی پاسخ استفاده میکند. در هر پاسخ، فیلد status_code مقدار دقیق نتیجهی عملیات را مشخص میکند و message معادل فارسی آن را برمیگرداند.
ساختار پاسخ خطا
تمام خطاها ساختار یکسانی دارند:
{
"success": false,
"error": {
"message": "اطلاعات دریافتی معتبر نیست",
"code": -2,
"type": "validation_error",
"details": {
"inputs": ["مبلغ تراکنش قابل قبول نیست"]
}
},
"status_code": -2,
"timestamp": "1404-06-28 10:30:00",
"meta": {
"request_id": "req_error_001"
}
}
| فیلد | نوع | توضیح |
|---|---|---|
success |
boolean | همیشه false در پاسخهای خطا |
error.message |
string | پیام خطا به فارسی |
error.code |
integer | کد وضعیت سفارشی (همان status_code) |
error.type |
string | دستهبندی خطا (به جدول انواع خطا مراجعه کنید) |
error.details |
object | جزئیات بیشتر؛ در خطاهای اعتبارسنجی شامل خطای هر فیلد |
status_code |
integer | کد وضعیت سفارشی |
timestamp |
string | زمان پاسخ به تاریخ خورشیدی (YYYY-MM-DD HH:mm:ss) |
meta.request_id |
string | شناسهی یکتای درخواست برای پیگیری با پشتیبانی |
یکنواختی ساختار پاسخ
همهی اندپوینتهای v2 — پرداخت، تأیید، تراکنشها، وبهوکها، گزارشها و احراز هویت — خطاها را در همان پاکت استاندارد بالا (error + status_code) بازمیگردانند. خطای اعتبارسنجی ورودی نیز در همه جا با کد -2، HTTP 422 و error.details.validation (کلیددار بر اساس نام فیلد) بازمیگردد.
تنها استثنا اندپوینت سلامت است که قالب سبکِ بررسی سرویس دارد (نه پاکت خطا):
| اندپوینت | شکل پاسخ |
|---|---|
/health |
`{ "status": "ok" \ |
کدهای وضعیت HTTP
| کد | معنی |
|---|---|
200 |
OK — درخواست موفق |
201 |
Created — تراکنش جدید ایجاد شد |
202 |
Accepted — درخواست تکراری هنوز در حال پردازش است (Idempotency) |
400 |
Bad Request — درخواست نامعتبر یا نقض قانون تجاری |
401 |
Unauthorized — توکن یا امضای نامعتبر |
403 |
Forbidden — دسترسی مجاز نیست (IP، وضعیت ترمینال) |
404 |
Not Found — تراکنش یا منبع یافت نشد |
408 |
Request Timeout — مهلت درخواست قبلی (Idempotency) بهسر رسید |
409 |
Conflict — تداخل کلید Idempotency با درخواست متفاوت |
422 |
Unprocessable Entity — خطا در اعتبارسنجی ورودی |
429 |
Too Many Requests — عبور از حد مجاز درخواست |
500 |
Internal Server Error — خطای داخلی سرور |
503 |
Service Unavailable — سرویس بانکی یا کارمزد موقتاً در دسترس نیست |
انواع خطا (Error Types)
هر خطا با یک type دستهبندی میشود تا مدیریت خطا در سمت شما سادهتر شود:
| نوع | کدهای نمونه | توضیح |
|---|---|---|
authentication_error |
-50, -51, -52 |
خطا در احراز هویت (توکن / IP) |
authorization_error |
-53, 403 |
دسترسی مجاز نیست |
validation_error |
-2 |
خطا در اعتبارسنجی ورودی |
business_logic_error |
-6, -19, -21, -33 |
نقض قوانین تجاری |
rate_limit_error |
-54 |
تعداد درخواست بیش از حد مجاز |
server_error |
-31, 996 |
خطای داخلی سرور |
gateway_error |
-101, -102, -103 |
خطا در ارتباط / امضای درگاه |
success |
100, 200 |
پاسخ موفق |
مرجع کامل کدها
کدهای موفقیت و وضعیت در جریان
این کدها خطا نیستند و وضعیت طبیعی چرخهی پرداخت را نشان میدهند.
| کد | پیام | HTTP | توضیح |
|---|---|---|---|
100 |
تراکنش موفق بود | 200 |
تراکنش با موفقیت ایجاد یا (در حالت direct_verify) تأیید شد |
101 |
تراکنش قبلا وریفای شده است | 200 |
تأیید تکراریِ تراکنشی که پیشتر با موفقیت وریفای شده (پاسخ idempotent) |
200 |
اتصال به درگاه موفق بود | 200 |
تأیید تراکنش با موفقیت انجام شد / اتصال به درگاه برقرار شد |
201 |
پرداخت در حال انجام است | 200 |
کاربر در صفحهی بانک است؛ در callback بهمعنای «موفق، منتظر تأیید» |
احراز هویت و دسترسی
این کدها توسط لایهی احراز هویت پیش از رسیدن درخواست به موتور پرداخت بازگردانده میشوند.
| کد | پیام | HTTP | علت |
|---|---|---|---|
-50 |
اطلاعات احراز هویت موجود نیست | 401 |
هدر Authorization: Bearer ... ارسال نشده است |
-51 |
اطلاعات احراز هویت نامعتبر است | 401 |
توکن اشتباه، منقضی، یا باطلشده است |
-52 |
IP در لیست سفید قرار ندارد | 403 |
آدرس IP فرستنده در IP whitelist ترمینال نیست |
-53 |
ترمینال غیرفعال یا مسدود شده است | 403 |
ترمینال غیرفعال، معلق (suspended) یا تحت بررسی (under_review) است |
-54 |
از حد مجاز اتصال عبور شده است | 429 |
از rate limit ترمینال (پیشفرض ۱۰۰ درخواست در دقیقه) عبور کردهاید |
403 |
ترمینال یافت نشد | 403 |
ترمینال فعالی برای این توکن یافت نشد |
451 |
قرارداد ارائه خدمات این پذیرنده امضا نشده یا منقضی شده است | 400 |
قرارداد کارمزد درگاه امضا/تأیید نشده — در پنل کاربری امضا کنید |
اعتبارسنجی ورودی
خطاهای مربوط به پارامترهای نامعتبر در درخواست پرداخت.
| کد | پیام | HTTP | علت |
|---|---|---|---|
-2 |
اطلاعات دریافتی معتبر نیست | 400 |
خطای کلی اعتبارسنجی (مبلغ، order_id، callback_url، …) |
-10 |
مبلغ تراکنش قابل قبول نیست | 400 |
مبلغ خارج از بازهی مجاز (۱۰۰٬۰۰۰ تا ۴٬۰۰۰٬۰۰۰٬۰۰۰ ریال) است |
-55 |
آدرس سایت و کالبک باید یکسان باشد | 400 |
دامنهی callback_url با دامنهی ثبتشدهی ترمینال یکی نیست |
-56 |
مبلغ نامعتبر است | 400 |
فرمت amount نامعتبر است |
-57 |
شماره سفارش نامعتبر است | 400 |
order_id خالی یا بیش از ۵۰ کاراکتر یا دارای کاراکتر غیرمجاز |
-58 |
شماره کارت نامعتبر است | 400 |
فرمت card_number نادرست است |
-59 |
آدرس کالبک نامعتبر است | 400 |
callback_url خالی، طولانیتر از حد، یا بدون http(s):// |
-60 |
توضیح نامعتبر است | 400 |
description بیش از ۲۵۵ کاراکتر یا دارای < / > است |
-61 |
کد مرچنت نامعتبر است | 400 |
شناسهی پذیرنده نامعتبر است |
-63 |
directVerify is not boolean | 400 |
مقدار direct_verify بولین (true/false) نیست |
-8 |
موبایل نامعتبر است | 400 |
فرمت mobile پذیرفته نشد |
-9 |
موبایل یا تلفن نامعتبر است | 400 |
شمارهی موبایل یا تلفن نامعتبر است |
بازگشت از درگاه و فرآیند پرداخت
این کدها در مرحلهی هدایت کاربر به درگاه و بازگشت از آن رخ میدهند. بسیاری از آنها بهصورت صفحهی خطا یا پارامتر status_code در callback ظاهر میشوند.
| کد | پیام | علت |
|---|---|---|
-1 |
تراکنش توسط کاربر لغو شد | کاربر در صفحهی بانک پرداخت را لغو کرد یا تراکنش ناموفق بود |
-3 |
آدرس بازگشت همخوانی ندارد | callback_url با مقدار ثبتشده همخوانی ندارد |
-4 |
هدر Referer موجود نیست | درخواست هدایت به درگاه فاقد هدر Referer است |
-5 |
آدرس Referer نامعتبر است | مقدار Referer قابل تجزیه نیست |
-6 |
تراکنش یافت نشد یا ترمینال موجود نیست | authority نامعتبر است یا ترمینال متناظر یافت نشد |
-7 |
سایت ثبت شده با آدرس Referer همخوانی ندارد | دامنهی Referer با دامنهی ثبتشدهی ترمینال یکی نیست |
-11 |
مبلغ پرداخت شده با مبلغ تراکنش همخوانی ندارد | بانک مبلغی متفاوت از مبلغ درخواست گزارش کرد — مبلغ برگشت میخورد |
-12 |
شماره کارت پرداخت کننده با شماره کارت ارسالی همخوانی ندارد | کارت پرداختکننده با card_number ارسالی مغایرت دارد — برگشت میخورد |
-13 |
تراکنش تکراری | order_id یا authority تکراری است |
-14 |
تراکنش قبلا تسویه/برگشت شده است | تراکنش پیشتر تسویه یا برگشت داده شده است |
-21 |
زمان مجاز برای ارسال تراکنش تمام شده است | پنجرهی هدایت به درگاه (۲۰ دقیقه) منقضی شده است |
-22 |
تراکنش به درگاه ارسال شد | تلاش مجدد برای ارسال تراکنشی که قبلاً به بانک ارسال شده |
-23 |
خطا در اتصال به درگاه بانکی | خطا در ارتباط با PSP / سوئیچ بانکی |
-24 |
خطا در موجودیت تراکنش | بررسی موجودیت/دردسترسبودن تراکنش ناموفق بود |
-30 |
خطا در فرآیند پرداخت، تراکنش برگشت شده است | خطای میانهی فرآیند پرداخت — مبلغ برگشت میخورد |
-31 |
خطای ناشناخته | خطای داخلی پیشبینینشده (HTTP 500) |
-32 |
تراکنش ناموفق | تراکنش در وضعیت ناموفق نهایی شد |
404 |
تراکنش یافت نشد | تراکنشی با این authority وجود ندارد |
تأیید تراکنش (Verification)
خطاهای فراخوانی POST /v2/verifications. این اندپوینت توسط موتور تأیید پردازش میشود و خطاها در قالب پاکت استاندارد با کدهای زیر بازمیگردند:
| کد | پیام | HTTP | علت |
|---|---|---|---|
-2 |
خطا در اعتبارسنجی ورودی | 422 |
پارامترهای ورودی (authority/amount/order_id) نامعتبرند |
-19 |
شناسه یکتا، شماره سفارش یا مبلغ اشتباه است | 400 |
تراکنش با authority/order_id/amount دادهشده یافت نشد |
-33 |
تراکنش دارای وضعیت صحیح برای وریفای نیست | 400 |
تراکنش در وضعیتی نیست که قابل تأیید باشد (پرداختنشده، ناموفق، …) |
-31 |
خطای ناشناخته | 500 |
خطای داخلی یا خطا در اتصال به PSP هنگام تأیید |
403 |
ترمینال یافت نشد | 403 |
ترمینال این درخواست یافت نشد |
کدهای امضای درخواست (Signing Errors)
این کدها فقط برای ترمینالهایی صادر میشوند که قابلیت امضای درخواست (require_signature=true) روی آنها فعال است.
| کد | پیام | HTTP | علت |
|---|---|---|---|
-101 |
Missing signature headers (X-Signature, X-Timestamp) | 401 |
هدر X-Signature یا X-Timestamp ارسال نشده است |
-102 |
Request timestamp is invalid or expired (max 5 minutes) | 401 |
اختلاف X-Timestamp با زمان سرور بیش از ۵ دقیقه است |
-103 |
Invalid request signature | 401 |
امضای X-Signature با محتوای درخواست همخوانی ندارد |
-104 |
Request nonce has already been used (replay detected) | 401 |
X-Nonce قبلاً در همین ترمینال مصرف شده است (درخواست تکراری) |
-105 |
Missing X-Nonce header | 400 |
در نسخهٔ ۲ امضا X-Nonce الزامی است |
-106 |
Replay protection is temporarily unavailable | 503 |
سرویس ضدتکرار موقتاً در دسترس نیست؛ با nonce و امضای تازه دوباره تلاش کنید |
-107 |
Unsupported X-Signature-Version | 400 |
مقدار X-Signature-Version فقط 1 یا 2 است |
-108 |
Signature version 2 is required for this terminal | 401 |
این ترمینال فقط امضای نسخهٔ ۲ میپذیرد (min_signature_version در پاسخ) |
-109 |
Invalid X-Nonce | 400 |
قالب X-Nonce: ۲۲ تا ۱۲۸ کاراکتر از [A-Za-z0-9_-] (دستکم ۱۶ بایت تصادفی) |
خطاهای سامانه و کارمزد
این کدها معمولاً نشاندهندهی مشکل پیکربندی سمت سرور هستند و در شرایط عادی به مرچنت بازنمیگردند. در صورت مشاهده با پشتیبانی تماس بگیرید.
| کد | پیام | HTTP | علت |
|---|---|---|---|
405 |
Invalid PSP ID | 400 |
شناسهی PSP انتخابشده در پیکربندی معتبر نیست |
995 |
خطا در محاسبه کارمزد فرم پرداخت | 400 |
خطای محاسبهی کارمزد فرم پرداخت |
996 |
خطا در محاسبه کارمزد درگاه | 503 |
سرویس محاسبهی کارمزد موقتاً در دسترس نیست |
997 |
پیکربندی ترمینال برای تراکنش یافت نشد | 400 |
ردیف terminal_config تراکنش موجود نیست |
998 |
ترمینال اصلی یافت نشد | 400 |
ترمینال اصلی برای محاسبهی کارمزد یافت نشد |
999 |
مبلغ کیف پول اصلی یافت نشد | 400 |
ردیف wallet_amounts ترمینال موجود نیست |
کلید Idempotency و کدهای آن
هنگام ارسال هدر Idempotency-Key، بسته به وضعیت درخواستِ قبلیِ همان کلید، یکی از پاسخهای زیر را دریافت میکنید:
| HTTP | پیام | معنی |
|---|---|---|
200 |
(پاسخ اصلی همراه با "idempotent_replay": true) |
کلید قبلاً با موفقیت پردازش شده؛ همان پاسخ ذخیرهشده برگردانده شد |
202 |
Request is still being processed | درخواست قبلی با همین کلید هنوز در حال پردازش است (retry_after: 5) |
400 |
Invalid idempotency key format | فرمت کلید نامعتبر است (باید ۱۶ تا ۶۴ کاراکتر a-z A-Z 0-9 _ - باشد) |
400 |
Previous request failed. Please use a new idempotency key... | درخواست قبلی ناموفق بود؛ با کلید جدید دوباره تلاش کنید |
408 |
Previous request timed out. Please retry. | درخواست قبلی بیش از ۳۰ ثانیه معطل ماند؛ دوباره تلاش کنید |
409 |
Request payload does not match the original request | همان کلید با بدنهای متفاوت ارسال شده است (تداخل) |
محدودیتهای API
محدودیت نرخ درخواست
محدودیتها بر اساس توکن Bearer (یا IP در نبود توکن) بهازای هر دقیقه اعمال میشوند. در صورت فراتر رفتن، پاسخ HTTP 429 Too Many Requests دریافت خواهید کرد.
| آدرس | بدون امضا | با امضای معتبر (X-Signature) |
|---|---|---|
POST /v2/payments (ایجاد پرداخت) |
۱۰۰ درخواست/دقیقه | نامحدود |
POST /v2/verifications (تأیید پرداخت) |
۱۰۰ درخواست/دقیقه | نامحدود |
POST /v2/refunds (استرداد) |
۳۰ درخواست/دقیقه | نامحدود |
GET /v2/reports/* (گزارشها) |
۶۰ درخواست/دقیقه | نامحدود |
GET /v2/transactions/* (تراکنشها) |
۶۰ درخواست/دقیقه | نامحدود |
POST /v2/webhooks/{subscribe,unsubscribe,test} |
۱۰ درخواست/دقیقه | (مستقل از امضا) |
POST /v2/webhooks/test |
۳ درخواست/دقیقه | (مستقل از امضا) |
محدودیت endpoint تولید امضا
POST /api/prepare-request سه محدودیت همزمان دارد — اگر هر کدام پر شوند، پاسخ ۴۲۹ میگیرید:
| محدودیت | مقدار | identifier |
|---|---|---|
| محدودیت per-IP | ۱۰۰ درخواست/دقیقه | آدرس IP |
| محدودیت per-token | ۲۰۰ درخواست/دقیقه | هدر X-API-Token (یا IP در نبودش) |
| محدودیت روزانه | ۱۰٬۰۰۰ درخواست/روز | هدر X-API-Token (یا IP در نبودش) |
نمایش محدودیت در هدر بازگشتی
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1642248000
Retry-After: 60
پاسخ JSON خطا:
{
"success": false,
"error": {
"message": "تعداد درخواستها بیش از حد مجاز است",
"code": -54,
"type": "rate_limit_error"
},
"status_code": -54,
"timestamp": "1404-06-29 10:30:00"
}
امنیت
HTTPS
تمام ارتباطات باید از طریق HTTPS انجام شود. درخواستهای HTTP به طور خودکار به HTTPS تغییر مسیر مییابند.
API Token
توکن idg_live_… خود را در مکان امن (Vault، Secret Manager، یا متغیر محیطی سرور) نگهداری کنید. هرگز آن را در کد سمت کلاینت (مرورگر، اپ موبایل) قرار ندهید و در مخازن عمومی Git قرار نگذارید. در صورت نشت، فوراً از پنل کاربری ابطال اضطراری را بزنید (به بخش ابطال اضطراری مراجعه کنید).
Idempotency
برای جلوگیری از تراکنشهای تکراری، از هدر Idempotency-Key استفاده کنید.
IP Whitelist
میتوانید دسترسی API خود را به IP های مشخص محدود کنید از طریق پنل کاربری.
امضای درخواست (Request Signing)
برای ترمینالهایی که قابلیت امضای درخواست (require_signature) فعال است، هر درخواست ارسالی باید با secret_key ترمینال امضا شده و دو هدر زیر ارسال شود:
| هدر | توضیح |
|---|---|
X-Signature |
امضای HMAC-SHA256 محاسبهشده |
X-Timestamp |
زمان Unix درخواست (ثانیه) |
X-Nonce |
اختیاری در نسخهٔ ۱ (رشتهٔ یکتای یکبارمصرف برای هر درخواست)؛ در نسخهٔ ۲ الزامی و جزو امضا — بخش «نسخهٔ ۲ امضا» پایین |
فرمول محاسبه امضا
payload = "METHOD:endpoint:json_body:timestamp"
signature = HMAC-SHA256(payload, secret_key)
| بخش | توضیح | مثال |
|---|---|---|
METHOD |
متد HTTP به حروف بزرگ | POST |
endpoint |
نام endpoint بدون /v2/ |
payments |
json_body |
بدنه درخواست به فرمت JSON (بدون فاصله، UTF-8) | {"amount":100000,...} |
timestamp |
زمان Unix (ثانیه) — همان مقدار هدر X-Timestamp |
1727800000 |
مثال عملی
<?php
$secretKey = 'your_secret_key';
$timestamp = time();
$method = 'POST';
$endpoint = 'payments';
$body = json_encode([
'amount' => 100000,
'callback_url' => 'https://yoursite.com/callback',
'order_id' => 'ORDER-123',
], JSON_UNESCAPED_UNICODE);
$payload = "{$method}:{$endpoint}:{$body}:{$timestamp}";
$signature = hash_hmac('sha256', $payload, $secretKey);
// ارسال هدرها
$headers = [
'Authorization: Bearer YOUR_API_TOKEN',
'X-Signature: ' . $signature,
'X-Timestamp: ' . $timestamp,
'Content-Type: application/json',
];
const crypto = require('crypto');
const secretKey = 'your_secret_key';
const timestamp = Math.floor(Date.now() / 1000);
const method = 'POST';
const endpoint = 'payments';
const body = JSON.stringify({
amount: 100000,
callback_url: 'https://yoursite.com/callback',
order_id: 'ORDER-123',
});
const payload = `${method}:${endpoint}:${body}:${timestamp}`;
const signature = crypto.createHmac('sha256', secretKey).update(payload).digest('hex');
const headers = {
Authorization: 'Bearer YOUR_API_TOKEN',
'X-Signature': signature,
'X-Timestamp': String(timestamp),
'Content-Type': 'application/json',
};
import hmac, hashlib, time, json
secret_key = 'your_secret_key'
timestamp = str(int(time.time()))
method = 'POST'
endpoint = 'payments'
body = json.dumps({
'amount': 100000,
'callback_url': 'https://yoursite.com/callback',
'order_id': 'ORDER-123',
}, ensure_ascii=False)
payload = f"{method}:{endpoint}:{body}:{timestamp}"
signature = hmac.new(secret_key.encode(), payload.encode(), hashlib.sha256).hexdigest()
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN',
'X-Signature': signature,
'X-Timestamp': timestamp,
'Content-Type': 'application/json',
}
نسخهٔ ۲ امضا (پیشنهادی — nonce درون امضا)
در فرمول بالا (نسخهٔ ۱) هدر X-Nonce جزو رشتهٔ امضا نیست؛ کسی که یک درخواست امضاشده را شنود کند میتواند آن را تا ۵ دقیقه با یک nonce تازه دوباره بفرستد. نسخهٔ ۲ متد، مسیر کامل، query، timestamp، nonce و هش بدنه را با هم امضا میکند و هر nonce فقط یک بار پذیرفته میشود.
| هدر | مقدار |
|---|---|
X-Signature-Version |
2 |
X-Timestamp |
زمان Unix (ثانیه)، حداکثر ±۵ دقیقه اختلاف با سرور |
X-Nonce |
دستکم ۱۶ بایت تصادفی امن بهصورت hex (۳۲ کاراکتر) یا base64url بدون =؛ برای هر درخواست تازه |
X-Signature |
hex کوچکِ HMAC-SHA256(secret_key, canonical) |
رشتهٔ canonical هفت خط است که با \n (LF) به هم وصل میشوند، بدون newline انتهایی:
IRDG-HMAC-SHA256-V2
METHOD
PATH
CANONICAL_QUERY
TIMESTAMP
NONCE
BODY_SHA256
| بخش | قاعده |
|---|---|
METHOD |
متد HTTP با حروف بزرگ |
PATH |
مسیر کامل با /v2/ و / ابتدایی، بدون query و بدون / انتهایی — مثلاً /v2/payments (برخلاف نسخهٔ ۱ که payments بود) |
CANONICAL_QUERY |
هر name=value را decode (+ = فاصله) و دوباره با RFC 3986 encode کنید (فقط A-Z a-z 0-9 - . _ ~ بیتغییر)، بر اساس نام و سپس مقدار به ترتیب بایتی مرتب کنید و با & بچسبانید. بدون query ⇒ خط خالی |
TIMESTAMP |
همان X-Timestamp |
NONCE |
همان X-Nonce |
BODY_SHA256 |
hex کوچکِ SHA-256 روی بایتهای خام بدنهٔ ارسالی (بدون بدنه: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855) |
<?php
$secretKey = 'your_secret_key';
$timestamp = (string) time();
$nonce = bin2hex(random_bytes(16));
$method = 'POST';
$path = '/v2/payments';
$query = '';
$body = json_encode([
'amount' => 100000,
'callback_url' => 'https://yoursite.com/callback',
'order_id' => 'ORDER-123',
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$canonical = implode("\n", [
'IRDG-HMAC-SHA256-V2', $method, $path, $query, $timestamp, $nonce, hash('sha256', $body),
]);
$signature = hash_hmac('sha256', $canonical, $secretKey);
$headers = [
'Authorization: Bearer YOUR_API_TOKEN',
'Content-Type: application/json',
'X-Signature-Version: 2',
'X-Timestamp: ' . $timestamp,
'X-Nonce: ' . $nonce,
'X-Signature: ' . $signature,
];
// دقیقاً همین $body را بهعنوان بدنهٔ درخواست بفرستید.
const crypto = require('crypto');
const secretKey = 'your_secret_key';
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = crypto.randomBytes(16).toString('hex');
const method = 'POST';
const path = '/v2/payments';
const query = '';
const body = JSON.stringify({
amount: 100000,
callback_url: 'https://yoursite.com/callback',
order_id: 'ORDER-123',
});
const canonical = [
'IRDG-HMAC-SHA256-V2', method, path, query, timestamp, nonce,
crypto.createHash('sha256').update(body, 'utf8').digest('hex'),
].join('\n');
const signature = crypto.createHmac('sha256', secretKey).update(canonical).digest('hex');
const headers = {
Authorization: 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
'X-Signature-Version': '2',
'X-Timestamp': timestamp,
'X-Nonce': nonce,
'X-Signature': signature,
};
import hashlib, hmac, json, secrets, time
secret_key = 'your_secret_key'
timestamp = str(int(time.time()))
nonce = secrets.token_hex(16)
method = 'POST'
path = '/v2/payments'
query = ''
body = json.dumps({
'amount': 100000,
'callback_url': 'https://yoursite.com/callback',
'order_id': 'ORDER-123',
}, ensure_ascii=False, separators=(',', ':')).encode()
canonical = '\n'.join([
'IRDG-HMAC-SHA256-V2', method, path, query, timestamp, nonce, hashlib.sha256(body).hexdigest(),
])
signature = hmac.new(secret_key.encode(), canonical.encode(), hashlib.sha256).hexdigest()
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
'X-Signature-Version': '2',
'X-Timestamp': timestamp,
'X-Nonce': nonce,
'X-Signature': signature,
}
# requests.post(url, data=body, headers=headers) — همان بایتهای body
بردار آزمون نسخهٔ ۲
پیادهسازی خود را با این مقادیر بسنجید (secret_key = irdg_test_secret_0123456789abcdef):
| ورودی | TV1 | TV2 |
|---|---|---|
| method / path | POST /v2/payments |
GET /v2/transactions |
| query | (خالی) | status=paid&page=2&from=2026-01-01 |
| timestamp | 1767225600 |
1767225600 |
| nonce | a1b2c3d4e5f60718293a4b5c6d7e8f90 |
Zm9vYmFyYmF6cXV4MTIzNDU2 |
| body | {"amount":100000,"callback_url":"https://shop.example/callback","order_id":"ORDER-123"} |
(خالی) |
| canonical query | (خالی) | from=2026-01-01&page=2&status=paid |
| sha256(body) | 288eaba9db252d57fe4f06e7ae1d2a0f77eb1a18b3b2930e6f9c524924c62a83 |
e3b0c442…7852b855 |
| X-Signature | 2821a709b57106aeb2a549c5af874045013accbb5ac1603918c533fd32ed7b1e |
1faeb16f154312cd5ed4960d16a3653720cc4cf2592602e275bdd8ae5a97b7a8 |
مهاجرت از نسخهٔ ۱ به ۲
- محاسبهٔ امضای نسخهٔ ۲ را اضافه کنید و TV1/TV2 را در تست خود بازتولید کنید.
- هدر
X-Signature-Version: 2و یکX-Nonceتازه را در هر درخواست بفرستید — در این مرحله هر دو نسخه پذیرفته میشوند. - پس از اطمینان، از پشتیبانی بخواهید ترمینال را روی «فقط نسخهٔ ۲» قفل کند.
تولید امضا از طریق API
اگر نمیخواهید امضا را سمت کلاینت محاسبه کنید، میتوانید از endpoint کمکی زیر استفاده کنید.
curl -X POST "https://ipg.irandargah.com/api/prepare-request" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"method": "POST",
"endpoint": "payments",
"data": {
"amount": 100000,
"callback_url": "https://yoursite.com/callback",
"order_id": "ORDER-123"
}
}'
<?php
$body = [
'method' => 'POST',
'endpoint' => 'payments',
'data' => [
'amount' => 100000,
'callback_url' => 'https://yoursite.com/callback',
'order_id' => 'ORDER-123',
],
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://ipg.irandargah.com/api/prepare-request');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer YOUR_API_TOKEN',
'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body, JSON_UNESCAPED_UNICODE));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$prepared = json_decode($response, true);
// $prepared['signature'], $prepared['timestamp'], $prepared['idempotency_key']
?>
const body = {
method: "POST",
endpoint: "payments",
data: {
amount: 100000,
callback_url: "https://yoursite.com/callback",
order_id: "ORDER-123",
},
};
const response = await fetch(
"https://ipg.irandargah.com/api/prepare-request",
{
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify(body),
}
);
const prepared = await response.json();
// prepared.signature, prepared.timestamp, prepared.idempotency_key
body := map[string]interface{}{
"method": "POST",
"endpoint": "payments",
"data": map[string]interface{}{
"amount": 100000,
"callback_url": "https://yoursite.com/callback",
"order_id": "ORDER-123",
},
}
jsonData, _ := json.Marshal(body)
req, _ := http.NewRequest("POST", "https://ipg.irandargah.com/api/prepare-request", bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
var prepared map[string]interface{}
json.NewDecoder(resp.Body).Decode(&prepared)
body = {
'method': 'POST',
'endpoint': 'payments',
'data': {
'amount': 100000,
'callback_url': 'https://yoursite.com/callback',
'order_id': 'ORDER-123',
},
}
response = requests.post(
'https://ipg.irandargah.com/api/prepare-request',
headers={
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
},
json=body
)
prepared = response.json()
# prepared['signature'], prepared['timestamp'], prepared['idempotency_key']
var body = new {
method = "POST",
endpoint = "payments",
data = new {
amount = 100000,
callback_url = "https://yoursite.com/callback",
order_id = "ORDER-123",
},
};
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_TOKEN");
var json = JsonConvert.SerializeObject(body);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://ipg.irandargah.com/api/prepare-request", content);
var result = await response.Content.ReadAsStringAsync();
پاسخ:
{
"success": true,
"idempotency_key": "idem_1727800000_abc123xyz",
"signature_required": true,
"signature": "a1b2c3d4e5f6...",
"timestamp": "1727800000",
"algorithm": "HMAC-SHA256",
"headers": {
"X-Idempotency-Key": "idem_1727800000_abc123xyz",
"X-Signature": "a1b2c3d4e5f6...",
"X-Timestamp": "1727800000",
"Content-Type": "application/json"
},
"expires_at": "2025-10-01T10:35:00.000Z"
}
امضای دریافتشده ۵ دقیقه اعتبار دارد.
تأیید امضای پاسخ (Response Signature Verification)
سرور ایراندرگاه پاسخ تمام endpointهای v2 را برای ترمینالهایی که secret_key دارند امضا میکند. دو هدر زیر به پاسخ اضافه میشوند:
| هدر | توضیح |
|---|---|
X-Response-Signature |
امضای HMAC-SHA256 بدنه پاسخ |
X-Response-Timestamp |
زمان Unix تولید پاسخ (ثانیه) |
فرمول تأیید
payload = response_body + ":" + X-Response-Timestamp
expected = HMAC-SHA256(payload, secret_key)
is_valid = timing_safe_compare(expected, X-Response-Signature)
بازه زمانی قابل قبول: ۵ دقیقه از زمان درج شده در X-Response-Timestamp.
مثال تأیید پاسخ
<?php
function verifyResponse(string $body, string $signature, string $timestamp, string $secretKey): bool
{
if (abs(time() - (int)$timestamp) > 300) {
return false; // timestamp منقضی شده
}
$payload = "{$body}:{$timestamp}";
$expected = hash_hmac('sha256', $payload, $secretKey);
return hash_equals($expected, $signature);
}
// استفاده
$isValid = verifyResponse(
$responseBody,
$response->getHeader('X-Response-Signature'),
$response->getHeader('X-Response-Timestamp'),
'your_secret_key'
);
const crypto = require('crypto');
function verifyResponse(body, signature, timestamp, secretKey) {
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return false; // timestamp منقضی شده
}
const payload = `${body}:${timestamp}`;
const expected = crypto.createHmac('sha256', secretKey).update(payload).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
import hmac, hashlib, time
def verify_response(body: str, signature: str, timestamp: str, secret_key: str) -> bool:
if abs(time.time() - int(timestamp)) > 300:
return False # timestamp منقضی شده
payload = f"{body}:{timestamp}"
expected = hmac.new(secret_key.encode(), payload.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
وضعیت سیستم
بررسی سلامت
curl "https://ipg.irandargah.com/health"
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://ipg.irandargah.com/health");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
const response = await fetch("https://ipg.irandargah.com/health");
const data = await response.json();
resp, err := http.Get("https://ipg.irandargah.com/health")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
response = requests.get('https://ipg.irandargah.com/health')
result = response.json()
using var client = new HttpClient();
var response = await client.GetAsync("https://ipg.irandargah.com/health");
var content = await response.Content.ReadAsStringAsync();
پاسخ موفق:
{
"status": "ok",
"timestamp": "1404-06-29 10:30:00",
"version": "2.0.0"
}
پاسخ ناسالم (یکی از سرویسهای زیرساخت در دسترس نیست —
HTTP 503):
{
"status": "unhealthy",
"timestamp": "1404-06-29 10:30:00",
"version": "2.0.0"
}
این آدرس برای بررسی وضعیت سلامت API استفاده میشود.
آدرس درخواست
https://ipg.irandargah.com/health
افزونه اقساط (Installments Plugin)
افزونهی اقساط به مشتریان شما اجازه میدهد خریدشان را در چند قسط پرداخت کنند و به شما (پذیرنده) این امکان را میدهد که وضعیت اقساطِ باز هر مشتری را استعلام کرده و تسویهی هر قسط را — دقیقاً مثل یک تراکنش معمولی — از طریق درگاه پرداخت خودتان انجام دهید.
آدرس پایه
https://api.irandargah.com/api/v1/plugin/installments
احراز هویت
احراز هویت این API دقیقاً مانند API اصلیِ درگاه پرداخت است و از همان توکن ترمینال استفاده میکند — نیازی به توکن جداگانه نیست:
Authorization: Bearer <توکن ترمینال شما>
# روش اول
curl "https://api.irandargah.com/api/v1/plugin/installments/open?mobile=09121234567" \
-H "Authorization: Bearer YOUR_API_TOKEN"
# روش دوم — معادل روش اول
curl "https://api.irandargah.com/api/v1/plugin/installments/open?mobile=09121234567" \
-H "X-API-Key: YOUR_API_TOKEN"
جریان کار در سایتهای اختصاصی (غیر CMS)
اگر از افزونهی رسمی ووکامرس استفاده نمیکنید و سایت فروشگاهی خودتان را دارید، تسویهی اقساط با این جریان انجام میشود:
- فهرست اقساط باز: با شماره موبایل مشتریِ واردشده، اقساط بازِ او نزد شما را از
GET /openبگیرید. - ایجاد پرداخت معمولی: برای قسطی که مشتری میخواهد تسویه کند، یک تراکنش عادی در درگاه پرداخت خودتان (
POST /v2/payments) به مبلغpayableAmountهمان قسط ایجاد کنید و کاربر را بهgateway_urlهدایت کنید. - تأیید تراکنش: پس از بازگشت کاربر، طبق روال معمولِ بخش «تأیید پرداخت» تراکنش را verify کنید.
- اتصال (Attach): پس از تأیید موفق،
authority(یاtransactionId) همان تراکنش را بهPOST /{installmentId}/attachبفرستید تا قسط تسویهشده علامت بخورد.
دریافت اقساط باز مشتری
curl "https://api.irandargah.com/api/v1/plugin/installments/open?mobile=09121234567" \
-H "Authorization: Bearer YOUR_API_TOKEN"
<?php
$mobile = '09121234567';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.irandargah.com/api/v1/plugin/installments/open?mobile={$mobile}");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer YOUR_API_TOKEN']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
?>
const mobile = "09121234567";
const response = await fetch(
`https://api.irandargah.com/api/v1/plugin/installments/open?mobile=${mobile}`,
{ headers: { Authorization: "Bearer YOUR_API_TOKEN" } }
);
const result = await response.json();
mobile := "09121234567"
url := fmt.Sprintf("https://api.irandargah.com/api/v1/plugin/installments/open?mobile=%s", mobile)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
client := &http.Client{}
resp, err := client.Do(req)
mobile = '09121234567'
response = requests.get(
'https://api.irandargah.com/api/v1/plugin/installments/open',
headers={'Authorization': 'Bearer YOUR_API_TOKEN'},
params={'mobile': mobile}
)
result = response.json()
var mobile = "09121234567";
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_TOKEN");
var response = await client.GetAsync($"https://api.irandargah.com/api/v1/plugin/installments/open?mobile={mobile}");
var json = await response.Content.ReadAsStringAsync();
پاسخ موفق:
{
"success": true,
"data": [
{
"installmentId": 48221,
"planNumber": "PLAN-2026-004871",
"sequence": 3,
"dueDate": "1404-07-10",
"amount": "5000000",
"paidAmount": "0",
"penaltyAmount": "120000",
"status": "OVERDUE",
"payableAmount": "5120000",
"penaltyOutstanding": "120000",
"collectPenalty": true
},
{
"installmentId": 48222,
"planNumber": "PLAN-2026-004871",
"sequence": 4,
"dueDate": "1404-08-10",
"amount": "5000000",
"paidAmount": "2000000",
"penaltyAmount": "0",
"status": "PARTIALLY_PAID",
"payableAmount": "3000000",
"penaltyOutstanding": "0",
"collectPenalty": false
}
],
"timestamp": "1404-06-29T10:30:00+03:30",
"duration": 42
}
این endpoint اقساط بازِ مشتریِ واردشده (بر اساس شماره موبایل) را که نزد ترمینال شما ثبت شده، برمیگرداند. فقط اقساطی که هنوز تسویه نشدهاند در پاسخ میآیند.
آدرس درخواست
https://api.irandargah.com/api/v1/plugin/installments/open
پارامترهای درخواست
| پارامتر | نوع | اجباری | توضیح |
|---|---|---|---|
mobile |
string | ✅ | شماره موبایل مشتری به فرمت 09xxxxxxxxx |
ساختار پاکتِ پاسخ
پاسخهای این API با پاکت استاندارد زیر بازگردانده میشوند — توجه کنید این پاکت با پاکتِ استاندارد API اصلیِ درگاه (بخش «کدهای خطا») کمی متفاوت است و بهجای message/status_code از duration استفاده میکند:
| فیلد | نوع | توضیح |
|---|---|---|
success |
boolean | وضعیت موفقیت درخواست |
data |
array | آرایهی اقساط باز (در پاسخ خطا: null) |
timestamp |
string | زمان پاسخ |
duration |
integer | زمان پردازش درخواست به میلیثانیه |
فیلدهای هر قسط
| فیلد | نوع | توضیح |
|---|---|---|
installmentId |
integer | شناسهی یکتای قسط — همین مقدار در آدرس endpoint اتصال (attach) استفاده میشود |
planNumber |
string | شناسهی طرح اقساطی که این قسط بخشی از آن است |
sequence |
integer | شمارهی ترتیبی قسط در طرح (مثلاً قسط سوم از دوازده) |
dueDate |
string | تاریخ سررسید قسط (تاریخ خورشیدی) |
amount |
string | مبلغ اصلیِ قسط به ریال (رشتهی عددی) |
paidAmount |
string | مبلغی که تاکنون بابت این قسط پرداخت شده به ریال |
penaltyAmount |
string | مجموع جریمهی تأخیرِ محاسبهشده روی این قسط به ریال |
status |
string | یکی از: UPCOMING, DUE, OVERDUE, PARTIALLY_PAID |
payableAmount |
string | مبلغ قابل پرداخت به ریال — مرجعِ اصلی برای مبلغ تراکنش |
penaltyOutstanding |
string | بخشی از جریمه که هنوز وصول نشده و قابل وصول است، به ریال |
collectPenalty |
boolean | آیا جریمه در این مرحله باید وصول شود؟ |
وضعیتهای قسط
| وضعیت | معنی |
|---|---|
UPCOMING |
سررسید هنوز نرسیده |
DUE |
امروز سررسید است |
OVERDUE |
از سررسید گذشته و مشمول جریمه شده |
PARTIALLY_PAID |
بخشی از مبلغ قسط قبلاً پرداخت شده و باقیمانده هنوز باز است |
اتصال (Attach) — تسویه قسط
پس از اینکه تراکنشِ پرداخت (به مبلغ payableAmount) روی درگاه خودتان با موفقیت تأیید (verify) شد، با یکی از دو شناسهی زیر آن تراکنش را به قسط متصل کنید تا بهعنوان تسویهشده ثبت شود:
curl -X POST "https://api.irandargah.com/api/v1/plugin/installments/48221/attach" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"authority": "2025100121424146HC"
}'
<?php
$installmentId = 48221;
$data = [
'authority' => '2025100121424146HC', // یا: 'transactionId' => 123 — دقیقاً یکی از این دو
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.irandargah.com/api/v1/plugin/installments/{$installmentId}/attach");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer YOUR_API_TOKEN',
'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
const installmentId = 48221;
const payload = { authority: "2025100121424146HC" }; // یا: { transactionId: 123 } — دقیقاً یکی از این دو
const response = await fetch(
`https://api.irandargah.com/api/v1/plugin/installments/${installmentId}/attach`,
{
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
}
);
const result = await response.json();
type AttachRequest struct {
Authority string `json:"authority,omitempty"`
// یا: TransactionID int `json:"transactionId,omitempty"` — دقیقاً یکی از این دو
}
installmentId := 48221
payload := AttachRequest{Authority: "2025100121424146HC"}
jsonData, _ := json.Marshal(payload)
url := fmt.Sprintf("https://api.irandargah.com/api/v1/plugin/installments/%d/attach", installmentId)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
req.Header.Set("Content-Type", "application/json")
installment_id = 48221
payload = {'authority': '2025100121424146HC'} # یا: {'transactionId': 123} — دقیقاً یکی از این دو
response = requests.post(
f'https://api.irandargah.com/api/v1/plugin/installments/{installment_id}/attach',
headers={'Authorization': 'Bearer YOUR_API_TOKEN'},
json=payload
)
result = response.json()
var installmentId = 48221;
var payload = new { authority = "2025100121424146HC" }; // یا: new { transactionId = 123 } — دقیقاً یکی از این دو
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_TOKEN");
var json = JsonConvert.SerializeObject(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync($"https://api.irandargah.com/api/v1/plugin/installments/{installmentId}/attach", content);
پاسخ موفق:
{
"success": true,
"data": {
"installmentId": 48221,
"status": "PARTIALLY_PAID",
"attached": true
},
"timestamp": "1404-06-29T10:35:00+03:30",
"duration": 58
}
آدرس درخواست
https://api.irandargah.com/api/v1/plugin/installments/{installmentId}/attach
پارامترهای ارسالی
دقیقاً یکی از دو فیلد زیر را ارسال کنید — نه هر دو، نه هیچکدام:
| پارامتر | نوع | توضیح |
|---|---|---|
transactionId |
integer | شناسهی داخلی تراکنشِ موفق و تأییدشدهی درگاه پرداخت |
authority |
string | مقدار authority همان تراکنشِ موفق و تأییدشدهی درگاه پرداخت |
خطاهای این endpoint
| HTTP | علت |
|---|---|
400 |
هم transactionId و هم authority ارسال شده، یا هیچکدام ارسال نشده |
401 |
توکن نامعتبر یا ارسالنشده است |
403 |
افزونهی اقساط برای این ترمینال فعال نیست — پیام: «این قابلیت برای حساب شما فعال نیست» |
404 |
قسط یا تراکنش یافت نشد، یا تراکنش/قسط متعلق به ترمینال شما نیست |
پشتیبانی آماده در افزونهی ووکامرس
اگر از افزونهی رسمی ووکامرسِ ایراندرگاه استفاده میکنید، لازم نیست موارد بالا را دستی پیادهسازی کنید. این افزونه یک قابلیت آماده به نام «اقساط من» دارد که تمام جریان استعلام و اتصال قسط را برای مشتریان شما مدیریت میکند.
اتصال دستیارهای هوش مصنوعی (MCP)
ایراندرگاه یک سرور MCP (Model Context Protocol) ارائه میدهد که به دستیارهای هوش مصنوعی (مثل Claude Code، Codex، Cursor، VS Code یا Claude Desktop) اجازه میدهد مستقیماً و بهصورت فقطخواندنی به دادههای پنل پذیرندگی شما — تراکنشها، تسویهها، هشدارها و مستندات — دسترسی داشته باشند، بدون اینکه مجبور باشید دادهای را کپی/پیست کنید.
ساخت توکن دسترسی
از پنل کاربری خود مسیر زیر را باز کنید:
تنظیمات ← دسترسی هوش مصنوعی (MCP)
با زدن دکمهی «ساخت توکن»، یک توکن جدید صادر میشود. این توکن فقط یکبار نمایش داده میشود؛ آن را در جای امنی (Vault، Secret Manager یا فایل تنظیمات دستیار خودتان) نگه دارید.
اتصال از Claude Desktop یا Cursor
فایل تنظیمات MCP دستیار خود را ویرایش کنید و بلوک زیر را اضافه کنید (بهجای <TOKEN> توکنی که از پنل گرفتید را قرار دهید):
{
"mcpServers": {
"irandargah": {
"url": "https://api.irandargah.com/mcp",
"headers": {
"Authorization": "Bearer <TOKEN>"
}
}
}
}
اتصال از Claude Code (CLI)
claude mcp add --transport http irandargah https://api.irandargah.com/mcp --header "Authorization: Bearer <TOKEN>"
اتصال از Codex (OpenAI)
Codex (نسخهی خط فرمان و افزونهی ادیتور) سرورهای MCP را از فایل ~/.codex/config.toml میخواند. بلوک زیر را به این فایل اضافه کنید:
[mcp_servers.irandargah]
url = "https://api.irandargah.com/mcp"
bearer_token_env_var = "IRANDARGAH_MCP_TOKEN"
سپس توکن را در متغیر محیطی IRANDARGAH_MCP_TOKEN قرار دهید تا در فایل تنظیمات ذخیره نشود:
export IRANDARGAH_MCP_TOKEN="<TOKEN>"
اتصال از VS Code (GitHub Copilot)
در ریشهی پروژه فایل .vscode/mcp.json را بسازید (یا از پالت فرمان، MCP: Open User Configuration را برای همهی پروژهها باز کنید) و بلوک زیر را اضافه کنید. VS Code هنگام اولین اتصال توکن را از شما میپرسد و آن را بهصورت امن نگه میدارد:
{
"inputs": [
{
"type": "promptString",
"id": "irandargah-token",
"description": "توکن MCP ایراندرگاه",
"password": true
}
],
"servers": {
"irandargah": {
"type": "http",
"url": "https://api.irandargah.com/mcp",
"headers": {
"Authorization": "Bearer ${input:irandargah-token}"
}
}
}
}
سپس در پنل چت Copilot حالت Agent را انتخاب کنید تا ابزارهای ایراندرگاه در دسترس باشند.
سایر دستیارها
هر دستیاری که از سرور MCP راهدور با انتقال Streamable HTTP و هدر دلخواه پشتیبانی کند، با همین دو مقدار وصل میشود:
- آدرس:
https://api.irandargah.com/mcp - هدر:
Authorization: Bearer <TOKEN>
ابزارهای در دسترس
سرور MCP ایراندرگاه مجموعهای از ابزارهای فقطخواندنی را در اختیار دستیار قرار میدهد:
مستندات
| ابزار | توضیح |
|---|---|
search_docs |
جستوجوی متنی در مستندات فنی ایراندرگاه و بازگرداندن بخشهای مرتبط |
get_doc |
دریافت کامل یک صفحهی مستندات با شناسهی (slug) آن |
list_docs |
فهرست تمام صفحات مستندات موجود |
get_error_code |
دریافت توضیح یک کد خطای API از جدول کدهای خطا |
دادههای پنل پذیرندگی
| ابزار | توضیح |
|---|---|
get_dashboard_stats |
آمار کلی داشبورد (تراکنش، مبلغ، نرخ موفقیت و …) |
get_failure_insights |
تحلیل علل ناموفق بودن تراکنشهای اخیر |
get_settlement_warnings |
هشدارهای فعال مربوط به تسویه (مثل شبای ثبتنشده یا حساب مسدود) |
search_transactions |
جستوجوی تراکنشها با فیلترهایی مثل تاریخ، وضعیت و مبلغ |
get_settlements |
فهرست تسویهها و وضعیت هرکدام |
get_wallet_and_terminals |
موجودی کیفپول و فهرست ترمینالهای فعال |
get_weekly_summary |
خلاصهی عملکرد هفتگی حساب |
محدودیتها
- فقطخواندنی: هیچ ابزاری دادهای در حساب شما تغییر نمیدهد.
- نرخ درخواست: دسترسی MCP محدود به سقف مشخصی در ساعت و در روز است؛ عبور از سقف با خطای
429پاسخ داده میشود. - اعتبار توکن: هر توکن حداکثر ۹۰ روز معتبر است و با ابطال از پنل بلافاصله غیرفعال میشود.
- حریم خصوصی: شماره کارت کامل و شماره موبایل مشتریان هرگز از طریق ابزارهای MCP در دسترس دستیار قرار نمیگیرد.
نشان اعتماد ایران درگاه
با قرار دادن کد زیر در بخشی از وبسایت خود که برای کاربران قابل مشاهده است (مانند فوتر)، نشان اعتماد ایراندرگاه نمایش داده میشود و مشتریان از پردازش امنِ پرداختها توسط ایراندرگاه مطمئن میشوند:
<script src="https://trust.irandargah.com/index.js"></script>
<iframe
src="https://trust.irandargah.com/seal.html"
style="border:0;width:96px;height:120px;overflow:hidden"
scrolling="no"
title="نماد اعتماد ایران درگاه"
></iframe>
اتصال فروشگاهسازها (IranDargah Connect)
IranDargah Connect مسیر رسمی اتصال پلتفرمهای فروشگاهساز و تجارت الکترونیک (که به تعداد زیادی پذیرنده سرویس میدهند) به ایراندرگاه است. بهجای اینکه هر پذیرنده توکن API را از پنل ایراندرگاه کپی و در تنظیمات پلتفرم شما جایگذاری کند، پذیرنده با یک کلیک و با رضایت صریح خودش به پلتفرم شما اجازهٔ محدود میدهد و پلتفرم شما از طریق API، درگاه را درخواست میدهد و اعتبار آن را دریافت میکند.
این سرویس بر پایهٔ OAuth 2.0 (Authorization Code + PKCE اجباری، با Refresh Token چرخشی) ساخته شده و برای همهٔ شرکا یکسان است؛ هیچ شریکی مسیر یا امتیاز اختصاصی ندارد.
معرفی و محدودهٔ دسترسی (Connect Overview)
آدرسها در محیط عملیاتی:
| مورد | مقدار |
|---|---|
| Issuer / آدرس پایهٔ API | https://api-v2.irandargah.com |
| صفحهٔ رضایت پذیرنده | https://panel.irandargah.com/oauth/authorize |
| Token / Revoke | https://api-v2.irandargah.com/api/v1/oauth/token و .../oauth/revoke |
| کلید عمومی (JWKS) | https://api-v2.irandargah.com/api/v1/oauth/jwks.json |
| API شریک | https://api-v2.irandargah.com/api/v1/connect/* |
Scopeها
| scope | دسترسی |
|---|---|
profile:read |
GET /connect/me — شناسهٔ پایدار پذیرنده (sub)، نام نمایشی، موبایل ماسکشده، وضعیت احراز هویت |
terminals:provision |
POST /connect/terminals — درخواست درگاه برای یک دامنه |
terminals:read |
GET /connect/terminals/:terminalId — وضعیت درگاههایی که همین شریک ساخته یا پیوند داده |
terminals:credentials |
POST /connect/terminals/:terminalId/credentials — توکن API و کلید امضای درگاه فعال |
آنچه شریک میتواند و نمیتواند ببیند
- شریک هیچ دسترسی به تراکنشها، تسویهها، کیف پول، مدارک احراز هویت یا ویرایش پروفایل پذیرنده ندارد؛ scope برای این موارد وجود ندارد.
- فقط درگاههایی دیده میشوند که توسط همین شریک و برای همین پذیرنده ساخته یا پیوند داده شدهاند. درگاههای دیگر پذیرنده دیده نمیشوند و درخواست روی آنها
404میدهد. - پذیرنده هر زمان از بخش «اتصالهای من» در پنل ایراندرگاه میتواند اتصال را قطع کند. قطع اتصال همهٔ توکنهای OAuth شما برای آن پذیرنده را فوراً باطل میکند، اما اعتبار درگاههای صادرشده (توکن API و کلید امضا) را باطل نمیکند؛ درگاه مال پذیرنده است و از بخش «درگاهها»ی پنل او قابل مدیریت است.
ثبتنام بهعنوان شریک (Partner Onboarding)
برای شروع با ایراندرگاه تماس بگیرید (پشتیبانی) و اطلاعات زیر را ارسال کنید:
| اطلاعات | توضیح |
|---|---|
| نام شریک | روی صفحهٔ رضایت به پذیرنده نمایش داده میشود |
| آدرس لوگو | آدرس https یک تصویر برای صفحهٔ رضایت |
| Redirect URIها | آدرسهای دقیق https (راهنمای زیر) |
| scopeهای موردنیاز | فقط آنچه واقعاً لازم دارید |
| کد معرف (اختیاری) | برای ثبتنام پذیرندگانِ جدیدی که از طریق شما به ایراندرگاه میآیند |
در پاسخ دو مقدار دریافت میکنید:
client_idبه شکلic_…(عمومی)؛client_secretبه شکلics_…که فقط یکبار نمایش داده میشود. ایراندرگاه آن را فقط بهصورت هش نگه میدارد و قابل بازیابی نیست؛ در صورت گمشدن باید secret جدید صادر شود.
آدرس بازگشت (Redirect URI)
- تطابق دقیق و کاراکتربهکاراکتر با آدرس ثبتشده بررسی میشود: scheme، host، مسیر، اسلش انتهایی و query. wildcard وجود ندارد. در محیط عملیاتی فقط
httpsپذیرفته میشود. - میتوانید چند Redirect URI ثبت کنید (مثلاً یکی برای محیط staging خودتان).
- مثال مناسب:
https://panel.platform.example/connect/irandargah/callback - مثال نامناسب:
https://shop-1234.example.ir/connect/callback(دامنهٔ هر فروشگاه متفاوت است و قابل ثبت نیست).
اگر صفحهٔ تنظیمات پلتفرم شما روی دامنهٔ خود هر فروشگاه سرو میشود، یک callback مرکزی روی دامنهٔ خود پلتفرم بسازید و بازگشت پذیرنده به فروشگاهش را با state سمت سرور مدیریت کنید: هنگام شروع اتصال، رکورد یکبارمصرفی با کلید state بسازید که شناسهٔ فروشگاه، آدرس بازگشت داخلی و code_verifier را نگه میدارد. در callback، با state رکورد را پیدا کنید و پس از ثبت اتصال، پذیرنده را به صفحهٔ تنظیمات همان فروشگاه هدایت کنید. دامنهٔ فروشگاه فقط در بدنهٔ POST /connect/terminals (فیلد domain) میآید، نه در Redirect URI.
مرور جریان (Authorization Flow)
- پذیرنده در پنل پلتفرم شما دکمهٔ «اتصال به ایراندرگاه» را میزند.
- سرور شما
state،code_verifierوcode_challengeمیسازد، آنها را سمت سرور ذخیره میکند و مرورگر را به صفحهٔ رضایت ایراندرگاه هدایت میکند. - پذیرنده وارد پنل ایراندرگاه میشود (یا ثبتنام میکند)، نام شما و scopeهای درخواستی را میبیند و «اجازه میدهم» یا «نمیدهم» را میزند.
- مرورگر به Redirect URI شما برمیگردد (
code،stateوiss، یاerror). - سرور شما
stateوissرا میسنجد وcodeرا (باcode_verifierوclient_secret) به توکن تبدیل میکند. - سرور شما با access token،
POST /connect/terminalsرا صدا میزند؛ تاACTIVEشدن وضعیت را poll میکند؛ سپسPOST .../credentialsرا میزند و توکن API درگاه را رمزشده ذخیره میکند.
پذیرنده پلتفرم (سرور) پنل ایراندرگاه API ایراندرگاه
| کلیک «اتصال» | | |
|------------------->| state, verifier, challenge | |
| | (ذخیره سمت سرور) | |
|<-- 302 به /oauth/authorize?...&code_challenge -------| |
|------------------------------------------------------>| ورود + رضایت |
|<-- 302 به redirect_uri?code=..&state=..&iss=.. -------| |
|------------------->| بررسی state و iss | |
| |-- POST /oauth/token (code, code_verifier, secret) --->|
| |<-- access_token, refresh_token, sub ------------------|
| |-- POST /connect/terminals ---------------------------->|
| |-- GET /connect/terminals/:id (poll) ----------------->|
| |-- POST /connect/terminals/:id/credentials ------------>|
درخواست مجوز (Authorize)
پذیرنده را (با ریدایرکت مرورگر) به آدرس زیر بفرستید:
GET https://panel.irandargah.com/oauth/authorize
| پارامتر | توضیح |
|---|---|
response_type |
همیشه code |
client_id |
شناسهٔ کلاینت (ic_…) |
redirect_uri |
دقیقاً یکی از آدرسهای ثبتشده |
scope |
فهرست scopeها با فاصله (حداقل یکی؛ فقط scopeهای مجاز شما) |
state |
رشتهٔ تصادفی غیرقابلپیشبینی، حداقل ۳۲ بایت (حداکثر ۵۱۲ کاراکتر) |
code_challenge |
BASE64URL(SHA256(code_verifier)) — ۴۳ کاراکتر |
code_challenge_method |
همیشه S256 |
code_verifier رشتهای تصادفی با ۴۳ تا ۱۲۸ کاراکتر از مجموعهٔ A-Z a-z 0-9 - . _ ~ است. PKCE اجباری است و روش plain پذیرفته نمیشود.
# ساخت code_verifier، code_challenge و state، و چاپ آدرس مجوز
CODE_VERIFIER=$(openssl rand -base64 48 | tr '+/' '-_' | tr -d '=\n')
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')
STATE=$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=\n')
echo "https://panel.irandargah.com/oauth/authorize?response_type=code\
&client_id=$CLIENT_ID\
&redirect_uri=https%3A%2F%2Fpanel.platform.example%2Fconnect%2Firandargah%2Fcallback\
&scope=profile%3Aread%20terminals%3Aprovision%20terminals%3Aread%20terminals%3Acredentials\
&state=$STATE\
&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256"
<?php
function b64url(string $bin): string {
return rtrim(strtr(base64_encode($bin), '+/', '-_'), '=');
}
$codeVerifier = b64url(random_bytes(48)); // 64 کاراکتر
$codeChallenge = b64url(hash('sha256', $codeVerifier, true));
$state = b64url(random_bytes(32));
// state و code_verifier را سمت سرور و وابسته به کاربرِ واردشدهٔ پلتفرم ذخیره کنید
// (نه در کوکی/مرورگر)، مثلاً با TTL ده دقیقه.
save_connect_state($state, [
'user_id' => $currentUser->id,
'shop_id' => $shop->id,
'code_verifier' => $codeVerifier,
]);
$url = 'https://panel.irandargah.com/oauth/authorize?' . http_build_query([
'response_type' => 'code',
'client_id' => getenv('IRANDARGAH_CLIENT_ID'),
'redirect_uri' => 'https://panel.platform.example/connect/irandargah/callback',
'scope' => 'profile:read terminals:provision terminals:read terminals:credentials',
'state' => $state,
'code_challenge' => $codeChallenge,
'code_challenge_method' => 'S256',
], '', '&', PHP_QUERY_RFC3986);
header('Location: ' . $url);
import { randomBytes, createHash } from "node:crypto";
const b64url = (buf) => Buffer.from(buf).toString("base64url");
const codeVerifier = b64url(randomBytes(48)); // 64 کاراکتر
const codeChallenge = b64url(createHash("sha256").update(codeVerifier).digest());
const state = b64url(randomBytes(32));
// state و codeVerifier را سمت سرور و وابسته به کاربرِ واردشدهٔ پلتفرم ذخیره کنید
// (نه در کوکی/مرورگر)، مثلاً با TTL ده دقیقه.
await saveConnectState(state, { userId: user.id, shopId: shop.id, codeVerifier });
const url = new URL("https://panel.irandargah.com/oauth/authorize");
url.search = new URLSearchParams({
response_type: "code",
client_id: process.env.IRANDARGAH_CLIENT_ID,
redirect_uri: "https://panel.platform.example/connect/irandargah/callback",
scope: "profile:read terminals:provision terminals:read terminals:credentials",
state,
code_challenge: codeChallenge,
code_challenge_method: "S256",
}).toString();
res.redirect(url.toString());
پاسخ به Redirect URI شما
پس از تأیید پذیرنده، مرورگر به این شکل به Redirect URI شما برمیگردد:
https://panel.platform.example/connect/irandargah/callback?code=...&state=...&iss=https%3A%2F%2Fapi-v2.irandargah.com
codeیکبارمصرف است و فقط ۶۰ ثانیه اعتبار دارد. بلافاصله مبادله کنید.stateرا با رکورد ذخیرهشدهٔ سمت سرور مقایسه کنید؛ نبود یا ناهمخوانی ⇒ درخواست را رد کنید.stateپس از استفاده باید حذف شود.issباید دقیقاًhttps://api-v2.irandargah.comباشد (RFC 9207؛ مقابله با حملهٔ mix-up).- اگر پذیرنده «نمیدهم» را بزند یا درخواست شما ایراد داشته باشد، بهجای
code، پارامترهایerrorوerror_description(وstateوiss) میآید:
error |
معنی |
|---|---|
access_denied |
پذیرنده اجازه نداد |
invalid_scope |
scope ناشناخته، خالی یا خارج از scopeهای مجاز کلاینت |
invalid_request |
state یا PKCE (S256) نامعتبر/ناموجود |
unsupported_response_type |
response_type غیر از code |
اگر client_id یا redirect_uri نامعتبر باشد، ریدایرکتی انجام نمیشود و خطا روی همان صفحهٔ ایراندرگاه به پذیرنده نشان داده میشود (برای جلوگیری از open redirect).
تبادل کد با توکن (Token Exchange)
POST https://api-v2.irandargah.com/api/v1/oauth/token
بدنهٔ درخواست باید application/x-www-form-urlencoded باشد (JSON پذیرفته نمیشود).
| پارامتر | توضیح |
|---|---|
grant_type |
authorization_code |
code |
کد دریافتی |
redirect_uri |
دقیقاً همان مقداری که در درخواست مجوز فرستادید |
code_verifier |
مقدار اصلی که code_challenge از آن ساخته شد |
client_id و client_secret |
احراز هویت کلاینت به روش client_secret_post، یا هدر Authorization: Basic — فقط یکی از دو روش |
curl -s https://api-v2.irandargah.com/api/v1/oauth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode grant_type=authorization_code \
--data-urlencode code="$CODE" \
--data-urlencode redirect_uri='https://panel.platform.example/connect/irandargah/callback' \
--data-urlencode client_id="$CLIENT_ID" \
--data-urlencode client_secret="$CLIENT_SECRET" \
--data-urlencode code_verifier="$CODE_VERIFIER"
<?php
$ch = curl_init('https://api-v2.irandargah.com/api/v1/oauth/token');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => http_build_query([
'grant_type' => 'authorization_code',
'code' => $_GET['code'],
'redirect_uri' => 'https://panel.platform.example/connect/irandargah/callback',
'client_id' => getenv('IRANDARGAH_CLIENT_ID'),
'client_secret' => getenv('IRANDARGAH_CLIENT_SECRET'),
'code_verifier' => $saved['code_verifier'],
]), // Content-Type: application/x-www-form-urlencoded بهطور خودکار تنظیم میشود
]);
$tokens = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
// $tokens['error'], $tokens['error_description']
}
const res = await fetch("https://api-v2.irandargah.com/api/v1/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: "https://panel.platform.example/connect/irandargah/callback",
client_id: process.env.IRANDARGAH_CLIENT_ID,
client_secret: process.env.IRANDARGAH_CLIENT_SECRET,
code_verifier: saved.codeVerifier,
}),
});
const tokens = await res.json();
if (!res.ok) {
// tokens.error, tokens.error_description
}
پاسخ موفق (200):
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "Xk3p...",
"scope": "profile:read terminals:provision terminals:read terminals:credentials",
"sub": "q7Zr1x..."
}
- access token یک JWT با اعتبار ۳۶۰۰ ثانیه است. میتوانید آن را opaque فرض کنید و فقط بهعنوان Bearer بفرستید؛ ابطال سمت ایراندرگاه فوری است و فقط سرور ما وضعیت واقعی آن را میداند. اگر خواستید خودتان امضا را بسنجید، کلید عمومی در JWKS است؛ علاوه بر امضا،
iss(برابر Issuer بالا)،aud(برابرclient_idشما) وexpرا بررسی کنید. حتی با اعتبارسنجی محلی، تنها منبع حقیقت پذیرفتهشدن توکن، پاسخ API است. subشناسهٔ دوبهدو (pairwise) پذیرنده است: برای هر زوج (پذیرنده، شریک) ثابت و پایدار است و برای شرکای مختلف متفاوت است. آن را بهعنوان کلید پیوند پذیرنده/فروشگاه در دیتابیس خودتان نگه دارید. شناسهٔ داخلی ایراندرگاه نیست و برای پذیرندهای که اتصال را قطع و دوباره برقرار کند تغییر نمیکند.refresh_tokenرا فوراً و رمزشده ذخیره کنید (بخش بعد).
تمدید توکن (Refresh Token)
curl -s https://api-v2.irandargah.com/api/v1/oauth/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode grant_type=refresh_token \
--data-urlencode refresh_token="$REFRESH_TOKEN"
<?php
$ch = curl_init('https://api-v2.irandargah.com/api/v1/oauth/token');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERPWD => getenv('IRANDARGAH_CLIENT_ID') . ':' . getenv('IRANDARGAH_CLIENT_SECRET'),
CURLOPT_POSTFIELDS => http_build_query([
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
]),
]);
$tokens = json_decode(curl_exec($ch), true);
const basic = Buffer.from(
`${process.env.IRANDARGAH_CLIENT_ID}:${process.env.IRANDARGAH_CLIENT_SECRET}`
).toString("base64");
const res = await fetch("https://api-v2.irandargah.com/api/v1/oauth/token", {
method: "POST",
headers: {
Authorization: `Basic ${basic}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
}),
});
const tokens = await res.json(); // refresh_token جدید را فوراً ذخیره کنید
قواعد مهم:
- Refresh Token چرخشی است: هر تمدید، یک
refresh_tokenتازه برمیگرداند و قبلی بلافاصله باطل میشود. - استفادهٔ دوبارهٔ یک Refresh Token باطلشده، کل اتصال را باطل میکند: پاسخ
invalid_grantو همهٔ توکنهای آن اتصال از کار میافتند؛ پذیرنده باید دوباره اجازه بدهد. دو تمدید همزمان با یک توکن نیز در حکم استفادهٔ دوباره است. - Refresh Token اگر ۹۰ روز استفاده نشود منقضی میشود (هر تمدید، مهلت را از نو شروع میکند).
- پارامتر اختیاری
scopeفقط اجازهٔ کمکردن scopeها را میدهد؛ افزودن scope جدید ممکن نیست و نیازمند رضایت دوبارهٔ پذیرنده است.
ابطال اتصال (Revoke)
مطابق RFC 7009، شریک میتواند اتصال را ابطال کند (مثلاً وقتی پذیرنده در پلتفرم شما «قطع اتصال» را میزند):
curl -s https://api-v2.irandargah.com/api/v1/oauth/revoke \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode token="$REFRESH_TOKEN"
با ارسال Refresh Token یا Access Token، کل اتصال (هر دو نوع توکن) باطل میشود. پاسخ همیشه 200 با بدنهٔ {} است، حتی اگر توکن ناشناخته باشد. ابطال، اعتبار درگاههای صادرشده را باطل نمیکند.
API شریک (Connect API)
همهٔ درخواستها با هدر Authorization: Bearer <access_token> و بدنهٔ application/json هستند. پاسخها مستقیماً JSON هستند (پاکت success/data درگاه پرداخت را ندارند) و خطاها به شکل { "code", "message" } با پیام فارسی هستند.
اطلاعات پذیرنده (GET /connect/me)
نیازمند scope profile:read.
curl -s https://api-v2.irandargah.com/api/v1/connect/me \
-H "Authorization: Bearer $ACCESS_TOKEN"
{
"sub": "q7Zr1x...",
"displayName": "علی رضایی",
"mobileMasked": "0912***4567",
"kyc": { "status": "APPROVED", "isActive": true },
"hasTaxPayerCode": true
}
kyc.status یکی از APPROVED، PENDING، REJECTED یا NOT_STARTED است. hasTaxPayerCode میگوید ایراندرگاه از قبل کد رهگیری مالیاتی پذیرنده را دارد یا نه؛ اگر true است لازم نیست از پذیرنده این کد را بگیرید.
درخواست درگاه (POST /connect/terminals)
نیازمند scope terminals:provision.
| فیلد | الزام | توضیح |
|---|---|---|
domain |
الزامی | فقط نام دامنه، مثل shop.example.ir. scheme، مسیر و پورت حذف و حروف کوچک میشود؛ www. ابتدایی نادیده گرفته میشود |
storeName |
الزامی | نام فارسی فروشگاه (حداکثر ۱۲۰ کاراکتر)؛ نام درگاه در ایراندرگاه |
taxPayerCode |
اختیاری | کد رهگیری ۱۰ رقمی پروندهٔ مالیاتی. اگر نفرستید، کد آخرین درگاه پذیرنده استفاده میشود |
idempotencyKey |
الزامی | رشتهٔ دلخواه تا ۱۰۰ کاراکتر (قالب UUID بررسی نمیشود) |
curl -s https://api-v2.irandargah.com/api/v1/connect/terminals \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"domain": "shop.example.ir",
"storeName": "فروشگاه نمونه",
"taxPayerCode": "1234567890",
"idempotencyKey": "shop-1234-irandargah-v1"
}'
<?php
$ch = curl_init('https://api-v2.irandargah.com/api/v1/connect/terminals');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $accessToken,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'domain' => 'shop.example.ir',
'storeName' => 'فروشگاه نمونه',
'taxPayerCode' => '1234567890', // اختیاری
'idempotencyKey' => hash('sha256', $shop->id . ':shop.example.ir'),
], JSON_UNESCAPED_UNICODE),
]);
$terminal = json_decode(curl_exec($ch), true); // status 201
const res = await fetch("https://api-v2.irandargah.com/api/v1/connect/terminals", {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
domain: "shop.example.ir",
storeName: "فروشگاه نمونه",
taxPayerCode: "1234567890", // اختیاری
idempotencyKey: createHash("sha256").update(`${shop.id}:shop.example.ir`).digest("hex"),
}),
});
const terminal = await res.json(); // status 201
پاسخ (201):
{
"terminalId": "t_9f3a1c...",
"status": "BLOCKED",
"blockers": [
{
"code": "KYC_REQUIRED",
"message": "احراز هویت پذیرنده در ایراندرگاه هنوز تأیید نشده است. ...",
"actionUrl": "https://panel.irandargah.com/authenticate"
}
],
"message": "پیششرطهای درخواست درگاه کامل نیست؛ پس از رفع، درخواست خودکار ادامه مییابد.",
"domain": "shop.example.ir",
"updatedAt": "2026-10-09T10:15:00.000Z"
}
رفتار idempotency:
- تکرار با همان
idempotencyKey⇒ همان درگاه. همان کلید با دامنهٔ دیگر ⇒409با کدidempotency_key_reused. - همان دامنه برای همان پذیرنده (از طرف شما) ⇒ همان درگاه، حتی با کلید دیگر.
- دامنهای که روی درگاه پذیرندهٔ دیگری در ایراندرگاه ثبت است ⇒ blocker با کد
DOMAIN_IN_USE. terminalIdشناسهٔ مبهمشدهٔ خارجی است (شروع باt_)؛ شناسهٔ داخلی نیست.
وضعیت درگاه (GET /connect/terminals/:terminalId)
نیازمند scope terminals:read. همان شکل پاسخ بالا را برمیگرداند.
curl -s https://api-v2.irandargah.com/api/v1/connect/terminals/t_9f3a1c... \
-H "Authorization: Bearer $ACCESS_TOKEN"
status |
معنی |
|---|---|
PENDING |
پیششرطها کامل است و درگاه در حال ثبت/راهاندازی است (از message مرحله را نشان دهید) |
BLOCKED |
پیششرطهایی ناقص است؛ فهرست در blockers |
ACTIVE |
درگاه فعال است و میتوانید اعتبار آن را دریافت کنید |
FAILED |
ثبت یا راهاندازی ناموفق/متوقف شد یا درگاه حذف شده است؛ علت در message (پشتیبانی ایراندرگاه پیگیری میکند) |
وقتی درخواست BLOCKED است، ایراندرگاه خودش آن را ادامه میدهد: پس از رفع پیششرطها توسط پذیرنده، درگاه بهصورت خودکار ساخته میشود (بررسی هر چند دقیقه یکبار، و تا ۳۰ روز پس از درخواست). نیازی به POST مجدد نیست؛ فقط GET را هر حدود ۲ دقیقه poll کنید. درخواستِ بیدرگاهِ قدیمیتر از ۳۰ روز دیگر خودکار پیگیری نمیشود و باید دوباره POST شود. اگر درخواستی بیدرگاه FAILED شد، با POST مجدد (همان دامنه) دوباره بررسی میشود.
Blockerها
هر blocker شکل { code, message, actionUrl } دارد. message فارسی و آمادهٔ نمایش است؛ آن را عیناً به پذیرنده نشان دهید. اگر actionUrl خالی نبود، کنار پیام دکمهای با عنوان «تکمیل در ایراندرگاه» بگذارید که به آن آدرس (در پنل ایراندرگاه) میرود. لازم نیست و نباید اطلاعات داخلی ایراندرگاه (تلفن ثابت، کد پستی، صنف و …) را خودتان از پذیرنده جمع کنید.
code |
معنی | actionUrl |
|---|---|---|
KYC_REQUIRED |
احراز هویت پذیرنده در ایراندرگاه تأیید نشده است | صفحهٔ احراز هویت پنل |
TAX_CODE_MISSING |
کد رهگیری ۱۰ رقمی پروندهٔ مالیاتی ثبت نشده؛ یا در taxPayerCode بفرستید یا در فرم ثبت درگاه وارد شود |
فرم ثبت درگاه |
PROFILE_INCOMPLETE |
اطلاعات لازم برای ثبت درگاه (تلفن ثابت، کد پستی، شهر، صنف، ایمیل) در ایراندرگاه نیست؛ پیام فیلدهای ناقص را نام میبرد | فرم ثبت درگاه (اگر فقط ایمیل کم است: تنظیمات پروفایل) |
ENAMAD_MISSING |
برای دامنه نماد اعتماد الکترونیکی (اینماد) یافت نشد (نماد موقت/خاکستری هم کافی است) | راهنمای اینماد در پنل |
ENAMAD_EXPIRED |
نماد اعتماد دامنه منقضی شده است | راهنمای اینماد در پنل |
ENAMAD_SUSPENDED |
نماد اعتماد دامنه تعلیق شده است | راهنمای اینماد در پنل |
ENAMAD_CHECK_FAILED |
استعلام اینماد موقتاً ممکن نشد؛ خودکار دوباره بررسی میشود و نیازی به اقدام پذیرنده نیست | null |
DOMAIN_IN_USE |
دامنه روی درگاه پذیرندهٔ دیگری در ایراندرگاه ثبت است؛ پذیرنده باید با پشتیبانی تماس بگیرد | null |
دریافت اعتبار درگاه (POST /connect/terminals/:terminalId/credentials)
نیازمند scope terminals:credentials و درگاهی با وضعیت ACTIVE؛ در غیر این صورت 409 با کد terminal_not_active و فهرست blockers.
| فیلد | پیشفرض | توضیح |
|---|---|---|
withSignatureKey |
false |
کلید امضای درخواست (secret_key) هم برگردانده شود؟ |
rotateIfExists |
false |
اگر اعتبار قبلاً صادر شده، اعتبار جدید ساخته شود؟ |
curl -s https://api-v2.irandargah.com/api/v1/connect/terminals/t_9f3a1c.../credentials \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"withSignatureKey": true, "rotateIfExists": false}'
<?php
$ch = curl_init("https://api-v2.irandargah.com/api/v1/connect/terminals/{$terminalId}/credentials");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $accessToken, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode(['withSignatureKey' => true, 'rotateIfExists' => false]),
]);
$creds = json_decode(curl_exec($ch), true);
// $creds['apiToken'] را فوراً رمزشده ذخیره کنید
const res = await fetch(
`https://api-v2.irandargah.com/api/v1/connect/terminals/${terminalId}/credentials`,
{
method: "POST",
headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
body: JSON.stringify({ withSignatureKey: true, rotateIfExists: false }),
}
);
const creds = await res.json(); // creds.apiToken را فوراً رمزشده ذخیره کنید
پاسخ (200):
{
"apiToken": "idg_live_xxxxxxxxxxxxxxxxxxxxxxxx",
"apiTokenFingerprint": "idg_live_••••abcd",
"signatureKey": "9c1e...",
"issuedAt": "2026-10-09T10:20:00.000Z",
"rotated": false,
"previousTokenExpiresAt": null,
"message": null
}
apiTokenهمان توکن Bearer درگاه پرداخت است (احراز هویت). متن آن فقط هنگام صدور تازه یا چرخش برگردانده میشود، چون ایراندرگاه توکن را بهصورت برگشتناپذیر (هش) ذخیره میکند.- اگر توکن قبلاً صادر شده و
rotateIfExistsنفرستاده باشید:apiToken: nullهمراه باapiTokenFingerprintو یکmessageتوضیحی. signatureKey(در صورتwithSignatureKey: true) برگشتپذیر است؛ اگر قبلاً صادر شده باشد همان کلید موجود برگردانده میشود، مگرrotateIfExists: trueکه کلید جدید میسازد.- اعتبار درگاه را رمزشده ذخیره کنید و هرگز در لاگ، URL یا کد سمت کلاینت قرار ندهید؛ در لاگهای ایراندرگاه نیز فقط fingerprint ثبت میشود.
پذیرندهای که از قبل درگاه دارد (Existing Gateway)
رایجترین حالت مهاجرت: پذیرنده پیشتر برای همین دامنه در ایراندرگاه درگاه فعال دارد و اکنون فروشگاهش را روی پلتفرم شما راهاندازی میکند.
- با
POST /connect/terminalsوdomainهمان فروشگاه، درگاهACTIVEموجودِ همان پذیرنده برای آن دامنه بلافاصله به این اتصال پیوند داده میشود و در همان پاسخstatus: "ACTIVE"میگیرید؛ درگاه جدیدی ساخته نمیشود و پیششرطهای جدید (KYC، اینماد، کد مالیاتی، …) دوباره سنجیده نمیشوند، چون درگاه پیشتر از آنها عبور کرده است. - پیوند فقط برای درگاههای همان پذیرنده برقرار میشود؛ دامنهٔ متعلق به پذیرندهٔ دیگر همچنان
DOMAIN_IN_USEمیدهد. - پس از پیوند، درگاه برای شما قابل
GETو دریافت اعتبار است و بخشی از اتصال شما محسوب میشود.
دریافت توکن برای درگاه موجود
چون توکنهای API موجود بهصورت برگشتناپذیر ذخیره شدهاند، POST .../credentials برای درگاهی که توکنش پیشتر صادر شده، apiToken: null و apiTokenFingerprint برمیگرداند. برای دریافت توکن قابلاستفاده باید rotateIfExists: true بفرستید:
curl -s https://api-v2.irandargah.com/api/v1/connect/terminals/t_9f3a1c.../credentials \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"rotateIfExists": true}'
{
"apiToken": "idg_live_newTokenxxxxxxxxxxxxxxxx",
"apiTokenFingerprint": "idg_live_••••wxyz",
"signatureKey": null,
"issuedAt": "2026-10-09T10:25:00.000Z",
"rotated": true,
"previousTokenExpiresAt": "2026-10-10T10:25:00.000Z",
"message": "توکن قبلی تا 2026-10-10T10:25:00.000Z همچنان معتبر است (بازهی مهلت)."
}
- توکن قبلی تا پایان بازهٔ مهلت (grace) همچنان معتبر میماند؛ مقدار پیشفرض آن ۲۴ ساعت است (همان چرخش توکن درگاه) و زمان دقیق پایان آن در
previousTokenExpiresAtبرگردانده میشود. فروشگاه فعلی پذیرنده در این مدت قطع نمیشود، اما پس از آن هر جایی (افزونه، سرور دیگر) که هنوز توکن قدیمی را دارد با خطای401مواجه میشود. به پذیرنده اطلاع دهید. - چرخش را فقط وقتی انجام دهید که پلتفرم شما بلافاصله توکن جدید را ذخیره میکند. توکن جدید فقط در همین یک پاسخ برگردانده میشود و بازیابیشدنی نیست؛ اگر پاسخ را از دست بدهید باید دوباره بچرخانید و بازهٔ مهلت قبلی نیز از نو محاسبه میشود.
rotateIfExists: trueهمراه باwithSignatureKey: true، کلید امضا را نیز میچرخاند و کلید قبلی بلافاصله بیاعتبار میشود (برای کلید امضا بازهٔ مهلت وجود ندارد). اگر فقط توکن لازم دارید،withSignatureKeyرا نفرستید.idempotencyKeyهیچ اثری روی این مسیر ندارد؛ هر فراخوانی باrotateIfExists: trueیک چرخش جدید است. آن را در retry خودکار بیقید و شرط تکرار نکنید.
کدهای خطا (Connect Errors)
خطاهای OAuth
خطاهای /oauth/token و /oauth/revoke به شکل RFC 6749 هستند:
{ "error": "invalid_grant", "error_description": "کد مجوز نامعتبر، منقضی یا قبلاً استفادهشده است." }
error |
HTTP | معنی |
|---|---|---|
invalid_request |
400 | پارامتر الزامی ناموجود، بدنهٔ غیر x-www-form-urlencoded، یا ارسال همزمان دو روش احراز هویت کلاینت |
invalid_client |
401 | client_id/client_secret نامعتبر یا کلاینت غیرفعال |
invalid_grant |
400 | کد نامعتبر/منقضی/مصرفشده، redirect_uri یا code_verifier ناهمخوان، Refresh Token نامعتبر/منقضی/باطل (از جمله استفادهٔ دوباره)، یا حساب پذیرنده در دسترس نیست |
unsupported_grant_type |
400 | grant_type غیر از authorization_code و refresh_token |
invalid_scope |
400 | scope درخواستی (در تمدید) خارج از scopeهای فعلی اتصال یا مجاز کلاینت |
slow_down |
429 | سقف نرخ؛ هدر Retry-After را رعایت کنید |
temporarily_unavailable |
503 | سرویس اتصال موقتاً در دسترس نیست |
server_error |
500 | خطای داخلی |
خطاهای API شریک
خطاهای /connect/* به شکل { "code", "message" } هستند (و برای 409 اعتبار، فیلد blockers):
code |
HTTP | معنی و اقدام |
|---|---|---|
invalid_token |
401 | Access Token ناموجود، نامعتبر، منقضی یا باطل. یکبار تمدید کنید؛ اگر تمدید invalid_grant داد، اتصال قطع شده است |
insufficient_scope |
403 | scope لازم در این اتصال داده نشده؛ مقدار لازم در message و هدر WWW-Authenticate است |
account_blocked |
403 | حساب پذیرنده در ایراندرگاه مسدود/معلق است |
invalid_request |
400 | اعتبارسنجی بدنه ناموفق (فیلد الزامی، طول بیش از حد، …) |
invalid_domain |
400 | domain معتبر نیست (فقط نام دامنه، بدون https:// و مسیر) |
invalid_tax_payer_code |
400 | taxPayerCode باید ۱۰ رقم باشد |
not_found |
404 | terminalId وجود ندارد یا متعلق به این شریک/پذیرنده نیست |
idempotency_key_reused |
409 | همین idempotencyKey قبلاً برای دامنهٔ دیگری استفاده شده |
terminal_not_active |
409 | درگاه هنوز ACTIVE نیست؛ blockers و message همراه است |
rate_limited |
429 | سقف نرخ؛ هدر Retry-After را رعایت کنید |
server_error |
500 | خطای داخلی |
محدودیت نرخ (Connect Rate Limits)
| مسیر | سقف |
|---|---|
/oauth/token، /oauth/revoke |
۶۰ در دقیقه به ازای IP و ۶۰۰ در دقیقه به ازای کلاینت |
صفحهٔ رضایت (authorize) |
۳۰ در دقیقه به ازای IP و ۶۰۰ در دقیقه به ازای کلاینت |
/connect/* |
۶۰ در دقیقه به ازای هر (کلاینت، پذیرنده) |
پس از عبور از سقف، پاسخ 429 همراه با هدر Retry-After (ثانیه) برمیگردد. برای poll وضعیت درگاه، فاصلهٔ حدود ۲ دقیقه کافی است و پیرامون این سقف قرار نمیگیرد.
چکلیست امنیتی شریک (Partner Security Checklist)
client_secretفقط روی سرور شما باشد؛ هرگز در مرورگر، اپ موبایل، مخزن کد یا لاگ.- PKCE با
S256را همیشه و باcode_verifierتازه برای هر درخواست استفاده کنید. stateرا تصادفی (≥ ۳۲ بایت)، سمت سرور، یکبارمصرف و وابسته به کاربرِ واردشدهٔ پلتفرم ذخیره کنید؛ در callback حتماً بسنجید و حذف کنید.code_verifierهرگز به مرورگر نرود.- در callback مقدار
issرا با Issuer ایراندرگاه مقایسه کنید. - همهٔ ارتباطات فقط روی HTTPS؛ TLS را غیرفعال نکنید.
- Refresh Token و توکن API درگاهها را رمزشده در سکون (مثلاً AES-256-GCM با کلید جدا از دیتابیس) ذخیره کنید؛ Access Token فقط در حافظه.
- هیچکدام از
client_secret،code، Access/Refresh Token، توکن API و کلید امضا را لاگ نکنید یا در URL قرار ندهید. - تمدید را per-پذیرنده قفلگذاری کنید و
refresh_tokenجدید را پیش از هر کاری ذخیره کنید. - قطع اتصال را مدیریت کنید: پاسخ
invalid_tokenکه با تمدید رفع نشود یاinvalid_grantدر تمدید، یعنی پذیرنده اتصال را قطع کرده؛ توکنهای ذخیرهشده را پاک کنید و به پذیرنده دکمهٔ «اتصال دوباره» نشان دهید. توکن API درگاه که پیشتر صادر شده همچنان معتبر است و نباید پاک شود. - فقط scopeهایی را بخواهید که لازم دارید و
message/actionUrlblockerها را به همان شکل به پذیرنده نشان دهید.
پشتیبانی
برای دریافت پشتیبانی فنی:
- ایمیل: contact@irandargah.com
- تلفن: ۰۳۱-۳۶۷۶۰۰۰۰
- پنل کاربری: https://panel.irandargah.com
- بخش پشتیبانی: https://irandargah.com/contact
- مستندات: https://docs.irandargah.com
تغییرات (Changelog)
این بخش تغییرات نسخههای API را فهرست میکند؛ جدیدترین تغییرات در بالا قرار دارند. برای دریافت اعلان تغییرات مهم، کانال پشتیبانی را دنبال کنید.
نسخه 2.5 — اتصال فروشگاهسازها (IranDargah Connect)
۱۷ مهر ۱۴۰۵
- IranDargah Connect: اتصال یککلیکی پلتفرمهای فروشگاهساز به ایراندرگاه با رضایت پذیرنده، بهجای کپیکردن دستی توکن API (مستندات).
- سرور OAuth 2.0: جریان Authorization Code با PKCE اجباری، Refresh Token چرخشی، ابطال (RFC 7009) و کلید عمومی JWKS روی
https://api-v2.irandargah.com/api/v1/oauth/*. - API شریک (
/api/v1/connect/*):GET /connect/me،POST /connect/terminals،GET /connect/terminals/:terminalIdوPOST /connect/terminals/:terminalId/credentialsبا blockerهای فارسی و لینک «تکمیل در ایراندرگاه». - پذیرندهی دارای درگاه: درگاه فعالِ موجود برای همان دامنه به اتصال پیوند داده میشود و توکن جدید با
rotateIfExists(و مهلت ۲۴ ساعتهی توکن قبلی) دریافت میشود (مستندات). - محیط آزمایشی OAuth و سند discovery ارائه نمیشود؛ هماهنگی تست از طریق پشتیبانی شرکا انجام میشود.
نسخه 2.4.1 — نمونهکدهای امنتر Callback و سختگیری در وضعیت تأیید
۱۳ مهر ۱۴۰۵
- نمونهکدهای Callback (PHP، Node.js، Python) اصلاح شدند: اتصال
authorityبه سفارش ذخیرهشده، تأیید با مبلغ ذخیرهشدهی سفارش و تطبیق مبلغ پاسخ تأیید. - توضیح
direct_verifyوstatus_code=100: مقادیر callback اطلاعاتیاند و در همهی حالتها پیش از تحویل کالا باید پرداخت با API تأیید شود. - وضعیت تأیید و پرداخت (
GET /v2/verifications/{authority}وGET /v2/payments/{authority}): پاسخ «تأییدشده» (status_code=200) اکنون دقیقاً با همان معیارPOST /v2/verificationsداده میشود. برای تراکنشهای تأییدشدهی عادی تغییری ایجاد نمیشود؛ فقط ردیفهایی که شاهد کافی برای تأیید قطعی ندارند دیگر «تأییدشده» گزارش نمیشوند. شکل پاسخها تغییری نکرده است.
نسخه 2.4 — امضای درخواست نسخهٔ ۲ (nonce درون امضا)
۱۱ مهر ۱۴۰۵
- امضای نسخهٔ ۲ (اختیاری): با هدر
X-Signature-Version: 2، متد، مسیر کامل، query،X-Timestamp،X-Nonceو هش بدنه با هم امضا میشوند و هر nonce فقط یک بار پذیرفته میشود؛ تکرار درخواستِ شنودشده با nonce تازه دیگر ممکن نیست (مستندات). - سازگاری کامل: امضای نسخهٔ ۱ بدون تغییر پذیرفته میشود و همان
secret_keyاستفاده میشود؛ در صورت درخواست، ترمینال روی «فقط نسخهٔ ۲» قفل میشود. - کدهای خطای جدید امضا:
-104تا-109(جدول).
نسخه 2.3.1 — اتصال MCP از Codex و VS Code
۸ مهر ۱۴۰۵
- راهنمای Codex (OpenAI): تعریف سرور در
~/.codex/config.tomlو نگهداری توکن در متغیر محیطیIRANDARGAH_MCP_TOKEN(مستندات). - راهنمای VS Code (GitHub Copilot): پیکربندی
.vscode/mcp.jsonبا دریافت امن توکن هنگام اولین اتصال و استفاده در حالت Agent (مستندات). - سایر دستیارها: هر دستیاری که MCP راهدور با انتقال Streamable HTTP را پشتیبانی کند، با آدرس
https://api.irandargah.com/mcpو هدرAuthorization: Bearerوصل میشود.
نسخه 2.3 — سرور MCP برای دستیارهای هوش مصنوعی
۳ مهر ۱۴۰۵
- سرور MCP ایراندرگاه: دسترسی فقطخواندنی دستیارهای هوش مصنوعی به تراکنشها، تسویهها، هشدارها و مستندات از طریق
https://api.irandargah.com/mcp(مستندات). - توکن اختصاصی MCP: ساخت و ابطال از مسیر «تنظیمات ← دسترسی هوش مصنوعی (MCP)» در پنل کاربری؛ اعتبار هر توکن ۹۰ روز.
- راهنمای اتصال: پیکربندی آماده برای Claude Desktop، Cursor و Claude Code.
- حریم خصوصی: شماره کارت کامل و شماره موبایل مشتریان هرگز در خروجی ابزارها قرار نمیگیرد.
نسخه 2.2 — API افزونهی اقساط
۳۰ مرداد ۱۴۰۵
- استعلام اقساط باز: دریافت اقساط تسویهنشدهی مشتری با شماره موبایل (
GET /api/v1/plugin/installments/open) با همان توکن ترمینال (مستندات). - اتصال قسط (Attach): علامتزدن قسط بهعنوان تسویهشده با
authorityیاtransactionIdتراکنشِ تأییدشدهی درگاه (POST /{installmentId}/attach) (مستندات). - «اقساط من» در افزونهی ووکامرس: پیادهسازی آمادهی همین جریان که با یک تیکباکس در تنظیمات افزونه فعال میشود.
- فعالسازی اختیاری: افزونهی اقساط باید برای ترمینال فعال شود؛ در غیر این صورت پاسخ
403برمیگردد.
نسخه 2.1.4 — شفافسازی روش ارسال Callback
۱ مرداد ۱۴۰۵
- روش پیشفرض POST: پارامترهای بازگشت بهطور پیشفرض در بدنهی یک درخواست POST (
application/x-www-form-urlencoded) بهcallback_urlارسال میشوند؛ فقط باaction=GETبه Query String اضافه میشوند (مستندات).
نسخه 2.1.3 — شناسهی درگاه در پیام وبهوک
۲۰ تیر ۱۴۰۵
- فیلد
terminal_id: پوشش (wrapper) پیام وبهوک اکنون شناسهی درگاهِ مبدأ رویداد را درterminal_idدارد و جایگزینmerchant_codeشده است؛ امضا همچنان فقط رویdataمحاسبه میشود و راستیآزمایی امضا تغییری نمیکند.
نسخه 2.1.2 — رفع اشکال endpointهای وبهوک
۱۴ تیر ۱۴۰۵
- ثبت وبهوک از طریق API:
POST /v2/webhooks/subscribe،POST /v2/webhooks/unsubscribeوGET /v2/webhooks/eventsدوباره مطابق مستندات پاسخ میدهند. - اشتراک بدون تکرار: ثبت دوبارهی همان URL، وبهوک موجود را بهروزرسانی میکند (پاسخ
200) و endpoint تکراری نمیسازد؛secretفقط در صورت ارسال صریح عوض میشود.
نسخه 2.1.1 — اصلاح کد وضعیت استعلام تأیید
۹ تیر ۱۴۰۵
- استعلام وضعیت تأیید:
GET /v2/verifications/{authority}برای تراکنشِ تأییدشده، مطابق مستندات،success: trueوstatus_code: 200برمیگرداند (پیشتر بهاشتباه1برمیگشت). - اصلاح نمونهی پاسخ:
status_codeپاسخ موفق ایجاد پرداخت (POST /v2/payments) در مستندات به مقدار درست200اصلاح شد. - نشان اعتماد: کد جایگزین مبتنی بر
iframeبرای سایتهایی که اسکریپت نشان در آنها نمایش داده نمیشود.
نسخه 2.1 — مستندات تعاملی و نشان اعتماد
۶ تیر ۱۴۰۵
- کنسول «امتحان کنید» (Try it): ارسال درخواست واقعی به هر endpoint از داخل مستندات، با سوئیچ محیط آزمایشی/عملیاتی.
- سوئیچ سراسری تست/زنده: درج خودکار توکن و آدرس پایهی محیط انتخابشده در همهی نمونهکدها.
- خروجی OpenAPI و Postman: دریافت مشخصات OpenAPI 3.0 و مجموعهی Postman v2.1 از روی endpointهای مستند.
- ابزار راستیآزمایی امضای وبهوک: بررسی امضای
HMAC-SHA256یک پیام نمونه داخل همین صفحه. - نشان اعتماد ایراندرگاه: کد نمایش نشان اعتماد در سایت پذیرنده به مستندات اضافه شد.
- بهبود مستندات: جستوجوی تماممتن (⌘K)، حالت تاریک، نمونهپاسخهای زبانهای (موفق/خطا)، فونت Vazirmatn و بخش «تغییرات».
نسخه 2.0 — راهاندازی نسل دوم API
۱ تیر ۱۴۰۵
- توکن واحد: احراز هویت با یک Bearer Token (با پیشوند
idg_live_/idg_test_). - چرخش امن توکن: پنجرهی ۲۴ ساعتهٔ grace خودکار پس از هر rotation (مستندات).
- ابطال اضطراری: امکان باطل کردن فوری توکن از پنل کاربری (مستندات).
- استاندارد نامگذاری
snake_caseبرای همهی فیلدهای ورودی و خروجی. - ساختار پاسخ یکسان: همهی پاسخها شامل
success,data,message,status_codeوtimestamp. - پشتیبانی از
Idempotency-Keyبرای جلوگیری از تراکنشهای تکراری. - مدیریت خطای پیشرفته با دستهبندی و جزئیات فیلدی، و مرجع کامل کدهای وضعیت و خطا.
- جلوگیری از Race Condition با قید یکتایی
order_idدر سطح دیتابیس. - وبهوکها: اشتراک رویدادها با امضای
HMAC-SHA256روی فیلدdata، تلاش مجدد با backoff نمایی و DLQ (مستندات). - پرداخت و تأیید: ایجاد، استعلام و لغو پرداخت (
/v2/payments)؛ تأیید، استعلام وضعیت تأیید و تلاش مجدد تأیید (/v2/verifications). - تراکنشها و گزارشها: فهرست و جزئیات تراکنش (
/v2/transactions) و گزارش روزانه و ماهانه (/v2/reports/daily،/v2/reports/monthly). - بررسی سلامت: endpoint عمومی
GET /healthبدون نیاز به توکن. - محیط آزمایشی:
sandbox.irandargah.comبا توکن تست و کارتهای تستی برای سناریوهای موفق و ناموفق (مستندات). - امضای درخواست و پاسخ: هدرهای
X-Signature/X-Timestampبرای ترمینالهای دارای امضا و امضای پاسخ باX-Response-Signature(مستندات). - محدودیت نرخ درخواست: سقف دقیقهای بهازای هر توکن با هدرهای
X-RateLimit-*و معافیت درخواستهای امضاشده. - IP Whitelist: محدودکردن دسترسی API به IPهای مشخص از پنل کاربری (مستندات).