Appearance
SDK Collection Link V2 — Node.js/TypeScript và PHP
SDK 0.1.0 đóng gói tạo thanh toán V2 và kiểm tra IPN. Dùng trên backend của merchant, không dùng ở trình duyệt/mobile vì cần Secret Key. Bản này được phân phối bằng file tải, chưa publish lên npm hoặc Packagist.
1. Cài đặt
Node.js >=20
Tải file .tgz về dự án, sau đó:
bash
npm install ./pay2s-collection-link-sdk-0.1.0.tgzjs
const { Pay2S, Pay2SError } = require('@pay2s/collection-link-sdk');TypeScript/ESM:
ts
import { Pay2S, type PaymentInput } from '@pay2s/collection-link-sdk';PHP >=8.1
Cần extension curl, json, mbstring. Giải nén ZIP vào packages/pay2s-collection-link/ trong dự án (file composer.json của SDK nằm ngay trong thư mục này). Thêm vào composer.json của dự án, giữ lại cấu hình có sẵn:
json
{
"repositories": [
{"type":"path","url":"packages/pay2s-collection-link","options":{"symlink":false}}
]
}Sau đó chạy:
bash
composer require pay2s/collection-link-sdk:0.1.0php
require __DIR__ . '/vendor/autoload.php';
use Pay2S\CollectionLink\Pay2S;Nếu chưa dùng Composer, có thể require trực tiếp src/Pay2SError.php và src/Pay2S.php trong thư mục SDK.
2. Cấu hình
Đặt ở môi trường backend, không commit giá trị thật:
dotenv
PAY2S_PARTNER_CODE=
PAY2S_ACCESS_KEY=
PAY2S_SECRET_KEY=js
const pay2s = new Pay2S({
partnerCode: process.env.PAY2S_PARTNER_CODE,
accessKey: process.env.PAY2S_ACCESS_KEY,
secretKey: process.env.PAY2S_SECRET_KEY,
timeoutMs: 30000
});Endpoint được cố định là https://payment.pay2s.vn/v1/gateway/api/create. SDK không tự tạo credential, kết nối ngân hàng hoặc cấu hình HĐĐT trên Pay2S.
3. Tạo thanh toán
Lưu đơn và requestId ở database trước khi gọi. Lấy tiền và sản phẩm từ dữ liệu server, không tin tổng tiền do trình duyệt gửi lên.
js
const result = await pay2s.createPayment({
orderId: 'ORDER_20260918_001',
requestId: 'REQUEST_20260918_001',
orderInfo: 'TT ORDER_20260918_001',
amount: 110000,
bankAccounts: [{ account_number: 'YOUR_ACCOUNT_NUMBER', bank_id: 'ACB' }],
redirectUrl: 'https://shop.example/payment/return',
ipnUrl: 'https://shop.example/api/pay2s/ipn',
metadata: {
invoiceType: 'vat',
customerInfo: { buyerContactName: 'Bán cho người tiêu dùng' },
items: [{ itemName: 'Sản phẩm mẫu', quantity: 1, unitPrice: 100000, taxRate: 10 }],
invoiceOptions: { requested: true, buyerNotTakingInvoice: true, source: 'api' }
}
});
// Lưu result.orderInfo (mã P2S... thực tế), result.payUrl vào đơn ở database.
// Sau khi lưu xong mới trả result.payUrl cho trình duyệt để khách thanh toán.metadata là object JSON, chưa Base64. SDK tạo extraData, cố định signatureVersion: "2", ký HMAC đúng thứ tự và gửi request. Dùng itemName, không dùng productName. Toàn bộ metadata người mua/hàng hóa xem Collection Link V2.
SDK yêu cầu orderId, requestId, orderInfo do website cung cấp; không tự sinh để tránh đổi mã khi gọi lại. amount là số hoặc chuỗi thập phân dương, tối đa 10 tỷ VND, tối đa 2 chữ số thập phân. Số tài khoản là chuỗi. quantity, unitPrice, taxRate là số JSON. Metadata tối đa 50 dòng, Base64 tối đa 32 KiB.
SDK kiểm tra định dạng cơ bản nhưng không tự tính tổng hoặc suy đoán loại mẫu/thuế. Merchant phải gửi tổng thanh toán khớp dữ liệu đơn và hàng hóa, đặc biệt khi có giảm thuế trực tiếp.
PHP tương đương
php
$pay2s = new Pay2S([
'partnerCode' => getenv('PAY2S_PARTNER_CODE'),
'accessKey' => getenv('PAY2S_ACCESS_KEY'),
'secretKey' => getenv('PAY2S_SECRET_KEY'),
]);
$result = $pay2s->createPayment([
'orderId' => 'ORDER_20260918_001',
'requestId' => 'REQUEST_20260918_001',
'orderInfo' => 'TT ORDER_20260918_001',
'amount' => 110000,
'bankAccounts' => [['account_number' => 'YOUR_ACCOUNT_NUMBER', 'bank_id' => 'ACB']],
'redirectUrl' => 'https://shop.example/payment/return',
'ipnUrl' => 'https://shop.example/api/pay2s/ipn',
'metadata' => [
'invoiceType' => 'vat',
'customerInfo' => ['buyerContactName' => 'Bán cho người tiêu dùng'],
'items' => [['itemName' => 'Sản phẩm mẫu', 'quantity' => 1, 'unitPrice' => 100000, 'taxRate' => 10]],
'invoiceOptions' => ['requested' => true, 'buyerNotTakingInvoice' => true, 'source' => 'api'],
],
]);
// Lưu $result['orderInfo'], $result['payUrl'] vào đơn trước khi trả URL cho khách.Muốn tự dùng HTTP client sẵn có: gọi buildPayment(input) để lấy payload đã ký, rồi POST JSON nguyên payload tới endpoint. Không sửa trường đã ký và không ký lại theo công thức V1.
4. Nhận IPN và xử lý đơn
verifyIpn(data) trả boolean, chỉ kiểm tra chữ ký m2signature và danh tính bộ khóa. assertPaidIpn(data, expected) kiểm tra thêm resultCode = 0, mã đơn, số tiền; kiểm tra orderInfo nếu truyền giá trị đã lưu từ response.
js
if (!pay2s.verifyIpn(req.body)) {
return res.status(401).json({ success: false });
}
// Tìm đơn trong database theo req.body.orderId, giới hạn đúng tài khoản merchant.
// expected phải lấy từ database, KHÔNG copy số tiền từ chính req.body.
pay2s.assertPaidIpn(req.body, {
orderId: storedOrder.orderId,
amount: storedOrder.amount,
orderInfo: storedOrder.pay2sOrderInfo
});PHP:
php
$ipn = json_decode(file_get_contents('php://input'), true, 512, JSON_THROW_ON_ERROR);
if (!is_array($ipn) || !$pay2s->verifyIpn($ipn)) {
http_response_code(401);
header('Content-Type: application/json');
echo json_encode(['success' => false]);
exit;
}
// $storedOrder lấy từ database và khóa trong transaction ở bước xử lý bên dưới.
$pay2s->assertPaidIpn($ipn, [
'orderId' => $storedOrder['order_id'],
'amount' => $storedOrder['amount'],
'orderInfo' => $storedOrder['pay2s_order_info'],
]);Các đoạn trên minh họa gọi SDK; req/res, storedOrder và transaction thuộc ứng dụng của bạn. Khi triển khai endpoint IPN, phải có đầy đủ luồng:
- Parse JSON có giới hạn dung lượng; dữ liệu sai trả 400, chữ ký sai trả 401.
- Xác thực chữ ký trước khi xử lý dữ liệu thanh toán.
- Bắt đầu transaction, đọc và khóa đơn theo
orderIdđúng tài khoản merchant. - Đối chiếu số tiền và
orderInfođã lưu bằngassertPaidIpn. - Nếu đơn đã paid: không cộng tiền/giao hàng lần hai. Nếu chưa: cập nhật trạng thái paid cùng mã giao dịch và ghi tác vụ giao hàng vào outbox trong cùng transaction.
- Commit rồi trả HTTP 200,
{"success":true}cho cả lần đầu và IPN hợp lệ gửi lại. Nếu database lỗi, rollback và trả lỗi để Pay2S có thể gửi lại.
Nếu IPN có chữ ký hợp lệ nhưng resultCode khác 0, assertPaidIpn ném lỗi NOT_PAID; xử lý như thông báo không thành công, tuyệt đối không cộng tiền. Xử lý exception trong handler của bạn, không để lỗi SDK làm treo request.
SDK không tự lưu trạng thái hoặc chống replay bằng database. Hai IPN giống nhau đều có chữ ký hợp lệ. Việc khóa đơn và cập nhật có điều kiện là bắt buộc để tránh cộng tiền hai lần.
requestId trong IPN có thể là ID nội bộ Pay2S, không bắt buộc bằng requestId lúc tạo đơn. requestTrace có thể xuất hiện nhưng không nằm trong chuỗi ký, không dùng nó để xác nhận thanh toán. Luôn nhận diện đơn bằng orderId đã ký và đối chiếu tiền từ database.
Không dùng thông tin redirect trình duyệt để đánh dấu paid. Chữ ký redirect khác chữ ký IPN và sẽ không qua verifyIpn.
5. Lỗi và gọi lại
Node.js dùng error.code; PHP dùng $error->errorCode. Cả hai có httpStatus và resultCode khi có phản hồi API.
| Mã SDK | Xử lý |
|---|---|
INVALID_INPUT | Sửa dữ liệu trước khi gọi; request chưa được gửi |
TRANSPORT_ERROR | Mạng/timeout; kết quả có thể chưa rõ, giữ orderId và đối soát |
INVALID_RESPONSE | Phản hồi không đúng JSON/cấu trúc; không kết luận đơn chưa tạo |
API_ERROR | Kiểm tra HTTP/resultCode; có thể là từ chối, rate limit hoặc lỗi hệ thống |
INVALID_IPN | Không xử lý thanh toán |
NOT_PAID | Không xác nhận thành công, không giao hàng/cộng tiền |
ORDER_MISMATCH | Đối soát mã đơn/số tiền/nội dung; không đánh dấu paid |
Không tự retry trong SDK. Nếu cần thử lại sau lỗi mạng, kiểm tra đơn/IPN trước và giữ mã đơn gốc; không tạo orderId mới chỉ vì timeout. Không đánh dấu thanh toán thất bại vĩnh viễn chỉ vì request tạo link bị timeout.
6. Kiểm thử và hướng dẫn cho AI tích hợp
Gói SDK có test fixture dùng khóa giả và transport mô phỏng. Node: node --test test.cjs; PHP: php tests.php trong thư mục gói. Test không gọi API thật.
Khi giao tài liệu cho AI coding assistant, yêu cầu:
Dùng SDK Collection Link V2 của Pay2S để tích hợp vào backend hiện tại. Đọc tài liệu SDK, Collection Link V2 và IPN. Tái sử dụng model đơn hàng, quản lý bí mật, transaction và queue sẵn có. Tạo endpoint thanh toán cùng endpoint IPN có kiểm tra chữ ký, đối chiếu dữ liệu từ database, khóa đơn và chống xử lý trùng. Không xác nhận thanh toán từ redirect. Không tự đổi orderId khi timeout. Viết test cho chữ ký sai, IPN gửi lại/đồng thời, sai tiền và database lỗi. Liệt kê cấu hình cần cung cấp, migration và file đã sửa.
Đây là SDK thanh toán kèm metadata HĐĐT. Chưa bao gồm API HĐĐT độc lập hoặc tra cứu MST. HĐĐT chỉ vào hàng chờ/tự động sau khi thanh toán theo cấu hình Pay2S; không tự gọi thêm API hóa đơn độc lập cho cùng đơn để tránh xuất trùng.
