To'lovlarni qabul qiling — bir necha daqiqada
inPAY REST API merchantlarga to'lov tranzaksiyalarini xavfsiz va tezkor yaratish imkoniyatini beradi. RESTful arxitektura, JSON format, Bearer Token autentifikatsiya.
Tezkor boshlash
5 daqiqada to'lov qabul qilishni boshlang
Autentifikatsiya
24 soatlik Bearer token olish
API bilan ishlash uchun Bearer Token olish kerak. Token 24 soat amal qiladi. Har bir so'rovda Authorization: Bearer {token} headerini yuboring.
| Parametr | Tip | Majburiy | Tavsif |
|---|---|---|---|
| merchant_id | integer | ✓ Ha | Merchant identifikatori |
| merchant_token | string | ✓ Ha | Merchant token (32 belgili) |
Accept: application/json
curl -X GET "https://inpay.uz/api/v1/authorization/?merchant_id=1353&merchant_token=6a7bf375b302cfcda6692e6f60402cb3" \ -H "Accept: application/json"
<?php $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => 'https://inpay.uz/api/v1/authorization/?merchant_id=1353&merchant_token=...', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Accept: application/json'], ]); $res = json_decode(curl_exec($curl), true); curl_close($curl); // Tokenni keshda saqlang (24 soat) apcu_store('inpay_token', $res['bearer_token'], 86400);
import requests r = requests.get( "https://inpay.uz/api/v1/authorization/", params={"merchant_id": 1353, "merchant_token": "..."}, headers={"Accept": "application/json"}, timeout=10, ) token = r.json()["bearer_token"] # Save token to cache for 24 hours
{
"success": true,
"bearer_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}To'lov yaratish
Yangi tranzaksiyani boshlash
Yangi to'lov tranzaksiyasini yaratish. Muvaffaqiyatli bo'lsa foydalanuvchini pay_url ga yo'naltiring. Endpoint oxirida / belgisi shart.
Content-Type: application/json
Authorization: Bearer {your_bearer_token}| Parametr | Tip | Majburiy | Tavsif |
|---|---|---|---|
| merchant_id | string | ✓ Ha | Merchant ID |
| token | string | ✓ Ha | Merchant token |
| amount | number | ✓ Ha | To'lov summasi (min: 1 000 so'm) |
| description | string | — Ixtiyoriy | To'lov haqida izoh |
| payment_method | string | — Ixtiyoriy | To'lov usuli (click, payme, plum, inPAY) |
| callback_url | string | — Ixtiyoriy | Webhook URL manzili |
| phone | string | — Ixtiyoriy | Telefon raqami (998901234567) |
| return_url | string | — Ixtiyoriy | To'lovdan keyin mijoz qaytariladigan sahifa — domen kassangizga tegishli bo'lishi shart |
| client_ip | string | — Ixtiyoriy | Haqiqiy to'lovchi (mijoz) IP manzili — server orqali ulanadigan integratsiyalar uchun tavsiya etiladi |
curl -X POST "https://inpay.uz/api/v1/create/" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \ -d '{ "merchant_id": "1353", "token": "6a7bf375b302cfcda6692e6f60402cb3", "amount": 15000, "description": "Order #12345", "payment_method": "click", "phone": "998901234567", "client_ip": "203.0.113.10", "callback_url": "https://merchant.uz/payment/callback", "return_url": "https://merchant.uz/thanks" }'
<?php $payload = [ 'merchant_id' => '1353', 'token' => '6a7bf375b302cfcda6692e6f60402cb3', 'amount' => 15000, 'description' => 'Order #12345', 'payment_method' => 'click', 'phone' => '998901234567', 'client_ip' => '203.0.113.10', 'callback_url' => 'https://merchant.uz/payment/callback', ]; $ch = curl_init('https://inpay.uz/api/v1/create/'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . $bearerToken, ], ]); $res = json_decode(curl_exec($ch), true); curl_close($ch); if ($res['success']) { header('Location: ' . $res['pay_url']); exit; }
const axios = require('axios'); const { data } = await axios.post('https://inpay.uz/api/v1/create/', { merchant_id: '1353', token: '6a7bf375b302cfcda6692e6f60402cb3', amount: 15000, description: 'Order #12345', payment_method: 'click', phone: '998901234567', client_ip: '203.0.113.10', callback_url: 'https://merchant.uz/payment/callback', }, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${bearerToken}`, }, }); console.log('order_id:', data.order_id); console.log('pay_url: ', data.pay_url);
import requests payload = { "merchant_id": "1353", "token": "6a7bf375b302cfcda6692e6f60402cb3", "amount": 15000, "description": "Order #12345", "payment_method": "click", "phone": "998901234567", "client_ip": "203.0.113.10", "callback_url": "https://merchant.uz/payment/callback", } r = requests.post( "https://inpay.uz/api/v1/create/", json=payload, headers={"Authorization": f"Bearer {bearer_token}"}, timeout=15, ) data = r.json() print(data["pay_url"])
{
"success": true,
"order_id": "1ff2f5a6d66f6e9c",
"pay_url": "https://inpay.uz/checkout/1ff2f5a6d66f6e9c/click",
"pay_url_link": "https://my.click.uz/services/pay?service_id=80905&merchant_id=43478&transaction_param=1ff2f5a6d66f6e9c&amount=15000&return_url=https%3A%2F%2Finpay.uz%2Fcheckout%2F1ff2f5a6d66f6e9c",
"pay_links": {
"click": "https://my.click.uz/services/pay?service_id=80905&merchant_id=43478&transaction_param=1ff2f5a6d66f6e9c&amount=15000&return_url=...",
"payme": "https://checkout.paycom.uz/bT02OGU2YzczYzJiZmRiNTMwNDA1ZTNmZWI7YWMub3JkZXJfaWQ9..."
},
"phone": "998901234567",
"message": "invoice yaratildi",
"security": {
"ip_mode": "optional",
"ip_check": "IP verified (optional)"
}
}Tranzaksiya holati
order_id orqali holatni tekshirish
| Parametr | Tip | Majburiy | Tavsif |
|---|---|---|---|
| order_id | string | ✓ Ha | To'lov yaratishda qaytarilgan buyurtma ID |
curl -X GET "https://inpay.uz/api/v1/transactions/?order_id=1ff2f5a6d66f6e9c" \ -H "Accept: application/json"
<?php $orderId = '1ff2f5a6d66f6e9c'; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://inpay.uz/api/v1/transactions/?order_id={$orderId}", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Accept: application/json'], ]); $data = json_decode(curl_exec($ch), true); curl_close($ch); // $data['status'] => pending | success | failed | cancelled
{
"success": true,
"order_id": "1ff2f5a6d66f6e9c",
"status": "success",
"amount": 15000,
"payment_method": "click",
"created_at": "2025-12-10 05:14:52",
"paid_at": "2025-12-10 05:15:23"
}Fiskal chek
order_id orqali soliq.uz fiskal chekini olish
Authorization: Bearer <token> majburiy — order shu merchantga tegishli bo'lsagina qaytadi.
| Parametr | Tip | Majburiy | Tavsif |
|---|---|---|---|
| order_id | string | ✓ Ha | To'lov order_id (Payme/Click) |
| merchant_id | string | ✓ Ha | Merchant ID (token egasi) |
curl -X GET "https://inpay.uz/api/v1/fiscal/?order_id=1ff2f5a6d66f6e9c&merchant_id=1353" \ -H "Authorization: Bearer YOUR_TOKEN"
{
"success": true,
"data": {
"order_id": "1ff2f5a6d66f6e9c",
"status": "success",
"fiscalized": true,
"fiscal_url": "https://ofd.soliq.uz/epi?t=EP...&r=...&s=...",
"fiscal_receipt_id": "52308469",
"payment_method": "payme"
}
}Kassa balansi
Kassada hozir ishlatish mumkin bo'lgan summa
Authorization: Bearer <token> majburiy. Token qaysi kassaga tegishli bo'lsa, faqat o'sha kassa balansi qaytadi.
| Parametr | Tip | Majburiy | Tavsif |
|---|---|---|---|
| merchant_id | string | ✓ Ha | Kassa ID — bearer token egasi bo'lgan kassa |
Eski ko'rinish ham ishlaydi: /balance/?merchant_id=94055. Ikkalasi bir xil javob qaytaradi.
curl -X GET "https://inpay.uz/api/v1/info/merchant/94055" \ -H "Authorization: Bearer YOUR_TOKEN"
{
"success": true,
"data": {
"merchant_id": "94055",
"business_name": "INSPIRE",
"balance": 1134300
}
}| Maydon | Tip | Tavsif |
|---|---|---|
| merchant_id | string | Kassa ID |
| business_name | string | Kassa nomi |
| balance | number | Hozir ishlatish va yechib olish mumkin bo'lgan summa, so'mda |
| HTTP | error_code | Tavsif |
|---|---|---|
| 401 | MISSING_AUTH_TOKEN | Authorization sarlavhasi yo'q |
| 401 | INVALID_TOKEN | Token noto'g'ri yoki u boshqa kassaga tegishli |
| 400 | MISSING_MERCHANT_ID | merchant_id berilmagan yoki formati noto'g'ri |
| 403 | MERCHANT_NOT_APPROVED | Kassa hali tasdiqlanmagan |
| 403 | MERCHANT_DISABLED_BY_ADMIN | Kassa admin tomonidan o'chirilgan |
| 403 | IP_NOT_WHITELISTED_STRICT | IP manzil oq ro'yxatda yo'q (strict rejim) |
| 405 | METHOD_NOT_ALLOWED | Faqat GET qabul qilinadi |
| 429 | RATE_LIMIT_EXCEEDED | Daqiqadagi 60 ta chegara oshdi |
Webhook bildirishnomalar
To'lov holati o'zgarganda real-time xabar
| Parametr | Tip | Tavsif |
|---|---|---|
| amount | string | To'lov summasi (masalan: "15000.00") |
| status | string | To'lov holati: success yoki failed |
| order_id | string | Buyurtma identifikatori |
| transaction_id | integer | inPAY tizimidagi tranzaksiya ID |
| created_at | string | Yaratilgan vaqt (ISO format) |
{
"amount": "15000.00",
"status": "success",
"order_id": "1ff2f5a6d66f6e9c",
"transaction_id": 149,
"created_at": "2025-12-10 05:14:52"
}<?php $input = file_get_contents('php://input'); $data = json_decode($input, true); if (!$data) { http_response_code(400); exit('Invalid JSON'); } if (($data['status'] ?? '') === 'success') { // Update order in your DB $pdo->prepare('UPDATE orders SET status=?, paid_at=NOW() WHERE order_id=?') ->execute(['paid', $data['order_id']]); } // Always respond 200 OK http_response_code(200); echo 'OK';
const express = require('express'); const app = express(); app.use(express.json()); app.post('/payment/callback', async (req, res) => { const { amount, status, order_id, transaction_id } = req.body; if (status === 'success') { await db.query( 'UPDATE orders SET status = ? WHERE order_id = ?', ['paid', order_id] ); } res.status(200).send('OK'); });
from flask import Flask, request app = Flask(__name__) @app.route("/payment/callback", methods=["POST"]) def callback(): data = request.get_json(silent=True) or {} if data.get("status") == "success": # update DB mark_paid(data["order_id"]) return "OK", 200
- Webhook URL ni callback_url da yuboring — aks holda kassadagi default URL ishlatiladi
- return_url — bu faqat mijozni brauzerda saytingizga qaytarish uchun. Buyurtmani faqat webhook bo'yicha bajaring: qaytish parametrlari (order_id, status, amount, signature) ishonchli manba emas.
- Handler JSON formatini qabul qilishi va HTTP 200 qaytarishi shart
- Webhook URL HTTPS bo'lishi tavsiya etiladi
To'lov tugmasi (Pay Button)
Server kodisiz integratsiya — saytingizga ikki qator HTML qo'yasiz
API kalit ham, Bearer token ham kerak emas. Summa va dizayn inPAY tomonida saqlanadi — brauzerda o'zgartirib bo'lmaydi.
Tugmani kabinetdagi «To'lov tugmasi» bo'limida yaratasiz: nomi, summasi va dizayni tanlanadi, natijada btn_ bilan boshlanuvchi token beriladi. Saytingizga faqat shu token joylashadi. Dizaynni keyin o'zgartirsangiz sayt kodiga tegish shart emas — tugma o'zi yangilanadi.
<script src="https://inpay.uz/widget.js"></script> <inpay-button token="btn_xxxxxxxxxxxxxxxxxxxx"></inpay-button>
| Parametr | Tip | Majburiy | Tavsif |
|---|---|---|---|
| token | string | ✓ Ha | Kabinetdan olingan tugma tokeni. Format: btn_ + 20 ta hex belgi. |
Boshqa atribut yo'q — summa, matn, o'lcham, rang va belgi tokenga bog'langan holda inPAY bazasidan keladi. Shuning uchun HTML kodni ko'rgan odam summani o'zgartira olmaydi.
- Sahifa ochilganda widget.js GET /api/widget/button ga murojaat qilib tugma ko'rinishini oladi.
- Mijoz tugmani bosadi → POST /api/widget/session yuboriladi.
- inPAY transactions jadvalida pending tranzaksiya yaratadi (source = "widget") va /checkout/<order_id> havolasini qaytaradi.
- Mijoz yangi oynada to'lovni amalga oshiradi (oyna bloklansa — o'sha sahifada ochiladi).
- To'lov tugagach kassangizning callback_url manziliga odatdagi webhook keladi — Webhook bo'limidagi format bilan bir xil.
| Parametr | Qiymatlar | Tavsif |
|---|---|---|
| style | classic · dark · outline · gradient · minimal · white | 6 ta tayyor dizayn |
| size | s · m · l | Tugma balandligi: 36 / 44 / 54 px |
| radius | 0 … 40 | Burchak radiusi, piksel |
| logo | none · inpay · kassa | Belgisiz, inPAY logotipi yoki kassangiz logotipi |
| full | 0 · 1 | To'liq kenglikdagi blok tugma |
Bu qiymatlar so'rovda yuborilmaydi — ular kabinetda tanlanadi va tokenga saqlanadi. Jadval javobdagi maydonlarni tushunish uchun keltirilgan.
Vidjet endpointlari
widget.js ichkarida shu ikki endpointni chaqiradi
Odatda ularni qo'lda chaqirish shart emas. Agar tugmani o'z dizayningiz bilan chizmoqchi bo'lsangiz — shu ikki so'rov yetarli.
Tugmaning ko'rinishi va summasini qaytaradi. Summa faqat ekranda ko'rsatish uchun.
{
"ok": true,
"title": "Premium obuna",
"amount": 99000,
"currency": "UZS",
"label": "inPAY orqali to'lash",
"style": "classic",
"size": "m",
"radius": 10,
"logo": "inpay",
"mlogo": "",
"full": 0
}Tranzaksiya yaratadi. Tanasi — faqat token. Summa so'rovdan olinmaydi, u bazadagi tugmadan olinadi.
curl -X POST "https://inpay.uz/api/widget/session" \ -H "Content-Type: application/json" \ -H "Origin: https://sizning-saytingiz.uz" \ -d '{"token":"btn_xxxxxxxxxxxxxxxxxxxx"}'
{
"ok": true,
"order_id": "9f3c1a7b25e40d68",
"url": "https://inpay.uz/checkout/9f3c1a7b25e40d68"
}| HTTP | error | Tavsif |
|---|---|---|
| 400 | token | Token yuborilmagan |
| 404 | not_found | Bunday tugma yo'q yoki o'chirilgan |
| 403 | origin | Domen kassaning whitelist ro'yxatida emas |
| 403 | kassa | Kassa tasdiqlanmagan yoki faol emas |
| 400 | amount | Tugma summasi belgilanmagan |
| 405 | method | /api/widget/session faqat POST qabul qiladi |
CardSystem — o'z saytingizda karta qabul qilish
Karta ma'lumoti sizning formangizda kiritiladi, SMS kod bilan tasdiqlanadi
| Hosted checkout | CardSystem | Card System (avto to'lov) | |
|---|---|---|---|
| Karta kim qabul qiladi | inPAY sahifasi | Sizning formangiz | inPAY sahifasi (bir marta) |
| Har to'lovda SMS kod | Ha | Ha | Yo'q |
| PCI mas'uliyati | inPAY'da | Sizda | inPAY'da |
| Qachon | Eng oddiy, tavsiya etiladi | To'liq o'z dizayni kerak bo'lsa | Obuna, takroriy to'lov |
Tarif: CardSystem Premium va Business tarifida avtomatik ochiladi.
Standard tarifda hosted checkout (/api/v1/create) ishlatiladi yoki admindan
alohida ruxsat so'raladi.
Diqqat: CardSystem'da karta raqami sizning serveringizdan o'tadi — uni
hech qachon saqlamang va logga yozmang, faqat HTTPS orqali uzating.
Agar shu talab sizga og'ir bo'lsa — hosted checkout (/api/v1/create) ni tanlang.
1. POST /api/v1/create → order_id olinadi (bearer token bilan) 2. POST /api/v1/cardsystem/create → karta yuboriladi, mijozga SMS ketadi → hash_token 3. POST /api/v1/cardsystem/verify → hash_token + SMS kod → to'lov yakunlanadi 4. webhook → serveringizga natija keladi
| Parametr | Tip | Majburiy | Tavsif |
|---|---|---|---|
| cardsystem_order_id | string | ✓ Ha | 1-qadamda olingan order_id |
| card_number | string | ✓ Ha | 16 raqam, probelsiz |
| exp_month | int | ✓ Ha | 1–12 |
| exp_year | int | ✓ Ha | Ikki raqam (masalan 29) |
{
"success": true,
"message": "SMS kod yuborildi",
"data": {
"cardsystem_order_id": "1ff2f5a6d66f6e9c",
"hash_token": "a91c…",
"amount": 15000,
"phone": "+998 90 *** ** 12",
"next_step": "/api/v1/cardsystem/verify"
}
}{ "hash_token": "a91c…", "otp_code": "123456" }
// 200 OK
{ "success": true, "message": "To'lov muvaffaqiyatli",
"data": { "cardsystem_order_id": "1ff2f5a6d66f6e9c", "amount": 15000,
"status": "success", "paid_at": "2026-07-27 09:20:11" } }| Nima | Qiymat | Nima bo'ladi |
|---|---|---|
| So'rov tezligi | 30 / daqiqa / IP | HTTP 429 |
| OTP urinishlari | 5 / 5 daqiqa | Sessiya bloklanadi |
| Sessiya muddati | 15 daqiqa | Qaytadan boshlash kerak |
| Firibgarlik qalqoni | Har so'rovda | Karta sinash / tezlik / yangi kassa chegarasi — karta bankka umuman yuborilmaydi (HTTP 403) |
| Yangi kassa mablag'i | T+7 kun | Pul avval «kutilayotgan balans»da turadi (chargeback oynasi) |
Avto to'lov — tokenlangan karta
Mijoz kartasini bir marta bog'laydi, keyingi to'lovlar kod so'ramasdan yechiladi
Obuna, bo'lib to'lash, muddatli xizmat, avtomatik to'ldirish — mijoz har safar karta raqami va
SMS kodini kiritmasligi kerak bo'lgan holatlar uchun. Karta bir marta bog'lanadi, siz keyin
faqat card_id va summa yuborasiz.
Avto to'lov tarif bilan avtomatik yoqilmaydi — u har bir kassa uchun alohida ariza asosida ochiladi. Sabab oddiy: bu imkoniyat mijoz kartasidan SMS kodsiz pul yechish huquqini beradi, shuning uchun har bir kassa qo'lda ko'rib chiqiladi.
| Qadam | Kim bajaradi | Natija |
|---|---|---|
| 1. Ariza | Savdogar — kabinet → Avto to'lov | Maqsad, sayt, kutilayotgan hajm, aloqa |
| 2. Ko'rib chiqish | inPAY admini | Odatda 1 ish kuni |
| 3. Tasdiq | Admin | Chegaralar (bitta to'lov / kunlik / oylik) va rejim belgilanadi |
| 4. Kalit | Savdogar o'zi yaratadi | key_id + secret — bir marta ko'rsatiladi |
Chegaralarni admin belgilaydi — tarif faqat taklif qilinadigan boshlang'ich
qiymatga ta'sir qiladi. Shubhali holatda imkoniyat to'xtatilishi va kalit
bekor qilinishi mumkin.
Chalkashtirmang: tarif bo'yicha avtomatik ochiladigan narsa — bu
CardSystem (o'z saytingizda karta qabul qilish),
avto to'lov emas.
1. bind → mijozga form_url beriladi (inpay.uz/cards/bind/<kassa>/<token>) 2. mijoz → o'sha sahifada karta + SMS kodni kiritadi, rozilik beradi 3. karta → status active, sizda faqat card_id qoladi (karta raqami YO'Q) 4. charge → istalgan payt: card_id + amount + idem_key + reason 5. mijozga → har yechimdan keyin SMS ketadi, u inpay.uz/cards/my da bekor qila oladi
- Karta raqami sizga hech qachon ko'rinmaydi — mijoz uni inPAY sahifasida kiritadi, sizga token emas,
card_idqaytadi. - Token bazada AES-256-GCM bilan shifrlanadi; kalit veb-ildizdan tashqarida.
- Har yechishda
reasonmajburiy — mijoz «nimaga yechildi?» deganda javob shu yerdan chiqadi. - Har karta uchun mandat raqami (mandate_id) beriladi — nizoda shu raqam bo'yicha isbot qilinadi.
- Mijoz bog'lashda rozilik chegarasini belgilashi mumkin — undan yuqori summa rad etiladi.
- Har amal o'zgartirib bo'lmaydigan xesh-zanjirli jurnalga yoziladi.
Kalit va imzo
Card System kassa kalitidan ALOHIDA kalit ishlatadi — har so'rov imzolanadi
| Header | Tip | Majburiy | Tavsif |
|---|---|---|---|
| X-Inpay-Key | string | ✓ Ha | Kalit ID (ap_...) |
| X-Inpay-Timestamp | int | ✓ Ha | Unix vaqt. ±300 soniyadan chetlashsa rad etiladi |
| X-Inpay-Nonce | string | ✓ Ha | Takrorlanmas satr — bir marta ishlatiladi (replay himoyasi) |
| X-Inpay-Signature | hex | ✓ Ha | HMAC-SHA256 imzo (pastda) |
| Content-Type | string | ✓ Ha | application/json |
Ikkala ko'rinish ham ishlaydi — birini tanlang va imzoda o'sha yo'lni ishlating:
| Ko'rinish | Manzil | Imzodagi PATH |
|---|---|---|
| Yo'l (tavsiya) | POST /api/v1/cards/bind | /api/v1/cards/bind |
| Parametr | POST /api/v1/cards/?action=bind | /api/v1/cards/ |
signature = HMAC_SHA256(secret, METHOD + "\n" + PATH + "\n" + TS + "\n" + NONCE + "\n" + sha256(BODY)) METHOD = "POST" (list uchun "GET") PATH = "/api/v1/cards/bind" ← qaysi manzilga so'rov yuborsangiz, imzoda ham SHU yo'l (?query qismi imzoga kirmaydi) TS = X-Inpay-Timestamp NONCE = X-Inpay-Nonce BODY = so'rov tanasi (bo'sh bo'lsa ham sha256'si olinadi)
<?php function inpayCards($action, $body, $keyId, $secret) { $path = "/api/v1/cards/{$action}"; // bind | confirm | charge | remove | list $json = json_encode($body, JSON_UNESCAPED_UNICODE); $ts = time(); $nonce = bin2hex(random_bytes(12)); $base = "POST\n{$path}\n{$ts}\n{$nonce}\n" . hash('sha256', $json); $sig = hash_hmac('sha256', $base, $secret); $ch = curl_init("https://inpay.uz{$path}"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => $json, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', "X-Inpay-Key: {$keyId}", "X-Inpay-Timestamp: {$ts}", "X-Inpay-Nonce: {$nonce}", "X-Inpay-Signature: {$sig}", ], ]); $res = json_decode(curl_exec($ch), true); curl_close($ch); return $res; }
import time, json, hmac, hashlib, secrets, requests def inpay_cards(action, body, key_id, secret): path = f'/api/v1/cards/{action}' raw = json.dumps(body, ensure_ascii=False) ts = str(int(time.time())) nonce = secrets.token_hex(12) base = f"POST\n{path}\n{ts}\n{nonce}\n" + hashlib.sha256(raw.encode()).hexdigest() sig = hmac.new(secret.encode(), base.encode(), hashlib.sha256).hexdigest() return requests.post( f"https://inpay.uz{path}", data=raw.encode(), headers={'Content-Type': 'application/json', 'X-Inpay-Key': key_id, 'X-Inpay-Timestamp': ts, 'X-Inpay-Nonce': nonce, 'X-Inpay-Signature': sig}, timeout=30).json()
const crypto = require('crypto'); async function inpayCards(action, body, keyId, secret) { const path = `/api/v1/cards/${action}`; const raw = JSON.stringify(body); const ts = Math.floor(Date.now() / 1000).toString(); const nonce = crypto.randomBytes(12).toString('hex'); const hash = crypto.createHash('sha256').update(raw).digest('hex'); const sig = crypto.createHmac('sha256', secret) .update(`POST\n${path}\n${ts}\n${nonce}\n${hash}`).digest('hex'); const r = await fetch(`https://inpay.uz${path}`, { method: 'POST', body: raw, headers: { 'Content-Type': 'application/json', 'X-Inpay-Key': keyId, 'X-Inpay-Timestamp': ts, 'X-Inpay-Nonce': nonce, 'X-Inpay-Signature': sig, }, }); return r.json(); }
1. Kartani bog'lash
Mijozga bir martalik xavfsiz sahifa havolasi beriladi
| Parametr | Tip | Majburiy | Tavsif |
|---|---|---|---|
| customer_ref | string | ✓ Ha | Mijozning sizdagi ID'si (64 belgigacha). Keyin shu bo'yicha kartalarini topasiz |
| return_url | string | Yo'q | Bog'langach mijoz qaytadigan manzil |
{
"success": true,
"data": {
"card_id": 128,
"bind_ref": "4ce13e39073e13ce98295cb88b89ccef",
"need_code": true,
"form_url": "https://inpay.uz/cards/bind/71337/4ce13e39073e13ce98295cb88b89ccef"
}
}
Mijozni shu havolaga yo'naltiring (yoki SMS/xabar bilan yuboring). Sahifa bank darajasida
himoyalangan: birinchi ochilgan brauzerga mixlanadi (boshqa qurilmada ochilmaydi),
nusxa-joylashtirish taqiqlangan, kod bitta-bittalab kiritiladi va
bitta xato koddan keyin havola o'ladi. Havola 30 daqiqa amal qiladi.
Bekor bo'lsa — yangi bind so'rovini yuboring.
Agar o'z ilovangizdan kod kiritmoqchi bo'lsangiz — ?action=confirm bilan
{card_id, code} yuborasiz. Lekin tavsiya etilgan yo'l — form_url,
chunki unda karta raqami umuman sizning tizimingizdan o'tmaydi.
2. Pul yechish
Bog'langan kartadan kod so'ramasdan yechish
| Parametr | Tip | Majburiy | Tavsif |
|---|---|---|---|
| card_id | int | ✓ Ha | bind qaytargan ID |
| amount | int | ✓ Ha | Summa, so'mda (tiyinsiz) |
| idem_key | string | ✓ Ha | Takroriy yechishning oldini oladi. Xuddi shu kalit qayta kelsa — eski natija qaytadi, pul ikki marta yechilmaydi |
| reason | string | ✓ Ha | Nima uchun yechilayotgani (kamida 5 belgi). Mijozga SMS'da shu matn ketadi |
| order_ref | string | Yo'q | Sizdagi buyurtma raqami |
| description | string | Yo'q | Qo'shimcha izoh (provayder yozuvida) |
{
"success": true,
"data": {
"charge_id": 4412,
"status": "success",
"amount": 49000,
"provider_ref": "PLM-90188233",
"mandate_id": "MND-7K3D9F2A"
}
}$res = inpayCards('charge', [ 'card_id' => 128, 'amount' => 49000, 'idem_key' => 'sub-2026-08-user-341', // oy + mijoz → takrorlanmaydi 'reason' => 'Avgust oyi obunasi', 'order_ref' => 'ORD-9912', ], $keyId, $secret); if ($res['success']) { // $res['data']['charge_id'] ni saqlang — nizoda shu bo'yicha tekshiriladi }
| Kod | HTTP | Sabab |
|---|---|---|
| REASON_REQUIRED | 400 | reason yo'q yoki 5 belgidan qisqa |
| IDEM_KEY | 400 | idem_key yuborilmagan |
| CONSENT_LIMIT | 400 | Mijoz bog'lashda belgilagan chegaradan oshdi |
| LIMIT_TX / LIMIT_DAY / LIMIT_MONTH | 400 | Tarif yoki admin chegarasidan oshdi |
| CARD_STATE | 400 | Karta faol emas (mijoz bekor qilgan yoki bloklangan) |
| CHARGE_FAILED | 402 | Bank rad etdi (mablag' yetarli emas, karta yopiq va h.k.) |
| SIGNATURE / TIMESTAMP / NONCE | 401 | Imzo noto'g'ri, vaqt farqi >300s yoki nonce takrorlandi |
3. Ro'yxat va bekor qilish
Mijozning bog'langan kartalari
{
"success": true,
"data": { "cards": [
{
"id": 128,
"customer_ref": "user-341",
"masked_pan": "860038******1234",
"expiry": "09/29",
"status": "active",
"mandate_id": "MND-7K3D9F2A",
"consent_max": 100000,
"last_used_at": "2026-07-27 09:14:02"
}
] }
}{ "card_id": 128 }
// javob: { "success": true, "data": { "card_id": 128, "status": "removed" } }
// token darhol o'chiriladi — qayta tiklab bo'lmaydiMijoz kartani sizning botingiz/ilovangiz orqali bog'lagan bo'lsa, uni o'sha joydan uza olishi kerak. Bu shartnoma talabi: kartani bog'lash oson, uzish qiyin bo'lgan xizmat mijoz shikoyatiga va kassaning muzlatilishiga olib keladi.
// «Kartani uzish» tugmasi bosilganda: POST /api/v1/cards/remove { "card_id": 128 } // Mijozga javob: "Kartangiz uzildi. Bundan keyin avtomatik to'lov amalga oshmaydi."
Karta egasida inPAY akkaunti bo'lmasa ham u inpay.uz/kartam sahifasidan
telefon raqami + SMS kod bilan kirib bog'lanishni bekor qila oladi.
Bu havola har bog'lash va har yechim SMS'ida yuboriladi.
Shundan keyin charge so'rovingiz CARD_STATE xatosi bilan qaytadi —
bu normal holat, mijozdan qayta bog'lashni so'rang.
Xato kodlari
API javob xatolari va ularning ma'nosi
| Xato kodi | HTTP | Tavsif |
|---|---|---|
| MISSING_AUTH_TOKEN | 401 | Authorization token topilmadi |
| INVALID_TOKEN | 401 | Bearer token noto'g'ri yoki muddati tugagan |
| MISSING_MERCHANT_ID | 400 | merchant_id parametri topilmadi |
| MERCHANT_NOT_FOUND | 404 | Merchant topilmadi |
| IP_NOT_WHITELISTED_STRICT | 403 | IP manzil whitelist da yo'q (Strict mode) |
| RATE_LIMIT_EXCEEDED | 429 | So'rovlar soni limitdan oshdi (100/soat) |
| CALLBACK_NOT_WHITELISTED | 403 | Callback URL whitelist da yo'q |
| MERCHANT_WEBSITE_NOT_WHITELISTED | 403 | Merchant website whitelist da active emas |
| AMOUNT_TOO_LOW | 400 | Summa juda kam (min: 1 000 so'm) |
| AMOUNT_TOO_HIGH | 400 | Summa maksimal limitdan oshdi |
| TRANSACTION_SAVE_FAILED | 500 | Tranzaksiya saqlanmadi (server xatosi) |
{
"success": false,
"message": "Minimal to'lov summasi 1000 so'm",
"error_code": "AMOUNT_TOO_LOW"
}- Har doim success maydonini tekshiring, so'ng error_code asosida xatolarni boshqaring
- 401/403 xatolarida autentifikatsiyani qayta tekshiring
- 429 xatolarida biroz kuting va qayta urinib ko'ring
- 500 xatolarida support bilan bog'laning: @merchants_uz
Xavfsizlik
IP Whitelist, Rate Limiting va token himoyasi
Har bir IP manzil uchun soatiga 100 ta so'rov limiti. Limit oshirilsa, RATE_LIMIT_EXCEEDED xatosi qaytariladi.
- Token 24 soat amal qiladi — keshda saqlang
- Tokenni server-side saqlang, hech kimga bermang
- Har so'rovda Authorization: Bearer {token} headerini yuboring
- Muddati tugagandan so'ng yangi token oling
Callback URL va merchant website whitelist da bo'lishi kerak. Sozlamalar uchun inPAY platformasiga kiring.
Eng yaxshi amaliyotlar
Ishonchli integratsiya uchun tavsiyalar