TNetPay Card-to-Card API Documentation
The payer transfers to the merchant card; TNetPay verifies the deposit via bank SMS on the merchant phone.
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.