Payment Gateway
This API suite enables the creation of payment requests and provides payment status updates via webhooks.

Quick Referenceβ
| Endpoint | Method | Description |
|---|---|---|
/api/v1/payments | POST | Create a new payment request |
/api/v1/payments | GET | List all payment requests |
/api/v1/payments/:id | GET | Get specific payment request |
Payment Methodsβ
There are two primary methods to generate a payment link:
1. Direct QR Code Paymentβ
Use the qr_code field from your bank account to receive payments via VietQR.
- Call Get Bank Accounts API
- Use the
qr_codeURL from the response - Payment notifications are sent to your registered webhook
2. Payment Request Creationβ
Create a dedicated payment request for each transaction with custom amount, items, and redirect URLs.
API Endpointsβ
Create Payment Requestβ
Create a new payment request for a transaction.
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": "[email protected]"
},
"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": "[email protected]"
},
"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: "[email protected]",
},
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: "[email protected]",
},
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))
}
Request Bodyβ
| Field | Type | Required | Description |
|---|---|---|---|
merchant | object | Merchant details | |
merchant.name | string | Business name | |
merchant.phone | string | Contact phone | |
merchant.address | string | Business address | |
merchant.email | string | Contact email | |
items | array | List of items in the transaction | |
items[].name | string | β | Item name |
items[].price | integer | β | Price per unit (smallest currency unit) |
items[].quantity | integer | β | Quantity |
items[].amount | integer | β | Total amount (price * quantity) |
customer | object | Customer details | |
customer.name | string | β | Customer full name |
customer.phone | string | Contact phone | |
customer.address | string | Billing/shipping address | |
customer.email | string | Email for notifications | |
payment_method | string | β | bank_transfer, card, or ewallet_momo |
account_id | uuid | Required for Shinhan bank_transfer. Get from Account API | |
amount | integer | β | Total amount (smallest currency unit) |
description | string | Payment note (max 30 characters) | |
reference_id | string | β | Your unique transaction ID |
reference_type | string | Set to invoice to link to an Invoice | |
success_redirect_url | string | Redirect URL on success | |
failure_redirect_url | string | Redirect URL on failure |
To create a payment for an invoice, just set reference_type: "invoice" and reference_id to the invoice ID. No merchant, items, or customer info needed.
Responseβ
- β 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"
}
}
}
| Code | Message | Description |
|---|---|---|
| 400 | Bad Request | Invalid request body or missing required fields |
| 401 | Unauthorized | Invalid or missing authentication |
| 404 | Not Found | Account ID not found |
| 422 | Unprocessable Entity | Validation failed |
{
"message": {
"content": "YΓͺu cαΊ§u khΓ΄ng hợp lα»",
"error": "amount is required"
},
"code": 104000,
"request_id": "abc123..."
}
Response Fieldsβ
| Field | Type | Description |
|---|---|---|
payment_request_id | string | Unique payment request identifier |
payment_request_code | string | Short payment code |
payment_url | string | Payment page URL (for card or ewallet_momo, empty for bank_transfer) |
bank_transfer_detail | object | Bank transfer info (for bank_transfer) |
bank_transfer_detail.qr_code | string | VietQR code string |
bank_transfer_detail.bank_name | string | Beneficiary bank code |
bank_transfer_detail.account_name | string | Beneficiary account name |
bank_transfer_detail.account_number | string | Beneficiary account number |
bank_transfer_detail.remark | string | Transfer remark (used as payment reference) |
Each payment link is valid for 30 minutes. Create a new payment request if the previous one expires.
Get Payment Requestsβ
Retrieve all payment requests or filter by reference.
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))
}
Query Parametersβ
| Parameter | Type | Description |
|---|---|---|
reference_type | string | Filter by reference type (e.g., invoice) |
reference_id | string | Filter by reference ID |
Responseβ
- β 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": "[email protected]"
},
"customer": {
"name": "Nguyen Van A",
"email": "[email protected]"
}
}
]
}
| Code | Message | Description |
|---|---|---|
| 401 | Unauthorized | Invalid authentication |
| 500 | Internal Server Error | Server error |
Payment Statusβ
| Status | Description |
|---|---|
unpaid | No payment received (paid_amount = 0) |
partial_paid | Partial payment (0 < paid_amount < amount) |
paid | Fully paid (paid_amount = amount) |
extra_paid | Overpaid (paid_amount > amount) |
To get a specific payment request:
GET /api/v1/payments/:payment_request_id
Payment Webhookβ
After successful payment collection, Finan sends a notification to your registered webhook URL.

Webhook Payloadβ
Your webhook will receive a POST request with the following data:
{
"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"
}
Webhook Fieldsβ
| Field | Type | Description |
|---|---|---|
id | string | Unique payment identifier |
amount | integer | Payment amount (smallest currency unit) |
created_at | datetime | Payment timestamp (ISO 8601) |
payment_method | string | bank_transfer, card, or ewallet_momo |
payment_request_id | string | Associated payment request ID |
reference_type | string | Reference type (e.g., invoice) |
reference_id | string | Your transaction reference ID |
Webhook Responseβ
Your webhook endpoint must respond with HTTP 200 to acknowledge receipt:
{
"status": "success",
"message": "Payment processed successfully",
"timestamp": "2024-01-20T14:30:05Z"
}
Always respond with HTTP 200 even if your internal processing fails. This prevents unnecessary retries. Handle failures asynchronously in your system.
Automatic Retryβ
Failed webhook deliveries are automatically retried with exponential backoff:
| Retry | Interval | Total Time |
|---|---|---|
| 1st | 5 min | 5 min |
| 2nd | 10 min | 15 min |
| 3rd | 15 min | 30 min |
- Auto-retry is enabled by default for all accounts
- Configure retry settings in Webhook Settings
- Events with
URL Not SetorTimeoutstatus are not auto-retried - Use manual retry for those cases
Verifying Webhook Signaturesβ
Verify webhook authenticity using the signature headers:
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",
})
}
Next Stepsβ
- Payout - Send payouts to external bank accounts
- Invoice - Create invoices and link them to payments
- Generate Signature - Authentication guide