Refundsの作成
Refundsを使用すると、正常にchargeされたトランザクションのお金をお客様に返すことができます。このガイドでは、APIとDashboardの両方を通じて全額返金を作成する方法について説明します。
概要
Refundsは、返品、キャンセル、お客様のdispute、その他の支払いを取り消す必要があるシナリオを処理するために不可欠です。返金を作成すると:
- 全額のcharge金額がお客様の決済方法に返される
- 資金は通常5-10営業日以内に返される(カード発行会社により異なる)
- 返金ステータスはプロセス全体を通じて追跡できる
- 元のトランザクション手数料はデフォルトでは返されない
- chargeは返金ステータスでトランザクション履歴に残る
主な機能
- 即時処理: RefundsはAPI経由で即座に処理される
- 複数の決済方法: カード返金および代替決済方法の返金をサポート
- ステータス追跡: リアルタイムの返金ステータス更新
- 自動通知: お客様への通知(設定されている場合)
- Metadataサポート: 返金理由を追跡するためのカスタムmetadataを追加
- Webhookイベント: 返金ステータスが変更されたときに通知を受け取る
Refundsを使用する場合
返金を作成する一般的なシナリオ:
- 商品の返品: お客様が商品を返品
- サービスのキャンセル: お客様がサービスまたはサブスクリプションをキャンセル
- 重複charge: 誤って二重charge
- お客様のDispute: お客様の苦情の解決
- 欠陥商品: 品質基準を満たさないアイテム
- 在庫切れ: 注文を履行できない
- 価格調整: 請求エラーの修正
APIを介したRefundsの作成
全額返金
金額を指定せずに全額返金を作成:
- Node.js
- Python
- Ruby
- PHP
- Go
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');
import omise
from datetime import datetime
omise.api_secret = 'skey_test_123456789'
def create_full_refund(charge_id):
"""Create a full refund for a charge"""
try:
refund = omise.Charge.retrieve(charge_id).refund(
metadata={
'reason': 'Customer requested refund',
'order_id': 'ORD-12345',
'refund_type': 'product_return'
}
)
print(f"Refund created: {refund.id}")
print(f"Amount refunded: {refund.amount}")
print(f"Status: {refund.status}")
print(f"Created at: {datetime.fromtimestamp(refund.created)}")
return refund
except omise.errors.BaseError as e:
print(f"Refund failed: {str(e)}")
raise
def create_refund_with_tracking(charge_id, reason):
"""Create refund with detailed tracking"""
try:
charge = omise.Charge.retrieve(charge_id)
refund = charge.refund(
metadata={
'reason': reason,
'initiated_by': 'customer_service',
'timestamp': datetime.now().isoformat()
}
)
# Log for audit trail
print(f"Refund {refund.id} created for charge {charge_id}")
print(f"Reason: {reason}")
return {
'success': True,
'refund_id': refund.id,
'amount': refund.amount,
'currency': refund.currency,
'status': refund.status
}
except omise.errors.BaseError as e:
return {
'success': False,
'error': str(e),
'code': e.code if hasattr(e, 'code') else None
}
# Example usage
create_full_refund('chrg_test_123456789')
create_refund_with_tracking('chrg_test_123456789', 'Product defective')
require 'omise'
Omise.api_key = 'skey_test_123456789'
# Create a full refund
def create_full_refund(charge_id)
begin
charge = Omise::Charge.retrieve(charge_id)
refund = charge.refund(
metadata: {
reason: 'Customer requested refund',
order_id: 'ORD-12345',
refund_type: 'product_return'
}
)
puts "Refund created: #{refund.id}"
puts "Amount refunded: #{refund.amount}"
puts "Status: #{refund.status}"
puts "Created at: #{Time.at(refund.created)}"
refund
rescue Omise::Error => e
puts "Refund failed: #{e.message}"
raise
end
end
# Create refund with tracking
def create_refund_with_tracking(charge_id, reason)
begin
charge = Omise::Charge.retrieve(charge_id)
refund = charge.refund(
metadata: {
reason: reason,
initiated_by: 'customer_service',
timestamp: Time.now.iso8601
}
)
# Log for audit trail
puts "Refund #{refund.id} created for charge #{charge_id}"
puts "Reason: #{reason}"
{
success: true,
refund_id: refund.id,
amount: refund.amount,
currency: refund.currency,
status: refund.status
}
rescue Omise::Error => e
{
success: false,
error: e.message,
code: e.code
}
end
end
# Example usage
create_full_refund('chrg_test_123456789')
create_refund_with_tracking('chrg_test_123456789', 'Product defective')
<?php
require_once 'vendor/autoload.php';
define('OMISE_SECRET_KEY', 'skey_test_123456789');
// Create a full refund
function createFullRefund($chargeId) {
try {
$charge = OmiseCharge::retrieve($chargeId);
$refund = $charge->refund([
'metadata' => [
'reason' => 'Customer requested refund',
'order_id' => 'ORD-12345',
'refund_type' => 'product_return'
]
]);
echo "Refund created: {$refund['id']}\n";
echo "Amount refunded: {$refund['amount']}\n";
echo "Status: {$refund['status']}\n";
echo "Created at: " . date('Y-m-d H:i:s', $refund['created']) . "\n";
return $refund;
} catch (Exception $e) {
echo "Refund failed: {$e->getMessage()}\n";
throw $e;
}
}
// Create refund with tracking
function createRefundWithTracking($chargeId, $reason) {
try {
$charge = OmiseCharge::retrieve($chargeId);
$refund = $charge->refund([
'metadata' => [
'reason' => $reason,
'initiated_by' => 'customer_service',
'timestamp' => date('c')
]
]);
// Log for audit trail
echo "Refund {$refund['id']} created for charge {$chargeId}\n";
echo "Reason: {$reason}\n";
return [
'success' => true,
'refund_id' => $refund['id'],
'amount' => $refund['amount'],
'currency' => $refund['currency'],
'status' => $refund['status']
];
} catch (Exception $e) {
return [
'success' => false,
'error' => $e->getMessage(),
'code' => $e->getCode()
];
}
}
// Example usage
createFullRefund('chrg_test_123456789');
createRefundWithTracking('chrg_test_123456789', 'Product defective');
?>
package main
import (
"fmt"
"log"
"time"
"github.com/omise/omise-go"
"github.com/omise/omise-go/operations"
)
const secretKey = "skey_test_123456789"
// CreateFullRefund creates a full refund for a charge
func CreateFullRefund(chargeID string) (*omise.Refund, error) {
client, err := omise.NewClient(secretKey, "")
if err != nil {
return nil, fmt.Errorf("failed to create client: %w", err)
}
refund := &omise.Refund{}
err = client.Do(refund, &operations.CreateRefund{
ChargeID: chargeID,
Metadata: map[string]interface{}{
"reason": "Customer requested refund",
"order_id": "ORD-12345",
"refund_type": "product_return",
},
})
if err != nil {
return nil, fmt.Errorf("refund failed: %w", err)
}
fmt.Printf("Refund created: %s\n", refund.ID)
fmt.Printf("Amount refunded: %d\n", refund.Amount)
fmt.Printf("Status: %s\n", refund.Status)
fmt.Printf("Created at: %s\n", refund.Created.Format(time.RFC3339))
return refund, nil
}
// RefundResult represents the result of a refund operation
type RefundResult struct {
Success bool `json:"success"`
RefundID string `json:"refund_id,omitempty"`
Amount int64 `json:"amount,omitempty"`
Currency string `json:"currency,omitempty"`
Status string `json:"status,omitempty"`
Error string `json:"error,omitempty"`
Code string `json:"code,omitempty"`
}
// CreateRefundWithTracking creates a refund with detailed tracking
func CreateRefundWithTracking(chargeID, reason string) *RefundResult {
client, err := omise.NewClient(secretKey, "")
if err != nil {
return &RefundResult{
Success: false,
Error: err.Error(),
}
}
refund := &omise.Refund{}
err = client.Do(refund, &operations.CreateRefund{
ChargeID: chargeID,
Metadata: map[string]interface{}{
"reason": reason,
"initiated_by": "customer_service",
"timestamp": time.Now().Format(time.RFC3339),
},
})
if err != nil {
return &RefundResult{
Success: false,
Error: err.Error(),
}
}
// Log for audit trail
fmt.Printf("Refund %s created for charge %s\n", refund.ID, chargeID)
fmt.Printf("Reason: %s\n", reason)
return &RefundResult{
Success: true,
RefundID: refund.ID,
Amount: refund.Amount,
Currency: refund.Currency,
Status: string(refund.Status),
}
}
func main() {
// Example usage
refund, err := CreateFullRefund("chrg_test_123456789")
if err != nil {
log.Fatalf("Failed to create refund: %v", err)
}
result := CreateRefundWithTracking("chrg_test_123456789", "Product defective")
if !result.Success {
log.Printf("Refund failed: %s", result.Error)
}
}
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の作成
ステップバイステップのプロセス
-
トランザクションに移動
- Omise Dashboardにログイン
- "Transactions" > "Charges"に移動
-
Chargeを見つける
- 検索バーを使用してcharge IDを見つける
- または日付、金額、またはステータスでフィルタリング
- chargeをクリックして詳細を表示
-
Refundを開始
- "Refund"ボタンをクリック
- 全額のcharge金額が事前入力される
- 一部返金の場合は金額を変更可能
-
Refund詳細を追加 (オプション)
- 返金理由を入力
- 内部メモを追加
- 返金カテゴリを選択(返品、キャンセルなど)
-
Refundを確認
- 返金詳細を確認
- "Confirm Refund"をクリック
- 返金は即座に処理される
-
ステータスを追跡
- 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);