pay2s-node
v0.3.0
Published
Node.js SDK for the Pay2S payment gateway, callbacks, IPN, and webhooks
Maintainers
Readme
pay2s-node
SDK Node.js cho Pay2S Payment API, Payment Notification/IPN và raw webhook.
Cài đặt
npm install pay2s-nodeYêu cầu Node.js >=18.18.0.
Khởi tạo client
import { Pay2sClient } from 'pay2s-node'
const client = new Pay2sClient({
environment: 'sandbox',
accessKey: process.env.PAY2S_ACCESS_KEY,
partnerCode: process.env.PAY2S_PARTNER_CODE,
secretKey: process.env.PAY2S_SECRET_KEY,
timeout: 10_000,
})environment tự chọn endpoint chính thức:
- Sandbox:
https://sandbox-payment.pay2s.vn - Production:
https://payment.pay2s.vn
Có thể dùng baseUrl và fetch để test hoặc chạy qua HTTP adapter riêng.
Tạo payment
Với flow thanh toán một lần thông thường, có thể dùng helper ngắn hơn. SDK tự điền các giá trị mặc định requestId=orderId, orderType=pay2s, requestType=pay2s và bankAccounts=[]:
const payment = await client.createOneTimePayment({
orderId: 'ORDER10001',
amount: 50000,
orderInfo: 'ThanhToan10001',
redirectUrl: 'https://merchant.example/payment/return',
ipnUrl: 'https://merchant.example/payment/ipn',
})
const paymentUrl = client.checkout.getPaymentUrl(payment)
// redirect người mua tới paymentUrlNếu account trả về tên field khác cho URL, helper vẫn nhận các dạng phổ biến paymentUrl, payment_url, payUrl và checkoutUrl. Dùng createPayment() bên dưới khi cần truyền toàn bộ field Pay2S và kiểm soát requestId riêng.
const payment = await client.createPayment({
partnerName: 'Demo Merchant',
requestId: 'REQ1000001',
amount: 50000,
orderId: 'ORDER10001',
orderInfo: 'ThanhToan10001',
orderType: 'pay2s',
redirectUrl: 'https://merchant.example/payment/return',
ipnUrl: 'https://merchant.example/payment/ipn',
requestType: 'pay2s',
bankAccounts: [],
extraData: '',
})SDK tự tạo signature theo canonical field order của Pay2S và giữ nguyên bankAccounts=Array khi ký. Nếu truyền signature, SDK sẽ kiểm tra chữ ký trước khi gửi request. SDK hiện validate orderInfo là 10-32 ký tự ASCII letters/digits; hãy đối chiếu constraint này với contract của account trước khi dùng dữ liệu production.
Client payment và QR
const clientPayment = await client.createClientPayment({
invoiceNumber: 'INV-100',
invoiceId: 39,
partnerCode: process.env.PAY2S_PARTNER_CODE,
accessKey: process.env.PAY2S_ACCESS_KEY,
orderId: 'ORDER10001',
orderInfo: 'ThanhToan10001',
amount: 50000,
accountNumber: '123456789',
ipnUrl: 'https://merchant.example/payment/ipn',
redirectUrl: 'https://merchant.example/payment/return',
requestId: 'REQ1000001',
requestType: 'pay2s',
signature: '<signature-from-pay2s-contract>',
})
const qr = await client.createQrCode({
bankShortName: 'ACB',
accountNumber: '123456789',
accountName: 'DEMO MERCHANT',
amount: 50000,
memo: 'TEST10001',
})Các method tương ứng với contract hiện có của Pay2S:
POST /v1/gateway/api/createPOST /api/create.phpPOST /v1/gateway/create_qr
Bank Codes
const banks = await client.listBankCodes()Method này gọi POST /v1/gateway/api/bankcodes. Nên cache có thời hạn và chọn bank code theo đúng environment/account.
OneQR
OneQR là API độc lập, dùng Basic Auth để lấy Bearer token rồi gọi các endpoint QR:
import { Pay2sOneQrClient } from 'pay2s-node'
const oneQr = new Pay2sOneQrClient({
accessKey: process.env.PAY2S_ACCESS_KEY,
secretKey: process.env.PAY2S_SECRET_KEY,
})
const qr = await oneQr.createQr('vcb', {
orderId: 'ORDER10001',
amount: 50000,
merchantName: 'Demo Merchant',
merchantId: 'MERCHANT_ID',
terminalId: 'TERMINAL_ID',
description: 'TEST PAYMENT',
})Các method còn có listQr, getQrTransaction và cancelQr. Host/path và quyền bank code phải đối chiếu contract account trước khi dùng Production.
Open Banking và Partner API
import { Pay2sOpenBankingClient, Pay2sPartnerClient } from 'pay2s-node'
const openBanking = new Pay2sOpenBankingClient({
pay2sToken: process.env.PAY2S_TOKEN,
})
const recipient = await openBanking.getRecipientName({ accountNumber: '123456789', bankCode: '970436' })
const partner = new Pay2sPartnerClient({
accessKey: process.env.PAY2S_ACCESS_KEY,
secretKey: process.env.PAY2S_SECRET_KEY,
analyticsToken: process.env.PAY2S_ANALYTICS_TOKEN,
})
const linkedBanks = await partner.listBanks()Open Banking dùng header pay2s-token; Partner API tự lấy và cache Bearer token trong memory. Các operation quản lý bank, webhook và history/resend được expose trên Pay2sPartnerClient. Metrics yêu cầu truyền analyticsToken riêng vì tài liệu hiện chưa công bố ổn định flow cấp token Analytics.
Payment Notification và IPN
const notification = client.parsePaymentNotification(request.url)
client.verifyPaymentNotification(notification)
const ipn = client.parseIpn(request.body)
client.verifyIpn(ipn)Luôn verify chữ ký trước khi cập nhật trạng thái đơn hàng. Việc chống xử lý callback trùng lặp thuộc application layer; nên dùng khóa idempotency như partnerCode:orderId:requestId:transId.
Raw webhook
Phải giữ nguyên raw request body trước khi JSON parse. verifyWebhook là HMAC helper generic; chỉ dùng khi contract Webhook của account xác nhận đúng header và secret:
import { parseWebhookEvent, verifyWebhook } from 'pay2s-node'
const signature = request.headers['x-signature']
if (typeof signature !== 'string') throw new Error('Missing webhook signature')
verifyWebhook(rawBodyBuffer, {
secret: process.env.PAY2S_WEBHOOK_SECRET,
signatureHeader: signature,
})
const event = parseWebhookEvent(rawBodyBuffer)verifyWebhook hỗ trợ chữ ký hex HMAC-SHA256 dạng trực tiếp hoặc header dạng sha256=<hex>, so sánh constant-time và từ chối payload/chữ ký không hợp lệ. Header authentication không được mặc định là x-signature hoặc Authorization: Bearer; hãy đối chiếu contract account trước khi bật Production.
Với Technical/Partner Webhook dùng Bearer token của Pay2S:
import { parseTransactionWebhook, verifyWebhookBearerToken } from 'pay2s-node'
verifyWebhookBearerToken({
authorizationHeader: request.headers.authorization,
token: process.env.PAY2S_WEBHOOK_TOKEN,
})
const event = parseTransactionWebhook(rawBodyBuffer)parseTransactionWebhook yêu cầu body có wrapper transactions; việc kiểm tra amount, account, checksum và idempotency vẫn thuộc application layer.
Typed responses
Các response export typed vẫn cho phép field mở rộng để tương thích contract theo account:
import type {
CreateClientPaymentResponse,
CreateQrCodeResponse,
PaymentResponse,
} from 'pay2s-node'
function getPaymentUrl(response: CreateClientPaymentResponse) {
return response.paymentUrl ?? response.payment_url
}PaymentResponse, CreateClientPaymentResponse và CreateQrCodeResponse expose các field phổ biến như requestId, orderId, resultCode, payment_url và qrUrl; field production phải đối chiếu API Reference.
Errors
Các error class được export gồm ValidationError, AuthenticationError, NetworkError, ApiError, ResponseError, SignatureVerificationError, Pay2sWebhookVerificationError và Pay2sWebhookParseError. Error giữ code, HTTP status nếu có, requestId nếu Pay2S trả về và cause gốc khi phù hợp.
| Error | Cách xử lý |
| --- | --- |
| ValidationError | Sửa input; không retry. |
| AuthenticationError | Kiểm tra environment/key/token; không retry mù. |
| SignatureVerificationError | Từ chối callback/request và điều tra contract/secret. |
| NetworkError | Retry hữu hạn chỉ khi operation an toàn và application đã deduplicate. |
| ApiError / ResponseError | Lưu status, code, requestId; xử lý theo business contract. |
SDK không tự retry payment request vì retry mù có thể tạo giao dịch trùng. Dùng requestId ổn định cho mỗi business order, lưu correlation fields (requestId, orderId, transId) và deduplicate callback ở application layer. defaultHeaders có thể dùng cho header idempotency nếu contract account cung cấp header đó; SDK không tự đặt một header chưa được Pay2S xác nhận.
SDK hiện không thêm status lookup, cancel hoặc refund vì tài liệu Pay2S công khai hiện không cung cấp contract ổn định cho các endpoint này.
Kiểm tra
npm run typecheck
npm test
npm run sandbox:smokeMặc định Sandbox smoke test được bỏ qua. Chỉ bật rõ ràng bằng PAY2S_RUN_SANDBOX=true và cung cấp credential cùng URL callback HTTPS trong environment.
