TNetPay Card-to-Card API Documentation

The payer transfers to the merchant card; TNetPay verifies the deposit via bank SMS on the merchant phone.

فارسی · OpenAPI 3.1

Prerequisites

  • KYC and admin-enabled card-to-card
  • Approved destination card and paired Android app
  • Prepaid C2C fee wallet balance

Fees (live settings)

Read fee rules from the public API — docs do not hardcode fee amounts:

GET https://tnetpay.com/api/c2c/public/pricing

Create payment

POST /v1/c2c/payments · amount = integer toman; payAmountRial = ×10

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 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

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

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"
  }'

Webhooks

The webhook signing key is not shared with merchants: on every webhook, call POST /api/v1/c2c/payments/{id}/verify with your API key and fulfil only when paid is true.

Sandbox

No public test keys are issued at the moment; test with a small real order.