Payment Hub API

Hub trung gian tại client.apinganhang.net — tích hợp SharkPay, WowPay, EeziePay và 1DPay. Website đối tác gọi Hub; Hub ký request, nhận callback, ghi logs_history và forward webhook về site.

Tổng quan

Endpoints chính

MethodPathMô tả
GET/POSTsettings.phpCấu hình gateway (sharkpay, wowpay, eeziepay, 1dpay)
GETbalance.php?gateway=Số dư cổng (WowPay, EeziePay, 1DPay)
POSTtest.phpTest kết nối / balance
POSTorders/create.phpTạo đơn nạp (payin)
GET/POSTorders/query.phpTra cứu đơn nạp
POSTpayouts/create.phpTạo lệnh rút (payout)
GET/POSTpayouts/query.phpTra cứu payout
GET/POSTwebsites.phpQuản lý website integration
Chống gửi tiền trùng cho payout (từ 2026-07-03): Hub claim merchant_order_no nguyên tử trước khi gọi cổng — gọi payouts/create.php nhiều lần với cùng merchant_order_no (retry do timeout mạng, double-click...) trong khi lệnh trước còn đang xử lý hoặc đã thành công sẽ bị từ chối ngay, Hub không gửi tiền lần 2: HTTP 409, {"success":false,"data":{"code":"DUPLICATE_ORDER","status":"pending|success"}}. Nếu lệnh trước đó thất bại rõ ràng ở tầng Hub/cổng (validate lỗi, cổng từ chối) thì có thể gọi lại với cùng merchant_order_no. Khuyến nghị: luôn truyền merchant_order_no duy nhất cho mỗi lệnh rút — nếu bỏ trống, Hub không chống được trùng.

Xác thực đối tác — Website HMAC Bắt buộc

Mọi request gọi Hub từ phía đối tác (tạo đơn, rút tiền, tra cứu) đều phải xác thực bằng cặp website_key / website_secret. Lấy hai giá trị này tại app.apinganhang.net → Cổng thanh toán → Website đã đăng ký.

Thuật toán ký

  1. Thu thập tất cả field của request (JSON body hoặc form-data).
  2. Loại bỏ các field: sign, website_secret, website_secret_plain, website_secret_runtime, secret.
  3. Loại bỏ field có giá trị rỗng ("") và field kiểu array/object.
  4. Sắp xếp các field còn lại theo tên key — thứ tự bảng chữ cái ASCII (case-sensitive, chữ hoa trước chữ thường).
  5. Nối chuỗi: key1=value1|key2=value2|key3=value3 — dấu pipe | ngăn cách giữa các cặp, không có dấu cách, không trailing pipe.
  6. Ký HMAC-SHA256: HMAC-SHA256(canonical_string, website_secret) — kết quả là chuỗi hex thường (lowercase).
  7. Gửi chữ ký trong field sign cùng với website_key trong body request.

Ví dụ minh hoạ — tạo đơn nạp (Payin)

Request body (trước khi ký):

{
  "website_key": "wk_abc123",
  "gateway":     "sharkpay",
  "amount":      100000,
  "channel":     "bank_transfer",
  "merchant_order_no": "ORDER_20260630_001",
  "notify_url":  "https://yourdomain.com/payment/callback",
  "return_url":  "https://yourdomain.com/payment/result"
}

Bước 1–3: giữ tất cả field, không field nào bị loại (không có sign/rỗng).

Bước 4: Sort theo key alphabetically:

amount, channel, gateway, merchant_order_no, notify_url, return_url, website_key

Bước 5: Canonical string:

amount=100000|channel=bank_transfer|gateway=sharkpay|merchant_order_no=ORDER_20260630_001|notify_url=https://yourdomain.com/payment/callback|return_url=https://yourdomain.com/payment/result|website_key=wk_abc123

Bước 6: HMAC-SHA256(canonical, "your_website_secret") → ví dụ: 3a7f9c2e...

Bước 7: Request cuối cùng:

{
  "website_key": "wk_abc123",
  "gateway":     "sharkpay",
  "amount":      100000,
  "channel":     "bank_transfer",
  "merchant_order_no": "ORDER_20260630_001",
  "notify_url":  "https://yourdomain.com/payment/callback",
  "return_url":  "https://yourdomain.com/payment/result",
  "sign": "3a7f9c2e..."
}

Implement mẫu — PHP

function hubSign(array $params, string $secret): string {
    $skip = ['sign', 'website_secret', 'website_secret_plain', 'website_secret_runtime', 'secret'];
    $pairs = [];
    foreach ($params as $k => $v) {
        if (in_array($k, $skip, true)) continue;
        if (is_array($v) || is_object($v)) continue;
        $val = trim((string)$v);
        if ($val === '') continue;
        $pairs[$k] = $val;
    }
    ksort($pairs, SORT_STRING);
    $canonical = implode('|', array_map(fn($k, $v) => "$k=$v", array_keys($pairs), $pairs));
    return hash_hmac('sha256', $canonical, $secret);
}

$body = [
    'website_key'       => 'wk_abc123',
    'gateway'           => 'sharkpay',
    'amount'            => 100000,
    'channel'           => 'bank_transfer',
    'merchant_order_no' => 'ORDER_20260630_001',
    'notify_url'        => 'https://yourdomain.com/payment/callback',
];
$body['sign'] = hubSign($body, 'your_website_secret');

$ch = curl_init('https://client.apinganhang.net/api/v1/payments/orders/create.php');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode($body),
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 20,
]);
$res = json_decode(curl_exec($ch), true);

Implement mẫu — JavaScript (Node.js)

const crypto = require('crypto');

function hubSign(params, secret) {
  const skip = new Set(['sign','website_secret','website_secret_plain','website_secret_runtime','secret']);
  const pairs = {};
  for (const [k, v] of Object.entries(params)) {
    if (skip.has(k)) continue;
    if (typeof v === 'object' && v !== null) continue;
    const val = String(v).trim();
    if (val === '') continue;
    pairs[k] = val;
  }
  const canonical = Object.keys(pairs).sort().map(k => `${k}=${pairs[k]}`).join('|');
  return crypto.createHmac('sha256', secret).update(canonical).digest('hex');
}

const body = {
  website_key:       'wk_abc123',
  gateway:           'sharkpay',
  amount:            100000,
  channel:           'bank_transfer',
  merchant_order_no: 'ORDER_20260630_001',
  notify_url:        'https://yourdomain.com/payment/callback',
};
body.sign = hubSign(body, 'your_website_secret');

await fetch('https://client.apinganhang.net/api/v1/payments/orders/create.php', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(body),
});

Implement mẫu — Python

import hmac, hashlib, json, requests

def hub_sign(params: dict, secret: str) -> str:
    skip = {'sign','website_secret','website_secret_plain','website_secret_runtime','secret'}
    pairs = {k: str(v).strip() for k, v in params.items()
             if k not in skip and not isinstance(v, (dict, list)) and str(v).strip() != ''}
    canonical = '|'.join(f'{k}={v}' for k, v in sorted(pairs.items()))
    return hmac.new(secret.encode(), canonical.encode(), hashlib.sha256).hexdigest()

body = {
    'website_key':       'wk_abc123',
    'gateway':           'sharkpay',
    'amount':            100000,
    'channel':           'bank_transfer',
    'merchant_order_no': 'ORDER_20260630_001',
    'notify_url':        'https://yourdomain.com/payment/callback',
}
body['sign'] = hub_sign(body, 'your_website_secret')
resp = requests.post(
    'https://client.apinganhang.net/api/v1/payments/orders/create.php',
    json=body, timeout=20
).json()

Callback webhook từ Hub về site đối tác

Sau khi giao dịch được xác nhận, Hub POST JSON đến notify_forward_url (cấu hình tại app.apinganhang.net) kèm header:

X-Payment-Signature: sha256=<HMAC-SHA256 của raw JSON body>

Secret dùng để ký là notify_forward_secret (xem tại app.apinganhang.net → Website đã đăng ký → icon mắt). Cách xác thực phía đối tác:

// PHP
$rawBody = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_X_PAYMENT_SIGNATURE'] ?? ''; // "sha256=abc..."
$secret = 'your_notify_forward_secret';
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
if (!hash_equals($expected, $sigHeader)) {
    http_response_code(403); exit('bad signature');
}
$data = json_decode($rawBody, true);
Lưu ý: website_secret và notify_forward_secret là 2 secret khác nhau. website_secret dùng để ký request gọi Hub; notify_forward_secret dùng để Hub ký webhook gửi về site.

Lỗi xác thực thường gặp

Mã lỗiNguyên nhânCách fix
WEBSITE_AUTH_FAILEDThiếu website_key hoặc sai keyLấy đúng website_key từ dashboard
WEBSITE_AUTH_FAILED (Chữ ký không hợp lệ)Canonical string sai — sort sai, có field rỗng, dùng | thay =Log ra canonical string và so sánh từng bước
WEBSITE_AUTH_FAILED (Website chưa có secret)Chưa tạo secret lần đầuVào app.apinganhang.net → Xoay secret lần đầu
Chữ ký đúng nhưng vẫn lỗiGửi JSON array [] hoặc nested object bị skip trong ký nhưng không skip khi xử lýChỉ dùng string/number ở cấp cao nhất khi ký

Webhook callback — payload mẫu từ Hub về site

Sau khi cổng thanh toán báo kết quả cho Hub, Hub xác thực callback của cổng, cập nhật đơn rồi POST JSON tới notify_forward_url của website đã tạo đơn, kèm header:

Content-Type: application/json
X-Payment-Signature: sha256=<HMAC-SHA256(raw JSON body, notify_forward_secret)>
User-Agent: Apinganhang-PaymentHub/1.0

Chữ ký ký trên đúng chuỗi JSON đã gửi (không phải JSON decode/encode lại), dùng secret notify_forward_secret của website (xem tại app.apinganhang.net → Website đã đăng ký → icon mắt) — khác với website_secret dùng để ký request gọi Hub.

Envelope chung cho mọi cổng (WowPay, SharkPay, EeziePay, 1DPay)

{
  "event":     "payment.<gateway>.callback",
  "gateway":   "sharkpay",
  "timestamp": 1790856752,
  "data": {
    "gateway":            "sharkpay",
    "success":            true,
    "status":             "...",
    "amount":             100000,
    "merchant_order_no":  "ORDER_20260630_001"
    /* + các field gốc do cổng gửi, khác nhau theo từng cổng — xem bên dưới */
  }
}

event luôn là payment.<gateway>.callback (ví dụ payment.sharkpay.callback, payment.wowpay.callback, payment.eeziepay.callback, payment.1dpay.callback) — kể cả cho lệnh rút (payout), không có event riêng payout.confirmed. Phân biệt payin/payout bằng mã đơn (merchant_order_no) phía site tự quản lý, hoặc field order_type nếu cổng đó có trả (xem ví dụ 1DPay).

Field trong dataLuôn có?Ý nghĩa
gatewayCóMã cổng viết thường: wowpay/sharkpay/eeziepay/1dpay
successCótrue = giao dịch thành công. Callback đang xử lý/thất bại có success: false — site phải tự kiểm tra field này, không coi mọi webhook nhận được là thành công
statusCóTrạng thái thô (tuỳ cổng) ứng với callback đó
amountCóSố tiền của đơn gốc khi tạo (payment_hub_orders.amount) — Hub không lấy theo số tiền trong callback của cổng, kể cả khi callback bị giả mạo/bơm số tiền
merchant_order_noCóMã đơn/lệnh của site đã dùng lúc tạo đơn
field khác (viết hoa/thường tuỳ cổng)Tuỳ cổngDữ liệu gốc cổng gửi về Hub được giữ nguyên trong data — tên field khác nhau theo từng cổng, xem phần riêng của từng cổng: WowPay (mch_order_no, real_amount...), SharkPay (payin/payout) và Settlement (settleOrderId...), EeziePay (billno...), 1DPay (payload mẫu đầy đủ + bảng field)

Xác thực chữ ký phía đối tác (giống nhau cho mọi cổng):

// PHP
$rawBody = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_X_PAYMENT_SIGNATURE'] ?? ''; // "sha256=abc..."
$secret = 'your_notify_forward_secret';
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
if (!hash_equals($expected, $sigHeader)) {
    http_response_code(403); exit('bad signature');
}
$payload = json_decode($rawBody, true);
$data = $payload['data'] ?? [];
if (($data['success'] ?? false) === true) {
    // tìm đơn theo $data['merchant_order_no'] -> nếu chưa xử lý, cộng $data['amount']; đã xử lý -> bỏ qua
} else {
    // đang xử lý / thất bại -> không cộng tiền, tuỳ xử lý UI/hoàn tiền phía site
}
http_response_code(200); echo 'OK';
Lưu ý: website_secret và notify_forward_secret là 2 secret khác nhau. website_secret dùng để ký request gọi Hub; notify_forward_secret dùng để Hub ký webhook gửi về site.
Chống trùng & chống bơm số tiền (từ 2026-07-03):
  • Idempotent — mỗi đơn chỉ được cộng tiền và forward webhook thành công 1 lần. Cổng thanh toán có thể gửi callback thành công nhiều lần (retry/replay), nhưng Hub dùng khoá nguyên tử trên đơn hàng (credited_at) nên chỉ callback thành công đầu tiên mới cộng logs_history và POST webhook về site. Các callback thành công trùng sau đó Hub vẫn ACK cho cổng nhưng không forward lại.
  • Callback đang xử lý / thất bại vẫn được forward. Chỉ callback thành công bị chặn forward lặp lại (trùng/replay) — các trạng thái khác (success: false, ví dụ đang xử lý hoặc gateway báo lỗi) được forward về site mỗi lần Hub nhận, để site cập nhật đúng trạng thái đơn (kể cả báo thất bại cho đơn rút).
  • Số tiền webhook luôn là số tiền đơn hàng gốc. Trường amount Hub gửi về site lấy từ số tiền đã chốt khi tạo đơn, không lấy theo số tiền trong callback của cổng.
  • Không có cơ chế tự động gửi lại (retry/redelivery) phía Hub. Hub gọi notify_forward_url đúng 1 lần cho mỗi callback cần forward (timeout 8 giây); nếu site đối tác timeout/lỗi/trả về khác 2xx, Hub không tự gửi lại — chỉ ghi nhận kết quả gửi (thành công/thất bại) vào log nội bộ. Site đối tác nên: (1) trả HTTP 2xx thật nhanh ngay khi xác thực chữ ký xong, xử lý nghiệp vụ sau nếu cần; (2) tự đối soát định kỳ qua API tra cứu đơn của từng cổng (không dựa hoàn toàn vào webhook) để không bỏ sót trường hợp webhook gửi thất bại.
  • Khuyến nghị: phía site đối tác vẫn nên tự chống trùng theo merchant_order_no (bỏ qua nếu đơn đã xử lý) như một lớp phòng thủ bổ sung.

Luồng Payin — EeziePay Form POST

EeziePay dùng form POST mở tab mới tới fundtransfer.php (không iframe). Hub trả về form_action + form_fields để site render form.

sequenceDiagram participant Site as Website đối tác participant Hub as client.apinganhang.net participant EZ as EeziePay Site->>Hub: POST orders/create gateway=eeziepay Hub->>Hub: SHA1 sign, lưu payment_hub_orders Hub-->>Site: form_action + form_fields (fundtransfer.php) Site->>EZ: POST form target=_blank EZ->>Hub: backend_url callback (deposit) Hub->>Hub: verify SHA1, logs_history, Telegram Hub-->>EZ: XML status OK Hub->>Site: forward JSON + X-Payment-Signature

Ký SHA1 (deposit v3.1)

UPPER(SHA1("service_version=3.1&partner_code=...&partner_orderid=...&...&key=EncryptionKey"))

Thứ tự field theo tài liệu chính thức; bỏ field rỗng; remarks không ký. Callback thành công: status 000 hoặc 002. Hub trả XML:

<xml><billno>...</billno><status>OK</status></xml>

Bank codes VND — Online banking & QR

EeziePay chọn kênh qua field bank_code khi tạo đơn. Hub forward nguyên giá trị vào form_fields.bank_code.

Kênhbank_code (ví dụ)Mô tả
Online bankingVCB.VNVietcombank Internet Banking
Online bankingTCB.VNTechcombank Internet Banking
QR chuyển khoảnVCB.QR.VNVietcombank QR
QR chuyển khoảnMM.QR.VNVí / MoMo QR

Hoặc truyền pay_type + bank thay cho bank_code:

// Online banking Vietcombank
{ "pay_type": "online_banking", "bank": "VCB" }  → bank_code = VCB.VN

// QR Vietcombank
{ "pay_type": "qr", "bank": "VCB" }              → bank_code = VCB.QR.VN

// QR MoMo
{ "pay_type": "qr", "bank": "MOMO" }             → bank_code = MM.QR.VN

Danh sách đầy đủ theo merchant: EeziePay Deposit API.

Cấu hình Domain API & endpoint paths

Domain API chỉ là gốc, ví dụ https://gogomart168com.com — không dán path fundtransfer.php hoặc fundtransfer_enquiry.php vào ô Domain.

5 path endpoint (Fund Transfer / Query / Payout / …) nhập riêng từng file trên dashboard. Hub ghép: {Domain}/{fundtransfer.php}. Nếu thấy URL dạng .../fundtransfer_enquiry.php/fundtransfer.php → Domain hoặc path bị cấu hình nhầm.

Luồng Payout — EeziePay

sequenceDiagram participant Site as Website đối tác participant Hub as client.apinganhang.net participant EZ as EeziePay Site->>Hub: POST payouts/create gateway=eeziepay Hub->>Hub: SHA1 sign v3.0, lưu order Hub->>EZ: POST withdrawal.php EZ-->>Hub: XML response Hub-->>Site: kết quả tạo lệnh EZ->>Hub: notify_url callback (payout) Hub->>Hub: verify, cập nhật order Hub-->>EZ: XML status OK Hub->>Site: forward webhook

Luồng Payin/Payout — WowPay JSON API

WowPay dùng JSON API thuần (không form/iframe). Hub ký MD5 uppercase rồi POST trực tiếp tới {Domain API}/api/payin/create/ hoặc /api/payout/create/.

sequenceDiagram participant Site as Website đối tác participant Hub as client.apinganhang.net participant WP as WowPay Site->>Hub: POST orders/create.php gateway=wowpay Hub->>Hub: chuẩn hóa input, amount ×100 → apply_amount, ký MD5 Hub->>WP: POST /api/payin/create/ WP-->>Hub: charge_info / cashier_info (qrcode, deeplink...) Hub-->>Site: pay_url + qr_code WP->>Hub: POST callbacks/wowpay.php (mch_order_no, status, real_amount) Hub->>Hub: verify MD5, tra merchant theo mch_order_no, cập nhật order Hub-->>WP: "OK" Hub->>Site: forward webhook + X-Payment-Signature

Ký MD5

UPPER(MD5(sort_by_key_asc("key1=value1&key2=value2&...&key=SecretKey")))

Field rỗng/null không tham gia ký. Áp dụng cho mọi request (payin/payout/query/balance) và cả callback gửi tới Hub.

Lưu ý quan trọng: callback payin/payout hiện tại của WowPay không gửi mch_id, chỉ có mch_order_no. Hub tra merchant bằng cách tìm đơn đã lưu (payment_hub_orders) theo mch_order_no, không còn dựa vào merchant ID trong callback.

Cấu hình WowPay Dashboard

Thiết lập tại app.apinganhang.net/integration/wowpay (yêu cầu gói VIP).

Trường trên APInganhangTrên WowPay / APIGhi chú
Bật cổng—Bật để cho phép tạo đơn/payout qua gateway=wowpay.
Domain APIHost API WowPayVí dụ https://api.open.mwowpay.com. Hub tự ghép /api/payin/create/, /api/payin/query/, /api/payout/create/, /api/payout/query/, /api/payin/balance/, /api/payout/balance/.
Merchant IDmch_idID merchant (số) do WowPay cấp.
API Keypayin_secret_key / payout_secret_keyWowPay cho dùng chung 1 key cho cả payin và payout.
Application IDkhông dùng cho WowPayTrường này chỉ hiển thị chung trên form; hiện không map vào field nào của API WowPay (khác SharkPay dùng product_id).
URL callback Hubcallback_url (tham số request, optional)https://client.apinganhang.net/api/v1/payments/callbacks/wowpay.php — Hub tự gửi field này ở mỗi request tạo đơn/payout. Nếu muốn an toàn hơn, khai báo thêm URL này làm callback mặc định trong merchant dashboard WowPay (áp dụng khi field callback_url bị bỏ trống).
Return URLreturn_urlOptional — URL chuyển khách (cashier redirect) sau khi thanh toán payin.
IP whitelist—IP server website đối tác được phép gọi API Hub. Để trống = không giới hạn.

user_id (bắt buộc theo API WowPay)

WowPay yêu cầu field user_id ở mọi request tạo đơn/payout — đây là ID khách hàng cuối (do website đối tác tự định danh), không phải ID cấu hình tĩnh của merchant. WowPay dùng field này để kiểm soát rủi ro/blacklist theo từng khách (xem mã lỗi 1225 User in blacklist).

Khuyến nghị cho đối tác: luôn truyền user_id hoặc customer_id khi gọi orders/create.php / payouts/create.php với gateway=wowpay, ứng với ID khách hàng của bạn. Nếu không truyền, Hub mặc định gửi "guest" sang WowPay cho tất cả giao dịch không định danh — có thể bị WowPay flag nhầm nếu 1 giao dịch "guest" bị đưa vào blacklist.

WowPay Payin API Tóm tắt

Gọi qua Hub: POST /api/v1/payments/orders/create.php với "gateway": "wowpay".

Field (gửi cho Hub)Bắt buộcMap sang API WowPay
merchant_order_no✓mch_order_no — phải duy nhất
amount (VNĐ)✓apply_amount = amount × 100 (cent)
pay_type / channel✓ (mặc định momo)pay_type — xem danh sách kênh: momo, zalo, bankcard, bankcard_qr, bankcard_free
bank_codekhi pay_type = bankcard/bankcard_qrbank_code — field cấp cao nhất, KHÔNG lồng trong object khác. Xem danh sách mã hợp lệ; Hub tự chuẩn hoá hoa/thường và vài tên thường gặp (VIETCOMBANK→VCB, MBBANK→MB...)
user_id / customer_idkhuyến nghịuser_id (mặc định "guest" nếu bỏ trống)
device_ip / client_iptự độngdevice_ip — Hub tự lấy IP request nếu không truyền
return_urloptionalreturn_url

Response data từ Hub: gateway_order_id (order_no của WowPay), pay_url (charge_info — link mở để thanh toán), qr_code (cashier_info.qrcode nếu có), amount, channel.

WowPay Payout API Tóm tắt

Gọi qua Hub: POST /api/v1/payments/payouts/create.php với "gateway": "wowpay". WowPay hiện chỉ hỗ trợ pay_type=bankcard (Hub tự gửi cố định).

Field (gửi cho Hub)Bắt buộcMap sang API WowPay
merchant_order_no✓mch_order_no
amount (VNĐ)✓amount = amount × 100 (cent) — payout API gọi field này là amount, không phải apply_amount
payout_bank_code / bank_code✓payout_bank_code — mã ngân hàng nhận tiền, xem danh sách mã hợp lệ (Hub tự chuẩn hoá hoa/thường và alias tên đầy đủ)
payout_account_no / account_no / bank_number✓payout_account_no
payout_name / account_name✓payout_name — tên người nhận
payout_phone_no / phoneoptionalpayout_phone_no
user_id / customer_idkhuyến nghịuser_id (mặc định "guest")

Danh sách bank_code WowPay Bắt buộc khi bankcard/bankcard_qr/payout

Áp dụng cho bank_code ở payin (pay_type=bankcard hoặc bankcard_qr) và payout_bank_code ở payout. Đây là mã WowPay yêu cầu — khác với mã ngân hàng dùng ở phần xác minh giao dịch ngân hàng (vd Hub dùng VIETCOMBANK, WowPay dùng VCB).

Lỗi thường gặp: thiếu hẳn field bank_code khi pay_type=bankcard/bankcard_qr → WowPay trả 1010 Bank Code Missing. Phải gửi field bank_code ở cấp cao nhất của request, không lồng trong form_fields hay đặt nhầm vào field pay_type/bank_type.
bank_codeTên ngân hàngbank_codeTên ngân hàng
ACBAsia Commercial BankNAMANam A Bank
SACSacombankNCBNational Citizen Bank
TCBTechcombankOCEANOCEAN Bank
VCBVietcombankPVCOMPVcomBank
VTBVietinbankSAIGONSaigonBank
DABDongA BankSEASeABank
BIDVBIDV BankSHBVNShinhan Bank VN
EXIMEximbankVIBVIB Bank
AGBAgribankVRBVietnam-Russia Bank
TPBTienPhong BankVABViet A Bank
MBMBBank (Quân Đội)VDBVietnam Development Bank
VPBVPBankGPBGPBank
OCBOCB (Phương Đông)VIETCAPITALBANKViet Capital Bank
SCBSCB BankPGBANKPGBank
SHBSHB (Sài Gòn Hà Nội)CIMBCIMB Bank
ABBABBankHLBHong Leong Bank
BACABac A BankIVBIndovina Bank
KLBKien Long BankPBVNVID Public Bank
BVBBaoViet BankUOBUOB Bank
HDBHDBankVIETBVietBank
LPBLienVietPostBankWOOWoori Bank
MSBMaritime BankYOLOYOLO (VPDirect)
DEUTDeutsche Bank
MBVModern Bank of Vietnam
VIKKIVikki Digital Bank
TIMOTimo Bank

Hub tự chuẩn hoá (viết hoa, bỏ khoảng trắng, map vài alias tên đầy đủ phổ biến: VIETCOMBANK→VCB, MBBANK→MB, VIETTINBANK→VTB, TPBANK→TPB, SEABANK→SEA...). Nếu gửi mã không nhận diện được, Hub trả lỗi VALIDATION_FAILED ngay (không cần đợi gọi WowPay).

Ví dụ tạo đơn WowPay

Payin — MoMo

POST /api/v1/payments/orders/create.php
Authorization: Bearer <token>
Content-Type: application/json

{
  "gateway": "wowpay",
  "merchant_order_no": "ORD20260623001",
  "amount": 50000,
  "pay_type": "momo",
  "user_id": "user123",
  "return_url": "https://shop.example.com/return"
}

Response thành công:

{
  "success": true,
  "data": {
    "gateway": "wowpay",
    "merchant_order_no": "ORD20260623001",
    "gateway_order_id": "2301051608200009",
    "pay_url": "https://pay.cashier.com/cashier/?token=...",
    "qr_code": "2|99|0936434203|PHAM VAN SINH||0|0|50000|9W6T7|transfer_myqr",
    "amount": 50000,
    "channel": "momo"
  }
}

Payin — Quét mã QR ngân hàng (bankcard_qr)

bank_code là field cấp cao nhất, bắt buộc khi pay_type là bankcard hoặc bankcard_qr:

{
  "gateway": "wowpay",
  "merchant_order_no": "ORD20260623002",
  "amount": 200000,
  "pay_type": "bankcard_qr",
  "bank_code": "VCB",
  "user_id": "user123"
}
Thiếu bank_code ở request trên sẽ bị WowPay trả 1010 Bank Code Missing. Xem danh sách mã đầy đủ.

Payout

POST /api/v1/payments/payouts/create.php
Authorization: Bearer <token>
Content-Type: application/json

{
  "gateway": "wowpay",
  "merchant_order_no": "POUT20260623001",
  "amount": 300000,
  "payout_name": "NGUYEN VAN A",
  "payout_bank_code": "VCB",
  "payout_account_no": "0123456789",
  "user_id": "user123"
}

Cấu hình SharkPay Dashboard

Thiết lập tại app.apinganhang.net/integration/sharkpay (yêu cầu gói VIP).

Trường trên APInganhangTrên SharkPay / APIGhi chú
Bật cổng—Bật để cho phép tạo đơn/payout qua gateway=sharkpay.
Domain APIHost API SharkPayVí dụ https://p100-gw.vippp.top. Hub tự ghép /api/pay/create_order, /api/pay/query_order, /api/agentpay/apply, /api/agentpay/query_order, /api/agentpay/query_balance, /api/settlement/create_order, /api/settlement/query_order.
Merchant IDmchIdID merchant (số) do SharkPay cấp.
API Key (Payin + Payout)key (tham gia ký MD5)SharkPay cho dùng chung 1 key cho payin, payout (Agent Pay) và Settlement.
Application IDkhông dùng cho SharkPayTrường này chỉ hiển thị chung trên form; không map vào field nào của API SharkPay.
Product IDproductIdBắt buộc khi tạo đơn payin (vd: 8036) — thiếu sẽ bị Hub chặn trước khi gọi SharkPay (VALIDATION_FAILED).
URL callback HubnotifyUrlhttps://client.apinganhang.net/api/v1/payments/callbacks/sharkpay.php — Hub tự gửi field này ở mỗi request tạo đơn/payout/settlement.
Return URLkhông có ở SharkPaySharkPay không hỗ trợ tham số redirect riêng cho payin (khác WowPay/EeziePay).
IP whitelist—IP server website đối tác được phép gọi API Hub. Để trống = không giới hạn.
Callback Source IPs—IP của SharkPay được phép gọi callback vào Hub. Để trống nếu chưa có IP chính xác từ SharkPay — IP sai/cũ sẽ làm Hub tự 403 mọi callback thật (đã từng xảy ra thật, xem ghi chú dưới).
Lưu ý IP whitelist callback: chữ ký MD5 đã là lớp bảo mật chính cho callback (xác thực mật mã, không phải IP). Chỉ điền Callback Source IPs khi đã xác nhận IP đó từ SharkPay support — điền sai sẽ chặn nhầm toàn bộ callback thật mà không có dấu hiệu rõ ràng (HTTP 403, đối tác báo "không tự xác nhận").

SharkPay Payin API Tóm tắt

Gọi qua Hub: POST /api/v1/payments/orders/create.php với "gateway": "sharkpay".

Field (gửi cho Hub)Bắt buộcMap sang API SharkPay
merchant_order_no✓mchOrderNo — phải duy nhất
amount (VNĐ)✓amount = amount × 100 (cent)
subject / title / descriptionoptionalsubject — Hub tự sinh "Pay {mã đơn}" nếu bỏ trống
body / descriptionoptionalbody — Hub tự dùng lại subject nếu bỏ trống
return_urloptionalreturnUrl
device_ip / client_iptự độngclientIp — Hub tự lấy IP request nếu không truyền

Response data từ Hub: gateway_order_id (payOrderId), pay_url (payUrl/codeUrl), qr_code, amount.

Ví dụ

POST /api/v1/payments/orders/create.php
Authorization: Bearer <token>
Content-Type: application/json

{
  "gateway": "sharkpay",
  "merchant_order_no": "ORD20260629001",
  "amount": 100000,
  "subject": "Nap tien tai khoan"
}

SharkPay Payout API Tóm tắt — Agent Pay

Gọi qua Hub: POST /api/v1/payments/payouts/create.php với "gateway": "sharkpay" (mặc định dùng Agent Pay — xem Settlement nếu cần hệ thống rút tiền thứ 2).

Field (gửi cho Hub)Bắt buộcMap sang API SharkPay
merchant_order_no✓mchOrderNo
amount (VNĐ)✓amount = amount × 100 (cent)
account_name / payout_name✓accountName — tên người nhận
account_no / bank_number / payout_account_no✓accountNo
bank_name✓bankName — tên ngân hàng dạng chữ thường (vd: "Vietcombank"), không phải mã enum
bank_net_nameoptionalbankNetName — tên chi nhánh
bank_code / payout_bank_codeoptionalbankCode
SharkPay Agent Pay không dùng danh sách bank_code cố định như WowPay — bankName là tên tự do, SharkPay tự nhận diện ngân hàng phía họ.

SharkPay — Settlement Hệ thống rút tiền thứ 2

SharkPay có 2 hệ thống rút tiền độc lập: Agent Pay (/api/agentpay/*, mặc định) và Settlement (/api/settlement/*, balance/field/status khác hẳn). Chọn bằng field payout_method khi gọi POST /api/v1/payments/payouts/create.php.

payout_methodAPI SharkPayKhi dùng
(bỏ trống) hoặc agentpay/api/agentpay/applyMặc định — hành vi cũ, không đổi gì
settlement (hoặc sett)/api/settlement/create_orderKhi cần dùng quỹ Settlement riêng (balance khác Agent Pay)
Khác biệt quan trọng so với Agent Pay:
  • bank_net_name (tên chi nhánh/phòng giao dịch) là bắt buộc cho Settlement (Agent Pay thì optional).
  • Mã đơn platform trả về tên field settOrderId lúc tạo đơn, nhưng lại là settleOrderId lúc query/callback — đúng theo tài liệu gốc SharkPay, không phải lỗi. Hub đã xử lý cả 2 tên field này khi đọc response.
  • Mã status Settlement: 1-chờ duyệt, 2-đã duyệt, 3-từ chối, 4-đang chuyển, 5-thành công, 6-thất bại (khác hẳn Agent Pay: 0-chờ xử lý, 1-đang xử lý, 2-thành công, 3-thất bại).

Ví dụ tạo lệnh Settlement

POST /api/v1/payments/payouts/create.php
Authorization: Bearer <token>
Content-Type: application/json

{
  "gateway": "sharkpay",
  "payout_method": "settlement",
  "merchant_order_no": "SETT20260629001",
  "amount": 300000,
  "account_name": "NGUYEN VAN A",
  "account_no": "0123456789",
  "bank_name": "Vietcombank",
  "bank_net_name": "CN Ha Noi",
  "user_id": "user123"
}

Tra cứu lại lệnh Settlement (truyền lại đúng payout_method=settlement, không cần biết settleOrderId — Hub tự tra theo merchant_order_no đã lưu):

POST /api/v1/payments/payouts/query.php
Authorization: Bearer <token>
Content-Type: application/json

{
  "gateway": "sharkpay",
  "payout_method": "settlement",
  "merchant_order_no": "SETT20260629001"
}

So sánh cổng

SharkPayWowPayEeziePay1DPay
PayinJSON/form APIJSON APIForm → fundtransfer.phpJSON API — QR ngân hàng (chi tiết)
SignMD5 uppercaseMD5 uppercaseSHA1 uppercaseAES-256-CBC + RSA-SHA1 (Hub tự xử lý)
Callback acktext successtext successXML <status>OK</status>text success
Balance—1 số dưFund Transfer + Fund Out1 số dư (available/current)
Amount VNDfen (×100)cent (×100)integer, không lẻinteger VND (không ×100)

Cấu hình EeziePay Dashboard

Thiết lập tại app.apinganhang.net/integration/eeziepay (yêu cầu gói VIP). Các trường trên form khớp với cấu hình merchant EeziePay như sau:

Trường trên APInganhangTrên EeziePay / APIGhi chú
Bật cổng—Bật để cho phép tạo đơn/payout qua gateway=eeziepay.
Domain APIGateway base URLURL gốc do EeziePay cấp, ví dụ https://gogomart168com.com. Hub ghép với các đường dẫn API bên dưới.
Fund Transfer / Query / Payout / Balance APIEndpoint pathsCopy từ merchant panel: fundtransfer.php, fundtransfer_enquiry.php, withdrawal.php, withdrawal_enquiry.php, querybalance.php.
Merchant IDpartner_codeMã đối tác / merchant trên EeziePay.
Encryption KeyEncryption / signing keyDùng chung cho payin + payout. Ký SHA1 uppercase theo thứ tự field trong tài liệu EeziePay.
URL callback Hubbackend_url (deposit), notify_url (payout)https://client.apinganhang.net/api/v1/payments/callbacks/eeziepay.php — Hub tự thêm ?type=deposit hoặc ?type=payout khi tạo lệnh.
Return URLredirect_urlURL chuyển khách về sau khi thanh toán (có thể ghi đè khi tạo đơn).
IP whitelist—IP server website của bạn được phép gọi API Hub (create order/payout). Để trống = không giới hạn.
Notify forward URL / secret—Sau khi Hub nhận callback từ EeziePay, forward JSON về website kèm header X-Payment-Signature: sha256=....

Quy trình cấu hình (khuyến nghị)

  1. Lấy Domain API, Merchant ID, Encryption Key và 5 đường dẫn API từ merchant EeziePay.
  2. Điền các trường trên vào tab EeziePay, bật cổng, nhấn Lưu.
  3. Copy URL callback Hub dán vào merchant EeziePay (nếu bảng điều khiển yêu cầu đăng ký URL cố định).
  4. Nhấn Kiểm tra kết nối — gọi balance inquiry qua Hub (endpoint cấu hình querybalance.php hoặc tương đương).
  5. Nhấn Cập nhật số dư — xem Fund Transfer (nạp) và Fund Out (chi hộ).
  6. Thêm Website Integration phía dưới nếu nhiều site; mỗi site có website_key + website_secret riêng.

Balance inquiry

EeziePay có API balance (thường POST {base_url}/querybalance.php hoặc balance.php tùy merchant). Hub bọc qua:

POST /api/v1/payments/balance.php
Authorization: Bearer <token>
Content-Type: application/json

{
  "gateway": "eeziepay",
  "currency": "VND"
}

Response mẫu:

{
  "success": true,
  "data": {
    "currency": "VND",
    "items": [
      { "type": "Fund Transfer", "available": "1000000", "current": "1000000" },
      { "type": "Fund Out", "available": "500000", "current": "500000" }
    ]
  }
}

Fund Transfer = số dư dùng nạp tiền; Fund Out = số dư chi hộ. Hỗ trợ VND và IDR.

EeziePay Deposit API Tóm tắt

Form POST tới {Domain API}/fundtransfer.php. Ký SHA1 uppercase theo thứ tự field:

service_version, partner_code, partner_orderid, member_id, member_ip,
currency, amount, backend_url, redirect_url, bank_code, trans_time

bank_code quyết định kênh online banking hay QR (xem bảng ở trên). Bỏ trống nếu để EeziePay hiển thị trang chọn ngân hàng.

Hub trả form_action + form_fields + pay_channel khi gọi POST /api/v1/payments/orders/create.php với gateway=eeziepay.

Tài liệu gốc: EeziePay Deposit EN

Ví dụ tạo đơn EeziePay

Online banking (Vietcombank)

POST /api/v1/payments/orders/create.php
Authorization: Bearer <token>
Content-Type: application/json

{
  "gateway": "eeziepay",
  "website_key": "wk_xxx",
  "website_secret": "ws_xxx",
  "merchant_order_no": "ORD20260614001",
  "amount": 100000,
  "currency": "VND",
  "bank_code": "VCB.VN",
  "member_id": "user123",
  "member_ip": "1.2.3.4",
  "return_url": "https://shop.example.com/return"
}

QR chuyển khoản (Vietcombank QR)

{
  "gateway": "eeziepay",
  "merchant_order_no": "ORD20260614002",
  "amount": 200000,
  "currency": "VND",
  "pay_type": "qr",
  "bank": "VCB",
  "return_url": "https://shop.example.com/return",
  "member_id": "user123",
  "member_ip": "1.2.3.4"
}

Response thành công:

{
  "success": true,
  "data": {
    "form_action": "https://gogomart168com.com/fundtransfer.php",
    "form_fields": { "...": "...", "bank_code": "VCB.QR.VN" },
    "pay_channel": "qr",
    "bank_code": "VCB.QR.VN"
  }
}

Site render form:

<form method="POST" action="{form_action}" target="_blank">
  <!-- hidden inputs từ form_fields -->
  <button type="submit">Thanh toán</button>
</form>

1DPay — Tổng quan gateway=1dpay

1DPay là cổng nạp QR ngân hàng (VietQR) + chi hộ (rút tiền) VND. Mã cổng khi gọi Hub: "gateway": "1dpay". Toàn bộ phần mã hoá/ký riêng của 1DPay do Hub tự xử lý — website đối tác không cần mã hoá gì thêm, chỉ gọi Hub như các cổng khác: header Authorization: Bearer <token> + website_key + sign (HMAC-SHA256 bằng website_secret, xem Xác thực đối tác).

Chức năngEndpoint HubGhi chú
Tạo đơn nạp (payin)POST orders/create.phpTrả QR (chuỗi + ảnh), STK, tên chủ TK, nội dung CK, link trang thanh toán
Tra cứu đơn nạpPOST orders/query.phpTrả state = success | failed | processing
Tạo lệnh rút (payout)POST payouts/create.php500.000 – 200.000.000 VND / lệnh
Tra cứu lệnh rútPOST payouts/query.phpGiống tra cứu đơn nạp
Kết quả giao dịchWebhook Hub → notify_forward_urlJSON + header X-Payment-Signature (xem bên dưới)
Bắt buộc: mỗi website dùng 1 website_key RIÊNG cho 1DPay. Hub gửi webhook kết quả về notify_forward_url + ký bằng notify_forward_secret của chính website (website_key) đã tạo đơn. Vì vậy hãy đăng ký 1 website riêng cho 1DPay tại app.apinganhang.net → Cổng thanh toán → Website đã đăng ký (VD tên TenSite-1dpay, notify_forward_url trỏ tới endpoint nhận callback 1DPay của site), rồi dùng bộ website_key / website_secret / notify_forward_secret đó cho mọi request gateway=1dpay. Không dùng chung khoá với website của WowPay/SharkPay/EeziePay — nếu dùng chung, webhook 1DPay sẽ bị gửi về URL của cổng khác và site không xác nhận được giao dịch.

1DPay Payin — Tạo đơn nạp QR ngân hàng

Gọi qua Hub: POST /api/v1/payments/orders/create.php với "gateway": "1dpay".

Field (gửi cho Hub)Bắt buộcÝ nghĩa
gateway✓1dpay
website_key + sign✓Khoá website riêng cho 1DPay + chữ ký HMAC (xem Xác thực)
merchant_order_no✓Mã đơn của site, duy nhất
amount (VNĐ)✓Số nguyên > 0
channel (hoặc pay_type / payment_method)khuyến nghịQR — nạp bằng quét mã QR ngân hàng (VietQR). Bỏ trống = QR. Xem bảng kênh bên dưới.
member_id (hoặc user_id / customer_id)khuyến nghịID/username khách nạp phía site — 1DPay dùng để định danh khách. Bỏ trống = guest.
device_ip / client_ipkhuyến nghịIP của khách nạp. Bỏ trống = Hub dùng IP server gọi API.
return_urloptionalURL đưa khách quay về sau khi thanh toán trên trang 1DPay (pay_url).

Kênh nạp (channel)

channel gửi HubKênh 1DPayGhi chú
QR (hoặc bỏ trống, BANK_QR, VIETQR, BANKCARD_QR)BQR — QR ngân hàngKênh đang dùng thực tế, đã chạy thật end-to-end. Khuyến nghị dùng kênh này.
BANK_TRANSFERBTR — chuyển khoảnChỉ dùng khi 1DPay đã mở kênh này cho merchant — hỏi quản trị Hub trước.
BANK / ONLINE_BANKINGBMR — online bankingBắt buộc thêm bank_code (ngân hàng khách dùng). Chỉ dùng khi đã được mở kênh.

Hub không áp hạn mức nạp riêng cho 1DPay; nếu số tiền nằm ngoài hạn mức 1DPay áp cho merchant, 1DPay từ chối và Hub trả lỗi GATEWAY_FAILED kèm mã lỗi của cổng (xem Mã lỗi). Hạn mức hiển thị cho khách nên cấu hình ở phía site.

Ví dụ request

POST https://client.apinganhang.net/api/v1/payments/orders/create.php
Authorization: Bearer <token>
Content-Type: application/json

{
  "gateway": "1dpay",
  "website_key": "ws_live_xxxxxxxxxxxxxxxx",
  "merchant_order_no": "ORD20261002001",
  "amount": 100000,
  "channel": "QR",
  "member_id": "user123",
  "device_ip": "113.161.1.2",
  "return_url": "https://yourdomain.com/profile?page=transaction-history",
  "sign": "<hmac_sha256>"
}

Chuỗi ký (canonical, sort key ASCII, nối bằng |):

amount=100000|channel=QR|device_ip=113.161.1.2|gateway=1dpay|member_id=user123|merchant_order_no=ORD20261002001|return_url=https://yourdomain.com/profile?page=transaction-history|website_key=ws_live_xxxxxxxxxxxxxxxx

Response thành công

{
  "success": true,
  "message": "Order created",
  "data": {
    "gateway": "1dpay",
    "merchant_order_no": "ORD20261002001",
    "gateway_order_id": "D2026100110148101",
    "pay_url": "https://cashier.1dpayapp.com/Deposit/VietQRDeposit/?a=...",
    "qr_code": "00020101021138520010A000000727...6304ABCD",
    "qr_image_base64": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ...",
    "bank_code": "EXB",
    "account_number": "1234567890",
    "account_name": "NGUYEN VAN B",
    "transfer_content": "KHOAN3JZ2TU",
    "amount": 100000,
    "actual_amount": 100000,
    "channel": "BQR",
    "raw": { "...": "phản hồi gốc của 1DPay (tham khảo, không cần dùng)" }
  }
}
FieldCách dùng
gateway_order_idMã giao dịch phía 1DPay (đơn nạp bắt đầu bằng D). Lưu lại để đối soát.
qr_image_base64Ảnh QR đã kèm tiền tố data URI (data:image/...;base64,...) → gán thẳng vào <img src>, không cần thêm tiền tố.
qr_codeChuỗi VietQR (EMVCo) — dùng nếu site muốn tự vẽ QR.
bank_code, account_number, account_nameNgân hàng / số tài khoản / tên chủ tài khoản nhận tiền — hiển thị cho khách chuyển khoản thủ công.
transfer_contentNội dung chuyển khoản bắt buộc ghi đúng — 1DPay dùng để khớp lệnh. Nhắc khách ghi đúng nội dung và đúng số tiền.
pay_urlTrang thanh toán của 1DPay (hiện QR + thông tin CK) — có thể mở tab mới thay vì tự hiển thị.
actual_amountSố tiền khách cần chuyển theo 1DPay (thường bằng amount).
Lưu ý: mỗi đơn nạp gắn với một bộ thông tin CK/QR riêng — luôn hiển thị đúng thông tin của đơn vừa tạo, không tái sử dụng QR/nội dung CK của đơn cũ. Khi khách chuyển khoản xong, kết quả đến qua webhook; site có thể chủ động tra cứu nếu chưa nhận được.

1DPay Payout — Tạo lệnh rút 500.000 – 200.000.000 VND

Gọi qua Hub: POST /api/v1/payments/payouts/create.php với "gateway": "1dpay".

Field (gửi cho Hub)Bắt buộcÝ nghĩa
gateway✓1dpay
website_key + sign✓Khoá website riêng cho 1DPay + chữ ký HMAC
merchant_order_no✓Mã lệnh rút duy nhất — Hub dùng để chống chi 2 lần
amount (VNĐ)✓Số nguyên, từ 500.000 đến 200.000.000
bank_code (hoặc payout_bank_code)✓Mã ngân hàng nhận — xem danh sách. Hub tự chuẩn hoá hoa/thường và alias phổ biến.
account_number (hoặc account_no / bank_number / payout_account_no)✓Số tài khoản người nhận
account_name (hoặc payout_name)✓Tên chủ tài khoản người nhận
member_id (hoặc user_id / customer_id)khuyến nghịID khách rút phía site. Bỏ trống = guest.
device_ip / client_ipoptionalIP khách. Bỏ trống = IP server gọi API.
Hạn mức: số tiền ngoài khoảng 500.000 – 200.000.000 VND bị Hub chặn ngay (không gọi sang cổng, không giữ merchant_order_no): HTTP 422
{
  "success": false,
  "message": "Số tiền rút qua 1dpay phải từ 500.000 đến 200.000.000 VND.",
  "data": { "code": "AMOUNT_OUT_OF_RANGE", "min": 500000, "max": 200000000 }
}
Lệnh rút lớn hơn 200.000.000 VND cần tách thành nhiều lệnh (mỗi lệnh 1 merchant_order_no riêng).

Ví dụ request

POST https://client.apinganhang.net/api/v1/payments/payouts/create.php
Authorization: Bearer <token>
Content-Type: application/json

{
  "gateway": "1dpay",
  "website_key": "ws_live_xxxxxxxxxxxxxxxx",
  "merchant_order_no": "WD20261002001",
  "amount": 1500000,
  "bank_code": "VCB",
  "account_number": "0123456789",
  "account_name": "NGUYEN VAN A",
  "member_id": "user123",
  "sign": "<hmac_sha256>"
}

Response thành công (đã gửi lệnh sang 1DPay)

{
  "success": true,
  "message": "Payout submitted",
  "data": {
    "gateway": "1dpay",
    "merchant_order_no": "WD20261002001",
    "gateway_order_id": "W2026100200000001",
    "bank_code": "VCB",
    "raw": { "...": "phản hồi gốc của 1DPay" }
  }
}

success: true ở bước này chỉ có nghĩa là 1DPay đã nhận lệnh — tiền chưa chắc đã chuyển. Kết quả cuối cùng (thành công / thất bại) đến qua webhook hoặc tra cứu.

Chống chi 2 lần:
  • Gọi lại cùng merchant_order_no khi lệnh trước đang xử lý/đã thành công → HTTP 409 DUPLICATE_ORDER, Hub không gửi tiền lần 2.
  • Nếu 1DPay không phản hồi rõ ràng (timeout/lỗi 5xx) → Hub trả GATEWAY_TIMEOUT (HTTP 502) và giữ khoá merchant_order_no, vì lệnh có thể đã được 1DPay nhận. Không tạo lệnh mới cho cùng yêu cầu rút — hãy tra cứu hoặc chờ webhook.
  • Nếu 1DPay từ chối rõ ràng (GATEWAY_FAILED) hoặc lỗi validate → chưa có tiền nào được gửi, có thể gọi lại cùng merchant_order_no sau khi sửa dữ liệu.

Danh sách bank_code 1DPay Payout

Mã ngân hàng nhận tiền khi rút qua 1DPay (khác một số mã của WowPay). Hub nhận cả mã 1DPay lẫn alias thông dụng / mã WowPay và tự chuyển đổi, ví dụ: VIETCOMBANK→VCB, TCB/TECHCOMBANK→TEC, VTB/VIETINBANK/CTG→VTN, MB/MBBANK→MBB, AGB/AGRIBANK→AGR, SAC/STB/SACOMBANK→SAB, EXIM/EIB→EXB, BACA→BAB, NAMA→NAB, SEA/SEABANK→SEAB, OCEAN→OB, CIMB→CIMBV, WOORI→WOO, SHINHAN→SHBVN, VIETCAPITALBANK/BVBANK→Viet Capital Bank. Mã không nhận diện được → HTTP 422 VALIDATION_FAILED (chưa gọi sang cổng).

ABB  ACB  ACBCORP  AGR  BAB  BIDV  BIDVCORP  BVB  CAKE  CIMBV  DAB  EXB  EXBCORP  GPB  HDB  HLB
HSBCVN  IVB  KLB  LIO  LPB  LPBANK  MBB  MBBCORP  MSB  MSBCORP  NAB  NCB  OB  OCB  OCBCORP  PBVN
PGB  PVCOM  SAB  SABCORP  SAIGON  SCB  SCBV  SEAB  SHB  SHBCORP  SHBVN  TCBCORP  TEC  TPB  TPBCORP
VAB  VBSP  VCB  VCBCORP  VIB  "Viet Capital Bank"  VIETB  VIKKI  VPB  VPBCORP  VRB  VTN  VTNCORP  WOO

Mã có hậu tố CORP là tài khoản doanh nghiệp của ngân hàng tương ứng.

1DPay — Tra cứu đơn nạp / lệnh rút

POST https://client.apinganhang.net/api/v1/payments/orders/query.php    (đơn nạp)
POST https://client.apinganhang.net/api/v1/payments/payouts/query.php   (lệnh rút)
Authorization: Bearer <token>
Content-Type: application/json

{
  "gateway": "1dpay",
  "website_key": "ws_live_xxxxxxxxxxxxxxxx",
  "merchant_order_no": "ORD20261002001",
  "sign": "<hmac_sha256>"
}

Response data gồm chi tiết giao dịch từ 1DPay, cộng thêm các field chuẩn hoá của Hub:

{
  "success": true,
  "message": "Queried",
  "data": {
    "...": "các field chi tiết của 1DPay (statusId, amount, ...)",
    "gateway": "1dpay",
    "merchant_order_no": "ORD20261002001",
    "gateway_order_id": "D2026100110148101",
    "gateway_status": "6",
    "state": "success"
  }
}

Bảng trạng thái

LoạiMã 1DPay (gateway_status / status)state (Hub chuẩn hoá)Site nên làm
Nạp (payin)6successCộng tiền (1 lần duy nhất)
7failedĐánh dấu thất bại
mã khác (VD 4)processingChờ tiếp — chưa phải thất bại
Rút (payout)4successHoàn tất lệnh rút
5failedLệnh rút thất bại → xử lý hoàn tiền cho khách phía site
mã khácprocessingChờ tiếp

Lưu ý: mã thành công của nạp (6) và rút (4) khác nhau — nên dựa vào field state (hoặc success trong webhook) thay vì tự so mã số. Tra cứu đơn không tồn tại → NOT_FOUND.

1DPay — Webhook kết quả (Hub → site) X-Payment-Signature

Khi 1DPay báo kết quả, Hub xác thực callback của cổng, cập nhật đơn rồi POST JSON tới notify_forward_url của website đã tạo đơn, kèm header:

Content-Type: application/json
X-Payment-Signature: sha256=<HMAC-SHA256(raw JSON body, notify_forward_secret)>
User-Agent: Apinganhang-PaymentHub/1.0

Payload mẫu — nạp thành công

{
  "event": "payment.1dpay.callback",
  "gateway": "1dpay",
  "timestamp": 1790856752,
  "data": {
    "CurrencyCode": "VND",
    "MerchantTransactionId": "ORD20261002001",
    "Status": 6,
    "Amount": 100000,
    "NetAmount": 99400,
    "PaymentMethodCode": "BQR",
    "UniqueReferenceNo": "KHOAN3JZ2TU",
    "BankReferenceNo": null,
    "Remark": null,
    "merchant_order_no": "ORD20261002001",
    "order_id": "D2026100110148101",
    "order_type": "payin",
    "status": "6",
    "state": "success",
    "success": true,
    "amount": 100000,
    "gateway": "1dpay"
  }
}
Field trong dataÝ nghĩa
merchant_order_noMã đơn/lệnh của site
order_typepayin (nạp) hoặc payout (rút)
successtrue = giao dịch thành công. Chỉ cộng tiền / hoàn tất lệnh rút khi success === true
state / statusTrạng thái chuẩn hoá / mã 1DPay — xem bảng trạng thái
amountSố tiền của đơn gốc khi tạo (Hub không lấy theo số tiền trong callback cổng) — dùng giá trị này để cộng tiền
NetAmountSố tiền thực nhận sau phí cổng (tham khảo đối soát)
order_idMã giao dịch 1DPay (D… nạp, W… rút)
Các field viết hoa (Status, Amount, UniqueReferenceNo...)Dữ liệu gốc 1DPay gửi — chỉ để tham khảo/đối soát; có thể có thêm field khác

Xác thực chữ ký (giống mọi cổng khác của Hub)

// PHP — ký trên RAW body, không decode rồi encode lại
$raw = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $raw, 'your_1dpay_notify_forward_secret');
if (!hash_equals($expected, $_SERVER['HTTP_X_PAYMENT_SIGNATURE'] ?? '')) {
    http_response_code(403); exit('bad signature');
}
$p = json_decode($raw, true);
$d = $p['data'] ?? [];
if (($p['gateway'] ?? '') !== '1dpay') { http_response_code(400); exit; }
if (($d['success'] ?? false) === true) {
    // tìm đơn theo $d['merchant_order_no'] + $d['order_type'];
    // nếu đơn chưa xử lý -> cộng $d['amount'] (nạp) / hoàn tất (rút); đã xử lý -> bỏ qua
} elseif (($d['state'] ?? '') === 'failed') {
    // đánh dấu thất bại (rút: hoàn tiền cho khách phía site)
}
http_response_code(200); echo 'OK';
// Node.js
const crypto = require('crypto');
const expected = 'sha256=' + crypto.createHmac('sha256', NOTIFY_FORWARD_SECRET).update(rawBody).digest('hex');
const ok = expected.length === sigHeader.length &&
           crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sigHeader));

1DPay — Mã lỗi thường gặp

HTTPdata.codeNguyên nhânXử lý
401WEBSITE_AUTH_FAILED / UnauthorizedThiếu/sai Bearer token, website_key hoặc chữ kýXem Xác thực đối tác
422VALIDATION_FAILEDThiếu merchant_order_no, amount ≤ 0, payout thiếu STK/tên, bank_code không hợp lệSửa dữ liệu rồi gửi lại
422AMOUNT_OUT_OF_RANGEPayout ngoài 500.000 – 200.000.000 VNDĐiều chỉnh/tách số tiền
422—"Gateway đang tắt hoặc chưa cấu hình đủ"Liên hệ quản trị Hub bật 1DPay cho tài khoản
200GATEWAY_FAILED (success:false)1DPay từ chối — message chứa mã lỗi cổng (VD PMT10005 Invalid Transaction Amount)Kiểm tra success, không chỉ HTTP status
502GATEWAY_TIMEOUTPayout: 1DPay không phản hồi rõ ràngKhông tạo lệnh mới; tra cứu / chờ webhook
409DUPLICATE_ORDERPayout trùng merchant_order_noTra cứu lệnh cũ

Tham khảo