การอนุมัติล่วงหน ้า (Auth & Capture)
อนุมัติการชำระเงินของลูกค้าล่วงหน้าและจับเงินในภายหลังโดยใช้ขั้นตอนการชำระเงินแบบ 2 ขั้นของ Omise เพื่อให้ได้ความยืดหยุ่นในการจับเวลาการชำระเงินและการปฏิบัติตามคำสั่งซื้อ
ภาพรวม
การอนุมัติล่วงหน้า (เรียกอีกอย่างว่า "Auth และ Capture") เป็นกระบวนการชำระเงินแบบ 2 ขั้นตอนซึ่งคุณต้องอนุมัติการชำระเงินเพื่อตรวจสอบความพร้อมของเงินและจับมันไว้ก่อน จากนั้นจึงจับการชำระเงินจริงในภายหลังเมื่อคุณพร้อมที่จะชำระเงิน สิ่งนี้มีประโยชน์สำหรับผลิตภัณฑ์ที่จัดทำตามคำสั่ง การจองโรงแรม บริการเช่า และสถานการณ์ใด ๆ ที่การปฏิบัติตามคำสั่งซื้อเกิดขึ้นหลังจากการสั่งซื้อ
แนวคิดหลัก:
- Authorize - ตรวจสอบการ์ดและจับเงิน (ยังไม่ได้เรียกเก็บเงินจากลูกค้า)
- Capture - รวบรวมเงินที่จับมาไว้ (เรียกเก็บเงินจากลูกค้า)
- Void - ยกเลิกการอนุมัติก่อน Capture (เพื่อให้เงินถูกปล่อยออกมา)
- Auto-capture - โหมดเริ่มต้น โดยจะ Authorize และ Capture ทันทีกันเลย
- Manual capture - กระบวนการแบบ 2 ขั้นตอน ต้องการการ Capture อย่างชัดแจ้ง
เมื่อจะใช้การอนุมัติล่วงหน้า
ยูสเคสที่ดี
✅ ผลิตภัณฑ์ที่จัดทำตามคำสั่ง
- Authorize เมื่อมีการสั่งซื้อ
- Capture เมื่อมีการส่งสินค้า
- Void ห ากสินค้าไม่พร้อม
✅ การจองโรงแรม
- Authorize เมื่อมีการจอง
- Capture เมื่อเช็คเอาต์
- ปรับจำนวนเงินสำหรับบริการเสริม
✅ บริการเช่า
- Authorize เงินประกัน
- Capture ค่าเสียหายหรือค่าธรรมเนียม
- ปล่อยถ้าไม่มีปัญหา
✅ บริการที่กำหนดเอง
- Authorize ต้นทุนโดยประมาณ
- Capture ต้นทุนจริงสุดท้าย
- บัญชีสำหรับการเปลี่ยนแปลงขอบเขต
✅ การตรวจสอบสินค้าคงคลัง
- Authorize ทันที
- ตรวจสอบความพร้อมของสินค้าคงคลัง
- Capture ถ้ามีสินค้า Void หากไม่มี
ไม่แนะนำ
❌ สินค้าดิจิทัล - ส่งมอบทันที ใช้ Auto-capture ❌ การสมัครสมาชิก - ใช้การเรียกเก็บเงินปกติ (ไม่สามารถ Pre-auth ซ้ำ ๆ ได้) ❌ สินค้าคุณค่าต่ำ - ความซับซ้อนที่เพิ่มเติมไม่มูลค่า ❌ การปฏิบัติตามคำสั่งซื้อทันที - ใช้ Auto-capture
วิธีการทำงาน
การใช้งาน
ขั้นตอนที่ 1: สร้างการอนุมัติ
- cURL
- Node.js
- PHP
- Python
- Ruby
- Go
- Java
- C#
curl https://api.omise.co/charges \
-u skey_test_YOUR_SECRET_KEY: \
-d "amount=100000" \
-d "currency=THB" \
-d "card=tokn_test_..." \
-d "capture=false"
const omise = require('omise')({
secretKey: 'skey_test_YOUR_SECRET_KEY'
});
const charge = await omise.charges.create({
amount: 100000, // THB 1,000.00
currency: 'THB',
card: tokenId,
capture: false, // Authorize เท่านั้น
description: 'Pre-auth for Order #12345',
metadata: {
order_id: '12345'
}
});
console.log('สถานะ Charge:', charge.status); // 'pending'
console.log('Authorized:', charge.authorized); // true
console.log('Paid:', charge.paid); // false
<?php
$charge = OmiseCharge::create(array(
'amount' => 100000,
'currency' => 'THB',
'card' => $tokenId,
'capture' => false
));
?>
import omise
omise.api_secret = 'skey_test_YOUR_SECRET_KEY'
charge = omise.Charge.create(
amount=100000,
currency='THB',
card=token_id,
capture=False
)
print('สถานะ:', charge.status) # 'pending'
require 'omise'
Omise.api_key = 'skey_test_YOUR_SECRET_KEY'
charge = Omise::Charge.create({
amount: 100000,
currency: 'THB',
card: token_id,
capture: false
})
puts charge.status # 'pending'
charge, err := client.Charges().Create(&operations.CreateCharge{
Amount: 100000,
Currency: "THB",
Card: tokenId,
Capture: false,
})
Charge charge = client.charges().create(new Charge.CreateParams()
.amount(100000L)
.currency("THB")
.card(tokenId)
.capture(false));
var charge = await client.Charges.Create(new CreateChargeRequest
{
Amount = 100000,
Currency = "THB",
Card = tokenId,
Capture = false
});
การตอบสนอง:
{
"object": "charge",
"id": "chrg_test_5rt6s9vah5lkvi1rh9c",
"amount": 100000,
"currency": "THB",
"status": "pending",
"authorized": true,
"paid": false,
"capture": false,
"capturable": true,
"expires_at": "2025-02-13T00:00:00Z"
}
ขั้นตอนที่ 2: จับเงิน
// หลังจากการปฏิบัติตามคำสั่งซื้อแล้ว
const capture = await omise.charges.capture('chrg_test_5rt6s9vah5lkvi1rh9c');
console.log('สถานะ Capture:', capture.status); // 'successful'
console.log('Paid:', capture.paid); // true
จับเงินด้วยจำนวนเงินที่แตกต่างกัน (ถ้าได้รับการสนับสนุน):
// จับเงินน้อยกว่าที่ได้รับการอนุมัติ (เช่น บิลสุดท้ายต่ำกว่า)
const capture = await omise.charges.capture('chrg_test_...', {
capture_amount: 80000 // จับเฉพาะ ฿800 ของ ฿1,000 ที่ได้รับการอนุมัติ
});
ขั้นตอนที่ 3: หรือยกเลิก/ยืมการอนุมัติ
// ยกเลิกการอนุมัติหากคำสั่งซื้อถ ูกยกเลิก
const reversed = await omise.charges.reverse('chrg_test_5rt6s9vah5lkvi1rh9c');
console.log('สถานะ:', reversed.status); // 'reversed' หรือ 'expired'
console.log('ยกเลิกเมื่อ:', reversed.reversed_at);
ตัวอย่างที่สมบูรณ์
const express = require('express');
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY
});
const app = express();
app.use(express.json());
// ขั้นตอนที่ 1: ลูกค้าวางสั่งซื้อ
app.post('/checkout', async (req, res) => {
try {
const { tokenId, amount, orderId } = req.body;
// อนุมัติการชำระเงิน (ไม่จับเงินเลย)
const charge = await omise.charges.create({
amount: amount,
currency: 'THB',
card: tokenId,
capture: false,
description: `Pre-auth for Order #${orderId}`,
metadata: {
order_id: orderId,
authorized_at: new Date().toISOString()
}
});
if (charge.status === 'pending' && charge.authorized) {
// บันทึกลงในฐานข้อมูล
await db.orders.create({
order_id: orderId,
charge_id: charge.id,
status: 'authorized',
amount: amount,
expires_at: charge.expires_at
});
res.json({
success: true,
message: 'Payment authorized',
order_id: orderId
});
} else {
res.status(400).json({
error: 'Authorization failed',
reason: charge.failure_message
});
}
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// ขั้นตอนที่ 2: ปฏิบัติตามคำสั่งซื้อและจับเงิน
app.post('/orders/:orderId/ship', async (req, res) => {
try {
const { orderId } = req.params;
const { trackingNumber } = req.body;
// รับคำสั่งซื้อ
const order = await db.orders.findOne({ order_id: orderId });
if (order.status !== 'authorized') {
return res.status(400).json({
error: 'Order not in authorized state'
});
}
// จับเงิน
const capture = await omise.charges.capture(order.charge_id);
if (capture.status === 'successful') {
// อัปเดตคำสั่งซื้อ
await db.orders.update({
order_id: orderId,
status: 'captured',
shipped: true,
tracking_number: trackingNumber,
captured_at: new Date()
});
res.json({
success: true,
message: 'Payment captured, order shipped'
});
} else {
res.status(400).json({
error: 'Capture failed'
});
}
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// ขั้นตอนที่ 3: ยกเลิกคำสั่งซื้อและปล่อยเงิน
app.post('/orders/:orderId/cancel', async (req, res) => {
try {
const { orderId } = req.params;
const { reason } = req.body;
const order = await db.orders.findOne({ order_id: orderId });
if (order.status !== 'authorized') {
return res.status(400).json({
error: 'Order cannot be canceled'
});
}
// ยกเลิกการอนุมัติ
const reversed = await omise.charges.reverse(order.charge_id);
// อัปเดตคำสั่งซื้อ
await db.orders.update({
order_id: orderId,
status: 'canceled',
cancellation_reason: reason,
canceled_at: new Date()
});
res.json({
success: true,
message: 'Authorization reversed, funds released'
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// งานพื้นหลัง: ยกเลิกการอนุมัติที่หมดอายุโดยอัตโนมัติ
cron.schedule('0 * * * *', async () => {
const expiredOrders = await db.orders.find({
status: 'authorized',
expires_at: { $lt: new Date() }
});
for (const order of expiredOrders) {
try {
await omise.charges.reverse(order.charge_id);
await db.orders.update({
order_id: order.order_id,
status: 'expired'
});
console.log(`Reversed expired order: ${order.order_id}`);
} catch (error) {
console.error(`Failed to reverse ${order.order_id}:`, error);
}
}
});
app.listen(3000);
การอนุมัติหมดอายุ
การอนุมัติ หมดอายุหากไม่จับเงิน:
| Network การ์ด | ระยะเวลาหมดอายุ |
|---|---|
| Visa | 7 วัน |
| Mastercard | 7 วัน |
| Amex | 7 วัน |
| JCB | 7 วัน |
การอนุมัติที่ไม่ได้จับเงินจะหมดอายุโดยอัตโนมัติหลังจากระยะเวลาที่กำหนดของเครือข่ายการ์ด ตรวจสอบวันที่หมดอายุและจับเงินก่อนการหมดอายุ
// ตรวจสอบว่าการอนุมัติกำลังจะหมดอายุหรือไม่
function isExpiringSoon(charge) {
const expiryDate = new Date(charge.expires_at);
const now = new Date();
const daysUntilExpiry = (expiryDate - now) / (1000 * 60 * 60 * 24);
return daysUntilExpiry < 2; // น้อยกว่า 2 วัน
}
// แจ้งเตือนหากกำลังจะหมดอายุ
if (isExpiringExpiringSoon(charge)) {
await sendExpiryAlert(order.id, charge.expires_at);
}
การปรับจำนวนเงิน Capture
สถานการณ์บางอย่างช่วยให้สามารถจับเงินจำนวนต่างกันได้:
จับเงินน้อยลง (การปรับลดลง)
// ได้รับการอนุมัติ ฿1,000 แต่ต้นทุนสุดท้ายคือ ฿800
const capture = await omise.charges.capture('chrg_test_...', {
capture_amount: 80000 // ฿800 แทน ฿1,000
});
ยูสเคส:
- บิลสุดท้ายต่ำกว่าค่าประมาณการ
- ลูกค้ากลับสินค้าบางชิ้นก่อนการจัดส่ง
- นำไปใช้ส่วนลดแบบโปรโมชั่น
- ตรวจพบความเสียหายหรือข้อบกพร่อง
จับเงินมากขึ้น (ไม่รองรับ)
คุณไม่สามารถจับเงินมากกว่าจำนวนเงินที่ได้รับการอนุมัติ หากต้นทุนสุดท้ายสูงกว่า ค ุณต้อง:
- สร้างการชำระเงินใหม่สำหรับความแตกต่าง หรือ
- ยกเลิกการอนุมัติดั้งเดิมและสร้างการชำระเงินใหม่สำหรับจำนวนเงินที่ถูกต้อง
แนวทางการใช้งานที่ดีที่สุด
1. ตรวจสอบการหมดอายุของการอนุมัติ
// งาน cron ประจำวัน
cron.schedule('0 8 * * *', async () => {
// ค้นหาการอนุมัติที่จะหมดอายุใน 2 วัน
const expiringCharges = await db.orders.find({
status: 'authorized',
expires_at: {
$gte: new Date(),
$lt: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000)
}
});
for (const order of expiringCharges) {
// แจ้งเตือนทีมการปฏิบัติตาม
await sendSlackNotification({
message: `⚠️ Order ${order.order_id} authorization expires in 2 days!`,
urgency: 'high'
});
// ส่งอีเมลถึงลูกค้าเกี่ยวกับการล่าช้า
await sendEmailToCustomer(order.customer_email, {
template: 'order_delay',
order_id: order.order_id
});
}
});
2. การสื่อสารที่ชัดเจน
// เมื่อทำการอนุมัติ
await sendEmail({
to: customer.email,
subject: 'Order Confirmed - Payment Authorized',
html: `
<h2>Order #${orderId} Confirmed</h2>
<p>We've authorized your payment of ฿${amount / 100}.</p>
<p><strong>Important:</strong> Your card will only be charged when your order ships.</p>
<ul>
<li>Estimated ship date: ${estimatedShipDate}</li>
<li>Authorization expires: ${expiresAt}</li>
</ul>
<p>You'll receive a confirmation email when your card is charged.</p>
`
});
3. จับเงินโดยอัตโนมัติเมื่อจัดส่ง
// รวมเข้ากับระบบการจัดส่ง
app.post('/webhooks/shipstation', async (req, res) => {
const shipment = req.body;
if (shipment.event === 'shipment_created') {
const order = await db.orders.findOne({
tracking_number: shipment.tracking_number
});
if (order && order.status === 'authorized') {
// จับเงินโดยอัตโนมัติเมื่อจัดส่ง
await omise.charges.capture(order.charge_id);
await db.orders.update({
order_id: order.order_id,
status: 'captured',
captured_at: new Date()
});
// แจ้งให้ลูกค้าทราบ
await sendShippedEmail(order.customer_email, {
tracking: shipment.tracking_number,
amount: order.amount
});
}
}
res.sendStatus(200);
});
4. จัดการการปฏิบัติตามบางส่วน
async function handlePartialShipment(orderId, shippedItems) {
const order = await db.orders.findOne({ order_id: orderId });
// คำนวณจำนวนเงินสำหรับสินค้าที่จัดส่ง
const shippedAmount = shippedItems.reduce((sum, item) => {
return sum + (item.price * item.quantity);
}, 0);
// จับเงินเฉพาะสำหรับสินค้าที่จัดส่ง
await omise.charges.capture(order.charge_id, {
capture_amount: shippedAmount
});
// สร้างการอนุมัติใหม่สำหรับสินค้าที่เหลือ
const remainingAmount = order.amount - shippedAmount;
if (remainingAmount > 0) {
const newCharge = await omise.charges.create({
amount: remainingAmount,
currency: 'THB',
customer: order.customer_id,
capture: false
});
await db.orders.create({
parent_order_id: orderId,
charge_id: newCharge.id,
status: 'authorized',
amount: remainingAmount
});
}
}
5. ระยะเวลาพิเศษสำหรับการตรวจสอบสต็อก
async function processOrder(orderId) {
const order = await db.orders.findOne({ order_id: orderId });
// ตรวจสอบสต็อก
const inStock = await checkInventory(order.items);
if (inStock) {
// จับเงินทันทีหากมีในสต็อก
await omise.charges.capture(order.charge_id);
await fulfillOrder(orderId);
} else {
// ให้ 24 ชั่วโมงในการเก็บสต็อก
setTimeout(async () => {
const stillInStock = await checkInventory(order.items);
if (stillInStock) {
await omise.charges.capture(order.charge_id);
await fulfillOrder(orderId);
} else {
// ยกเลิกและคืนเงิน
await omise.charges.reverse(order.charge_id);
await notifyCustomer(order.customer_email, 'out_of_stock');
}
}, 24 * 60 * 60 * 1000); // 24 ชั่วโมง
}
}
การทดสอบ
โหมดการทดสอบการอนุมัติล่วงหน้า
ใช้การ์ดทดสอบเพื่อตรวจสอบการใช้งานการอนุมัติล่วงหน้าของคุณ:
const omise = require('omise')({
secretKey: 'skey_test_YOUR_SECRET_KEY'
});
// การทดสอบการอนุมัติ
const charge = await omise.charges.create({
amount: 100000,
currency: 'THB',
card: 'tokn_test_5rt6s9vah5lkvi1rh9c', // โทเค็นการ์ดทดสอบ
capture: false,
metadata: {
test_scenario: 'pre_authorization'
}
});
console.log('สถานะ:', charge.status); // 'pending'
console.log('Authorized:', charge.authorized); // true
console.log('Capturable:', charge.capturable); // true