مستندات API درگاه کارت به کارت تی‌نت‌پی (برای سایت‌های اختصاصی)

این راهنما برای سایت‌هایی است که با PHP، Node.js، Python یا هر زبان دیگری نوشته شده‌اند و از افزونهٔ ووکامرس استفاده نمی‌کنند. خلاصه: با API یک پرداخت می‌سازید، مشتری را به paymentUrl می‌فرستید، مشتری مبلغ دقیق را به کارت خودتان واریز می‌کند و پس از تأیید خودکار (با پیامک بانک روی اپ اندروید شما) وب‌هوک دریافت می‌کنید و با verify نتیجه را قطعی می‌کنید.

معرفی درگاه کارت به کارت · English · OpenAPI

۱. پیش‌نیازها و دریافت کلید API

  1. در تی‌نت‌پی ثبت‌نام کنید و احراز هویت (KYC) را کامل کنید.
  2. احراز هویت را کامل کنید و به بخش کارت‌به‌کارت پنل بروید، کارت مقصد را ثبت کنید تا تأیید شود و اپ اندروید را با کد جفت‌سازی وصل کنید.
  3. کیف کارمزد کارت‌به‌کارت را شارژ کنید. کارمزد فقط برای پرداخت‌های تأییدشده کسر می‌شود؛ مقدار فعلی: GET https://tnetpay.com/api/c2c/public/pricing.
  4. در پنل، بخش «کلیدهای API» یک کلید بسازید و فقط سمت سرور نگه دارید. کلید را در هدر X-API-Key (یا Authorization: Bearer ...) بفرستید.

آدرس پایه: https://tnetpay.com/api/v1/c2c

۲. ساخت پرداخت

POST /payments — هدر Idempotency-Key الزامی است (حداکثر ۱۲۸ نویسهٔ ASCII بدون فاصله)؛ نبودن آن خطای ۴۰۰ IDEMPOTENCY_KEY_REQUIRED می‌دهد.

فیلدنوعتوضیح
amountعدد صحیح (تومان)مبلغ سفارش به تومان؛ باید بزرگ‌تر از صفر باشد. الزامی.
orderIdرشتهشناسهٔ سفارش در سایت شما. الزامی.
callbackUrlURLآدرس وب‌هوک شما (POST). الزامی.
returnUrlURLبازگشت مشتری پس از پرداخت. اختیاری.
descriptionرشتهتوضیح سفارش. اختیاری.
payerMobileرشتهموبایل پرداخت‌کننده. اختیاری.
metadataشیء JSONداده‌های دلخواه شما. اختیاری.
cURL
curl -X POST 'https://tnetpay.com/api/v1/c2c/payments' \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Idempotency-Key: order-1001' \
  -d '{
    "amount": 250000,
    "orderId": "1001",
    "callbackUrl": "https://shop.example/tnetpay/webhook",
    "returnUrl": "https://shop.example/order/1001",
    "description": "Order #1001"
  }'

نمونهٔ پاسخ (مبالغ رشته‌ای هستند):

JSON
{
  "id": "c2c_01M68MJSCW2YKGWSFCD89MJS1T",
  "status": "pending",
  "amount": "250000",
  "payAmount": "250037",
  "payAmountRial": "2500370",
  "uniqueSuffix": "37",
  "fee": "...",
  "card": { "bank": "...", "bankName": "...", "holderName": "...", "number": "6037 .... .... 1234", "last4": "1234" },
  "paymentUrl": "https://tnetpay.com/pay/c2c/c2c_01M68MJSCW2YKGWSFCD89MJS1T",
  "expiresAt": "2026-10-31T09:47:49.000Z",
  "mode": "live"
}

payAmount مبلغ دقیقی است که مشتری باید واریز کند: مبلغ سفارش به‌اضافهٔ یک عدد کوچک یکتا (uniqueSuffix) تا هر واریز فقط با یک سفارش جور شود. payAmountRial همان مبلغ به ریال است.

۳. هدایت مشتری به صفحهٔ پرداخت

شناسهٔ id را روی سفارش ذخیره کنید و مشتری را به paymentUrl هدایت کنید. صفحهٔ پرداخت شمارهٔ کارت، نام صاحب کارت، مبلغ دقیق و زمان باقی‌مانده را نشان می‌دهد و وضعیت را خودکار به‌روز می‌کند. پس از تأیید، اگر returnUrl داده باشید، مشتری با پارامترهای ?id=...&status=paid برمی‌گردد. این پارامترها فقط برای نمایش است؛ تحویل سفارش را فقط بر اساس verify انجام دهید.

۴. بررسی وضعیت

برای سرور خودتان: GET /payments/{id} (جزئیات کامل) یا POST /payments/{id}/verify (پاسخ کوتاه و قطعی). وضعیت‌ها: pending، paid، manual_review، expired، cancelled، rejected.

cURL
curl -X POST 'https://tnetpay.com/api/v1/c2c/payments/c2c_01M68MJSCW2YKGWSFCD89MJS1T/verify' \
  -H 'X-API-Key: YOUR_API_KEY'
JSON
{
  "status": "paid",
  "paid": true,
  "amount": "250000",
  "payAmount": "250037",
  "paidAt": "2026-10-31T09:30:12.000Z",
  "orderId": "1001"
}

برای صفحهٔ سمت مشتری (بدون کلید API) یک وضعیت عمومی و حداقلی هم هست:

cURL
curl 'https://tnetpay.com/api/pay/c2c/c2c_01M68MJSCW2YKGWSFCD89MJS1T/status'
# {"id":"c2c_...","status":"pending","final":false,"expiresAt":"...","paidAt":null,"serverTime":"..."}

۵. وب‌هوک و تأیید آن

وقتی وضعیت پرداخت تغییر کند، تی‌نت‌پی یک درخواست POST با بدنهٔ JSON به callbackUrl می‌فرستد. رویدادها: c2c.payment.paid، c2c.payment.cancelled، c2c.payment.rejected. هدرها: X-TNetPay-Event، X-TNetPay-Delivery، X-TNetPay-Attempt و امضای X-TNetPay-Signature/X-TNetPay-Timestamp.

JSON
{
  "event": "c2c.payment.paid",
  "id": "c2c_01M68MJSCW2YKGWSFCD89MJS1T",
  "status": "paid",
  "orderId": "1001",
  "amount": "250000",
  "payAmount": "250037",
  "payAmountRial": "2500370",
  "fee": "...",
  "paidAt": "2026-10-31T09:30:12.000Z",
  "timestamp": "2026-10-31T09:30:13.000Z"
}

روش تأیید: کلید امضای وب‌هوک در اختیار پذیرنده قرار نمی‌گیرد، پس به بدنهٔ وب‌هوک اعتماد نکنید. با دریافت هر وب‌هوک، POST /payments/{id}/verify را با کلید API خودتان صدا بزنید و فقط اگر paid برابر true بود و orderId و مبلغ با سفارش شما یکی بود، سفارش را پرداخت‌شده کنید.

با کد 2xx پاسخ دهید. اگر پاسخ شما خطا باشد یا در دسترس نباشید، ارسال دوباره تقریباً پس از ۱ دقیقه، ۵ دقیقه، ۳۰ دقیقه، ۲ ساعت، ۶ ساعت و ۲۴ ساعت تکرار می‌شود؛ پس پردازش وب‌هوک را تکرارپذیر بنویسید (یک سفارش فقط یک بار پرداخت‌شده شود).

۶. Idempotency، انقضا و لغو

  • Idempotency-Key: برای هر سفارش یک کلید ثابت (مثلاً order-1001) بفرستید. تکرار همان درخواست همان پرداخت قبلی را برمی‌گرداند؛ تکرار با بدنهٔ متفاوت خطای IDEMPOTENCY_CONFLICT می‌دهد.
  • انقضا: هر پرداخت expiresAt دارد (مدت آن در payment.ttlMinutes پاسخ pricing آمده است). پس از آن، صفحهٔ پرداخت پایان‌یافته نشان داده می‌شود. واریزی که کمی دیرتر برسد ممکن است تا مدت payment.graceMinutes همچنان تطبیق داده شود؛ پس سفارش را فقط با verify قطعی کنید و پس از این مدت در صورت نیاز سفارش جدید بسازید.
  • لغو: POST /payments/{id}/cancel فقط برای پرداخت‌های pending.
  • واریز با مبلغ اشتباه: خودکار تأیید نمی‌شود و در پنل برای بررسی دستی نمایش داده می‌شود.

۷. خطاها

خطاها معمولاً به شکل {"error":{"code":"...","message_fa":"...","message_en":"..."}} برمی‌گردند (خطاهای احراز کلید به شکل استاندارد statusCode/message).

HTTPکدمعنی و راه‌حل
401API Key is required / Invalid API Keyهدر X-API-Key (یا Authorization: Bearer) نیست یا کلید نامعتبر است.
403KYC_REQUIREDاحراز هویت پذیرنده تأیید نشده یا حساب غیرفعال است.
403C2C_DISABLEDسرویس کارت‌به‌کارت برای این حساب فعال نیست (احراز هویت را کامل کنید یا با پشتیبانی تماس بگیرید).
400INVALID_AMOUNTمبلغ صفر/منفی است، یا (در لغو) پرداخت دیگر در انتظار نیست.
400IDEMPOTENCY_KEY_REQUIREDهدر Idempotency-Key نیامده یا نامعتبر است (حداکثر ۱۲۸ نویسهٔ ASCII بدون فاصله).
400NO_ACTIVE_CARDهیچ کارت فعال و تأییدشده‌ای برای دریافت واریز ندارید.
402C2C_SUSPENDED_LOW_BALANCE(در فیلد error پاسخ) موجودی کیف کارمزد برای این پرداخت و پرداخت‌های باز دیگر کافی نیست؛ کیف را شارژ کنید.
400AMOUNT_OUT_OF_RANGEدر این لحظه مبلغ یکتای آزاد برای این مبلغ پیدا نشد (پرداخت‌های باز زیادی با همین مبلغ)؛ کمی بعد دوباره تلاش کنید.
409IDEMPOTENCY_CONFLICTهمان Idempotency-Key قبلاً با بدنهٔ متفاوت ارسال شده است.
404NOT_FOUNDشناسهٔ پرداخت پیدا نشد یا متعلق به حساب شما نیست.
429—تعداد درخواست زیاد است (ایجاد پرداخت: حداکثر ۶۰ در دقیقه برای هر کلید)؛ کمی بعد دوباره تلاش کنید.

۸. آزمایش

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

۹. نمونه‌کد کامل

PHP (بدون فریم‌ورک)

PHP
<?php
// 1) Create a payment and redirect the buyer
const TNETPAY_API = 'https://tnetpay.com/api/v1/c2c';
$apiKey = getenv('TNETPAY_API_KEY');

function tnetpay_request(string $method, string $path, ?array $body = null, array $extraHeaders = []): array {
    global $apiKey;
    $ch = curl_init(TNETPAY_API . $path);
    $headers = array_merge(['X-API-Key: ' . $apiKey, 'Accept: application/json'], $extraHeaders);
    $opts = [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 20];
    if ($body !== null) {
        $headers[] = 'Content-Type: application/json';
        $opts[CURLOPT_POSTFIELDS] = json_encode($body);
    }
    $opts[CURLOPT_HTTPHEADER] = $headers;
    curl_setopt_array($ch, $opts);
    $raw = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    $data = json_decode((string) $raw, true) ?: [];
    if ($code >= 400 || isset($data['error'])) {
        throw new RuntimeException('TNetPay error: ' . ($data['error']['code'] ?? $code));
    }
    return $data;
}

$orderId = '1001';
$payment = tnetpay_request('POST', '/payments', [
    'amount'      => 250000,               // toman, integer
    'orderId'     => $orderId,
    'callbackUrl' => 'https://shop.example/tnetpay/webhook.php',
    'returnUrl'   => 'https://shop.example/order.php?id=' . $orderId,
], ['Idempotency-Key: order-' . $orderId]);
// save $payment['id'] on your order, then:
header('Location: ' . $payment['paymentUrl']);
exit;

// 2) webhook.php — never trust the body alone; re-check with verify
$event = json_decode(file_get_contents('php://input'), true);
$check = tnetpay_request('POST', '/payments/' . rawurlencode($event['id']) . '/verify');
if ($check['paid'] === true && $check['orderId'] === $orderId /* your stored order */) {
    // mark the order paid (only once)
}
http_response_code(200);

Node.js

Node.js
// Node.js 18+ (global fetch)
const API = 'https://tnetpay.com/api/v1/c2c';
const KEY = process.env.TNETPAY_API_KEY;

async function tnetpay(method, path, body, headers = {}) {
  const res = await fetch(API + path, {
    method,
    headers: { 'X-API-Key': KEY, 'Content-Type': 'application/json', ...headers },
    body: body ? JSON.stringify(body) : undefined,
  });
  const data = await res.json();
  if (!res.ok || data.error) throw new Error('TNetPay: ' + (data.error?.code || res.status));
  return data;
}

// create + redirect (Express)
app.post('/checkout/:orderId', async (req, res) => {
  const p = await tnetpay('POST', '/payments', {
    amount: 250000,
    orderId: req.params.orderId,
    callbackUrl: 'https://shop.example/tnetpay/webhook',
    returnUrl: 'https://shop.example/order/' + req.params.orderId,
  }, { 'Idempotency-Key': 'order-' + req.params.orderId });
  // store p.id on the order
  res.redirect(p.paymentUrl);
});

// webhook: confirm with verify before fulfilling
app.post('/tnetpay/webhook', express.json(), async (req, res) => {
  const v = await tnetpay('POST', '/payments/' + encodeURIComponent(req.body.id) + '/verify');
  if (v.paid) { /* mark order v.orderId as paid, idempotently */ }
  res.sendStatus(200);
});

Python

Python
import os, requests

API = "https://tnetpay.com/api/v1/c2c"
HEADERS = {"X-API-Key": os.environ["TNETPAY_API_KEY"]}

def tnetpay(method, path, json=None, extra=None):
    r = requests.request(method, API + path, json=json, headers={**HEADERS, **(extra or {})}, timeout=20)
    data = r.json()
    if r.status_code >= 400 or "error" in data:
        raise RuntimeError("TNetPay: %s" % (data.get("error", {}).get("code") or r.status_code))
    return data

# create + redirect
p = tnetpay("POST", "/payments", {
    "amount": 250000,
    "orderId": "1001",
    "callbackUrl": "https://shop.example/tnetpay/webhook",
    "returnUrl": "https://shop.example/order/1001",
}, {"Idempotency-Key": "order-1001"})
redirect_to = p["paymentUrl"]

# webhook handler (Flask)
@app.post("/tnetpay/webhook")
def webhook():
    event = request.get_json(force=True)
    v = tnetpay("POST", "/payments/%s/verify" % event["id"])
    if v["paid"]:
        pass  # mark order v["orderId"] paid, once
    return "", 200

بازگشت به معرفی درگاه کارت به کارت · پشتیبانی