一部返金
一部返金を使用すると、お客様にcharge金額の一部を返すことができます。これは、一部返品、価格調整、送料返金、または全額返金が必要ない状況で役立ちます。
概要
一部返金は、返品と調整の処理方法に柔軟性を提供します:
- 柔軟な金額: 元のcharge金額までの任意の金額を返金
- 複数の返金: 完全に返金されるまで複数の一部返金を作成
- 残高の追跡: まだ返金できる金額を追跡
- 同じプロセス: 全額返金と同じAPIを使用、金額を指定するだけ
- 監査証跡: すべての一部返金がcharge上で追跡される
主な機能
- 正確な金額コントロール: 正確な返金額を指定
- 通貨対応: chargeの通貨を自動的に使用
- 残高追跡: 残りの返金可能金額を表示
- Metadataサポート: 各返金にコンテキストをタグ付け
- カウント制限なし: 必要な数だけ一部返金を作成
- 自動検証: システムが過剰返金を防止
一部返金を使用する場合
一部返金の一般的なシナリオ:
- 一部商品返品: お客様が注文からいくつかのアイテムを返品
- 価格調整: 請求エラーまたは割引の適用を修正
- 送料返金: 配送料のみを返金
- 損傷したアイテム: 損傷した商品の一部補償
- サービスクレジット: サービス問題の一部返金を提供
- プロモーション調整: 購入後のクーポン適用
- 数量の修正: 不正確な数量の返金
- バンドル調整: バンドルから特定のアイテムを返金
APIを介した一部返金の作成
基本的な一部返金
amountパラメータを指定して一部返金を作成:
- Node.js
- Python
- PHP
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の送料を返金
import omise
omise.api_secret = 'skey_test_123456789'
def create_partial_refund(charge_id, amount):
"""chargeの一部返金を作成"""
try:
refund = omise.Charge.retrieve(charge_id).refund(
amount=amount, # 最小通貨単位での金額
metadata={
'reason': 'Partial product return',
'items_returned': '2 out of 5 items'
}
)
print(f"Partial refund created: {refund.id}")
print(f"Amount refunded: {refund.amount}")
# 残りを計算
charge = omise.Charge.retrieve(charge_id)
remaining = charge.amount - charge.refunded_amount
print(f"Remaining refundable: {remaining}")
return refund
except omise.errors.BaseError as e:
print(f"Partial refund failed: {str(e)}")
raise
def refund_shipping_cost(charge_id, shipping_amount):
"""送料のみを返金"""
refund = omise.Charge.retrieve(charge_id).refund(
amount=shipping_amount,
metadata={
'reason': 'shipping_refund',
'type': 'delivery_fee'
}
)
print(f"Refunded shipping: {shipping_amount / 100} THB")
return refund
def refund_line_items(charge_id, items):
"""注文から特定のラインアイテムを返金"""
total_refund = sum(item['price'] * item['quantity'] for item in items)
refund = omise.Charge.retrieve(charge_id).refund(
amount=total_refund,
metadata={
'reason': 'partial_line_item_refund',
'items': str(items),
'item_count': len(items)
}
)
return refund
# 使用例
create_partial_refund('chrg_test_123456789', 50000) # 500 THBを返金
refund_shipping_cost('chrg_test_123456789', 10000) # 100 THBを返金
# 特定のアイテムを返金
items_to_refund = [
{'name': 'T-Shirt', 'price': 29900, 'quantity': 1},
{'name': 'Socks', 'price': 9900, 'quantity': 2}
]
refund_line_items('chrg_test_123456789', items_to_refund)
<?php
require_once 'vendor/autoload.php';
define('OMISE_SECRET_KEY', 'skey_test_123456789');
// 一部返金を作成
function createPartialRefund($chargeId, $amount) {
try {
$charge = OmiseCharge::retrieve($chargeId);
$refund = $charge->refund([
'amount' => $amount, // 最小通貨単位での金額
'metadata' => [
'reason' => 'Partial product return',
'items_returned' => '2 out of 5 items'
]
]);
echo "Partial refund created: {$refund['id']}\n";
echo "Amount refunded: {$refund['amount']}\n";
// 残りを計算
$charge->reload();
$remaining = $charge['amount'] - $charge['refunded_amount'];
echo "Remaining refundable: {$remaining}\n";
return $refund;
} catch (Exception $e) {
echo "Partial refund failed: {$e->getMessage()}\n";
throw $e;
}
}
// 送料のみを返金
function refundShippingCost($chargeId, $shippingAmount) {
$charge = OmiseCharge::retrieve($chargeId);
$refund = $charge->refund([
'amount' => $shippingAmount,
'metadata' => [
'reason' => 'shipping_refund',
'type' => 'delivery_fee'
]
]);
echo "Refunded shipping: " . ($shippingAmount / 100) . " THB\n";
return $refund;
}
// 残りの返金可能金額を取得
function getRemainingRefundable($chargeId) {
$charge = OmiseCharge::retrieve($chargeId);
$remaining = $charge['amount'] - $charge['refunded_amount'];
return [
'charge_amount' => $charge['amount'],
'refunded_amount' => $charge['refunded_amount'],
'remaining' => $remaining,
'currency' => $charge['currency']
];
}
// 使用例
createPartialRefund('chrg_test_123456789', 50000);
refundShippingCost('chrg_test_123456789', 10000);
$remaining = getRemainingRefundable('chrg_test_123456789');
print_r($remaining);
?>
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コードのエラーを返します。返金を作成する前に、常に残りの返金可能金額を確認してください。