Integration Guide
This guide walks through a complete integration flow β from setup to receiving your first payment webhook.
API Overviewβ
Base URL: https://api.finan.one/open
All endpoints require authentication headers: x-client-id, x-signature, x-timestamp.
End-to-End Flowβ
Flow 1: Accept Payments (Most Common)β
1. Setup Account β GET /api/v1/master-bank-accounts
2. Create Payment β POST /api/v1/payments
3. Share Payment Link β Send payment_url or qr_code to customer
4. Receive Webhook β POST to your registered callback URL
5. Verify Payment β GET /api/v1/payments/:id (optional fallback)
- Go
- JavaScript
package main
import (
"context"
"fmt"
"log"
finan "github.com/finan-one/finan-go"
)
func main() {
client := finan.NewClient("YOUR_CLIENT_ID", "YOUR_SECRET_KEY")
ctx := context.Background()
// Step 1: Get your bank account
accounts, err := client.GetBankAccounts(ctx)
if err != nil {
log.Fatal(err)
}
accountID := accounts[0].AccountID
// Step 2: Create a payment request
payment, err := client.CreatePayment(ctx, &finan.CreatePaymentRequest{
PaymentMethod: finan.PaymentMethodBankTransfer,
AccountID: accountID,
Amount: 500000, // 500,000 VND
ReferenceID: "ORDER-001",
Description: "Don hang #001",
Customer: &finan.PaymentCustomer{
Name: "Nguyen Van A",
Email: "[email protected]",
},
})
if err != nil {
log.Fatal(err)
}
// Step 3: Share with customer
fmt.Println("Payment URL:", payment.PaymentURL)
fmt.Println("QR Code:", payment.BankTransferDetails.QRCode)
// Step 4: Webhook is sent automatically when payment is received
// Step 5: (Optional) Check payment status
status, _ := client.GetPayment(ctx, payment.PaymentRequestID)
fmt.Println("Status:", status.Status) // unpaid, paid, partial_paid, extra_paid
}
import { FinanClient } from '@finan-one/finan-js';
const client = new FinanClient('YOUR_CLIENT_ID', 'YOUR_SECRET_KEY');
// Step 1: Get your bank account
const accounts = await client.getBankAccounts();
const accountId = accounts[0].account_id;
// Step 2: Create a payment request
const payment = await client.createPayment({
payment_method: 'bank_transfer',
account_id: accountId,
amount: 500000, // 500,000 VND
reference_id: 'ORDER-001',
description: 'Don hang #001',
customer: {
name: 'Nguyen Van A',
email: '[email protected]',
},
});
// Step 3: Share with customer
console.log('Payment URL:', payment.payment_url);
console.log('QR Code:', payment.bank_transfer_detail?.qr_code);
// Step 4: Webhook is sent automatically when payment is received
// Step 5: (Optional) Check payment status
const status = await client.getPayment(payment.payment_request_id);
console.log('Status:', status.status); // unpaid, paid, partial_paid, extra_paid
Flow 2: Invoice-Based Paymentβ
1. Create Customer β POST /api/v1/customers
2. Create Product β POST /api/v1/products
3. Create Invoice β POST /api/v1/invoices (returns payment_link)
4. Share Invoice Link β Send payment_link to customer
5. Receive Webhook β POST to your registered callback URL
6. Check Invoice β GET /api/v1/invoices/:id
- Go
- JavaScript
// Step 1: Create customer (one-time)
// See: /docs/api/business/customer
// Step 2: Create product (one-time)
// See: /docs/api/business/product
// Step 3: Create invoice
invoice, err := client.CreateInvoice(ctx, &finan.CreateInvoiceRequest{
InvoiceCode: "INV-001",
TransactionDate: "2024-01-20T10:00:00Z",
DueDate: "2024-01-27T10:00:00Z",
TaxType: "price_excluding_tax",
Items: []finan.InvoiceItem{
{Code: "PRD0001", TaxCode: "TAX_CODE_10", Quantity: 2, UnitPrice: 100000},
},
Customer: finan.InvoiceCustomer{Code: "CUST123"},
PaymentMethods: []string{"bank_transfer", "card"},
AccountID: accountID,
})
fmt.Println("Invoice Payment Link:", invoice.PaymentLink)
// Step 1: Create customer (one-time)
// See: /docs/api/business/customer
// Step 2: Create product (one-time)
// See: /docs/api/business/product
// Step 3: Create invoice
const invoice = await client.createInvoice({
invoice_code: 'INV-001',
transaction_date: '2024-01-20T10:00:00Z',
due_date: '2024-01-27T10:00:00Z',
tax_type: 'price_excluding_tax',
items: [
{ code: 'PRD0001', tax_code: 'TAX_CODE_10', quantity: 2, unit_price: 100000 },
],
customer: { code: 'CUST123' },
payment_methods: ['bank_transfer', 'card'],
account_id: accountId,
});
console.log('Invoice Payment Link:', invoice.payment_link);
Flow 3: Send Payoutβ
1. Get Account β GET /api/v1/master-bank-accounts
2. Create Payout β POST /api/v1/payouts (returns id + OTP sent)
3. Receive OTP β From bank via SMS/Email
4. Verify OTP β POST /api/v1/payouts/:id/otps
5. Check Status β GET /api/v1/payouts/:id
Prerequisites
Payout requires IP whitelisting and is available for Enterprise accounts only.
Response Formatβ
All API responses follow this wrapper structure:
{
"message": {
"content": "Thα»±c thi API thΓ nh cΓ΄ng"
},
"code": 102000,
"request_id": "af2684a45bca4d444ef60c6dc90b4139",
"data": { ... },
"meta": {
"page": 1,
"page_size": 50,
"total_pages": 1,
"total_rows": 1
}
}
| Field | Type | Description |
|---|---|---|
message.content | string | Human-readable status message |
code | integer | Internal status code (102000 = success) |
request_id | string | Unique request ID for debugging/support |
data | object/array | Response payload |
meta | object | Pagination metadata (list endpoints only) |
Paginationβ
List endpoints return paginated results. Use these query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
page_size | integer | 50 | Items per page |
Example:
curl -X GET 'https://api.finan.one/open/api/v1/payments?page=2&page_size=50' \
-H 'x-client-id: YOUR_CLIENT_ID' \
-H 'x-signature: YOUR_SIGNATURE' \
-H 'x-timestamp: 1699999999'
Pagination metadata (in meta field):
{
"meta": {
"page": 2,
"page_size": 50,
"total_pages": 3,
"total_rows": 150
}
}
Rate Limitingβ
| Tier | Limit | Window |
|---|---|---|
| Standard | 100 requests | per minute |
| Enterprise | 500 requests | per minute |
When rate limited, the API returns 429 Too Many Requests:
{
"message": {
"content": "YΓͺu cαΊ§u khΓ΄ng hợp lα»",
"error": "Rate limit exceeded. Retry after 30 seconds."
},
"code": 104000,
"request_id": "abc123..."
}
Best Practices
- Cache responses for frequently accessed data (e.g., bank accounts)
- Use webhooks instead of polling for payment status
- Batch operations where possible
Error Response Formatβ
Error responses follow the same wrapper structure with an error-level code:
{
"message": {
"content": "Bad Request"
},
"code": 400000,
"request_id": "abc123...",
"data": null
}
HTTP Status Codesβ
| Code | Meaning | When |
|---|---|---|
200 | Success | GET, PUT, DELETE operations |
201 | Created | POST operations |
400 | Bad Request | Invalid request body or parameters |
401 | Unauthorized | Invalid signature, expired timestamp, or wrong client ID |
403 | Forbidden | IP not whitelisted (payout only) |
404 | Not Found | Resource does not exist |
409 | Conflict | Duplicate resource (e.g., duplicate customer_code) |
422 | Unprocessable | Validation failed (e.g., insufficient balance) |
429 | Too Many Requests | Rate limit exceeded |
500 | Server Error | Internal error β retry with backoff |
Webhook Setupβ
- Register your webhook URL via the Account API during client initialization
- Finan sends a
POSTrequest to your URL when a payment is received - Always respond with HTTP
200β even if your processing fails - Verify the webhook signature using
x-signatureheader (see how)
Retry policy: Failed webhooks are retried 3 times over 30 minutes (5m β 15m β 30m).
Checklist Before Going Liveβ
- Signature generation works for both GET and POST requests
- Webhook endpoint is deployed and returns HTTP 200
- Webhook signature verification is implemented
- Error handling covers all HTTP status codes
- Payment link expiry (30 minutes) is handled with retry logic
- IP whitelisted for Payout API (if applicable)
- Switch base URL from staging to production
Next Stepsβ
- Authentication β How to sign requests
- Account β Set up bank accounts
- Code Reference β Tax codes, bank codes, category codes