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

Refundsの作成

Refundsを使用すると、正常にchargeされたトランザクションのお金をお客様に返すことができます。このガイドでは、APIとDashboardの両方を通じて全額返金を作成する方法について説明します。

概要

Refundsは、返品、キャンセル、お客様のdispute、その他の支払いを取り消す必要があるシナリオを処理するために不可欠です。返金を作成すると:

  • 全額のcharge金額がお客様の決済方法に返される
  • 資金は通常5-10営業日以内に返される(カード発行会社により異なる)
  • 返金ステータスはプロセス全体を通じて追跡できる
  • 元のトランザクション手数料はデフォルトでは返されない
  • chargeは返金ステータスでトランザクション履歴に残る

主な機能

  • 即時処理: RefundsはAPI経由で即座に処理される
  • 複数の決済方法: カード返金および代替決済方法の返金をサポート
  • ステータス追跡: リアルタイムの返金ステータス更新
  • 自動通知: お客様への通知(設定されている場合)
  • Metadataサポート: 返金理由を追跡するためのカスタムmetadataを追加
  • Webhookイベント: 返金ステータスが変更されたときに通知を受け取る

Refundsを使用する場合

返金を作成する一般的なシナリオ:

  • 商品の返品: お客様が商品を返品
  • サービスのキャンセル: お客様がサービスまたはサブスクリプションをキャンセル
  • 重複charge: 誤って二重charge
  • お客様のDispute: お客様の苦情の解決
  • 欠陥商品: 品質基準を満たさないアイテム
  • 在庫切れ: 注文を履行できない
  • 価格調整: 請求エラーの修正

APIを介したRefundsの作成

全額返金

金額を指定せずに全額返金を作成:

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

// Create a full refund
async function createFullRefund() {
try {
const refund = await omise.charges.createRefund('chrg_test_123456789', {
metadata: {
reason: 'Customer requested refund',
order_id: 'ORD-12345',
refund_type: 'product_return'
}
});

console.log('Refund created:', refund.id);
console.log('Amount refunded:', refund.amount);
console.log('Status:', refund.status);
console.log('Created at:', new Date(refund.created * 1000));

return refund;
} catch (error) {
console.error('Refund failed:', error.message);
throw error;
}
}

// Create refund with webhook handling
async function createRefundWithTracking(chargeId, reason) {
try {
const refund = await omise.charges.createRefund(chargeId, {
metadata: {
reason: reason,
initiated_by: 'customer_service',
timestamp: new Date().toISOString()
}
});

// Log for audit trail
console.log(`Refund ${refund.id} created for charge ${chargeId}`);
console.log(`Reason: ${reason}`);

return {
success: true,
refundId: refund.id,
amount: refund.amount,
currency: refund.currency,
status: refund.status
};
} catch (error) {
return {
success: false,
error: error.message,
code: error.code
};
}
}

// Example usage
createFullRefund();
createRefundWithTracking('chrg_test_123456789', 'Product defective');

APIレスポンス

成功した返金作成は以下を返します:

{
"object": "refund",
"id": "rfnd_test_5xyz789abc",
"location": "/charges/chrg_test_123456789/refunds/rfnd_test_5xyz789abc",
"amount": 100000,
"currency": "thb",
"charge": "chrg_test_123456789",
"transaction": "trxn_test_5xyz789abc",
"created": "2024-01-15T10:30:00Z",
"status": "pending",
"metadata": {
"reason": "Customer requested refund",
"order_id": "ORD-12345",
"refund_type": "product_return"
},
"funding_amount": 100000,
"funding_currency": "thb"
}

Dashboardを介したRefundsの作成

ステップバイステップのプロセス

  1. トランザクションに移動

    • Omise Dashboardにログイン
    • "Transactions" > "Charges"に移動
  2. Chargeを見つける

    • 検索バーを使用してcharge IDを見つける
    • または日付、金額、またはステータスでフィルタリング
    • chargeをクリックして詳細を表示
  3. Refundを開始

    • "Refund"ボタンをクリック
    • 全額のcharge金額が事前入力される
    • 一部返金の場合は金額を変更可能
  4. Refund詳細を追加 (オプション)

    • 返金理由を入力
    • 内部メモを追加
    • 返金カテゴリを選択(返品、キャンセルなど)
  5. Refundを確認

    • 返金詳細を確認
    • "Confirm Refund"をクリック
    • 返金は即座に処理される
  6. ステータスを追跡

    • charge詳細ページで返金ステータスを表示
    • すべての返金試行について"Refunds"セクションを確認
    • ステータス更新のためのwebhookイベントを監視

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

返金ステータスの理解:

Pending

  • Refundが作成され、処理中
  • 通常数分かかる
  • お客様はまだ資金を受け取っていない

Successful

  • Refundが完了
  • 資金は決済プロバイダーに送信された
  • お客様は5-10営業日以内に資金を受け取る

Failed

  • Refundを処理できなかった
  • 一般的な理由: 残高不足、無効なカード、期限切れのカード
  • 問題が続く場合はサポートに連絡

ステータスフロー図

Refundを作成 → Pending → Successful → お客様に資金が返される

Failed → サポートに連絡

一般的な使用例

1. 商品返品フロー

async function handleProductReturn(orderId, chargeId) {
// 注文ステータスを更新
await updateOrderStatus(orderId, 'return_requested');

// Refundを作成
const refund = await omise.charges.createRefund(chargeId, {
metadata: {
reason: 'product_return',
order_id: orderId,
return_tracking: 'TRACK123456',
warehouse_received: 'pending'
}
});

// お客様に通知を送信
await sendCustomerEmail({
template: 'refund_initiated',
orderId: orderId,
refundId: refund.id,
estimatedDays: '5-10 business days'
});

return refund;
}

2. サービスキャンセル

def handle_subscription_cancellation(subscription_id, charge_id, prorate=True):
"""Handle subscription cancellation with optional proration"""

# Calculate refund amount if prorating
if prorate:
charge = omise.Charge.retrieve(charge_id)
days_used = calculate_days_used(subscription_id)
days_in_period = 30
refund_amount = int(charge.amount * (days_in_period - days_used) / days_in_period)
else:
refund_amount = None # Full refund

# Create refund
refund = omise.Charge.retrieve(charge_id).refund(
amount=refund_amount,
metadata={
'reason': 'subscription_cancellation',
'subscription_id': subscription_id,
'prorated': prorate
}
)

# Cancel subscription
cancel_subscription(subscription_id)

return refund

3. 重複Chargeの処理

def handle_duplicate_charge(original_charge_id, duplicate_charge_id)
begin
# Verify it's actually a duplicate
original = Omise::Charge.retrieve(original_charge_id)
duplicate = Omise::Charge.retrieve(duplicate_charge_id)

if original.amount == duplicate.amount &&
original.customer == duplicate.customer &&
(duplicate.created - original.created) < 300 # Within 5 minutes

# Refund the duplicate
refund = duplicate.refund(
metadata: {
reason: 'duplicate_charge',
original_charge: original_charge_id,
auto_detected: true
}
)

# Log the incident
log_duplicate_charge(original_charge_id, duplicate_charge_id)

# Notify customer
send_apology_email(duplicate.customer)

return { success: true, refund: refund }
else
return { success: false, reason: 'not_duplicate' }
end
rescue Omise::Error => e
return { success: false, error: e.message }
end
end

ベストプラクティス

1. 常にMetadataを追加

すべての返金にコンテキストを含める:

const refund = await omise.charges.createRefund(chargeId, {
metadata: {
reason: 'customer_request',
category: 'product_return',
requested_by: 'customer_service_rep_42',
ticket_id: 'SUPPORT-12345',
timestamp: new Date().toISOString()
}
});

2. 適切なエラー処理を実装

def safe_refund(charge_id, reason):
"""Create refund with comprehensive error handling"""
try:
refund = omise.Charge.retrieve(charge_id).refund(
metadata={'reason': reason}
)
return {'success': True, 'refund_id': refund.id}

except omise.errors.InvalidRequestError as e:
# Charge already refunded or doesn't exist
return {'success': False, 'error': 'invalid_request', 'message': str(e)}

except omise.errors.AuthenticationError as e:
# API key issues
return {'success': False, 'error': 'authentication', 'message': str(e)}

except omise.errors.BaseError as e:
# Other Omise errors
return {'success': False, 'error': 'api_error', 'message': str(e)}

except Exception as e:
# Unexpected errors
return {'success': False, 'error': 'unknown', 'message': str(e)}

3. Refundステータスを追跡

def track_refund_status(charge_id, refund_id)
charge = Omise::Charge.retrieve(charge_id)
refund = charge.refunds.retrieve(refund_id)

case refund.status
when 'pending'
puts "Refund is being processed"
when 'successful'
puts "Refund completed successfully"
notify_customer_refund_complete(charge.customer)
when 'failed'
puts "Refund failed: #{refund.failure_message}"
escalate_to_support(refund_id)
end

refund
end

エラー処理

一般的なエラー

すでに返金済み

// Error: charge_already_refunded
{
"object": "error",
"location": "https://www.omise.co/api-errors#charge-already-refunded",
"code": "charge_already_refunded",
"message": "charge was already fully refunded"
}

// このエラーを処理
try {
const refund = await omise.charges.createRefund(chargeId);
} catch (error) {
if (error.code === 'charge_already_refunded') {
console.log('This charge has already been refunded');
// 既存の返金を確認
const charge = await omise.charges.retrieve(chargeId);
console.log('Existing refunds:', charge.refunds.data);
}
}

残高不足

# Error: insufficient_fund
try:
refund = omise.Charge.retrieve(charge_id).refund()
except omise.errors.InvalidRequestError as e:
if 'insufficient' in str(e).lower():
print("Insufficient balance to process refund")
# 財務チームに連絡するか決済を待つ
notify_finance_team(charge_id)

無効なCharge

# Error: invalid_charge
begin
refund = Omise::Charge.retrieve(charge_id).refund
rescue Omise::InvalidRequestError => e
if e.message.include?('not found')
puts "Charge not found or invalid"
# Charge IDを確認
end
end

Webhook統合

Refund webhookイベントを処理:

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

if (event.key === 'refund.create') {
const refund = event.data;
console.log(`Refund created: ${refund.id}`);

// データベースを更新
await updateRefundStatus(refund.charge, refund.id, 'pending');

} else if (event.key === 'refund.success') {
const refund = event.data;
console.log(`Refund successful: ${refund.id}`);

// 注文ステータスを更新
await updateOrderStatus(refund.metadata.order_id, 'refunded');

// お客様に通知
await notifyCustomer(refund.charge, 'refund_complete');

} else if (event.key === 'refund.fail') {
const refund = event.data;
console.log(`Refund failed: ${refund.id}`);

// サポートにエスカレーション
await createSupportTicket({
type: 'refund_failed',
refundId: refund.id,
chargeId: refund.charge
});
}

res.sendStatus(200);
});

Refundsのテスト

テストモード

テストAPIキーを使用して返金フローをテスト:

// テスト返金の作成
const testRefund = await omise.charges.createRefund('chrg_test_123', {
metadata: { test: true }
});

// 返金詳細を確認
console.assert(testRefund.status === 'pending');
console.assert(testRefund.amount === testCharge.amount);

FAQ

chargeが作成された直後に返金できますか?

はい、chargeがsuccessfulステータスになるとすぐに返金できます。ただし、chargeがまだpendingステータスの場合は、返金を開始する前に完了するのを待つ必要があります。

お客様が返金を受け取るまでどのくらいかかりますか?

返金のタイミングは決済方法とカード発行会社によって異なります:

  • クレジットカード: 通常5-10営業日
  • デビットカード: 通常5-10営業日
  • 代替決済方法: 方法によって異なる

返金はOmise側で即座に処理されますが、お客様のアカウントへの実際のクレジットは銀行またはカード発行会社によって異なります。

返金手数料は返されますか?

いいえ、Omiseが請求した元のトランザクション手数料は返金されません。返金しても、元のchargeのトランザクション手数料は引き続き請求されます。これはすべての決済プロセッサで標準的な慣行です。

別のカードに返金できますか?

いいえ、返金はchargeに使用された元の決済方法に返す必要があります。これはセキュリティ要件であり、回避できません。

お客様のカードが期限切れの場合はどうなりますか?

元のカードが期限切れでも、返金は通常処理され、お客様の銀行がアカウントにクレジットします。ただし、場合によっては返金が失敗することがあります。問題が発生した場合はサポートに連絡してください。

関連リソース

次のステップ