2Pey2Pey / Developers
Merchant API · v1
محیط عملیاتی · PRODUCTION

مستندات پذیرندگان 2Pey

از صدور کلید API تا دریافت امن رویداد پرداخت؛ راهنمای یکپارچه‌سازی فاکتورهای 2Pey برای تیم‌های فنی و عملیاتی.

01POST /v1/invoicescreate invoice
02Telegram notificationpayer confirms payment
03invoice.paidsigned webhook delivered
01 / INTEGRATION PATH

مسیر اتصال، از ربات تا تسویه

کل جریان در پنج گام روشن انجام می‌شود. تمام تنظیمات اتصال از مسیر «تنظیمات پذیرنده ← اتصال، کلیدها و مستندات» در ربات در دسترس است.

01

تأیید حساب پذیرنده

ثبت‌نام را در ربات کامل کنید و منتظر تأیید حساب بمانید. تنها پذیرندگان تأییدشده به API عملیاتی دسترسی دارند.

02

ساخت کلید API

در «اتصال، کلیدها و مستندات ← مدیریت کلید API» کلید بسازید و همان لحظه در سامانه خود ذخیره کنید؛ بعداً از ربات قابل بازیابی نیست.

03

تنظیم نشانی و رمز امضا

در بخش اتصال، نشانی امن وب‌هوک و رمز امضا را ثبت کنید تا بتوانید پیام‌های ارسالی را بررسی کنید.

04

ایجاد فاکتور

فاکتور عمومی را بدون تعیین پرداخت‌کننده بسازید، یا برای فاکتور خصوصی یک شناسهٔ تأییدپذیر وارد کنید.

05

پردازش وب‌هوک

امضا را روی بدنه خام اعتبارسنجی، رویداد را با Event ID ذخیره و سپس پاسخ موفق 2xx برگردانید.

از کجا شروع کنم؟ — ابزارهای اتصال در یک بخش جمع شده‌اند
تنظیمات پذیرندهاتصال، کلیدها و مستندات
کلید APIنشانی وب‌هوکرمز امضامستندات فنی
نکته درباره کلیدها: دیدن این صفحه کلید شما را تغییر نمی‌دهد. برای ساخت، تعویض یا حذف، گزینه مربوط را انتخاب و تأیید کنید. کلید تازه فقط همان لحظه نمایش داده می‌شود؛ آن را در جای امن نگه دارید.
02 / QUICK START

اولین فاکتور در یک درخواست

Base URLhttps://api.2pey.com
AuthenticationAuthorization: Bearer <api-key>
Content typeapplication/json

برای جلوگیری از ساخت فاکتور تکراری هنگام timeout یا retry، برای هر سفارش یک Idempotency-Key پایدار با طول ۱ تا ۱۲۸ کاراکتر بفرستید. حذف شناسهٔ پرداخت‌کننده فاکتور عمومی می‌سازد؛ برای فاکتور خصوصی، شناسهٔ تأییدشدهٔ همان پرداخت‌کننده را بفرستید.

curl -X POST 'https://api.2pey.com/v1/invoices' \
  -H 'Authorization: Bearer <api-key>' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: order-8472-v1' \
  --data '{
    "payer_telegram_id": 123456789,
    "amount_toman": 5000000,
    "description": "سفارش شماره ۸۴۷۲",
    "merchant_reference": "order-8472"
  }'
201 · application/json
{
  "public_id": "inv_Lysi_CWcMNFLzS9e",
  "status": "pending",
  "payer_telegram_id": 123456789,
  "amount_toman": 5000000,
  "description": "سفارش شماره ۸۴۷۲",
  "merchant_reference": "order-8472",
  "due_at": "2026-10-10T12:47:00Z",
  "created_at": "2026-10-03T12:47:00Z",
  "paid_at": null,
  "payment_url": "https://t.me/2PeyRobot?start=pay_...",
  "miniapp_url": "https://2pey.com/checkout?invoice=inv_Lysi_CWcMNFLzS9e&access=..."
}
03 / API REFERENCE

مرجع endpointها

همه endpointها به هدر Bearer نیاز دارند. پاسخ‌ها JSON هستند و زمان‌ها با ISO-8601 برگردانده می‌شوند.

COMMANDS / RESTدرخواست از سامانه شما

ایجاد، خواندن، ویرایش و لغو فاکتور با کلید API

EVENTS / WEBHOOKاعلان به سامانه شما

تغییر وضعیت با امضای HMAC و شناسه یکتای رویداد

POST/v1/invoices

فاکتور تازه ایجاد می‌کند. ارسال دوباره همان Idempotency-Key و همان payload، پاسخ فاکتور قبلی را با کد 200 برمی‌گرداند.

فیلدنوعالزامتوضیح
allowed_source_panstring | nullخیرکارت مبدأ مجاز برای کارت‌به‌کارت، به‌صورت شماره معتبر ۱۶ رقمی. حذف یا null در ایجاد یعنی بدون محدودیت. در ویرایش، حذف فیلد محدودیت فعلی را حفظ و null آن را پاک می‌کند. فقط چهار رقم آخر در پاسخ نشان داده می‌شود.
require_loginbooleanخیربرای فاکتور عمومی: با مقدار true، پرداخت فقط پس از ورود با حساب تأییدشده انجام می‌شود. پیش‌فرض false و پرداخت مهمان مجاز است. این گزینه پرداخت‌کنندهٔ مشخص تعیین نمی‌کند.
payer_telegram_idintegerخیربرای سازگاری؛ فاکتور خصوصی با شناسهٔ تلگرام. برای فاکتور عمومی حذف شود.
payerobjectخیرفاکتور خصوصی با type و value: نوع‌های telegram_id، phone، email یا account_id. در زمان پرداخت، گیرنده باید مالکیت همان شناسه را تأیید کند؛ برای موبایل و ایمیل، ثبت‌نام قبلی در زمان صدور لازم نیست.
amount_tomanintegerبلهمبلغ صحیح به تومان؛ بیشتر از صفر و حداکثر ۱۰٬۰۰۰٬۰۰۰٬۰۰۰
descriptionstringخیرشرح قابل نمایش به پرداخت‌کننده؛ حداکثر ۵۰۰ کاراکتر
due_atdatetimeخیرزمان ISO-8601 دارای timezone؛ پیش‌فرض ۷ روز، حداکثر ۹۰ روز آینده
merchant_referencestringخیرشناسه یکتای سفارش در سامانه شما؛ حداکثر ۱۲۸ کاراکتر

کارت مبدأ اعلامی: در کارت‌به‌کارت، پرداخت‌کننده کارت ذخیره‌شدهٔ خود را انتخاب یا کارت تازه وارد می‌کند. این انتخاب و استعلام اطلاعات کارت به معنی تأیید هویت، مالکیت بانکی یا اثبات کارت واقعی انتقال نیست؛ احراز الزامی جداگانه اجرا می‌شود. محدودیت allowed_source_pan فقط کارت اعلام‌شده در این جریان را کنترل می‌کند و پرداخت کیف پول یا رمزارز را محدود نمی‌کند. شمارهٔ کامل کارت را در گزارش‌ها، لینک‌ها و webhookها ذخیره نکنید.

اگر هر دو فیلد پرداخت‌کننده حذف شوند، فاکتور عمومی است و دارندهٔ لینک امن پرداخت می‌تواند مهمان پرداخت کند. پاسخ ایجاد شامل payer_type و miniapp_url دارای توکن دسترسی است؛ لینک را مثل رمز سفارش محرمانه نگه دارید. شناسهٔ فاکتور به‌تنهایی مجوز پرداخت نیست. برای فاکتور خصوصی، پرداخت‌کننده باید با همان شناسه وارد شود.

فاکتور عمومی · curl
curl -X POST 'https://api.2pey.com/v1/invoices' \\
  -H 'Authorization: Bearer <api-key>' \\
  -H 'Content-Type: application/json' \\
  -H 'Idempotency-Key: public-order-8472-v1' \\
  --data '{"amount_toman":500000,"description":"سفارش ۸۴۷۲","merchant_reference":"order-8472"}'
فاکتور خصوصی با ایمیل تأییدشده
{
  "payer": {"type": "email", "value": "customer@example.com"},
  "amount_toman": 500000,
  "merchant_reference": "order-8473"
}
201 CREATED200 REPLAY401403409422429
GET/v1/invoices?offset=0&limit=20

فهرست صفحه‌بندی‌شده فاکتورهای همین پذیرنده را برمی‌گرداند. offset از صفر و limit از ۱ تا ۱۰۰ است؛ مقدار پیش‌فرض limit برابر ۲۰ است.

cURL
curl 'https://api.2pey.com/v1/invoices?offset=0&limit=20' \
  -H 'Authorization: Bearer <api-key>'
نمایش پاسخ 200 · فهرست
200 · application/json
{
  "items": [{
    "public_id": "inv_Lysi_CWcMNFLzS9e",
    "payer_telegram_id": 123456789,
    "amount_toman": 5000000,
    "description": "سفارش شماره ۸۴۷۲",
    "merchant_reference": "order-8472",
    "status": "pending",
    "due_at": "2026-10-10T12:47:00Z",
    "created_at": "2026-10-03T12:47:00Z",
    "paid_at": null,
    "payment_url": null,
    "miniapp_url": "https://2pey.com?invoice=inv_Lysi_CWcMNFLzS9e"
  }],
  "offset": 0,
  "limit": 20,
  "total": 1,
  "has_more": false
}
GET/v1/invoices/{public_id}

آخرین وضعیت یک فاکتور را با شناسه عمومی دریافت می‌کند. شناسه باید متعلق به همان پذیرنده باشد. پاسخ، همان فیلدهای نمونه ساخت فاکتور را دارد؛ payment_url در خواندن مجدد معمولاً null است.

cURL
curl 'https://api.2pey.com/v1/invoices/inv_Lysi_CWcMNFLzS9e' \
  -H 'Authorization: Bearer <api-key>'
نمایش پاسخ 200 · جزئیات
200 · application/json
{
  "public_id": "inv_Lysi_CWcMNFLzS9e",
  "payer_telegram_id": 123456789,
  "amount_toman": 5000000,
  "description": "سفارش شماره ۸۴۷۲",
  "merchant_reference": "order-8472",
  "status": "pending",
  "due_at": "2026-10-10T12:47:00Z",
  "created_at": "2026-10-03T12:47:00Z",
  "paid_at": null,
  "payment_url": null,
  "miniapp_url": "https://2pey.com?invoice=inv_Lysi_CWcMNFLzS9e"
}
PATCH/v1/invoices/{public_id}

فقط فیلدهای ارسالی را ویرایش می‌کند: amount_toman، description ، due_at، allowed_source_pan، source_bank_name، source_holder_name و یکی از allowed_destination_card_id یا allowed_destination_pan. فاکتور باید در وضعیت pending، منقضی‌نشده و بدون هیچ تخصیص قبلی کارت پرداخت باشد. payer_telegram_id و merchant_reference تغییرپذیر نیستند؛ برای تغییر آن‌ها فاکتور را لغو و فاکتور تازه ایجاد کنید.

cURL · partial update
curl -X PATCH 'https://api.2pey.com/v1/invoices/inv_Lysi_CWcMNFLzS9e' \
  -H 'Authorization: Bearer <api-key>' \
  -H 'Content-Type: application/json' \
  --data '{"amount_toman": 4800000, "description": "سفارش اصلاح‌شده ۸۴۷۲"}'

پاسخ موفق 200 و شکل آن InvoiceView است. بدنه خالی یا فیلد ناشناخته را ارسال نکنید.

نمایش پاسخ 200 · فاکتور ویرایش‌شده
200 · application/json
{
  "public_id": "inv_Lysi_CWcMNFLzS9e",
  "payer_telegram_id": 123456789,
  "amount_toman": 4800000,
  "description": "سفارش اصلاح‌شده ۸۴۷۲",
  "merchant_reference": "order-8472",
  "status": "pending",
  "due_at": "2026-10-10T12:47:00Z",
  "created_at": "2026-10-03T12:47:00Z",
  "paid_at": null,
  "payment_url": null,
  "miniapp_url": "https://2pey.com?invoice=inv_Lysi_CWcMNFLzS9e"
}
200 UPDATED404409422
DELETE/v1/invoices/{public_id}

حذف نرم است: فاکتور pending را به وضعیت cancelled می‌برد و رویداد invoice.cancelled تولید می‌کند. سابقه پرداخت و حسابرسی پاک نمی‌شود. فاکتور پرداخت‌شده، منقضی‌شده یا دارای سابقه تخصیص کارت پرداخت قابل حذف نیست.

cURL · soft cancellation
curl -X DELETE 'https://api.2pey.com/v1/invoices/inv_Lysi_CWcMNFLzS9e' \
  -H 'Authorization: Bearer <api-key>'

پاسخ موفق 200 شامل نمای فاکتور با "status": "cancelled" است. برای سازگاری، مسیر POST /v1/invoices/{public_id}/cancel نیز موجود است.

نمایش پاسخ 200 · فاکتور لغوشده
200 · application/json
{
  "public_id": "inv_Lysi_CWcMNFLzS9e",
  "payer_telegram_id": 123456789,
  "amount_toman": 5000000,
  "description": "سفارش شماره ۸۴۷۲",
  "merchant_reference": "order-8472",
  "status": "cancelled",
  "due_at": "2026-10-10T12:47:00Z",
  "created_at": "2026-10-03T12:47:00Z",
  "paid_at": null,
  "payment_url": null,
  "miniapp_url": "https://2pey.com?invoice=inv_Lysi_CWcMNFLzS9e"
}
200 CANCELLED404409
POST/v1/invoices/{public_id}/cancel

فاکتور در انتظار پرداخت را لغو می‌کند. فاکتور پرداخت‌شده، منقضی‌شده یا دارای سابقه تخصیص کارت پرداخت قابل لغو نیست و پاسخ تعارض دریافت می‌کند. درخواست تکراری برای فاکتور لغوشده همان وضعیت لغوشده را برمی‌گرداند.

cURL
curl -X POST 'https://api.2pey.com/v1/invoices/inv_Lysi_CWcMNFLzS9e/cancel' \
  -H 'Authorization: Bearer <api-key>'

نمونه درخواست از سرور

کلید API را فقط در کد سمت سرور نگه دارید. پیش از اجرا، ORDER_ID و PAYER_TELEGRAM_ID را در متغیرهای محیطی تنظیم کنید. نمونهٔ Python به pip install requests و JavaScript به Node.js 18+ نیاز دارد.

Python · requests
import os
import requests

order_id = os.environ["ORDER_ID"]
response = requests.post(
    "https://api.2pey.com/v1/invoices",
    headers={
        "Authorization": f"Bearer {os.environ['TWOPEY_API_KEY']}",
        "Idempotency-Key": f"invoice:create:{order_id}",
    },
    json={
        "payer_telegram_id": int(os.environ["PAYER_TELEGRAM_ID"]),
        "amount_toman": 5000000,
        "merchant_reference": order_id,
    },
    timeout=15,
)
response.raise_for_status()
invoice = response.json()
print(invoice["public_id"])
Node.js · fetch
const orderId = process.env.ORDER_ID;
const payerId = Number(process.env.PAYER_TELEGRAM_ID);
if (!Number.isSafeInteger(payerId) || payerId <= 0) throw new Error('Invalid payer ID');
const response = await fetch('https://api.2pey.com/v1/invoices', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.TWOPEY_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': `invoice:create:${orderId}`,
  },
  body: JSON.stringify({
    payer_telegram_id: payerId,
    amount_toman: 5000000,
    merchant_reference: orderId,
  }),
});
if (!response.ok) throw new Error(`2Pey API: ${response.status}`);
const invoice = await response.json();
console.log(invoice.public_id);

کدهای پاسخ مهم

کدمعنااقدام پیشنهادی
200موفق یا replay همان درخواست idempotentپاسخ را مانند موفقیت عادی پردازش کنید.
201فاکتور جدید ساخته شدpublic_id را کنار سفارش ذخیره کنید.
401 / 403کلید نامعتبر یا دسترسی ناکافیکلید و وضعیت پذیرنده را در ربات بررسی کنید.
404فاکتور برای این پذیرنده پیدا نشدشناسه عمومی و مالکیت پذیرنده را بررسی کنید.
409تعارض وضعیت، سابقه تلاش پرداخت یا Idempotency-Key با payload دیگرابتدا وضعیت فاکتور و درخواست قبلی را بررسی کنید. اگر ویرایش یا لغو دیگر مجاز نیست، برای سفارش جدید فاکتور تازه بسازید.
422اعتبارسنجی ورودی ناموفقنوع مبلغ، Telegram ID و timezone تاریخ را اصلاح کنید.
429سهمیهٔ درخواست یا ایجاد فاکتور تمام شدهدر محدودیت ترافیک، به‌اندازهٔ Retry-After ثانیه صبر کنید. برای سقف روزانه، زمان بازشدن سهمیه را بررسی کنید؛ درخواست مالی را با همان Idempotency-Key تکرار کنید.

سهمیه و تلاش دوباره

پیش‌فرض هر پذیرنده ۱۲۰۰ درخواست در پنجرهٔ لغزان ۶۰ ثانیه است؛ ساخت فاکتور از همین مجموع، حداکثر ۳۰۰ درخواست دارد. سهمیه میان کلیدهای یک پذیرنده مشترک است و ممکن است براساس تنظیمات خدمات تغییر کند. سقف روزانهٔ فاکتور نیز مستقل باقی می‌ماند.

پاسخ 429 محدودسازی ترافیک دارای Retry-After به ثانیه است. هدرهای X-RateLimit-Limit، X-RateLimit-Remaining و X-RateLimit-Window سهمیهٔ پاسخ موفق را نشان می‌دهند. پاسخ 503 می‌تواند به معنی شلوغی موقت باشد؛ با تأخیر و کمی زمان تصادفی دوباره تلاش کنید.

در خطای ارتباط، 502 یا 504، نتیجهٔ عملیات مالی را نامعلوم در نظر بگیرید. ابتدا وضعیت فاکتور را دریافت کنید. تکرار ساخت فاکتور باید با همان Idempotency-Key و همان داده باشد.

04 / INVOICE BEHAVIOR

فاکتور چگونه به پرداخت می‌رسد؟

  • مبلغ همیشه یک عدد صحیح و به واحد تومان است.
  • فاکتور فقط برای حسابی باز می‌شود که Telegram ID آن در payer_telegram_id ثبت شده است.
  • due_at باید ISO-8601 و دارای offset زمانی مانند +03:30 یا Z باشد.
  • پاسخ ساخت شامل لینک پرداخت خصوصی است؛ آن را فقط در اختیار پرداخت‌کننده همان فاکتور بگذارید.
  • اگر کاربر قبلاً ربات را باز کرده و آن را مسدود نکرده باشد، اعلان فاکتور بلافاصله در تلگرام ارسال می‌شود.
  • چهار اعلان ربات «ایجاد»، «پرداخت»، «لغو» و «انقضای فاکتور» مستقل از هم قابل تنظیم‌اند؛ هر چهار مورد به‌صورت پیش‌فرض فعال هستند.
  • تنظیم اعلان‌های ربات هیچ اثری روی تحویل وب‌هوک ندارد؛ رویدادهای وب‌هوک مستقل ارسال می‌شوند.
  • پرداخت‌کننده می‌تواند روش کیف پول یا کارت‌به‌کارت هوشمند را انتخاب کند؛ وضعیت قطعی را از وب‌هوک بگیرید.
فرمان‌ها و رویدادها: ایجاد، ویرایش و لغو فاکتور را با REST انجام دهید. برای آگاهی از تغییر وضعیت، وب‌هوک امضاشده دریافت کنید.
منبع حقیقت پرداخت: نمایش صفحه موفق به کاربر برای تحویل سفارش کافی نیست. فقط پس از دریافت و اعتبارسنجی invoice.paid سفارش را پرداخت‌شده در نظر بگیرید.
05 / SIGNED WEBHOOKS

رویدادهای امضاشده

2Pey تغییرات چرخه عمر فاکتور را با POST به URL ثبت‌شده ارسال می‌کند. endpoint شما باید عمومی، HTTPS و سریع باشد.

invoice.created
invoice.updated
invoice.paid
invoice.expired
invoice.cancelled
invoice.card_validation_failed

رویداد invoice.updated پس از ویرایش موفق PATCH منتشر می‌شود. مانند سایر رویدادها ممکن است تحویل آن تکرار شود؛ با Event ID پردازش تکراری را حذف کنید.

سازگاری اتصال‌های قبلی
دامنهٔ جدید API برابر https://api.2pey.com و صفحهٔ پرداخت برابر https://2pey.com است. مسیرهای API، کلیدها و شناسه‌های قبلی معتبرند. هر وب‌هوک یک بار ارسال می‌شود؛ هدرهای قبلی X-SyncPay-* نیز با همان مقادیر همراه هدرهای جدید هستند. نام‌های قبلی SDK و متغیرهای محیطی در بستهٔ Python پشتیبانی می‌شوند؛ نام‌های TWOPEY_* اولویت دارند.

هدرهای هر درخواست

هدرمحتوا
X-2Pey-Eventنوع رویداد، برای مثال invoice.paid
X-2Pey-Event-Idشناسه یکتای تحویل؛ مبنای deduplication
X-2Pey-Timestamptimestamp مورد استفاده در امضا
X-2Pey-Signatureامضای HMAC با پیشوند sha256=
فرمول دقیق امضا
sha256=hex(HMAC-SHA256(secret, X-2Pey-Timestamp + "." + raw_body))
بدنه خام را نگه دارید. JSON را پیش از اعتبارسنجی parse و دوباره serialize نکنید؛ کوچک‌ترین تغییر فاصله یا ترتیب می‌تواند امضا را عوض کند.

نمونه اعتبارسنجی با Python

Python · standard library
import hashlib
import hmac

def verify_twopey(raw_body: bytes, headers, secret: str) -> bool:
    timestamp = headers["X-2Pey-Timestamp"]
    received = headers["X-2Pey-Signature"]
    signed = timestamp.encode("utf-8") + b"." + raw_body
    digest = hmac.new(
        secret.encode("utf-8"), signed, hashlib.sha256
    ).hexdigest()
    expected = "sha256=" + digest
    return hmac.compare_digest(expected, received)

# 1) raw bytes را از request بخوانید
# 2) امضا را بررسی کنید
# 3) Event ID را به‌صورت unique ذخیره کنید
# 4) پس از ثبت پایدار، پاسخ 2xx بدهید

نمونه اعتبارسنجی با Node.js

Node.js · built-in crypto
const crypto = require('node:crypto');

function verifyTwoPey(rawBody, headers, secret) {
  const timestamp = headers['x-2pey-timestamp'];
  const received = headers['x-2pey-signature'];
  const signed = Buffer.concat([
    Buffer.from(timestamp + '.', 'utf8'), rawBody
  ]);
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(signed)
    .digest('hex');

  const a = Buffer.from(expected, 'utf8');
  const b = Buffer.from(received || '', 'utf8');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

برای جلوگیری از replay، سن timestamp را در یک پنجره زمانی کوتاه (برای مثال ۵ دقیقه) بررسی کنید. سپس امضا را با مقایسه زمان ثابت بسنجید. پنجره زمانی را با زمان سرور خود و تأخیر تحویل هماهنگ کنید.

ممکن است تحویل وب‌هوک در خطا یا timeout تکرار شود. ابتدا رویداد را با X-2Pey-Event-Id در یک ستون unique ثبت کنید، سپس منطق کسب‌وکار را اجرا کنید. تنها پس از ثبت پایدار پاسخ 2xx بدهید.

06 / SAFE TESTING

آزمایش وب‌هوک

برای آزمایش، یک نشانی امن روی سامانه خودتان بسازید و از رمز جداگانه تست استفاده کنید.

  1. یک endpoint آزمایشی عمومی HTTPS روی زیرساخت خود ایجاد کنید که بدنه خام و هدرها را دریافت کند.
  2. نشانی آزمایشی و یک رمز جداگانه برای محیط آزمایش ثبت کنید؛ رمز اصلی خود را برای تست به‌کار نبرید.
  3. یک فاکتور کم‌مبلغ برای Telegram ID حساب تست خودتان با Idempotency-Key یکتا بسازید.
  4. دریافت invoice.created را بررسی و امضا را با کد بالا اعتبارسنجی کنید.
  5. فاکتور را پرداخت یا لغو کنید و رویداد متناظر را فقط یک‌بار با Event ID پردازش کنید.
  6. عمداً پاسخ 500 بدهید و retry را مشاهده کنید؛ سپس پاسخ 2xx بدهید و مطمئن شوید پردازش تکراری رخ نمی‌دهد.
Test invoice
curl -X POST 'https://api.2pey.com/v1/invoices' \
  -H 'Authorization: Bearer <test-api-key>' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: webhook-test-001' \
  --data '{
    "payer_telegram_id": 123456789,
    "amount_toman": 10000,
    "description": "تست اتصال وب‌هوک"
  }'
07 / PRODUCTION CHECKLIST

پیش از ورود به تولید

  • API key و webhook secret را فقط در secret manager یا متغیر محیطی سمت سرور نگه دارید؛ هرگز در فرانت‌اند قرار ندهید.
  • کلید افشاشده را فوراً از ربات بچرخانید و کلید قبلی را باطل کنید.
  • برای API و webhook فقط HTTPS معتبر و عمومی استفاده کنید.
  • برای هر عملیات ایجاد، Idempotency-Key پایدار و وابسته به سفارش بسازید.
  • Event ID را با قید unique ذخیره و پردازش رویداد را idempotent کنید.
  • مبلغ را integer تومان نگه دارید؛ اعشار یا تبدیل شناور انجام ندهید.
  • در logها API key، secret، امضا یا لینک خصوصی پرداخت را ثبت نکنید.
08 / TROUBLESHOOTING

رفع اشکال سریع

پرداخت‌کننده پیام «این فاکتور برای شما نیست» می‌بیند.

Telegram ID حساب فعلی با payer_telegram_id فاکتور یکسان نیست. شناسه عددی کاربر را اصلاح و یک فاکتور جدید صادر کنید؛ لینک قابل انتقال به حساب دیگر نیست.

لینک فاکتور نامعتبر است.

لینک ناقص، دستکاری‌شده یا مربوط به فاکتوری ناشناخته است. دقیقاً از مقدار payment_url پاسخ API استفاده کنید و آن را بازسازی نکنید.

فاکتور منقضی شده و دکمه پرداخت ندارد.

زمان due_at گذشته است. فاکتور منقضی دوباره فعال نمی‌شود؛ فاکتور تازه‌ای با زمان timezone-aware آینده ایجاد کنید.

اعلان فاکتور در تلگرام تحویل نشده است.

کاربر باید قبلاً ربات را Start کرده باشد و آن را مسدود نکرده باشد. حتی بدون اعلان، لینک پرداخت را می‌توانید از کانال امن خودتان در اختیار همان کاربر بگذارید.

امضای webhook همیشه نامعتبر است.

معمولاً بدنه JSON پیش از محاسبه امضا parse یا دوباره serialize شده، secret اشتباه است، یا timestamp از هدر درست خوانده نشده. HMAC را دقیقاً روی timestamp، یک نقطه و بایت‌های خام بدنه حساب کنید.

چرا PATCH پاسخ 409 می‌دهد؟

فاکتور باید pending و منقضی‌نشده باشد و هیچ کارت پرداختی در گذشته به آن تخصیص نیافته باشد. وضعیت را با GET بررسی کنید؛ در صورت نیاز فاکتور تازه بسازید.

آیا DELETE فاکتور را از گزارش‌ها پاک می‌کند؟

خیر. DELETE لغو نرم انجام می‌دهد و رکورد فاکتور و سابقه آن برای حسابرسی باقی می‌ماند.

وب‌هوک دریافت نمی‌شود.

مقصد باید از اینترنت عمومی قابل دسترس و دارای HTTPS معتبر باشد. firewall، DNS، certificate و timeout پاسخ را بررسی کنید؛ endpoint محلی یا private IP قابل تحویل نیست.

09 / LIVE API

کنسول Try API

درخواست‌ها روی API واقعی و داده‌های تولید اجرا می‌شوند. تنها با انتخاب دکمه ارسال، درخواست فرستاده می‌شود؛ ایجاد، ویرایش و لغو تأیید دوم دارند.

کلید API فقط در حافظه همین صفحه است و در localStorage، sessionStorage، کوکی یا نشانی صفحه ذخیره نمی‌شود.

پیش‌نمایش درخواست

پاسخ سرور

هنوز درخواستی ارسال نشده است.
DIRECT RECEIVING

دریافت مستقیم و هزینهٔ خدمات

در روش network چرخهٔ همکاران حفظ می‌شود. در روش direct فقط کارت‌های خود پذیرنده نمایش داده می‌شوند؛ اصل فاکتور به کارت شما می‌رود و هزینهٔ خدمات جداگانه با رمزارز تسویه می‌شود.

کارت‌ها، اتصال APK و کلید احراز را از «نوع همکاری» در ربات مدیریت کنید. بسته‌ها تنها پس از فعال‌سازی مدیر قابل خریدند. پلن‌ها هم مدت (حداکثر ۳۰ روز) و هم سهمیه دارند؛ پرداخت موفق از هر سه روش یک سهمیه مصرف می‌کند. پس از پایان پلن، تعرفهٔ مستقیم عمومی یا اختصاصی اعمال می‌شود. هزینهٔ پرداخت کارتی به بدهی خدمات اضافه می‌شود؛ در پرداخت کیف پولی یا رمزارزی از تسویه کسر می‌شود و دوباره بدهی ثبت نمی‌شود. تسویهٔ دوره با رسیدن به جمع پرداخت‌های کارتی یا فاصلهٔ روز تعیین‌شده لازم است. پیش از خرید پلن، هزینه‌های قبلی را تسویه کنید.

GET  /v1/merchant/services
GET  /v1/merchant/services/bills?page=0&limit=20
POST /v1/merchant/services/bills/issue
PUT  /v1/merchant/services/receiving-mode  {"mode":"direct"}
PUT  /v1/merchant/services/plan            {"plan":"package"}  # monthly = percentage mode
POST /v1/merchant/services/packages/{id}/purchase {"asset":"USDT"}
POST /v1/merchant/services/bills/{id}/pay         {"asset":"GRAM"}
POST /v1/merchant/services/packages/{id}/transfer {"asset":"GRAM","network":"TON"}
POST /v1/merchant/services/bills/{id}/transfer    {"asset":"USDT","network":"TRC20"}
POST /v1/merchant/services/deposits/{deposit_id}/txid {"txid":"..."}

تمام درخواست‌ها همان کلید API پذیرنده را می‌خواهند. خرید بسته و درخواست انتقال باید هدر Idempotency-Key داشته باشند. انتقال تنها با مبلغ دقیق، دارایی، مقصد، زمان و تأیید زنجیره تسویه می‌شود؛ هش مالک واریز را تعیین نمی‌کند.

from twopey_client import TwoPeyClient
client = TwoPeyClient()
account = client.service_account()
client.set_receiving_mode("direct")
# Choose a real enabled package ID from account["packages"].
# client.purchase_package(package_id, asset="USDT", idempotency_key="order-001")
# client.pay_service_bill(bill_id, asset="GRAM")

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

DIRECT INVOICE CARDS

مبدأ اختیاری فاکتور و مقصد مستقیم

هر پذیرنده، چه شبکه‌ای و چه مستقیم، می‌تواند مبدأ فاکتور را تعیین کند. تعیین مقصد فقط برای پذیرندهٔ مستقیم ممکن است. مقصد باید کارت فعال و از قبل ثبت‌شدهٔ همان پذیرنده باشد؛ کارت شخص دیگر یا کارت ثبت‌نشده پذیرفته نمی‌شود.

GET /v1/merchant/direct-cards
Authorization: Bearer YOUR_MERCHANT_API_KEY

POST /v1/invoices
Authorization: Bearer YOUR_MERCHANT_API_KEY
Idempotency-Key: order-example-001
Content-Type: application/json

{
  "amount_toman": 580000,
  "allowed_source_pan": "SOURCE_CARD_NUMBER",
  "source_bank_name": "بانک مبدأ",
  "source_holder_name": "نام صاحب کارت مبدأ",
  "allowed_destination_card_id": 123
}

برای مقصد یکی از allowed_destination_card_id یا allowed_destination_pan را بفرستید، نه هر دو. اگر شمارهٔ مقصد را بفرستید، سامانه آن را فقط میان کارت‌های فعال خودتان جست‌وجو می‌کند و شناسهٔ کارت را ثبت می‌کند؛ کارت جدید ساخته نمی‌شود. پاسخ فاکتور شناسهٔ مقصد و چهار رقم آخر مبدأ را برمی‌گرداند.

  • بدون تعیین کارت: مشتری مبدأ و مقصد را در جریان معمول انتخاب می‌کند.
  • فقط مبدأ: انتخاب مبدأ حذف می‌شود و مشتری مقصد را انتخاب می‌کند.
  • فقط مقصد: انتخاب مقصد حذف می‌شود و مشتری مبدأ را انتخاب می‌کند.
  • هر دو: انتخاب مبدأ و مقصد حذف می‌شود؛ احراز الزامی، در صورت وجود، اجرا می‌شود و اطلاعات و مبلغ دقیق انتقال در مرحلهٔ نهایی نمایش داده می‌شوند.

در مبدأ تعیین‌شده، فیلدهای اختیاری source_bank_name و source_holder_name همراه allowed_source_pan پذیرفته می‌شوند. اگر هر دو کامل باشند، بدون استعلام به‌عنوان اطلاعات اعلامیِ همان فاکتور نمایش داده می‌شوند؛ نام بانکِ شناخته‌شده از پیش‌شماره کارت گرفته می‌شود. اطلاعات اعلامی به سوابق استعلام‌شدهٔ کاربر تبدیل نمی‌شوند و جایگزین احراز الزامی نیستند. اطلاعات استعلام‌شدهٔ همان پرداخت‌کننده تا ۲۴ ساعت قابل استفادهٔ مجدد است. اگر اطلاعات کافی موجود نباشد، استعلام فقط با کلید API.IR خود پذیرنده انجام می‌شود؛ در حالت شبکه‌ای از کلید همکار و در هیچ‌کدام از کلید سیستم به‌عنوان جایگزین استفاده نمی‌شود. پذیرندهٔ شبکه‌ای نیز می‌تواند کلید استعلام خود را از ربات یا بخش کلیدهای مینی‌اپ تنظیم کند. مقصد ثابت همچنان کارت ثبت‌شدهٔ خود پذیرنده است و مشخصاتش از همان کارت خوانده می‌شود. شمارهٔ واقعی مبدأ انتقال از پیامک فعلی قابل اثبات نیست.

در ویرایش فاکتورِ بدون تلاش پرداخت، نفرستادن فیلد مقصد یعنی حفظ مقدار قبلی و مقدار null یعنی پاک‌کردن آن. تکثیر، محدودیت‌ها را حفظ و صلاحیت مقصد را دوباره کنترل می‌کند. اگر مقصد تعیین‌شده غیرفعال، حذف، تعویض یا برای مبلغ نامناسب شود، پرداخت کارتی به کارت دیگری هدایت نمی‌شود. فاکتور دارای مقصد ثابت، حتی پس از تغییر نوع همکاری، دریافت مستقیم به همان کارت را حفظ می‌کند؛ انتخاب مقصد جدید برای فاکتورهای تازه است. این گزینه‌ها روش کیف پول یا رمزارز را محدود نمی‌کنند.

cards = client.direct_cards()["items"]
# Choose your own active destination card id.
invoice = client.create_invoice(
    amount_toman=580000,
    allowed_source_pan=os.environ["TWOPEY_ALLOWED_SOURCE_PAN"],
    allowed_destination_card_id=cards[0]["id"],
    merchant_reference="order-example-001",
)
client.update_invoice(invoice["public_id"], allowed_destination_card_id=None)

خطای اطلاعات کارت و وب‌هوک

اگر استعلام کارت مبدأ تعیین‌شده پاسخ موفق ولی فاقد نام صاحب کارت یا اطلاعات لازم برای تشخیص بانک کارت بدهد، وضعیت فاکتور به card_error تغییر می‌کند و پرداخت متوقف می‌شود. این وضعیت به معنی «اطلاعات کارت قابل تأیید نبود» است، نه اثبات قطعی نامعتبر بودن حساب. یک رویداد invoice.card_validation_failed در همان تراکنش ثبت و با همان امضا، تکرار امن و سیاست ارسال وب‌هوک‌های فعلی تحویل می‌شود.

{"event":"invoice.card_validation_failed","invoice":{
  "id":"inv_example","status":"card_error",
  "card_validation_error":"source_card_details_missing"
}}

شماره کارت، نام صاحب کارت، کلید و پاسخ خام بانک در این رویداد ارسال نمی‌شوند. برای ادامه پس از خطای اطلاعات، فاکتور تازه با اطلاعات اصلاح‌شده صادر کنید. خطای اتصال، timeout، نبود/اشکال کلید و پاسخ ناموفق یا نامفهوم سرویس، فاکتور را card_error نمی‌کنند و امکان تلاش دوباره باقی می‌ماند. ساختار نامعتبر ورودی پیش از صدور با 422 رد می‌شود و برای فاکتورِ ایجادنشده وب‌هوک صادر نمی‌شود.

APK STATUS

وضعیت زندهٔ APK و نمایش کارت آفلاین

این وضعیت مربوط به برنامهٔ بانکی APK است، نه اعتبار کلید API پذیرنده. کلید API معتبر برای همهٔ درخواست‌ها لازم است و فقط روی سرور شما نگهداری می‌شود.

GET https://api.2pey.com/v1/merchant/connection
Authorization: Bearer YOUR_MERCHANT_API_KEY

PUT https://api.2pey.com/v1/merchant/services/offline-cards
Authorization: Bearer YOUR_MERCHANT_API_KEY
Content-Type: application/json

{"show_cards_when_apk_offline": true}

تنظیم آفلاین فقط در حالت مستقیم قابل تغییر است و پیش‌فرض آن false است. فعال‌کردن آن فقط شرط آنلاین‌بودن APK را برای کارت‌های خودتان برمی‌دارد؛ سقف‌ها، مبلغ، احراز، بدهی خدمات و قواعد تأیید پرداخت همچنان برقرارند. هنگام قطع APK دریافت و تأیید پیامک می‌تواند با تأخیر انجام شود؛ قطع اتصال، اثبات واریز یا مجوز تأیید خودکار پرداخت نیست. حالت شبکه‌ای تغییری نمی‌کند.

وب‌سوکت سمت سرور پذیرنده

wss://api.2pey.com/v1/merchant/connection/ws

// First frame, within 8 seconds:
{"type":"auth","api_key":"YOUR_MERCHANT_API_KEY"}

// Initial snapshot and subsequent changes:
{"type":"connection.status","sequence":1,"data":{
  "merchant_public_id":"mrc_example","receiving_mode":"direct",
  "apk_connected":false,"apk_ready":false,"connection_status":"disconnected",
  "show_cards_when_apk_offline":true,"cards_may_be_shown":true,
  "active_cards_count":2,"blocked_overdue":false,
  "last_seen_at":"2026-10-15T08:00:00+00:00","checked_at":"2026-10-15T08:01:00+00:00"
}}

// Connection heartbeat; not a new APK state:
{"type":"heartbeat","checked_at":"...","last_seen_at":"..."}

apk_connected اتصال معتبر دستگاه و apk_ready اتصال همراه با فعال‌بودن پایش APK است؛ هر دو ممکن است هنگام نبود پاسخ معتبر null باشند. مقدار false قطع/غیرفعال‌بودن مشاهده‌شده و null وضعیت نامشخص است. برای دکمهٔ ربات خودتان می‌توانید همین مقدارها را استفاده کنید؛ در صورت قطع وب‌سوکت، وضعیت قبلی را قطعی فرض نکنید.

cards_may_be_shown مجازبودن عمومی نمایش بر اساس حالت مستقیم، اتصال یا تنظیم آفلاین، کارت فعال و بدهی سررسیدشده است؛ تضمین وجود کارت مناسب برای مبلغ هر فاکتور نیست. صلاحیت نهایی هر پرداخت در زمان انتخاب و رزرو بررسی می‌شود. اطلاعات کارت، کلید APK و پیامک خام در این مسیر ارسال نمی‌شود.

وضعیت حدود هر ۵ ثانیه بررسی، در تغییر ارسال و هنگام سکون حدود هر ۱۵ ثانیه heartbeat فرستاده می‌شود. تشخیص قطع بدون پیام خروج تابع تازگی آخرین ارتباط دستگاه (حدود ۳۰ ثانیه) و کش کوتاه‌مدت است؛ این سرویس اتصال لحظه‌ایِ بدون تأخیر را تضمین نمی‌کند. کلید، مجوز و وضعیت حساب در طول اتصال دوباره کنترل می‌شوند؛ لغو کلید با کد 1008 اتصال را می‌بندد. در خطای موقت یا کد 1013 با فاصلهٔ افزایشی و تصادفی دوباره متصل شوید؛ پس از 1008 بدون اصلاح اعتبارنامه تکرار نکنید. شمارهٔ sequence فقط در همان اتصال معتبر است.

client.connection_status()
client.set_offline_card_visibility(True)
# Server-side live example:
# python connection_watch.py

دریافت نمونهٔ کامل Python و connection_watch.py

10 / DOWNLOAD

بسته کامل Python

این بسته شامل کلاینت، CLI، سرور وب‌هوک، README، فایل requirements و نمونه تنظیم متغیرهای محیطی است. کلید و رمز واقعی خود را فقط روی سرور تنظیم کنید.

دریافت بسته Python ↙