Creating Transfers to Bank Accounts
Transfers allow you to send funds from your Omise account balance to recipient bank accounts. This guide covers creating single and batch transfers, handling fees, and tracking transfer status.
Overviewโ
Transfers in Omise enable you to:
- Send Payouts: Transfer funds to recipients
- Automated Processing: Immediate or scheduled transfers
- Fee Management: Understand and handle transfer fees
- Status Tracking: Monitor transfer progress in real-time
- Batch Operations: Process multiple transfers efficiently
- Failure Handling: Handle failed transfers gracefully
Key Featuresโ
- Instant Transfers: Funds sent immediately (subject to bank processing)
- Fee Transparency: Clear fee structure per transfer
- Multiple Recipients: Send to any created recipient
- Metadata Support: Track custom information
- Webhook Notifications: Real-time status updates
- Balance Validation: Automatic balance checks
Transfer Requirementsโ
Before creating a transfer:
- Sufficient Balance: Must have enough funds in your account
- Active Recipient: Recipient must be verified and active
- Minimum Amount: Must meet minimum transfer requirements
- Fee Coverage: Balance must cover transfer amount + fees
Transfer Feesโ
const TRANSFER_FEES = {
thb: {
domestic: 2500, // 25 THB per transfer
minimum: 2000 // 20 THB minimum transfer
}
};
function calculateTransferCost(amount) {
return {
amount: amount,
fee: TRANSFER_FEES.thb.domestic,
total: amount + TRANSFER_FEES.thb.domestic
};
}
// Example
const cost = calculateTransferCost(100000); // 1,000 THB
console.log(`Amount: ${cost.amount / 100} THB`);
console.log(`Fee: ${cost.fee / 100} THB`);
console.log(`Total deducted: ${cost.total / 100} THB`);
Creating Transfers via APIโ
Basic Transferโ
- Node.js
- Python
- Ruby
- PHP
- Go
const omise = require('omise')({
secretKey: 'skey_test_123456789',
});
// Create a transfer
async function createTransfer(recipientId, amount) {
try {
// Check balance first
const balance = await omise.balance.retrieve();
const totalCost = amount + 2500; // amount + fee
if (balance.available < totalCost) {
throw new Error(`Insufficient balance. Need ${totalCost}, have ${balance.available}`);
}
// Create transfer
const transfer = await omise.transfers.create({
recipient: recipientId,
amount: amount,
metadata: {
purpose: 'vendor_payment',
invoice_id: 'INV-001',
payment_date: new Date().toISOString()
}
});
console.log('Transfer created:', transfer.id);
console.log('Amount:', transfer.amount / 100, 'THB');
console.log('Fee:', transfer.fee / 100, 'THB');
console.log('Status:', transfer.status);
return transfer;
} catch (error) {
console.error('Transfer failed:', error.message);
throw error;
}
}
// Create transfer with full validation
async function createValidatedTransfer(recipientId, amount, metadata = {}) {
// Validate recipient
const recipient = await omise.recipients.retrieve(recipientId);
if (!recipient.active) {
throw new Error('Recipient is not active');
}
if (!recipient.verified) {
throw new Error('Recipient is not verified');
}
// Validate amount
if (amount < 2000) { // 20 THB minimum
throw new Error('Amount below minimum transfer amount');
}
// Check balance
const balance = await omise.balance.retrieve();
const totalCost = amount + 2500;
if (balance.available < totalCost) {
return {
success: false,
reason: 'insufficient_balance',
available: balance.available,
required: totalCost,
shortfall: totalCost - balance.available
};
}
// Create transfer
const transfer = await omise.transfers.create({
recipient: recipientId,
amount: amount,
metadata: metadata
});
return {
success: true,
transfer: transfer,
recipient_name: recipient.name,
bank: recipient.bank_account.brand
};
}
// Example usage
createTransfer('recp_test_123456789', 100000); // 1,000 THB
createValidatedTransfer('recp_test_123456789', 500000, {
payment_type: 'monthly_salary',
employee_id: 'EMP-042',
period: '2024-01'
}).then(result => {
if (result.success) {
console.log(`โ Transfer ${result.transfer.id} created`);
console.log(` To: ${result.recipient_name}`);
console.log(` Bank: ${result.bank}`);
} else {
console.log(`โ Transfer failed: ${result.reason}`);
}
});
import omise
from datetime import datetime
omise.api_secret = 'skey_test_123456789'
TRANSFER_FEE = 2500 # 25 THB
MIN_AMOUNT = 2000 # 20 THB
def create_transfer(recipient_id, amount):
"""Create a transfer to a recipient"""
try:
# Check balance
balance = omise.Balance.retrieve()
total_cost = amount + TRANSFER_FEE
if balance.available < total_cost:
raise ValueError(f"Insufficient balance. Need {total_cost}, have {balance.available}")
# Create transfer
transfer = omise.Transfer.create(
recipient=recipient_id,
amount=amount,
metadata={
'purpose': 'vendor_payment',
'invoice_id': 'INV-001',
'payment_date': datetime.now().isoformat()
}
)
print(f"Transfer created: {transfer.id}")
print(f"Amount: {transfer.amount / 100} THB")
print(f"Fee: {transfer.fee / 100} THB")
print(f"Status: {transfer.status}")
return transfer
except omise.errors.BaseError as e:
print(f"Transfer failed: {str(e)}")
raise
def create_validated_transfer(recipient_id, amount, metadata=None):
"""Create transfer with full validation"""
# Validate recipient
recipient = omise.Recipient.retrieve(recipient_id)
if not recipient.active:
raise ValueError('Recipient is not active')
if not recipient.verified:
raise ValueError('Recipient is not verified')
# Validate amount
if amount < MIN_AMOUNT:
raise ValueError(f'Amount below minimum transfer amount of {MIN_AMOUNT}')
# Check balance
balance = omise.Balance.retrieve()
total_cost = amount + TRANSFER_FEE
if balance.available < total_cost:
return {
'success': False,
'reason': 'insufficient_balance',
'available': balance.available,
'required': total_cost,
'shortfall': total_cost - balance.available
}
# Create transfer
transfer = omise.Transfer.create(
recipient=recipient_id,
amount=amount,
metadata=metadata or {}
)
return {
'success': True,
'transfer': transfer,
'recipient_name': recipient.name,
'bank': recipient.bank_account.brand
}
def calculate_transfer_cost(amount):
"""Calculate total cost including fees"""
return {
'amount': amount,
'fee': TRANSFER_FEE,
'total': amount + TRANSFER_FEE,
'amount_formatted': f"{amount / 100:.2f} THB",
'fee_formatted': f"{TRANSFER_FEE / 100:.2f} THB",
'total_formatted': f"{(amount + TRANSFER_FEE) / 100:.2f} THB"
}
# Example usage
create_transfer('recp_test_123456789', 100000)
result = create_validated_transfer('recp_test_123456789', 500000, {
'payment_type': 'monthly_salary',
'employee_id': 'EMP-042',
'period': '2024-01'
})
if result['success']:
print(f"โ Transfer {result['transfer'].id} created")
print(f" To: {result['recipient_name']}")
print(f" Bank: {result['bank']}")
else:
print(f"โ Transfer failed: {result['reason']}")
require 'omise'
Omise.api_key = 'skey_test_123456789'
TRANSFER_FEE = 2500 # 25 THB
MIN_AMOUNT = 2000 # 20 THB
# Create a transfer
def create_transfer(recipient_id, amount)
begin
# Check balance
balance = Omise::Balance.retrieve
total_cost = amount + TRANSFER_FEE
if balance.available < total_cost
raise "Insufficient balance. Need #{total_cost}, have #{balance.available}"
end
# Create transfer
transfer = Omise::Transfer.create(
recipient: recipient_id,
amount: amount,
metadata: {
purpose: 'vendor_payment',
invoice_id: 'INV-001',
payment_date: Time.now.iso8601
}
)
puts "Transfer created: #{transfer.id}"
puts "Amount: #{transfer.amount / 100.0} THB"
puts "Fee: #{transfer.fee / 100.0} THB"
puts "Status: #{transfer.status}"
transfer
rescue Omise::Error => e
puts "Transfer failed: #{e.message}"
raise
end
end
# Create transfer with validation
def create_validated_transfer(recipient_id, amount, metadata = {})
# Validate recipient
recipient = Omise::Recipient.retrieve(recipient_id)
raise 'Recipient is not active' unless recipient.active
raise 'Recipient is not verified' unless recipient.verified
# Validate amount
if amount < MIN_AMOUNT
raise "Amount below minimum transfer amount of #{MIN_AMOUNT}"
end
# Check balance
balance = Omise::Balance.retrieve
total_cost = amount + TRANSFER_FEE
if balance.available < total_cost
return {
success: false,
reason: 'insufficient_balance',
available: balance.available,
required: total_cost,
shortfall: total_cost - balance.available
}
end
# Create transfer
transfer = Omise::Transfer.create(
recipient: recipient_id,
amount: amount,
metadata: metadata
)
{
success: true,
transfer: transfer,
recipient_name: recipient.name,
bank: recipient.bank_account.brand
}
end
# Calculate costs
def calculate_transfer_cost(amount)
{
amount: amount,
fee: TRANSFER_FEE,
total: amount + TRANSFER_FEE,
amount_formatted: "#{amount / 100.0} THB",
fee_formatted: "#{TRANSFER_FEE / 100.0} THB",
total_formatted: "#{(amount + TRANSFER_FEE) / 100.0} THB"
}
end
# Transfer with retry logic
def create_transfer_with_retry(recipient_id, amount, max_retries = 3)
retries = 0
begin
create_validated_transfer(recipient_id, amount)
rescue Omise::Error => e
retries += 1
if retries < max_retries && e.message.include?('insufficient')
puts "Insufficient balance, retrying in 60 seconds... (#{retries}/#{max_retries})"
sleep 60
retry
else
raise
end
end
end
# Example usage
create_transfer('recp_test_123456789', 100000)
result = create_validated_transfer('recp_test_123456789', 500000, {
payment_type: 'monthly_salary',
employee_id: 'EMP-042',
period: '2024-01'
})
if result[:success]
puts "โ Transfer #{result[:transfer].id} created"
puts " To: #{result[:recipient_name]}"
puts " Bank: #{result[:bank]}"
else
puts "โ Transfer failed: #{result[:reason]}"
end
<?php
require_once 'vendor/autoload.php';
define('OMISE_SECRET_KEY', 'skey_test_123456789');
define('TRANSFER_FEE', 2500); // 25 THB
define('MIN_AMOUNT', 2000); // 20 THB
// Create a transfer
function createTransfer($recipientId, $amount) {
try {
// Check balance
$balance = OmiseBalance::retrieve();
$totalCost = $amount + TRANSFER_FEE;
if ($balance['available'] < $totalCost) {
throw new Exception("Insufficient balance. Need {$totalCost}, have {$balance['available']}");
}
// Create transfer
$transfer = OmiseTransfer::create([
'recipient' => $recipientId,
'amount' => $amount,
'metadata' => [
'purpose' => 'vendor_payment',
'invoice_id' => 'INV-001',
'payment_date' => date('c')
]
]);
echo "Transfer created: {$transfer['id']}\n";
echo "Amount: " . ($transfer['amount'] / 100) . " THB\n";
echo "Fee: " . ($transfer['fee'] / 100) . " THB\n";
echo "Status: {$transfer['status']}\n";
return $transfer;
} catch (Exception $e) {
echo "Transfer failed: {$e->getMessage()}\n";
throw $e;
}
}
// Create validated transfer
function createValidatedTransfer($recipientId, $amount, $metadata = []) {
// Validate recipient
$recipient = OmiseRecipient::retrieve($recipientId);
if (!$recipient['active']) {
throw new Exception('Recipient is not active');
}
if (!$recipient['verified']) {
throw new Exception('Recipient is not verified');
}
// Validate amount
if ($amount < MIN_AMOUNT) {
throw new Exception('Amount below minimum transfer amount of ' . MIN_AMOUNT);
}
// Check balance
$balance = OmiseBalance::retrieve();
$totalCost = $amount + TRANSFER_FEE;
if ($balance['available'] < $totalCost) {
return [
'success' => false,
'reason' => 'insufficient_balance',
'available' => $balance['available'],
'required' => $totalCost,
'shortfall' => $totalCost - $balance['available']
];
}
// Create transfer
$transfer = OmiseTransfer::create([
'recipient' => $recipientId,
'amount' => $amount,
'metadata' => $metadata
]);
return [
'success' => true,
'transfer' => $transfer,
'recipient_name' => $recipient['name'],
'bank' => $recipient['bank_account']['brand']
];
}
// Calculate transfer cost
function calculateTransferCost($amount) {
return [
'amount' => $amount,
'fee' => TRANSFER_FEE,
'total' => $amount + TRANSFER_FEE,
'amount_formatted' => number_format($amount / 100, 2) . ' THB',
'fee_formatted' => number_format(TRANSFER_FEE / 100, 2) . ' THB',
'total_formatted' => number_format(($amount + TRANSFER_FEE) / 100, 2) . ' THB'
];
}
// Batch transfers
function batchCreateTransfers($transfers) {
$results = [
'successful' => [],
'failed' => []
];
foreach ($transfers as $index => $transferData) {
try {
$transfer = createValidatedTransfer(
$transferData['recipient_id'],
$transferData['amount'],
$transferData['metadata'] ?? []
);
if ($transfer['success']) {
$results['successful'][] = [
'index' => $index,
'transfer_id' => $transfer['transfer']['id'],
'recipient' => $transfer['recipient_name']
];
} else {
$results['failed'][] = [
'index' => $index,
'recipient_id' => $transferData['recipient_id'],
'reason' => $transfer['reason']
];
}
} catch (Exception $e) {
$results['failed'][] = [
'index' => $index,
'recipient_id' => $transferData['recipient_id'],
'error' => $e->getMessage()
];
}
}
echo "\nBatch transfer complete:\n";
echo "Successful: " . count($results['successful']) . "\n";
echo "Failed: " . count($results['failed']) . "\n";
return $results;
}
// Example usage
createTransfer('recp_test_123456789', 100000);
$result = createValidatedTransfer('recp_test_123456789', 500000, [
'payment_type' => 'monthly_salary',
'employee_id' => 'EMP-042',
'period' => '2024-01'
]);
if ($result['success']) {
echo "โ Transfer {$result['transfer']['id']} created\n";
echo " To: {$result['recipient_name']}\n";
echo " Bank: {$result['bank']}\n";
} else {
echo "โ Transfer failed: {$result['reason']}\n";
}
?>
package main
import (
"errors"
"fmt"
"log"
"time"
"github.com/omise/omise-go"
"github.com/omise/omise-go/operations"
)
const (
secretKey = "skey_test_123456789"
transferFee = 2500 // 25 THB
minAmount = 2000 // 20 THB
)
// TransferResult represents the result of a transfer operation
type TransferResult struct {
Success bool
Transfer *omise.Transfer
RecipientName string
Bank string
Reason string
Available int64
Required int64
Shortfall int64
}
// CreateTransfer creates a transfer to a recipient
func CreateTransfer(recipientID string, amount int64) (*omise.Transfer, error) {
client, err := omise.NewClient(secretKey, "")
if err != nil {
return nil, fmt.Errorf("failed to create client: %w", err)
}
// Check balance
balance := &omise.Balance{}
if err := client.Do(balance, &operations.RetrieveBalance{}); err != nil {
return nil, fmt.Errorf("failed to retrieve balance: %w", err)
}
totalCost := amount + transferFee
if balance.Available < totalCost {
return nil, fmt.Errorf("insufficient balance: need %d, have %d", totalCost, balance.Available)
}
// Create transfer
transfer := &omise.Transfer{}
err = client.Do(transfer, &operations.CreateTransfer{
Recipient: recipientID,
Amount: amount,
Metadata: map[string]interface{}{
"purpose": "vendor_payment",
"invoice_id": "INV-001",
"payment_date": time.Now().Format(time.RFC3339),
},
})
if err != nil {
return nil, fmt.Errorf("transfer failed: %w", err)
}
fmt.Printf("Transfer created: %s\n", transfer.ID)
fmt.Printf("Amount: %.2f THB\n", float64(transfer.Amount)/100)
fmt.Printf("Fee: %.2f THB\n", float64(transfer.Fee)/100)
fmt.Printf("Status: %s\n", transfer.Status)
return transfer, nil
}
// CreateValidatedTransfer creates a transfer with full validation
func CreateValidatedTransfer(recipientID string, amount int64, metadata map[string]interface{}) *TransferResult {
client, err := omise.NewClient(secretKey, "")
if err != nil {
return &TransferResult{
Success: false,
Reason: err.Error(),
}
}
// Validate recipient
recipient := &omise.Recipient{}
if err := client.Do(recipient, &operations.RetrieveRecipient{RecipientID: recipientID}); err != nil {
return &TransferResult{
Success: false,
Reason: "recipient_not_found",
}
}
if !recipient.Active {
return &TransferResult{
Success: false,
Reason: "recipient_not_active",
}
}
if !recipient.Verified {
return &TransferResult{
Success: false,
Reason: "recipient_not_verified",
}
}
// Validate amount
if amount < minAmount {
return &TransferResult{
Success: false,
Reason: fmt.Sprintf("amount_below_minimum: %d", minAmount),
}
}
// Check balance
balance := &omise.Balance{}
if err := client.Do(balance, &operations.RetrieveBalance{}); err != nil {
return &TransferResult{
Success: false,
Reason: "balance_check_failed",
}
}
totalCost := amount + transferFee
if balance.Available < totalCost {
return &TransferResult{
Success: false,
Reason: "insufficient_balance",
Available: balance.Available,
Required: totalCost,
Shortfall: totalCost - balance.Available,
}
}
// Create transfer
transfer := &omise.Transfer{}
err = client.Do(transfer, &operations.CreateTransfer{
Recipient: recipientID,
Amount: amount,
Metadata: metadata,
})
if err != nil {
return &TransferResult{
Success: false,
Reason: err.Error(),
}
}
return &TransferResult{
Success: true,
Transfer: transfer,
RecipientName: recipient.Name,
Bank: recipient.BankAccount.Brand,
}
}
// CalculateTransferCost calculates the total cost of a transfer
type TransferCost struct {
Amount int64
Fee int64
Total int64
}
func CalculateTransferCost(amount int64) TransferCost {
return TransferCost{
Amount: amount,
Fee: transferFee,
Total: amount + transferFee,
}
}
// BatchTransferResult holds batch transfer results
type BatchTransferResult struct {
Successful []BatchSuccess
Failed []BatchFailure
}
type BatchSuccess struct {
Index int
TransferID string
Recipient string
}
type BatchFailure struct {
Index int
RecipientID string
Reason string
}
// BatchCreateTransfers creates multiple transfers
func BatchCreateTransfers(transfers []struct {
RecipientID string
Amount int64
Metadata map[string]interface{}
}) BatchTransferResult {
result := BatchTransferResult{
Successful: []BatchSuccess{},
Failed: []BatchFailure{},
}
for i, t := range transfers {
transferResult := CreateValidatedTransfer(t.RecipientID, t.Amount, t.Metadata)
if transferResult.Success {
result.Successful = append(result.Successful, BatchSuccess{
Index: i,
TransferID: transferResult.Transfer.ID,
Recipient: transferResult.RecipientName,
})
} else {
result.Failed = append(result.Failed, BatchFailure{
Index: i,
RecipientID: t.RecipientID,
Reason: transferResult.Reason,
})
}
}
fmt.Printf("\nBatch transfer complete:\n")
fmt.Printf("Successful: %d\n", len(result.Successful))
fmt.Printf("Failed: %d\n", len(result.Failed))
return result
}
func main() {
// Example: Create single transfer
transfer, err := CreateTransfer("recp_test_123456789", 100000)
if err != nil {
log.Fatalf("Failed to create transfer: %v", err)
}
// Example: Create validated transfer
result := CreateValidatedTransfer("recp_test_123456789", 500000, map[string]interface{}{
"payment_type": "monthly_salary",
"employee_id": "EMP-042",
"period": "2024-01",
})
if result.Success {
fmt.Printf("โ Transfer %s created\n", result.Transfer.ID)
fmt.Printf(" To: %s\n", result.RecipientName)
fmt.Printf(" Bank: %s\n", result.Bank)
} else {
fmt.Printf("โ Transfer failed: %s\n", result.Reason)
}
}
API Responseโ
{
"object": "transfer",
"id": "trsf_test_5xyz789abc",
"livemode": false,
"location": "/transfers/trsf_test_5xyz789abc",
"recipient": "recp_test_123456789",
"bank_account": {
"object": "bank_account",
"brand": "bbl",
"last_digits": "7890",
"name": "John Doe"
},
"sent": true,
"paid": false,
"amount": 100000,
"currency": "thb",
"fee": 2500,
"failure_code": null,
"failure_message": null,
"transaction": "trxn_test_5xyz789abc",
"created": "2024-01-15T10:30:00Z",
"metadata": {
"purpose": "vendor_payment",
"invoice_id": "INV-001"
}
}
Transfer Status Trackingโ
Status Lifecycleโ
const TRANSFER_STATUSES = {
'pending': 'Transfer created, waiting to be sent',
'sent': 'Transfer sent to bank, waiting for confirmation',
'paid': 'Transfer completed successfully',
'failed': 'Transfer failed',
'reversed': 'Transfer was reversed'
};
async function trackTransferStatus(transferId) {
const transfer = await omise.transfers.retrieve(transferId);
console.log(`Transfer ${transferId}:`);
console.log(` Status: ${transfer.sent ? 'Sent' : 'Pending'}`);
console.log(` Paid: ${transfer.paid ? 'Yes' : 'No'}`);
if (transfer.failure_code) {
console.log(` Failed: ${transfer.failure_message}`);
}
return {
id: transfer.id,
sent: transfer.sent,
paid: transfer.paid,
failed: !!transfer.failure_code,
status_description: getStatusDescription(transfer)
};
}
function getStatusDescription(transfer) {
if (transfer.failure_code) return 'Failed';
if (transfer.paid) return 'Completed';
if (transfer.sent) return 'Processing';
return 'Pending';
}
Common Use Casesโ
1. Payroll Processingโ
class PayrollProcessor:
def __init__(self):
self.transfer_fee = 2500
def process_payroll(self, employees):
"""Process payroll for multiple employees"""
results = {
'processed': [],
'failed': [],
'total_amount': 0,
'total_fees': 0
}
# Check total required balance
total_needed = sum(emp['salary'] for emp in employees)
total_fees = len(employees) * self.transfer_fee
grand_total = total_needed + total_fees
balance = omise.Balance.retrieve()
if balance.available < grand_total:
raise ValueError(f"Insufficient balance for payroll. Need {grand_total}, have {balance.available}")
# Process each employee
for employee in employees:
try:
transfer = omise.Transfer.create(
recipient=employee['recipient_id'],
amount=employee['salary'],
metadata={
'type': 'payroll',
'employee_id': employee['employee_id'],
'employee_name': employee['name'],
'period': employee['pay_period'],
'department': employee['department']
}
)
results['processed'].append({
'employee_id': employee['employee_id'],
'name': employee['name'],
'transfer_id': transfer.id,
'amount': employee['salary'],
'fee': self.transfer_fee
})
results['total_amount'] += employee['salary']
results['total_fees'] += self.transfer_fee
print(f"โ Processed: {employee['name']} - {employee['salary']/100:.2f} THB")
except Exception as e:
results['failed'].append({
'employee_id': employee['employee_id'],
'name': employee['name'],
'error': str(e)
})
print(f"โ Failed: {employee['name']} - {str(e)}")
# Generate report
print(f"\n{'='*50}")
print(f"Payroll Summary:")
print(f" Processed: {len(results['processed'])}")
print(f" Failed: {len(results['failed'])}")
print(f" Total Amount: {results['total_amount']/100:.2f} THB")
print(f" Total Fees: {results['total_fees']/100:.2f} THB")
print(f" Grand Total: {(results['total_amount'] + results['total_fees'])/100:.2f} THB")
return results
# Example usage
processor = PayrollProcessor()
employees = [
{
'employee_id': 'EMP-001',
'name': 'John Doe',
'recipient_id': 'recp_test_111',
'salary': 5000000, # 50,000 THB
'department': 'Engineering',
'pay_period': '2024-01'
},
{
'employee_id': 'EMP-002',
'name': 'Jane Smith',
'recipient_id': 'recp_test_222',
'salary': 4500000, # 45,000 THB
'department': 'Marketing',
'pay_period': '2024-01'
}
]
payroll_results = processor.process_payroll(employees)
2. Vendor Payment Managementโ
class VendorPaymentManager
def initialize
@transfer_fee = 2500
end
def pay_invoice(invoice)
# Validate invoice
raise 'Invoice already paid' if invoice[:paid]
raise 'Invoice not approved' unless invoice[:approved]
# Get recipient
recipient_id = get_recipient_for_vendor(invoice[:vendor_id])
# Create transfer
transfer = Omise::Transfer.create(
recipient: recipient_id,
amount: invoice[:amount],
metadata: {
type: 'invoice_payment',
invoice_id: invoice[:invoice_id],
vendor_id: invoice[:vendor_id],
due_date: invoice[:due_date],
payment_terms: invoice[:payment_terms]
}
)
# Update invoice status
mark_invoice_paid(invoice[:invoice_id], transfer.id)
puts "โ Invoice #{invoice[:invoice_id]} paid"
puts " Vendor: #{invoice[:vendor_name]}"
puts " Amount: #{invoice[:amount] / 100.0} THB"
puts " Transfer: #{transfer.id}"
transfer
end
def batch_pay_due_invoices(date = Date.today)
# Get invoices due today or earlier
due_invoices = get_due_invoices(date)
results = {
successful: [],
failed: []
}
due_invoices.each do |invoice|
begin
transfer = pay_invoice(invoice)
results[:successful] << {
invoice_id: invoice[:invoice_id],
transfer_id: transfer.id,
amount: invoice[:amount]
}
rescue => e
results[:failed] << {
invoice_id: invoice[:invoice_id],
error: e.message
}
end
end
generate_payment_report(results)
results
end
private
def get_recipient_for_vendor(vendor_id)
# Implement vendor-to-recipient mapping
"recp_test_#{vendor_id}"
end
def mark_invoice_paid(invoice_id, transfer_id)
# Implement invoice update logic
end
def get_due_invoices(date)
# Implement invoice retrieval logic
[]
end
def generate_payment_report(results)
puts "\n#{'='*50}"
puts "Vendor Payment Report"
puts "Successful: #{results[:successful].length}"
puts "Failed: #{results[:failed].length}"
total = results[:successful].sum { |r| r[:amount] }
puts "Total Paid: #{total / 100.0} THB"
end
end
3. Marketplace Seller Payoutsโ
class MarketplacePayoutManager {
private $transferFee = 2500;
private $platformCommission = 0.15; // 15%
public function calculateSellerPayout($orderId) {
$order = $this->getOrder($orderId);
$grossAmount = $order['total_amount'];
$commission = intval($grossAmount * $this->platformCommission);
$netAmount = $grossAmount - $commission;
return [
'order_id' => $orderId,
'gross_amount' => $grossAmount,
'commission' => $commission,
'net_amount' => $netAmount,
'transfer_fee' => $this->transferFee,
'seller_receives' => $netAmount
];
}
public function processSellerPayout($sellerId, $orders) {
// Calculate total payout
$totalPayout = 0;
$orderDetails = [];
foreach ($orders as $orderId) {
$payout = $this->calculateSellerPayout($orderId);
$totalPayout += $payout['net_amount'];
$orderDetails[] = $payout;
}
// Get seller recipient
$recipientId = $this->getSellerRecipient($sellerId);
// Create transfer
try {
$transfer = OmiseTransfer::create([
'recipient' => $recipientId,
'amount' => $totalPayout,
'metadata' => [
'type' => 'seller_payout',
'seller_id' => $sellerId,
'order_count' => count($orders),
'order_ids' => implode(',', $orders),
'payout_period' => date('Y-m')
]
]);
// Mark orders as paid
foreach ($orders as $orderId) {
$this->markOrderPaid($orderId, $transfer['id']);
}
echo "โ Payout processed for seller {$sellerId}\n";
echo " Orders: " . count($orders) . "\n";
echo " Amount: " . ($totalPayout / 100) . " THB\n";
echo " Transfer: {$transfer['id']}\n";
return [
'success' => true,
'transfer' => $transfer,
'orders' => $orderDetails,
'total_amount' => $totalPayout
];
} catch (Exception $e) {
echo "โ Payout failed: {$e->getMessage()}\n";
return [
'success' => false,
'error' => $e->getMessage()
];
}
}
public function scheduledPayoutRun() {
// Get all sellers eligible for payout
$sellers = $this->getSellersForPayout();
$results = [
'successful' => 0,
'failed' => 0,
'total_amount' => 0
];
foreach ($sellers as $seller) {
$unpaidOrders = $this->getUnpaidOrders($seller['id']);
if (empty($unpaidOrders)) {
continue;
}
$result = $this->processSellerPayout($seller['id'], $unpaidOrders);
if ($result['success']) {
$results['successful']++;
$results['total_amount'] += $result['total_amount'];
} else {
$results['failed']++;
}
}
echo "\n" . str_repeat('=', 50) . "\n";
echo "Marketplace Payout Run Complete\n";
echo "Successful: {$results['successful']}\n";
echo "Failed: {$results['failed']}\n";
echo "Total Paid: " . ($results['total_amount'] / 100) . " THB\n";
return $results;
}
private function getOrder($orderId) {
// Implement order retrieval
return ['order_id' => $orderId, 'total_amount' => 100000];
}
private function getSellerRecipient($sellerId) {
// Implement seller-to-recipient mapping
return "recp_test_{$sellerId}";
}
private function markOrderPaid($orderId, $transferId) {
// Implement order update
}
private function getSellersForPayout() {
// Implement seller retrieval
return [];
}
private function getUnpaidOrders($sellerId) {
// Implement unpaid order retrieval
return [];
}
}
Best Practicesโ
1. Always Validate Before Transferโ
async function safeTransfer(recipientId, amount, metadata) {
// Pre-transfer validation checklist
const validations = [];
// 1. Validate recipient
try {
const recipient = await omise.recipients.retrieve(recipientId);
if (!recipient.active) validations.push('Recipient not active');
if (!recipient.verified) validations.push('Recipient not verified');
} catch (error) {
validations.push('Recipient not found');
}
// 2. Validate amount
if (amount < 2000) validations.push('Amount below minimum');
if (amount % 1 !== 0) validations.push('Amount must be integer');
// 3. Check balance
const balance = await omise.balance.retrieve();
const totalCost = amount + 2500;
if (balance.available < totalCost) {
validations.push(`Insufficient balance: need ${totalCost}, have ${balance.available}`);
}
// 4. Validate metadata
if (metadata && typeof metadata !== 'object') {
validations.push('Metadata must be an object');
}
if (validations.length > 0) {
throw new Error(`Validation failed:\n- ${validations.join('\n- ')}`);
}
// All validations passed, create transfer
return await omise.transfers.create({
recipient: recipientId,
amount: amount,
metadata: metadata
});
}
2. Implement Retry Logicโ
def create_transfer_with_retry(recipient_id, amount, max_retries=3, metadata=None):
"""Create transfer with retry logic for transient failures"""
for attempt in range(max_retries):
try:
transfer = omise.Transfer.create(
recipient=recipient_id,
amount=amount,
metadata=metadata or {}
)
return transfer
except omise.errors.InvalidRequestError as e:
# Don't retry validation errors
raise
except omise.errors.APIError as e:
if attempt < max_retries - 1:
wait_time = 2 ** attempt # Exponential backoff
print(f"Attempt {attempt + 1} failed, retrying in {wait_time}s...")
time.sleep(wait_time)
else:
raise
return None
3. Monitor and Log Transfersโ
class TransferLogger
def self.log_transfer(transfer, context = {})
log_entry = {
event: 'transfer_created',
transfer_id: transfer.id,
recipient: transfer.recipient,
amount: transfer.amount,
fee: transfer.fee,
status: transfer.sent ? 'sent' : 'pending',
created_by: context[:user_id],
timestamp: Time.now.iso8601,
metadata: transfer.metadata
}
# Save to log file
File.open('transfers.log', 'a') do |f|
f.puts JSON.generate(log_entry)
end
# Also save to database for reporting
save_to_database(log_entry)
end
def self.save_to_database(entry)
# Implement database logging
end
end
FAQโ
How long do transfers take to complete?โ
Transfers are typically completed within 1-2 business days. The exact timing depends on the recipient's bank and when the transfer was initiated. Transfers created on weekends or holidays may take longer.
What happens if a transfer fails?โ
If a transfer fails, you'll see a failure_code and failure_message on the transfer object. Common reasons include invalid account details or recipient account issues. The amount is automatically returned to your balance.
Can I cancel a transfer after it's created?โ
No, transfers cannot be canceled once created. If you need to recover funds, you would need to request the recipient to send the money back.
Are transfer fees refunded if a transfer fails?โ
Yes, if a transfer fails, both the transfer amount and fee are returned to your available balance.
Can I transfer to international bank accounts?โ
Currently, Omise supports transfers to Thai bank accounts only. For international payments, you would need to use alternative methods.
What's the maximum transfer amount?โ
There's no maximum transfer amount set by Omise, but your account balance and individual bank limits may apply. Very large transfers may require additional verification.
How do I know when a transfer is completed?โ
You can track transfer status via the API or set up webhooks to receive notifications when transfers are sent (transfer.send) or completed (transfer.pay).
Can I schedule transfers for future dates?โ
Yes, you can use Transfer Schedules to automate recurring transfers or schedule them for specific dates.
Related Resourcesโ
- Recipients Management - Create and manage recipients
- Transfer Schedules - Automate recurring transfers
- Balance Management - Monitor your account balance
- Transaction History - View transfer history
- Webhooks - Handle transfer events
Next Stepsโ
- Set up automated schedules for recurring transfers
- Monitor your account balance regularly
- Implement webhook handlers for transfer events
- Review transaction history for reporting
- Learn about reconciliation for accounting