Skip to main content

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)
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
}

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
// 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)

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
}
}
FieldTypeDescription
message.contentstringHuman-readable status message
codeintegerInternal status code (102000 = success)
request_idstringUnique request ID for debugging/support
dataobject/arrayResponse payload
metaobjectPagination metadata (list endpoints only)

Pagination​

List endpoints return paginated results. Use these query parameters:

ParameterTypeDefaultDescription
pageinteger1Page number
page_sizeinteger50Items 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​

TierLimitWindow
Standard100 requestsper minute
Enterprise500 requestsper 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​

CodeMeaningWhen
200SuccessGET, PUT, DELETE operations
201CreatedPOST operations
400Bad RequestInvalid request body or parameters
401UnauthorizedInvalid signature, expired timestamp, or wrong client ID
403ForbiddenIP not whitelisted (payout only)
404Not FoundResource does not exist
409ConflictDuplicate resource (e.g., duplicate customer_code)
422UnprocessableValidation failed (e.g., insufficient balance)
429Too Many RequestsRate limit exceeded
500Server ErrorInternal error β€” retry with backoff

Webhook Setup​

  1. Register your webhook URL via the Account API during client initialization
  2. Finan sends a POST request to your URL when a payment is received
  3. Always respond with HTTP 200 β€” even if your processing fails
  4. Verify the webhook signature using x-signature header (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​