ข้ามไปยังเนื้อหาหลัก

Refunds บางส่วน

Refunds บางส่วนช่วยให้คุณคืนส่วนหนึ่งของจำนวน charge ให้กับลูกค้าของคุณ สิ่งนี้มีประโยชน์สำหรับสถานการณ์เช่นการคืนสินค้าบางส่วน การปรับราคา refunds ค่าจัดส่ง หรือสถานการณ์ใดๆ ที่ไม่จำเป็นต้อง refund เต็มจำนวน

ภาพรวม

Refunds บางส่วนให้ความยืดหยุ่นในวิธีที่คุณจัดการการคืนสินค้าและการปรับเปลี่ยน:

  • จำนวนที่ยืดหยุ่น: Refund จำนวนใดก็ได้จนถึงจำนวน charge เดิม
  • Refunds หลายครั้ง: สร้าง refunds บางส่วนหลายครั้งจนกว่าจะ refund เต็มจำนวน
  • ยอดคงเหลือที่เหลืออยู่: ติดตามว่ายังสามารถ refund ได้เท่าไหร่
  • กระบวนการเดียวกัน: ใช้ API เดียวกันกับ refunds เต็มจำนวน เพียงระบุจำนวน
  • รอยบันทึกการตรวจสอบ: Refunds บางส่วนทั้งหมดถูกติดตามบน charge

คุณสมบัติหลัก

  • การควบคุมจำนวนที่แม่นยำ: ระบุจำนวน refund ที่แน่นอน
  • รับรู้สกุลเงิน: ใช้สกุลเงินของ charge โดยอัตโนมัติ
  • การติดตามยอดคงเหลือ: ดูจำนวนที่ refund ได้ที่เหลือ
  • รองรับ Metadata: แท็กแต่ละ refund ด้วยบริบท
  • ไม่มีข้อจำกัดจำนวน: สร้าง refunds บางส่วนได้มากเท่าที่ต้องการ
  • การตรวจสอบอัตโนมัติ: ระบบป้องกันการ refund เกิน

เมื่อใดควรใช้ Refunds บางส่วน

สถานการณ์ทั่วไปสำหรับ refunds บางส่วน:

  • การคืนสินค้าบางส่วน: ลูกค้าคืนบางรายการจากคำสั่งซื้อ
  • การปรับราคา: แก้ไขข้อผิดพลาดในการเรียกเก็บเงินหรือใช้ส่วนลด
  • Refunds ค่าจัดส่ง: Refund ค่าส่งเท่านั้น
  • สินค้าที่เสียหาย: การชดเชยบางส่วนสำหรับสินค้าที่เสียหาย
  • เครดิตบริการ: เสนอ refunds บางส่วนสำหรับปัญหาบริการ
  • การปรับโปรโมชั่น: ใช้คูปองหลังการซื้อ
  • การแก้ไขจำนวน: Refund สำหรับจำนวนที่ไม่ถูกต้อง
  • การปรับชุด: Refund รายการเฉพาะจากชุด

การสร้าง Refunds บางส่วนผ่าน API

Refund บางส่วนพื้นฐาน

ระบุพารามิเตอร์ amount เพื่อสร้าง refund บางส่วน:

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

// สร้าง refund บางส่วน
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;
}
}

// คำนวณจำนวนที่ refund ได้ที่เหลือ
function calculateRemaining(refund) {
return omise.charges.retrieve(refund.charge).then(charge => {
const totalRefunded = charge.refunded_amount || 0;
const remaining = charge.amount - totalRefunded;
return remaining;
});
}

// Refund ค่าส่งเท่านั้น
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); // Refund 500 THB
refundShippingCost('chrg_test_123456789', 10000); // Refund ค่าส่ง 100 THB

API Response

{
"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"
}
}

Refunds บางส่วนหลายครั้ง

คุณสามารถสร้าง refunds บางส่วนหลายครั้งสำหรับ charge เดียว:

async function handleMultiplePartialRefunds(chargeId) {
// Charge เดิม: 100,000 (1,000 THB)

// Refund บางส่วนครั้งแรก - ค่าส่ง
const refund1 = await omise.charges.createRefund(chargeId, {
amount: 10000, // 100 THB
metadata: { reason: 'shipping_refund' }
});

// Refund บางส่วนครั้งที่สอง - รายการหนึ่ง
const refund2 = await omise.charges.createRefund(chargeId, {
amount: 30000, // 300 THB
metadata: { reason: 'item_return', item: 'Product A' }
});

// Refund บางส่วนครั้งที่สาม - รายการอื่น
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. Refunds รายการบรรทัด

Refund รายการเฉพาะจากคำสั่งซื้อ:

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):
"""Refund รายการเฉพาะตาม IDs"""
order = self.get_order_details()

# คำนวณจำนวน refund
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
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):
"""Refund เปอร์เซ็นต์ของ 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. การคิดตามสัดส่วนสำหรับ Subscription

Refunds ตามสัดส่วนสำหรับการยกเลิก subscription:

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;

// คำนวณจำนวน refund
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`);

การติดตามยอด Refund

ติดตามว่าได้ refund เท่าไหร่และเหลือเท่าไหร่:

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):
"""รับสรุป refund ที่ครอบคลุม"""
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):
"""ตรวจสอบว่าสามารถ refund จำนวนได้หรือไม่"""
self.refresh()
remaining = self.charge.amount - self.charge.refunded_amount
return amount <= remaining

def get_refund_percentage(self):
"""รับเปอร์เซ็นต์ของ charge ที่ถูก refund"""
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("สามารถ refund 500 THB ได้")
else:
print("ยอดคงเหลือที่เหลือไม่เพียงพอ")

แนวปฏิบัติที่ดีที่สุด

1. ตรวจสอบจำนวน Refund

ตรวจสอบเสมอก่อนสร้าง refund:

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)');
}

// สร้าง refund
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. จัดการ Edge Cases

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

// ตรวจสอบว่า charge สามารถ refund ได้หรือไม่
if (!$charge['paid']) {
throw new Exception('Charge ยังไม่ได้ชำระ');
}

if ($charge['refunded']) {
throw new Exception('Charge ถูก refund เต็มจำนวนแล้ว');
}

// ตรวจสอบยอดคงเหลือที่เหลือ
$remaining = $charge['amount'] - $charge['refunded_amount'];
if ($amount > $remaining) {
throw new Exception("จำนวนเกินยอดคงเหลือที่เหลือ {$remaining}");
}

// สร้าง refund
$refund = $charge->refund([
'amount' => $amount,
'metadata' => $metadata
]);

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

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

FAQ

จำนวน refund ขั้นต่ำคือเท่าไหร่?

จำนวน refund ขั้นต่ำขึ้นอยู่กับสกุลเงิน สำหรับบาทไทย (THB) ขั้นต่ำคือ 1 สตางค์ (0.01 THB) ระบุจำนวนในหน่วยสกุลเงินที่เล็กที่สุดเสมอ (สตางค์สำหรับ THB, เซนต์สำหรับ USD ฯลฯ)

ฉันสามารถ refund มากกว่าจำนวน charge เดิมได้หรือไม่?

ไม่ได้ ผลรวมของ refunds ทั้งหมด (เต็มจำนวนและบางส่วน) ไม่สามารถเกินจำนวน charge เดิม API จะคืนข้อผิดพลาดถ้าคุณพยายาม refund เกิน

ฉันสามารถสร้าง refunds บางส่วนกี่ครั้งสำหรับ charge หนึ่ง?

ไม่มีข้อจำกัดจำนวน refunds บางส่วนที่คุณสามารถสร้างได้ ตราบใดที่ผลรวมไม่เกินจำนวน charge เดิม คุณสามารถสร้าง refunds บางส่วนได้มากเท่าที่ต้องการจนกว่า charge จะ refund เต็มจำนวน

จะเกิดอะไรขึ้นถ้าฉันพยายาม refund มากกว่ายอดคงเหลือที่เหลือ?

API จะคืนข้อผิดพลาดด้วยโค้ด invalid_request ที่ระบุว่าจำนวน refund เกินยอดคงเหลือที่มี ตรวจสอบจำนวนที่ refund ได้ที่เหลือเสมอก่อนสร้าง refund

แหล่งข้อมูลที่เกี่ยวข้อง

ขั้นตอนถัดไป