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

振込・出金

OmiseのTransfer APIを使用して、銀行口座への出金を自動化し、受取人を管理し、定期的な振込をスケジュールして、シームレスな資金配分を実現します。

概要

振込を使用すると、Omiseアカウントの残高から銀行口座へ資金を送金できます。これは、マーケットプレイスの出金、アフィリエイト手数料、カード以外の決済方法への返金、サプライヤーへの支払い、または資金を自動的に配分する必要があるあらゆるシナリオに役立ちます。

主な機能:

  • 自動出金 - API駆動またはスケジュールされた振込
  • 複数の受取人 - 受取人情報を保存して再利用
  • 柔軟なスケジューリング - 日次、週次、または月次の振込
  • 複数通貨 - 複数の通貨をサポート
  • バッチ振込 - 複数の出金を効率的に処理
  • Webhook通知 - 振込ステータスのリアルタイム更新
  • 銀行確認 - 自動受取人銀行検証

仕組み

振込のライフサイクル:

  1. pending - 振込が作成され、処理待ち
  2. sent - 振込が銀行に送信されました
  3. paid - 受取人が資金を受け取りました
  4. failed - 振込が失敗しました(残高不足、無効な口座)

実装

ステップ 1: 受取人を作成

curl https://api.omise.co/recipients \
-u skey_test_YOUR_SECRET_KEY: \
-d "name=John Doe" \
-d "email=john@example.com" \
-d "type=individual" \
-d "bank_account[brand]=bbl" \
-d "bank_account[number]=1234567890" \
-d "bank_account[name]=John Doe"

サポートされている銀行(タイ):

  • bbl - バンコク銀行
  • kbank - カシコン銀行
  • scb - サイアム商業銀行
  • ktb - クルンタイ銀行
  • bay - アユタヤ銀行 (クルンシー)
  • tmb - TMB銀行
  • citi - シティバンク
  • uob - ユナイテッド・オーバーシーズ銀行
  • その他...

ステップ 2: 振込を作成

const transfer = await omise.transfers.create({
amount: 100000, // THB 1,000.00
recipient: recipient.id,
metadata: {
order_id: '12345',
payout_date: new Date().toISOString()
}
});

console.log('振込ID:', transfer.id);
console.log('ステータス:', transfer.status); // 'pending'

ステップ 3: 振込ステータスを確認

// 振込を取得
const transfer = await omise.transfers.retrieve('trsf_test_...');

console.log('ステータス:', transfer.status);
// 'pending' → 'sent' → 'paid' or 'failed'

if (transfer.status === 'paid') {
console.log('振込が正常に完了しました');
console.log('支払日:', transfer.paid_at);
} else if (transfer.status === 'failed') {
console.log('振込が失敗しました:', transfer.failure_message);
}

ステップ 4: Webhookを処理

app.post('/webhooks/omise', (req, res) => {
const event = req.body;

switch (event.key) {
case 'transfer.create':
handleTransferCreated(event.data);
break;

case 'transfer.send':
handleTransferSent(event.data);
break;

case 'transfer.pay':
handleTransferPaid(event.data);
break;

case 'transfer.fail':
handleTransferFailed(event.data);
break;
}

res.sendStatus(200);
});

async function handleTransferPaid(transfer) {
console.log(`振込${transfer.id}が完了しました`);

// データベースを更新
await db.payouts.update({
transfer_id: transfer.id,
status: 'completed',
paid_at: transfer.paid_at
});

// 受取人に通知
await sendPayoutConfirmation(transfer.recipient, transfer);
}

完全なマーケットプレイス出金の例

// 販売者への出金を行うマーケットプレイスプラットフォーム
const express = require('express');
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY
});

const app = express();
app.use(express.json());

// 販売者を出金用に登録
app.post('/sellers/:sellerId/register-payout', async (req, res) => {
try {
const { sellerId } = req.params;
const { bank_brand, account_number, account_name } = req.body;

const seller = await db.sellers.findOne({ id: sellerId });

// Omiseで受取人を作成
const recipient = await omise.recipients.create({
name: account_name,
email: seller.email,
type: 'individual',
bank_account: {
brand: bank_brand,
number: account_number,
name: account_name
},
metadata: {
seller_id: sellerId
}
});

// 受取人IDを保存
await db.sellers.update({
id: sellerId,
omise_recipient_id: recipient.id,
payout_enabled: true
});

res.json({
success: true,
message: '出金方法が登録されました'
});

} catch (error) {
res.status(500).json({ error: error.message });
}
});

// 販売者への出金を処理
app.post('/sellers/:sellerId/payout', async (req, res) => {
try {
const { sellerId } = req.params;
const { amount, order_ids } = req.body;

const seller = await db.sellers.findOne({ id: sellerId });

if (!seller.omise_recipient_id) {
return res.status(400).json({
error: '販売者が出金方法を登録していません'
});
}

// 最小出金額を確認
if (amount < 10000) { // ฿100 最小
return res.status(400).json({
error: '最小出金額は ฿100 です'
});
}

// 振込を作成
const transfer = await omise.transfers.create({
amount: amount,
recipient: seller.omise_recipient_id,
metadata: {
seller_id: sellerId,
order_ids: order_ids.join(','),
payout_date: new Date().toISOString()
}
});

// 出金を記録
await db.payouts.create({
seller_id: sellerId,
transfer_id: transfer.id,
amount: amount,
order_ids: order_ids,
status: 'pending',
created_at: new Date()
});

res.json({
success: true,
transfer_id: transfer.id,
status: transfer.status
});

} catch (error) {
res.status(500).json({ error: error.message });
}
});

// スケジュールされた日次出金(深夜0時に実行)
cron.schedule('0 0 * * *', async () => {
console.log('日次販売者出金を処理中...');

// 未払い収益がある販売者を検索
const sellers = await db.sellers.find({
pending_earnings: { $gte: 10000 }, // ฿100+ 最小
payout_enabled: true
});

for (const seller of sellers) {
try {
const transfer = await omise.transfers.create({
amount: seller.pending_earnings,
recipient: seller.omise_recipient_id,
metadata: {
seller_id: seller.id,
payout_type: 'daily_automatic'
}
});

// 未払い収益をリセット
await db.sellers.update({
id: seller.id,
pending_earnings: 0,
last_payout_at: new Date()
});

console.log(`${seller.name}への出金: ฿${seller.pending_earnings / 100}`);

} catch (error) {
console.error(`${seller.name}への出金失敗:`, error.message);
await notifyAdminPayoutFailed(seller, error);
}
}
});

// Webhookハンドラー
app.post('/webhooks/omise', (req, res) => {
const event = req.body;

if (event.key === 'transfer.pay') {
const transfer = event.data;

// 出金ステータスを更新
db.payouts.update({
transfer_id: transfer.id,
status: 'completed',
completed_at: new Date()
});

// 販売者に通知
const sellerId = transfer.metadata.seller_id;
sendPayoutConfirmationEmail(sellerId, transfer);
}

if (event.key === 'transfer.fail') {
const transfer = event.data;

// 失敗を処理
db.payouts.update({
transfer_id: transfer.id,
status: 'failed',
failure_reason: transfer.failure_message
});

// 販売者と管理者に通知
const sellerId = transfer.metadata.seller_id;
sendPayoutFailedEmail(sellerId, transfer);
notifyAdminPayoutFailed(transfer);
}

res.sendStatus(200);
});

app.listen(3000);

スケジュールされた振込

定期的な自動振込を作成:

// 日次振込スケジュールを作成
const schedule = await omise.schedules.create({
every: 1,
period: 'day',
start_date: '2025-02-15',
transfer: {
recipient: recipient.id,
amount: 50000, // ฿500 日次
currency: 'THB'
}
});

// 月次振込スケジュールを作成
const monthlySchedule = await omise.schedules.create({
every: 1,
period: 'month',
on: {
days_of_month: [1] // 毎月1日
},
start_date: '2025-03-01',
transfer: {
recipient: recipient.id,
amount: 1000000, // ฿10,000 月次
currency: 'THB'
}
});

ベストプラクティス

1. 受取人情報を検証

async function validateRecipient(bankAccount) {
// 口座番号形式を検証
if (!/^\d{10}$/.test(bankAccount.number)) {
throw new Error('銀行口座番号は10桁である必要があります');
}

// 口座名を検証
if (bankAccount.name.length < 3) {
throw new Error('口座名が短すぎます');
}

// 重複を確認
const existing = await db.recipients.findOne({
bank_account_number: bankAccount.number
});

if (existing) {
throw new Error('受取人は既に登録されています');
}

return true;
}

2. 最小出金額の閾値を設定

const MIN_PAYOUT = {
THB: 10000, // ฿100
SGD: 1000, // $10
MYR: 5000 // RM50
};

function validatePayoutAmount(amount, currency) {
if (amount < MIN_PAYOUT[currency]) {
return {
valid: false,
error: `最小出金額は${MIN_PAYOUT[currency] / 100} ${currency}です`
};
}

return { valid: true };
}

3. 残高不足を処理

async function createTransferSafely(transferData) {
// まず残高を確認
const account = await omise.account.retrieve();
const availableBalance = account.balance;

if (availableBalance < transferData.amount) {
throw new Error(`残高不足。利用可能: ฿${availableBalance / 100}、必要: ฿${transferData.amount / 100}`);
}

// 振込を作成
return await omise.transfers.create(transferData);
}

4. バッチ処理

async function processBatchPayouts(payouts) {
const results = [];

for (const payout of payouts) {
try {
const transfer = await omise.transfers.create({
amount: payout.amount,
recipient: payout.recipient_id
});

results.push({
payout_id: payout.id,
transfer_id: transfer.id,
status: 'success'
});

// レート制限を避けるため振込間で遅延
await sleep(1000);

} catch (error) {
results.push({
payout_id: payout.id,
status: 'failed',
error: error.message
});
}
}

return results;
}

5. 失敗した振込を再試行

async function retryFailedTransfer(originalTransferId) {
const originalTransfer = await omise.transfers.retrieve(originalTransferId);

if (originalTransfer.status !== 'failed') {
throw new Error('失敗した振込のみ再試行できます');
}

// 同じ詳細で新しい振込を作成
const retryTransfer = await omise.transfers.create({
amount: originalTransfer.amount,
recipient: originalTransfer.recipient,
metadata: {
...originalTransfer.metadata,
retry_of: originalTransferId,
retry_count: (originalTransfer.metadata.retry_count || 0) + 1
}
});

return retryTransfer;
}

よくある質問

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

振込のタイミングは異なります:

  • 作成から送信: 24時間以内
  • 送信から支払い: 1〜3営業日
  • 合計: 通常2〜4営業日

地域の特定の銀行処理時間を確認してください。

振込手数料はいくらですか?

振込手数料は地域と銀行によって異なります。現在の料金については、Omiseダッシュボードを確認するか、support@omise.coにお問い合わせください。

振込をキャンセルできますか?

振込はpendingステータスの間のみキャンセルできます(銀行に送信される前)。次を使用します:

await omise.transfers.destroy('trsf_test_...');
振込が失敗した場合はどうなりますか?

一般的な失敗の理由:

  • アカウント残高不足
  • 無効な銀行口座番号
  • 受取人口座が閉鎖されている
  • 銀行拒否(不正チェック)

失敗した振込は自動的に再試行されません。失敗メッセージを確認し、問題を解決した後に新しい振込を作成してください。

海外の銀行に振込できますか?

振込の利用可能性とサポートされている銀行は地域によって異なります。現在、ほとんどの振込はタイ、シンガポール、マレーシア国内です。国際振込機能についてはサポートにお問い合わせください。

複数の通貨をどのように処理しますか?

各通貨に対して個別の受取人を作成し、アカウント残高にその通貨で十分な資金があることを確認してください。Omiseは振込のために通貨を自動的に変換しません。

受取人は複数の振込を受け取ることができますか?

はい、受取人が作成されたら、その受取人への無制限の振込を作成できます。

関連リソース

次のステップ

  1. 受取人を作成
  2. 振込を作成
  3. Webhookを設定
  4. 出金ロジックを実装
  5. 振込をテスト
  6. 本番稼働