Chuyển tới nội dung chính

Payment Gateway

api_payment là cổng thanh toán tập trung của Bot Bán Hàng, tích hợp nhiều nền tảng thanh toán trong nước và quốc tế qua SDK pattern thống nhất. Mọi platform đều implement cùng một interface — developer chỉ cần gọi $PAYMENT.createTransaction() mà không cần biết chi tiết từng cổng.

Kiến trúc tổng quan

Các nền tảng hỗ trợ

Nền tảngPlatform enumLoạiMô tả
MoMoMOMOVí điện tửThanh toán qua app MoMo, hỗ trợ QR và redirect
ZaloPayZALOPAYVí điện tửThanh toán qua ZaloPay wallet
VNPayVNPAYCổng thanh toánCổng thanh toán liên ngân hàng, thẻ ATM/Visa/Master
StripeSTRIPEQuốc tếThẻ quốc tế Visa/Mastercard, Apple Pay, Google Pay
SquareSQUAREPOSThanh toán tại quầy qua thiết bị Square
MPOSMPOSPOS di độngThanh toán qua thiết bị POS di động
SePaySEPAYChuyển khoảnNhận tiền chuyển khoản tự động qua VietQR
Chuyển khoảnTRANSFERNgân hàngChuyển khoản ngân hàng thủ công
Tiền mặtCASHCODThu tiền mặt khi giao hàng

SDK Pattern — $PAYMENT

$PAYMENT là facade class trung tâm, được inject qua NestJS DI. Nó tự động định tuyến request tới đúng provider dựa trên platform enum.

Cách gọi

Có 3 cú pháp tương đương:

// Cú pháp 1: Truyền platform riêng
await $PAYMENT.createTransaction('MOMO', {
amount: 50000,
order_id: 'ORD-001',
description: 'Thanh toán đơn hàng #001',
})

// Cú pháp 2: Platform nằm trong data
await $PAYMENT.createTransaction({
platform: 'MOMO',
amount: 50000,
order_id: 'ORD-001',
description: 'Thanh toán đơn hàng #001',
})

// Cú pháp 3: Gọi trực tiếp qua platform facade
await $PAYMENT.MOMO.createTransaction({
amount: 50000,
order_id: 'ORD-001',
})

4 method chính

MethodMô tảInput chính
createTransaction()Tạo giao dịch thanh toán mớiamount, order_id, description, return_url
queryTransaction()Truy vấn trạng thái giao dịchorder_id, transaction_code
refundTransaction()Hoàn tiền giao dịchamount, order_id, transaction_id
handleWebhook()Xác thực và xử lý callback từ cổngpayload, headers, raw_body

Kết quả trả về

Mỗi method trả về output chuẩn hóa — dù gọi cổng nào, format response đều giống nhau:

// CreatePaymentOutput
{
status: 'PENDING', // TransactionStatus enum
transaction_code: 'TXN123', // Mã giao dịch bên cổng
qr_code: 'data:image/...', // QR Code (nếu có)
payment_url: 'https://...', // Link redirect tới trang thanh toán
payment_form: { // Form POST (VNPay)
method: 'POST',
action: 'https://...',
fields: { vnp_Amount: '5000000', ... }
},
raw: { ... } // Dữ liệu thô từ cổng
}

Luồng tạo thanh toán

Provider Architecture

Interface chuẩn — IPaymentProvider

Mỗi cổng thanh toán phải implement interface này:

interface IPaymentProvider {
readonly platform: PaymentPlatForm
readonly capabilities: PaymentProviderCapabilities
createPayment(data): Promise<CreatePaymentOutput>
queryPayment(data): Promise<QueryPaymentOutput>
refundPayment(data): Promise<RefundPaymentOutput>
verifyWebhook(data): Promise<WebhookOutput>
checkConfig(config): Promise<CheckConfigOutput>
}

Capabilities — khai báo tính năng

Mỗi provider khai báo rõ mình hỗ trợ tính năng gì. Nếu gọi method chưa hỗ trợ, AbstractPaymentProvider tự ném PaymentException:

// MoMo: hỗ trợ đầy đủ
capabilities = {
create: true, query: true, refund: true,
webhook: true, check_config: true,
setting: true, multiple_settings: false,
}

// Cash: chỉ tạo giao dịch
capabilities = {
create: true, query: false, refund: false,
webhook: false, check_config: false,
setting: false, multiple_settings: false,
}

Luồng kế thừa

Webhook / IPN

Tất cả callback từ cổng thanh toán đi vào WebhookController (/webhook/:platform).

Bảo mật

Signature verification bắt buộc chạy trước khi xử lý bất kỳ webhook nào. Không bao giờ tin tưởng dữ liệu webhook mà chưa verify chữ ký.

Logging & Audit

PaymentLoggerService

Tự động redact (ẩn) các trường nhạy cảm trước khi ghi log:

  • secretkey, accesskey, signature, token, mac, authorization, webhooksecret, key1, key2, callbackkey

PaymentAuditLogService

Ghi chi tiết mọi request/response với cổng thanh toán:

  • Direction: INBOUND (webhook đến) / OUTBOUND (gọi API cổng)
  • Tracking: transaction_id, transaction_code, provider_order_id
  • Context: business_id, branch_id, setting_id, source_ip
  • Debug: request_data, response_data, error_data (đã redact)

Xử lý lỗi

PaymentException là class lỗi chuyên biệt cho hệ thống thanh toán:

throw new PaymentException(
'Chưa cấu hình MOMO_DOMAIN', // message
'MOMO', // platform
'PAYMENT_CONFIG_MISSING', // error code
)

Khi gọi method mà platform chưa hỗ trợ, AbstractPaymentProvider tự throw:

PaymentException: Nền tảng CASH chưa hỗ trợ hoàn tiền
code: PAYMENT_OPERATION_NOT_SUPPORTED

Cấu trúc folder

src/
├── api/
│ ├── transaction/ # CRUD giao dịch, tạo/truy vấn/hoàn tiền
│ ├── webhook/ # Nhận IPN/callback từ các cổng
│ ├── setting/ # Quản lý cấu hình platform cho merchant
│ ├── public/ # API công khai (payment link, checkout)
│ └── private/ # API nội bộ giữa các service
├── services/payment/
│ ├── core/
│ │ ├── payment-provider.interface.ts # Interface chuẩn
│ │ ├── abstract-payment.provider.ts # Base class
│ │ ├── payment-factory.service.ts # $PAYMENT facade
│ │ ├── payment-logger.service.ts # Log + redact
│ │ ├── payment-audit-log.service.ts # Audit trail
│ │ └── payment.exception.ts # Exception class
│ ├── momo/
│ │ ├── index.ts # MomoClient — gọi API MoMo
│ │ ├── provider.ts # MomoPaymentProvider
│ │ ├── example.ts # Dữ liệu mẫu
│ │ ├── index.spec.ts # Test MomoClient
│ │ ├── provider.spec.ts # Test Provider
│ │ └── document.md # Tài liệu luồng chạy
│ ├── zalopay/ # Cấu trúc tương tự
│ ├── vnpay/
│ ├── stripe/
│ ├── square/
│ ├── mpos/
│ ├── sepay/
│ ├── transfer/
│ └── cash/
└── interface/payment/ # TypeScript contracts (namespace per platform)

Biến môi trường

Mỗi platform cần cấu hình domain riêng:

BiếnMô tả
MOMO_DOMAINDomain API MoMo (test-payment.momo.vn / payment.momo.vn)
MOMO_CALLBACK_URLURL nhận webhook MoMo
ZALOPAY_DOMAINDomain API ZaloPay
VNPAY_DOMAINDomain API VNPay
DEFAULT_RETURN_URLURL redirect mặc định sau thanh toán
thông tin

Không dùng biến environment hay endpoint từ UI config. Mỗi platform có biến domain riêng (MOMO_DOMAIN, ZALOPAY_DOMAIN, ...) theo quy ước BBH.

Thêm platform mới

  1. Tạo folder services/payment/<platform>/ theo cấu trúc chuẩn
  2. Implement class kế thừa AbstractPaymentProvider — khai báo platform, capabilities, và override các method cần hỗ trợ
  3. Tạo SDK client class xử lý signature, request, response mapping
  4. Đăng ký provider vào PaymentModule (DI token PAYMENT_PROVIDER_REGISTRY)
  5. Thêm webhook endpoint vào WebhookController nếu cần
  6. Viết test: happy case, validation error, provider error, timeout, invalid signature
  7. Tạo document.md mô tả luồng chạy