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

一部返金

一部返金を使用すると、お客様にcharge金額の一部を返すことができます。これは、一部返品、価格調整、送料返金、または全額返金が必要ない状況で役立ちます。

概要

一部返金は、返品と調整の処理方法に柔軟性を提供します:

  • 柔軟な金額: 元のcharge金額までの任意の金額を返金
  • 複数の返金: 完全に返金されるまで複数の一部返金を作成
  • 残高の追跡: まだ返金できる金額を追跡
  • 同じプロセス: 全額返金と同じAPIを使用、金額を指定するだけ
  • 監査証跡: すべての一部返金がcharge上で追跡される

主な機能

  • 正確な金額コントロール: 正確な返金額を指定
  • 通貨対応: chargeの通貨を自動的に使用
  • 残高追跡: 残りの返金可能金額を表示
  • Metadataサポート: 各返金にコンテキストをタグ付け
  • カウント制限なし: 必要な数だけ一部返金を作成
  • 自動検証: システムが過剰返金を防止

一部返金を使用する場合

一部返金の一般的なシナリオ:

  • 一部商品返品: お客様が注文からいくつかのアイテムを返品
  • 価格調整: 請求エラーまたは割引の適用を修正
  • 送料返金: 配送料のみを返金
  • 損傷したアイテム: 損傷した商品の一部補償
  • サービスクレジット: サービス問題の一部返金を提供
  • プロモーション調整: 購入後のクーポン適用
  • 数量の修正: 不正確な数量の返金
  • バンドル調整: バンドルから特定のアイテムを返金

APIを介した一部返金の作成

基本的な一部返金

amountパラメータを指定して一部返金を作成:

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

// 一部返金を作成
async function createPartialRefund(chargeId, amount) {
try {
const refund = await omise.charges.createRefund(chargeId, {
amount: amount, // 最小通貨単位での金額
metadata: {
reason: 'Partial product return',
items_returned: '2 out of 5 items'
}
});

console.log('Partial refund created:', refund.id);
console.log('Amount refunded:', refund.amount);
console.log('Remaining refundable:', calculateRemaining(refund));

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

// 残りの返金可能金額を計算
function calculateRemaining(refund) {
return omise.charges.retrieve(refund.charge).then(charge => {
const totalRefunded = charge.refunded_amount || 0;
const remaining = charge.amount - totalRefunded;
return remaining;
});
}

// 送料のみを返金
async function refundShippingCost(chargeId, shippingAmount) {
const refund = await omise.charges.createRefund(chargeId, {
amount: shippingAmount,
metadata: {
reason: 'shipping_refund',
type: 'delivery_fee'
}
});

console.log(`Refunded shipping: ${shippingAmount / 100} THB`);
return refund;
}

// 使用例
createPartialRefund('chrg_test_123456789', 50000); // 500 THBを返金
refundShippingCost('chrg_test_123456789', 10000); // 100 THBの送料を返金

APIレスポンス

{
"object": "refund",
"id": "rfnd_test_5xyz789abc",
"location": "/charges/chrg_test_123456789/refunds/rfnd_test_5xyz789abc",
"amount": 50000,
"currency": "thb",
"charge": "chrg_test_123456789",
"transaction": "trxn_test_5xyz789abc",
"created": "2024-01-15T10:30:00Z",
"status": "pending",
"metadata": {
"reason": "Partial product return",
"items_returned": "2 out of 5 items"
}
}

複数の一部返金

単一のchargeに対して複数の一部返金を作成できます:

async function handleMultiplePartialRefunds(chargeId) {
// 元のcharge: 100,000 (1,000 THB)

// 最初の一部返金 - 送料
const refund1 = await omise.charges.createRefund(chargeId, {
amount: 10000, // 100 THB
metadata: { reason: 'shipping_refund' }
});

// 2番目の一部返金 - 1つのアイテム
const refund2 = await omise.charges.createRefund(chargeId, {
amount: 30000, // 300 THB
metadata: { reason: 'item_return', item: 'Product A' }
});

// 3番目の一部返金 - 別のアイテム
const refund3 = await omise.charges.createRefund(chargeId, {
amount: 25000, // 250 THB
metadata: { reason: 'item_return', item: 'Product B' }
});

// 残りを確認: 100,000 - 10,000 - 30,000 - 25,000 = 35,000 (350 THB)
const charge = await omise.charges.retrieve(chargeId);
console.log('Total refunded:', charge.refunded_amount);
console.log('Remaining:', charge.amount - charge.refunded_amount);

return [refund1, refund2, refund3];
}

一般的な使用例

1. ラインアイテムの返金

注文から特定のアイテムを返金:

class OrderRefundManager:
def __init__(self, charge_id):
self.charge_id = charge_id
self.charge = omise.Charge.retrieve(charge_id)

def refund_items(self, item_ids):
"""IDで特定のアイテムを返金"""
order = self.get_order_details()

# 返金額を計算
refund_amount = 0
refunded_items = []

for item_id in item_ids:
item = next((i for i in order['items'] if i['id'] == item_id), None)
if item:
refund_amount += item['price'] * item['quantity']
refunded_items.append(item)

# 返金を作成
refund = self.charge.refund(
amount=refund_amount,
metadata={
'reason': 'line_item_refund',
'refunded_items': str(refunded_items),
'item_count': len(refunded_items)
}
)

# 注文を更新
self.update_order_items(item_ids, 'refunded')

return refund

def refund_by_percentage(self, percentage):
"""合計chargeのパーセンテージを返金"""
if not 0 < percentage <= 100:
raise ValueError("Percentage must be between 0 and 100")

refund_amount = int(self.charge.amount * percentage / 100)

refund = self.charge.refund(
amount=refund_amount,
metadata={
'reason': 'percentage_refund',
'percentage': percentage
}
)

return refund

# 使用例
manager = OrderRefundManager('chrg_test_123456789')
manager.refund_items(['item_1', 'item_3'])
manager.refund_by_percentage(10) # 10%割引

2. サブスクリプションの日割り計算

サブスクリプションキャンセルの日割り返金:

class SubscriptionRefund {
constructor(chargeId, subscriptionStart, subscriptionEnd) {
this.chargeId = chargeId;
this.subscriptionStart = new Date(subscriptionStart);
this.subscriptionEnd = new Date(subscriptionEnd);
}

async calculateProratedRefund(cancellationDate) {
const charge = await omise.charges.retrieve(this.chargeId);

// 日数を計算
const totalDays = this.daysBetween(this.subscriptionStart, this.subscriptionEnd);
const usedDays = this.daysBetween(this.subscriptionStart, cancellationDate);
const remainingDays = totalDays - usedDays;

// 返金額を計算
const dailyRate = charge.amount / totalDays;
const refundAmount = Math.floor(dailyRate * remainingDays);

return {
totalAmount: charge.amount,
totalDays: totalDays,
usedDays: usedDays,
remainingDays: remainingDays,
refundAmount: refundAmount,
effectiveRate: dailyRate
};
}

async processProratedRefund(cancellationDate, reason) {
const calculation = await this.calculateProratedRefund(cancellationDate);

const refund = await omise.charges.createRefund(this.chargeId, {
amount: calculation.refundAmount,
metadata: {
reason: 'subscription_cancellation_prorated',
cancellation_date: cancellationDate.toISOString(),
subscription_start: this.subscriptionStart.toISOString(),
subscription_end: this.subscriptionEnd.toISOString(),
total_days: calculation.totalDays,
used_days: calculation.usedDays,
remaining_days: calculation.remainingDays,
cancellation_reason: reason
}
});

return {
refund: refund,
calculation: calculation
};
}

daysBetween(date1, date2) {
const oneDay = 24 * 60 * 60 * 1000;
return Math.round(Math.abs((date2 - date1) / oneDay));
}
}

// 使用例
const subsRefund = new SubscriptionRefund(
'chrg_test_123456789',
'2024-01-01',
'2024-01-31'
);

const result = await subsRefund.processProratedRefund(
new Date('2024-01-15'),
'Customer requested cancellation'
);

console.log(`Refunding ${result.calculation.refundAmount} for ${result.calculation.remainingDays} unused days`);

返金残高の追跡

返金された金額と残りを監視:

class RefundTracker:
def __init__(self, charge_id):
self.charge_id = charge_id
self.refresh()

def refresh(self):
"""chargeデータを更新"""
self.charge = omise.Charge.retrieve(self.charge_id)

def get_refund_summary(self):
"""包括的な返金サマリーを取得"""
self.refresh()

refunds = self.charge.refunds.data if hasattr(self.charge, 'refunds') else []

return {
'charge_id': self.charge_id,
'original_amount': self.charge.amount,
'refunded_amount': self.charge.refunded_amount,
'remaining_amount': self.charge.amount - self.charge.refunded_amount,
'refund_count': len(refunds),
'fully_refunded': self.charge.refunded,
'currency': self.charge.currency,
'refunds': [
{
'id': r.id,
'amount': r.amount,
'status': r.status,
'created': r.created,
'reason': r.metadata.get('reason', 'N/A') if r.metadata else 'N/A'
}
for r in refunds
]
}

def can_refund(self, amount):
"""金額を返金できるかを確認"""
self.refresh()
remaining = self.charge.amount - self.charge.refunded_amount
return amount <= remaining

def get_refund_percentage(self):
"""返金されたchargeのパーセンテージを取得"""
self.refresh()
if self.charge.amount == 0:
return 0
return (self.charge.refunded_amount / self.charge.amount) * 100

# 使用例
tracker = RefundTracker('chrg_test_123456789')
summary = tracker.get_refund_summary()

if tracker.can_refund(50000):
print("Can refund 500 THB")
else:
print("Insufficient remaining balance")

ベストプラクティス

1. 返金額を検証

常に返金を作成する前に検証:

async function safePartialRefund(chargeId, amount, reason) {
// chargeを取得
const charge = await omise.charges.retrieve(chargeId);

// 金額を検証
if (amount <= 0) {
throw new Error('Refund amount must be positive');
}

const remaining = charge.amount - charge.refunded_amount;
if (amount > remaining) {
throw new Error(`Amount exceeds remaining refundable balance. Remaining: ${remaining}`);
}

// 通貨を検証(金額が最小単位であることを確認)
if (amount % 1 !== 0) {
throw new Error('Amount must be an integer (smallest currency unit)');
}

// 返金を作成
const refund = await omise.charges.createRefund(chargeId, {
amount: amount,
metadata: {
reason: reason,
validated_at: new Date().toISOString()
}
});

return refund;
}

2. 詳細な記録を維持

class RefundAuditLog
def self.log_refund(charge_id, refund, context = {})
log_entry = {
timestamp: Time.now.iso8601,
charge_id: charge_id,
refund_id: refund.id,
amount: refund.amount,
currency: refund.currency,
status: refund.status,
reason: refund.metadata['reason'],
user_id: context[:user_id],
ip_address: context[:ip_address],
user_agent: context[:user_agent],
notes: context[:notes]
}

# データベースまたはログシステムに保存
save_audit_log(log_entry)

# コンプライアンスのためにファイルにもログ
File.open('refund_audit.log', 'a') do |f|
f.puts JSON.generate(log_entry)
end
end
end

3. エッジケースを処理

function createRefundWithValidation($chargeId, $amount, $metadata = []) {
try {
$charge = OmiseCharge::retrieve($chargeId);

// chargeが返金可能かを確認
if (!$charge['paid']) {
throw new Exception('Charge has not been paid yet');
}

if ($charge['refunded']) {
throw new Exception('Charge has already been fully refunded');
}

// 残高を確認
$remaining = $charge['amount'] - $charge['refunded_amount'];
if ($amount > $remaining) {
throw new Exception("Amount exceeds remaining balance of {$remaining}");
}

// 返金を作成
$refund = $charge->refund([
'amount' => $amount,
'metadata' => $metadata
]);

return [
'success' => true,
'refund' => $refund
];

} catch (Exception $e) {
return [
'success' => false,
'error' => $e->getMessage()
];
}
}

FAQ

最小返金額はいくらですか?

最小返金額は通貨によって異なります。タイバーツ(THB)の場合、最小は1サタン(0.01 THB)です。常に最小通貨単位(THBの場合はサタン、USDの場合はセントなど)で金額を指定してください。

元のcharge金額より多く返金できますか?

いいえ、すべての返金(全額および一部)の合計は元のcharge金額を超えることはできません。過剰に返金しようとすると、APIはエラーを返します。

1つのchargeに対していくつの一部返金を作成できますか?

合計が元のcharge金額を超えない限り、作成できる一部返金の数に制限はありません。chargeが完全に返金されるまで、必要な数だけ一部返金を作成できます。

残高より多く返金しようとするとどうなりますか?

APIは、返金額が利用可能な残高を超えていることを示すinvalid_requestコードのエラーを返します。返金を作成する前に、常に残りの返金可能金額を確認してください。

関連リソース

次のステップ