メインコンテンツへスキップ

銀行口座への振込の作成

振込により、Omiseアカウント残高から受取人の銀行口座に資金を送信できます。このガイドでは、単一および一括振込の作成、手数料の処理、および振込ステータスの追跡について説明します。

概要

Omiseの振込により、以下が可能になります:

  • 出金を送信: 受取人への資金振込
  • 自動化された処理: 即座またはスケジュール済み振込
  • 手数料の管理: 振込ごとの明確な手数料構造を理解
  • ステータス追跡: 振込進度をリアルタイムで監視
  • バッチ操作: 複数の振込を効率的に処理
  • 失敗の処理: 失敗した振込を適切に処理

主な機能

  • 即座振込: 資金を即座に送信(銀行処理の対象)
  • 手数料の透明性: 振込ごとの明確な手数料構造
  • 複数受取人: 任意の作成された受取人に送信
  • メタデータのサポート: カスタム情報を追跡
  • Webhook通知: リアルタイムステータス更新
  • 残高検証: 自動残高チェック

振込要件

振込を作成する前に:

  1. 十分な残高: アカウントに十分な資金がある必要があります
  2. アクティブな受取人: 受取人が検証されアクティブである必要があります
  3. 最小金額: 最小振込要件を満たす必要があります
  4. 手数料カバレッジ: 残高は振込額+手数料をカバーする必要があります

振込手数料

const TRANSFER_FEES = {
thb: {
domestic: 2500, // 振込ごと25 THB
minimum: 2000 // 最小振込20 THB
}
};

function calculateTransferCost(amount) {
return {
amount: amount,
fee: TRANSFER_FEES.thb.domestic,
total: amount + TRANSFER_FEES.thb.domestic
};
}

// 例
const cost = calculateTransferCost(100000); // 1,000 THB
console.log(`金額: ${cost.amount / 100} THB`);
console.log(`手数料: ${cost.fee / 100} THB`);
console.log(`合計控除額: ${cost.total / 100} THB`);

APIを介した振込の作成

基本的な振込

const omise = require('omise')({
secretKey: 'skey_test_123456789',
});

// 振込を作成
async function createTransfer(recipientId, amount) {
try {
// 最初に残高を確認
const balance = await omise.balance.retrieve();
const totalCost = amount + 2500; // amount + fee

if (balance.available < totalCost) {
throw new Error(`残高不足。必要: ${totalCost}、利用可能: ${balance.available}`);
}

// 振込を作成
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.id);
console.log('金額:', transfer.amount / 100, 'THB');
console.log('手数料:', transfer.fee / 100, 'THB');
console.log('ステータス:', transfer.status);

return transfer;
} catch (error) {
console.error('振込失敗:', error.message);
throw error;
}
}

// 完全な検証で振込を作成
async function createValidatedTransfer(recipientId, amount, metadata = {}) {
// 受取人を検証
const recipient = await omise.recipients.retrieve(recipientId);

if (!recipient.active) {
throw new Error('受取人がアクティブではありません');
}

if (!recipient.verified) {
throw new Error('受取人が検証されていません');
}

// 金額を検証
if (amount < 2000) { // 最小振込額20 THB
throw new Error('金額が最小振込額を下回っています');
}

// 残高をチェック
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
};
}

// 振込を作成
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
};
}

// 使用例
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(`✓ 振込${result.transfer.id}を作成しました`);
console.log(` 宛先: ${result.recipient_name}`);
console.log(` 銀行: ${result.bank}`);
} else {
console.log(`✗ 振込失敗: ${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):
"""受取人への振込を作成"""
try:
# 残高をチェック
balance = omise.Balance.retrieve()
total_cost = amount + TRANSFER_FEE

if balance.available < total_cost:
raise ValueError(f"残高不足。必要: {total_cost}、利用可能: {balance.available}")

# 振込を作成
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.id}")
print(f"金額: {transfer.amount / 100} THB")
print(f"手数料: {transfer.fee / 100} THB")
print(f"ステータス: {transfer.status}")

return transfer
except omise.errors.BaseError as e:
print(f"振込失敗: {str(e)}")
raise

def create_validated_transfer(recipient_id, amount, metadata=None):
"""完全な検証で振込を作成"""

# 受取人を検証
recipient = omise.Recipient.retrieve(recipient_id)

if not recipient.active:
raise ValueError('受取人がアクティブではありません')

if not recipient.verified:
raise ValueError('受取人が検証されていません')

# 金額を検証
if amount < MIN_AMOUNT:
raise ValueError(f'金額が最小振込額{MIN_AMOUNT}を下回っています')

# 残高をチェック
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
}

# 振込を作成
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):
"""手数料を含む総費用を計算"""
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"
}

# 使用例
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"✓ 振込{result['transfer'].id}を作成しました")
print(f" 宛先: {result['recipient_name']}")
print(f" 銀行: {result['bank']}")
else:
print(f"✗ 振込失敗: {result['reason']}")

APIレスポンス

{
"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"
}
}

振込ステータスの追跡

ステータスライフサイクル

const TRANSFER_STATUSES = {
'pending': '振込が作成され、送信待ち',
'sent': '振込が銀行に送信され、確認待ち',
'paid': '振込が正常に完了',
'failed': '振込が失敗',
'reversed': '振込が取り消されました'
};

async function trackTransferStatus(transferId) {
const transfer = await omise.transfers.retrieve(transferId);

console.log(`振込${transferId}:`);
console.log(` ステータス: ${transfer.sent ? '送信済み' : '保留中'}`);
console.log(` 支払済み: ${transfer.paid ? 'はい' : 'いいえ'}`);

if (transfer.failure_code) {
console.log(` 失敗: ${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 '失敗';
if (transfer.paid) return '完了';
if (transfer.sent) return '処理中';
return '保留中';
}

よくあるユースケース

1. 給与処理

class PayrollProcessor:
def __init__(self):
self.transfer_fee = 2500

def process_payroll(self, employees):
"""複数の従業員の給与を処理"""
results = {
'processed': [],
'failed': [],
'total_amount': 0,
'total_fees': 0
}

# 必要な総残高をチェック
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"給与処理に十分な残高がありません。必要: {grand_total}、利用可能: {balance.available}")

# 各従業員を処理
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"✓ 処理完了: {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"✗ 失敗: {employee['name']} - {str(e)}")

# レポートを生成
print(f"\n{'='*50}")
print(f"給与サマリー:")
print(f" 処理済み: {len(results['processed'])}")
print(f" 失敗: {len(results['failed'])}")
print(f" 合計金額: {results['total_amount']/100:.2f} THB")
print(f" 合計手数料: {results['total_fees']/100:.2f} THB")
print(f" 総計: {(results['total_amount'] + results['total_fees'])/100:.2f} THB")

return results

# 使用例
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)

ベストプラクティス

1. 振込前に常に検証

async function safeTransfer(recipientId, amount, metadata) {
// 振込前検証チェックリスト
const validations = [];

// 1. 受取人を検証
try {
const recipient = await omise.recipients.retrieve(recipientId);
if (!recipient.active) validations.push('受取人がアクティブではありません');
if (!recipient.verified) validations.push('受取人が検証されていません');
} catch (error) {
validations.push('受取人が見つかりません');
}

// 2. 金額を検証
if (amount < 2000) validations.push('金額が最小額を下回っています');
if (amount % 1 !== 0) validations.push('金額は整数である必要があります');

// 3. 残高をチェック
const balance = await omise.balance.retrieve();
const totalCost = amount + 2500;
if (balance.available < totalCost) {
validations.push(`残高不足: 必要${totalCost}、利用可能${balance.available}`);
}

// 4. メタデータを検証
if (metadata && typeof metadata !== 'object') {
validations.push('メタデータはオブジェクトである必要があります');
}

if (validations.length > 0) {
throw new Error(`検証失敗:\n- ${validations.join('\n- ')}`);
}

// すべての検証に合格、振込を作成
return await omise.transfers.create({
recipient: recipientId,
amount: amount,
metadata: metadata
});
}

2. 再試行ロジックを実装

def create_transfer_with_retry(recipient_id, amount, max_retries=3, metadata=None):
"""一時的な障害に対する再試行ロジック付き振込を作成"""

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:
# 検証エラーは再試行しない
raise

except omise.errors.APIError as e:
if attempt < max_retries - 1:
wait_time = 2 ** attempt # 指数バックオフ
print(f"試行{attempt + 1}が失敗しました。{wait_time}秒後に再試行します...")
time.sleep(wait_time)
else:
raise

return None

3. 振込を監視およびログに記録

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
}

# ログファイルに保存
File.open('transfers.log', 'a') do |f|
f.puts JSON.generate(log_entry)
end

# レポート用にデータベースにも保存
save_to_database(log_entry)
end

def self.save_to_database(entry)
# データベースログの実装
end
end

よくある質問

振込にはどのくらいの時間がかかりますか?

振込は通常1〜2営業日以内に完了します。正確なタイミングは受取人の銀行と振込が開始された時刻によって異なります。週末または祝日に作成された振込は時間がかかる場合があります。

振込が失敗した場合はどうなりますか?

振込が失敗した場合、振込オブジェクトに「failure_code」と「failure_message」が表示されます。一般的な理由は、無効なアカウント詳細または受取人口座の問題です。金額は自動的に残高に返金されます。

振込を作成した後でキャンセルできますか?

いいえ、振込を作成した後でキャンセルすることはできません。資金を回収する必要がある場合は、受取人に資金を返送するよう要求する必要があります。

振込が失敗した場合、振込手数料は払い戻されますか?

はい、振込が失敗した場合、振込額と手数料の両方が利用可能な残高に返金されます。

国際銀行口座に振込できますか?

現在、Omiseはタイの銀行口座への振込のみをサポートしています。国際送金については、他の方法を使用する必要があります。

最大振込額はいくらですか?

Omiseによって設定された最大振込額はありませんが、アカウント残高と個別の銀行制限が適用される場合があります。非常に大きな振込には追加の確認が必要な場合があります。

振込が完了したことをどのように知ることができますか?

APIを介して振込ステータスを追跡することも、振込が送信される(transfer.send)または完了される(transfer.pay)ときに通知を受け取るようにWebhookを設定することもできます。

将来の日付の振込をスケジュールできますか?

はい、振込スケジュールを使用して、定期的な振込をスケジュールするか、特定の日付にスケジュールすることができます。

関連リソース

次のステップ