Cổng thanh toán
Bộ API này cho phép tạo yêu cầu thanh toán và cung cấp cập nhật trạng thái thanh toán qua webhook.

Tham chiếu nhanh
| Endpoint | Phương thức | Mô tả |
|---|---|---|
/api/v1/payments | POST | Tạo yêu cầu thanh toán mới |
/api/v1/payments | GET | Liệt kê tất cả yêu cầu thanh toán |
/api/v1/payments/:id | GET | Lấy yêu cầu thanh toán cụ thể |
Phương thức thanh toán
Có hai phương thức chính để tạo liên kết thanh toán:
1. Thanh toán trực tiếp qua mã QR
Sử dụng trường qr_code từ tài khoản ngân hàng của bạn để nhận thanh toán qua VietQR.
- Gọi API Lấy danh sách tài khoản ngân hàng
- Sử dụng URL
qr_codetừ phản hồi - Thông báo thanh toán được gửi đến webhook đã đăng ký của bạn
2. Tạo yêu cầu thanh toán
Tạo yêu cầu thanh toán riêng cho mỗi giao dịch với số tiền, danh sách sản phẩm và URL chuyển hướng tùy chỉnh.
API Endpoints
Tạo yêu cầu thanh toán
Tạo yêu cầu thanh toán mới cho một giao dịch.
POST /api/v1/payments
- cURL
- Go
curl -X POST 'https://api.finan.one/open/api/v1/payments' \
-H 'Content-Type: application/json' \
-H 'x-client-id: YOUR_CLIENT_ID' \
-H 'x-signature: YOUR_SIGNATURE' \
-H 'x-timestamp: 1699999999' \
-d '{
"merchant": {
"name": "Finan pte",
"phone": "02833223243",
"address": "195/10A, Dien Bien Phu, P15, Binh Thanh",
"email": "merchant@finan.one"
},
"items": [
{
"name": "So Ban Hang Pro 3 Nam",
"price": 3000000,
"quantity": 2,
"amount": 6000000
}
],
"customer": {
"name": "Nguyen Van A",
"phone": "0933450210",
"address": "15, Le Duan, q3, Quan 1",
"email": "nguyena@gmail.com"
},
"payment_method": "bank_transfer",
"account_id": "54957437-0cb5-4992-ad0e-76d26ba4ddc3",
"amount": 6000000,
"description": "thanh toan don hang",
"reference_id": "54fgi4537-0cb5-4992-ad0e-76d26ba4ddc3",
"reference_type": "invoice",
"success_redirect_url": "https://your-site.com/success",
"failure_redirect_url": "https://your-site.com/failure"
}'
package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"time"
)
type Merchant struct {
Name string `json:"name"`
Phone string `json:"phone"`
Address string `json:"address"`
Email string `json:"email"`
}
type Item struct {
Name string `json:"name"`
Price int64 `json:"price"`
Quantity int `json:"quantity"`
Amount int64 `json:"amount"`
}
type Customer struct {
Name string `json:"name"`
Phone string `json:"phone,omitempty"`
Address string `json:"address,omitempty"`
Email string `json:"email,omitempty"`
}
type CreatePaymentRequest struct {
Merchant *Merchant `json:"merchant,omitempty"`
Items []Item `json:"items,omitempty"`
Customer *Customer `json:"customer,omitempty"`
PaymentMethod string `json:"payment_method"`
AccountID string `json:"account_id,omitempty"`
Amount int64 `json:"amount"`
Description string `json:"description,omitempty"`
ReferenceID string `json:"reference_id"`
ReferenceType string `json:"reference_type,omitempty"`
SuccessRedirectURL string `json:"success_redirect_url,omitempty"`
FailureRedirectURL string `json:"failure_redirect_url,omitempty"`
}
func generateSignature(secretKey, method, path, payload, timestamp string) string {
message := secretKey + "_" + method + "_" + path + "_" + payload + "_" + timestamp
hash := sha256.Sum256([]byte(message))
return hex.EncodeToString(hash[:])
}
func main() {
clientID := "YOUR_CLIENT_ID"
secretKey := "YOUR_SECRET_KEY"
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
reqBody := CreatePaymentRequest{
Merchant: &Merchant{
Name: "Finan pte",
Phone: "02833223243",
Address: "195/10A, Dien Bien Phu, P15, Binh Thanh",
Email: "merchant@finan.one",
},
Items: []Item{
{
Name: "So Ban Hang Pro 3 Nam",
Price: 3000000,
Quantity: 2,
Amount: 6000000,
},
},
Customer: &Customer{
Name: "Nguyen Van A",
Phone: "0933450210",
Address: "15, Le Duan, q3, Quan 1",
Email: "nguyena@gmail.com",
},
PaymentMethod: "bank_transfer",
AccountID: "54957437-0cb5-4992-ad0e-76d26ba4ddc3",
Amount: 6000000,
Description: "thanh toan don hang",
ReferenceID: "54fgi4537-0cb5-4992-ad0e-76d26ba4ddc3",
ReferenceType: "invoice",
SuccessRedirectURL: "https://your-site.com/success",
FailureRedirectURL: "https://your-site.com/failure",
}
jsonBody, _ := json.Marshal(reqBody)
signature := generateSignature(secretKey, "POST", "/api/v1/payments", string(jsonBody), timestamp)
req, _ := http.NewRequest("POST", "https://api.finan.one/open/api/v1/payments", bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-client-id", clientID)
req.Header.Set("x-signature", signature)
req.Header.Set("x-timestamp", timestamp)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
Nội dung request
| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
merchant | object | Thông tin đơn vị bán hàng | |
merchant.name | string | Tên doanh nghiệp | |
merchant.phone | string | Số điện thoại liên hệ | |
merchant.address | string | Địa chỉ doanh nghiệp | |
merchant.email | string | Email liên hệ | |
items | array | Danh sách sản phẩm trong giao dịch | |
items[].name | string | ✅ | Tên sản phẩm |
items[].price | integer | ✅ | Giá mỗi đơn vị (đơn vị tiền tệ nhỏ nhất) |
items[].quantity | integer | ✅ | Số lượng |
items[].amount | integer | ✅ | Tổng số tiền (price * quantity) |
customer | object | Thông tin khách hàng | |
customer.name | string | ✅ | Họ tên đầy đủ khách hàng |
customer.phone | string | Số điện thoại liên hệ | |
customer.address | string | Địa chỉ thanh toán/giao hàng | |
customer.email | string | Email nhận thông báo | |
payment_method | string | ✅ | bank_transfer, card, hoặc ewallet_momo |
account_id | uuid | Bắt buộc cho Shinhan bank_transfer. Lấy từ API Tài khoản | |
amount | integer | ✅ | Tổng số tiền (đơn vị tiền tệ nhỏ nhất) |
description | string | Ghi chú thanh toán (tối đa 30 ký tự) | |
reference_id | string | ✅ | ID giao dịch duy nhất của bạn |
reference_type | string | Đặt thành invoice để liên kết với Hóa đơn | |
success_redirect_url | string | URL chuyển hướng khi thành công | |
failure_redirect_url | string | URL chuyển hướng khi thất bại |
Để tạo thanh toán cho hóa đơn, chỉ cần đặt reference_type: "invoice" và reference_id là ID hóa đơn. Không cần thông tin merchant, items hay customer.
Phản hồi
- ✅ Success (200)
- ❌ Error
{
"message": { "content": "Tạo thành công" },
"code": 102001,
"request_id": "d0a463bc089ea27859e2ebb5d33857c3",
"data": {
"payment_request_id": "3fde043e-c3d6-40e8-9031-6eadfa02d2a2",
"payment_request_code": "PRJXH9KU",
"payment_url": "",
"bank_transfer_detail": {
"qr_code": "00020101021238570010A000000727...",
"bank_name": "MB",
"account_name": "TRAN TU THIEN",
"account_number": "0933450210",
"remark": "TTVD7ZZS"
}
}
}
| Mã | Thông báo | Mô tả |
|---|---|---|
| 400 | Bad Request | Nội dung request không hợp lệ hoặc thiếu trường bắt buộc |
| 401 | Unauthorized | Xác thực không hợp lệ hoặc thiếu |
| 404 | Not Found | Không tìm thấy Account ID |
| 422 | Unprocessable Entity | Xác thực dữ liệu thất bại |
{
"message": {
"content": "Yêu cầu không hợp lệ",
"error": "amount is required"
},
"code": 104000,
"request_id": "abc123..."
}
Các trường phản hồi
| Trường | Kiểu | Mô tả |
|---|---|---|
payment_request_id | string | Định danh duy nhất của yêu cầu thanh toán |
payment_request_code | string | Mã thanh toán ngắn |
payment_url | string | URL trang thanh toán (cho card hoặc ewallet_momo, trống cho bank_transfer) |
bank_transfer_detail | object | Thông tin chuyển khoản ngân hàng (cho bank_transfer) |
bank_transfer_detail.qr_code | string | Chuỗi mã VietQR |
bank_transfer_detail.bank_name | string | Mã ngân hàng thụ hưởng |
bank_transfer_detail.account_name | string | Tên tài khoản thụ hưởng |
bank_transfer_detail.account_number | string | Số tài khoản thụ hưởng |
bank_transfer_detail.remark | string | Nội dung chuyển khoản (dùng làm tham chiếu thanh toán) |
Mỗi liên kết thanh toán có hiệu lực trong 30 phút. Tạo yêu cầu thanh toán mới nếu yêu cầu trước đó hết hạn.
Lấy danh sách yêu cầu thanh toán
Truy xuất tất cả yêu cầu thanh toán hoặc lọc theo tham chiếu.
GET /api/v1/payments
- cURL
- Go
# List all payments
curl -X GET 'https://api.finan.one/open/api/v1/payments' \
-H 'Content-Type: application/json' \
-H 'x-client-id: YOUR_CLIENT_ID' \
-H 'x-signature: YOUR_SIGNATURE' \
-H 'x-timestamp: 1699999999'
# Filter by reference
curl -X GET 'https://api.finan.one/open/api/v1/payments?reference_type=invoice&reference_id=INV-001' \
-H 'Content-Type: application/json' \
-H 'x-client-id: YOUR_CLIENT_ID' \
-H 'x-signature: YOUR_SIGNATURE' \
-H 'x-timestamp: 1699999999'
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
"strconv"
"time"
)
func generateSignature(secretKey, method, path, payload, timestamp string) string {
message := secretKey + "_" + method + "_" + path + "_" + payload + "_" + timestamp
hash := sha256.Sum256([]byte(message))
return hex.EncodeToString(hash[:])
}
func main() {
clientID := "YOUR_CLIENT_ID"
secretKey := "YOUR_SECRET_KEY"
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
signature := generateSignature(secretKey, "GET", "/api/v1/payments", "", timestamp)
req, _ := http.NewRequest("GET", "https://api.finan.one/open/api/v1/payments", nil)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-client-id", clientID)
req.Header.Set("x-signature", signature)
req.Header.Set("x-timestamp", timestamp)
// Optional: Add query parameters
q := req.URL.Query()
q.Add("reference_type", "invoice")
q.Add("reference_id", "INV-001")
req.URL.RawQuery = q.Encode()
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
Tham số truy vấn
| Tham số | Kiểu | Mô tả |
|---|---|---|
reference_type | string | Lọc theo loại tham chiếu (ví dụ: invoice) |
reference_id | string | Lọc theo ID tham chiếu |
Phản hồi
- ✅ Success (200)
- ❌ Error
{
"message": { "content": "Thực thi API thành công" },
"code": 102000,
"request_id": "abc123...",
"data": [
{
"payment_request_id": "PR-550e8400-e29b-41d4-a716-446655440000",
"paid_amount": 6000000,
"status": "paid",
"payment_method": "bank_transfer",
"amount": 6000000,
"description": "thanh toan don hang",
"reference_type": "invoice",
"reference_id": "INV-001",
"payments": [
{
"id": "PAY-001",
"amount": 6000000,
"method": "bank_transfer",
"created_at": "2024-01-20T14:30:00Z"
}
],
"merchant": {
"name": "Finan pte",
"email": "merchant@finan.one"
},
"customer": {
"name": "Nguyen Van A",
"email": "nguyena@gmail.com"
}
}
]
}
| Mã | Thông báo | Mô tả |
|---|---|---|
| 401 | Unauthorized | Xác thực không hợp lệ |
| 500 | Internal Server Error | Lỗi máy chủ |
Trạng thái thanh toán
| Trạng thái | Mô tả |
|---|---|
unpaid | Chưa nhận được thanh toán (paid_amount = 0) |
partial_paid | Thanh toán một phần (0 < paid_amount < amount) |
paid | Đã thanh toán đầy đủ (paid_amount = amount) |
extra_paid | Thanh toán vượt mức (paid_amount > amount) |
Để lấy một yêu cầu thanh toán cụ thể:
GET /api/v1/payments/:payment_request_id
Webhook thanh toán
Sau khi thu tiền thành công, Finan gửi thông báo đến URL webhook đã đăng ký của bạn.

Dữ liệu Webhook
Webhook của bạn sẽ nhận được một request POST với dữ liệu sau:
{
"id": "PAY-001",
"reference_id": "DH970422",
"payment_method": "bank_transfer",
"payment_request_id": "PR-550e8400-e29b-41d4-a716-446655440000",
"amount": 6000000,
"created_at": "2024-01-20T14:30:00Z"
}
Các trường Webhook
| Trường | Kiểu | Mô tả |
|---|---|---|
id | string | Định danh thanh toán duy nhất |
amount | integer | Số tiền thanh toán (đơn vị tiền tệ nhỏ nhất) |
created_at | datetime | Thời gian thanh toán (ISO 8601) |
payment_method | string | bank_transfer, card, hoặc ewallet_momo |
payment_request_id | string | ID yêu cầu thanh toán liên quan |
reference_type | string | Loại tham chiếu (ví dụ: invoice) |
reference_id | string | ID tham chiếu giao dịch của bạn |
Phản hồi Webhook
Endpoint webhook của bạn phải phản hồi với HTTP 200 để xác nhận đã nhận:
{
"status": "success",
"message": "Payment processed successfully",
"timestamp": "2024-01-20T14:30:05Z"
}
Luôn phản hồi với HTTP 200 ngay cả khi xử lý nội bộ thất bại. Điều này tránh các lần thử lại không cần thiết. Xử lý lỗi bất đồng bộ trong hệ thống của bạn.
Tự động thử lại
Các lần gửi webhook thất bại sẽ được tự động thử lại với khoảng thời gian tăng dần theo cấp số nhân:
| Lần thử lại | Khoảng thời gian | Tổng thời gian |
|---|---|---|
| Lần 1 | 5 phút | 5 phút |
| Lần 2 | 10 phút | 15 phút |
| Lần 3 | 15 phút | 30 phút |
- Tự động thử lại được bật mặc định cho tất cả tài khoản
- Cấu hình cài đặt thử lại trong Cài đặt Webhook
- Các sự kiện có trạng thái
URL Not SethoặcTimeoutkhông được tự động thử lại - Sử dụng thử lại thủ công cho các trường hợp đó
Xác minh chữ ký Webhook
Xác minh tính xác thực của webhook bằng các header chữ ký:
func verifyWebhookSignature(secretKey, method, path, body, timestamp, receivedSignature string) bool {
expectedSignature := generateSignature(secretKey, method, path, body, timestamp)
return expectedSignature == receivedSignature
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
clientID := r.Header.Get("x-client-id")
signature := r.Header.Get("x-signature")
timestamp := r.Header.Get("x-timestamp")
if !verifyWebhookSignature(secretKey, "POST", "/webhook", string(body), timestamp, signature) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
// Process the webhook payload
var payment WebhookPayload
json.Unmarshal(body, &payment)
// Always respond 200
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
"status": "success",
})
}
Bước tiếp theo
- Chi hộ - Gửi chi hộ đến tài khoản ngân hàng bên ngoài
- Hóa đơn - Tạo hóa đơn và liên kết với thanh toán
- Tạo chữ ký - Hướng dẫn xác thực