Recurring PaymentsและSubscriptions
ใช้งาน subscription billing, recurring paymentsและ scheduled chargesอัตโนมัติด้วยบัตรที่บันทึกไว้โดยใช้ Customers APIและSchedulesของ Omise
ภาพรวม
Recurring paymentsช่วยให้คุณเรียกเก็บเงินจากลูกค้าโดยอั ตโนมัติตามกำหนดเวลาปกติสำหรับ subscriptions, memberships, ผลิตภัณฑ์ SaaSหรือบริการที่มีการเรียกเก็บเงินซ้ำ Omiseมีสองวิธีการ: recurring chargesแบบ manualโดยใช้ Customers APIหรือ recurring chargesแบบอัตโนมัติโดยใช้ Schedules
คุณสมบัติหลัก:
- ✅ บัตรที่บันทึกไว้ - เก็บวิธีการชำระเงินของลูกค้าอย่างปลอดภัย
- ✅ Schedulesที่ยืดหยุ่น - รายวัน รายสัปดาห์ รายเดือน รายปี
- ✅ การเรียกเก็บเงินอัตโนมัติ - การจัดการ subscriptionแบบตั้งค่าแล้วทิ้งไว้
- ✅ การควบคุมแบบ manual - เรียกเก็บเงินลูกค้าด้วยโปรแกรม
- ✅ ตรรกะการลองใหม่ - ลองชำระเงินที่ล้มเหลวอีกครั้งโดยอัตโนมัติ
- ✅ การเรียกเก็บเงินแบบ prorated - การเปลี่ยน subscriptionกลางรอบ
- ✅ หลายบัตร - ลูกค้าสามารถบันทึกวิธีการชำระเงินหลายวิธี
การเปรียบเทียบวิธีการ
| คุณสมบัติ | Customers API (Manual) | Schedules (อัตโนมัติ) |
|---|---|---|
| การควบคุม | ควบคุมเต็มรูปแบบ | อัตโนมัติ |
| ความยืดหยุ่น | ยืดหยุ่นมาก | Schedulesคงที่ |
| ตรรกะการลองใหม่ | คุณต้องใช้งาน | มีในตัว |
| ความซับซ้อน | โค้ดมากขึ้น | โค้ดน้อยลง |
| กรณีการใช้งาน | การเรียกเก็บเงินแบบกำหนดเอง | Subscriptionsมาตรฐาน |
| การคำนวณ Proration | คุณคำนวณ | คุณต้องใช้งาน |
| เหมาะที่สุดสำหรับ | กฎการเรียกเก็บเงินที่ซับซ้อน | Recurring chargesแบบง่าย |
วิธีการทำงาน
วิธี Customers API
วิธี Schedules
Customers API (Manual Recurring)
ขั้นตอนที่ 1: สร้าง Customerพร้อมบัตร
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY
});
// สร้าง customerพร้อมบัตรเริ่มต้น
const customer = await omise.customers.create({
email: 'user@example.com',
description: 'John Doe - Premium Plan',
card: tokenId, // จาก Omise.jsหรือ mobile SDK
metadata: {
plan: 'premium',
billing_cycle: 'monthly',
signup_date: new Date().toISOString()
}
});
console.log('ลูกค้า ID:', customer.id);
console.log('Default card:', customer.default_card);
ขั้นตอนที่ 2: บันทึก ลูกค้า ID
// บันทึกในฐานข้อมูลของคุณ
await db.users.update({
user_id: userId,
omise_customer_id: customer.id,
subscription_status: 'active',
subscription_plan: 'premium',
next_billing_date: calculateNextBillingDate(),
amount: 29900 // ฿299.00
});
ขั้นตอนที่ 3: เรียกเก็บเงิน Customerแบบ Recurring
// งานที ่กำหนดเวลา (ทำงานทุกวัน)
async function processRecurringBilling() {
const today = new Date().toDateString();
// ค้นหาลูกค้าที่ครบกำหนดชำระเงิน
const dueCustomers = await db.users.find({
subscription_status: 'active',
next_billing_date: today
});
for (const user of dueCustomers) {
try {
// เรียกเก็บเงินจากบัตรที่บันทึกไว้ของลูกค้า
const charge = await omise.charges.create({
amount: user.amount,
currency: 'THB',
customer: user.omise_customer_id,
description: `Subscription renewal - ${user.subscription_plan}`,
metadata: {
user_id: user.user_id,
plan: user.subscription_plan,
billing_period: today
}
});
if (charge.status === 'successful') {
// อัปเดต subscription
await extendSubscription(user.user_id);
await sendReceiptEmail(user.email, charge);
console.log(`✓ Charged ${user.email}: ${charge.amount / 100}`);
} else {
// จัดการความล้มเหลว
await handleFailedPayment(user, charge);
}
} catch (error) {
console.error(`✗ Failed to charge ${user.email}:`, error.message);
await handlePaymentError(user, error);
}
}
}
// กำหนดเวลาให้ทำงานทุกวัน
// ใช้ node-cronหรือ schedulerของคุณ
cron.schedule('0 0 * * *', processRecurringBilling);
ขั้นตอนที่ 4: จัดการการชำระเงินที่ล้มเหลว
async function handleFailedPayment(user, charge) {
// เพิ่มจำนวนการลองใหม่
const retryCount = (user.payment_retry_count || 0) + 1;
await db.users.update({
user_id: user.user_id,
payment_retry_count: retryCount,
last_payment_attempt: new Date(),
last_payment_error: charge.failure_message
});
// ตรรกะการลองใหม่
if (retryCount <= 3) {
// ลองใหม่หลังจาก 3, 5, 7 วัน
const retryDays = [3, 5, 7][retryCount - 1];
const retryDate = new Date();
retryDate.setDate(retryDate.getDate() + retryDays);
await db.users.update({
user_id: user.user_id,
next_billing_date: retryDate.toDateString()
});
// ส่งอีเมลให้ลูกค้า
await sendPaymentFailedEmail(user.email, {
reason: charge.failure_message,
retryDate: retryDate,
updateCardUrl: `https://yoursite.com/billing/update-card`
});
} else {
// ระงับ subscriptionหลังจากลองใหม่ 3 ครั้ง
await db.users.update({
user_id: user.user_id,
subscription_status: 'suspended',
suspended_at: new Date()
});
await sendSubscriptionSuspendedEmail(user.email);
}
}
ขั้นตอนที่ 5: อัปเดตบัตร
// ลูกค้าอัปเดตบัตรของพวกเขา
app.post('/billing/update-card', async (req, res) => {
const { userId, newTokenId } = req.body;
try {
const user = await db.users.findOne({ user_id: userId });
// อัปเดตบัตรเริ่มต้นของลูกค้า
const customer = await omise.customers.update(user.omise_customer_id, {
card: newTokenId
});
// รีเซ็ตจำนวนการลองใหม่
await db.users.update({
user_id: userId,
payment_retry_count: 0,
subscription_status: 'active'
});
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
Schedules (อัตโนมัติ Recurring)
ข ั้นตอนที่ 1: สร้าง ลูกค้า
// สร้าง customerก่อน
const customer = await omise.customers.create({
email: 'user@example.com',
description: 'John Doe',
card: tokenId
});
ขั้นตอนที่ 2: สร้าง Schedule
// สร้าง recurring scheduleรายเดือน
const schedule = await omise.schedules.create({
every: 1,
period: 'month',
start_date: '2025-02-15', // วันเรียกเก็บเงินครั้งแรก
end_date: '2026-02-15', // ตัวเลือก: วันสิ้นสุด schedule
charge: {
customer: customer.id,
amount: 29900,
currency: 'THB',
description: 'Monthly subscription - Premium Plan'
}
});
console.log('Schedule ID:', schedule.id);
console.log('Next occurrence:', schedule.next_occurrence_dates[0]);
ตัวเลือกระยะเวลา
// รายวัน
const dailySchedule = await omise.schedules.create({
every: 1,
period: 'day',
start_date: '2025-02-10',
charge: { /* พารามิเตอร์ charge */ }
});
// รายสัปดาห์
const weeklySchedule = await omise.schedules.create({
every: 1,
period: 'week',
on: { weekdays: ['monday'] }, // เรียกเก็บเงินทุกวันจันทร์
start_date: '2025-02-10',
charge: { /* พารามิเตอร์ charge */ }
});
// รายเดือน (วันที่เฉพาะ)
const monthlySchedule = await omise.schedules.create({
every: 1,
period: 'month',
on: { days_of_month: [1] }, // วันที่ 1 ของทุกเดือน
start_date: '2025-02-01',
charge: { /* พารามิเตอร์ charge */ }
});
// รายปี
const yearlySchedule = await omise.schedules.create({
every: 1,
period: 'year',
start_date: '2025-02-15',
charge: { /* พารามิเตอร์ charge */ }
});
ขั้นตอนที่ 3: จัดการ Schedule Webhooks
app.post('/webhooks/omise', (req, res) => {
const event = req.body;
switch (event.key) {
case 'charge.complete':
handleScheduledCharge(event.data);
break;
case 'schedule.suspend':
// Scheduleถูกระงับเนื่องจาก chargesล้มเหลว
handleScheduleSuspended(event.data);
break;
case 'schedule.expiring':
// Scheduleกำลังจะหมดอายุ
handleScheduleExpiring(event.data);
break;
}
res.sendStatus(200);
});
async function handleScheduledCharge(charge) {
if (charge.schedule) {
console.log('Schedule charge:', charge.schedule);
if (charge.status === 'successful') {
// ขยาย subscriptionของผู้ใช้
await extendSubscription(charge.customer);
await sendReceiptEmail(charge.customer);
} else {
// Omiseลองใหม่โดยอัตโนมัติ แต่แจ้งผู้ใช้
await sendPaymentIssueEmail(charge.customer);
}
}
}
ขั้นตอนที่ 4: จัดการ Schedules
// หยุด schedule
await omise.schedules.update('schd_test_...', {
status: 'suspended'
});
// กลับมาใช้ schedule
await omise.schedules.update('schd_test_...', {
status: 'active'
});
// ยกเลิก schedule
await omise.schedules.destroy('schd_test_...');
ตัวอย่างระบบ Subscriptionที่สมบูรณ์
const express = require('express');
const cron = require('node-cron');
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY
});
const app = express();
app.use(express.json());
// แผน Subscription
const PLANS = {
basic: { amount: 9900, name: 'Basic', features: ['Feature A'] },
premium: { amount: 29900, name: 'Premium', features: ['Feature A', 'Feature B'] },
enterprise: { amount: 99900, name: 'Enterprise', features: ['All features'] }
};
// สร้าง subscription
app.post('/subscribe', async (req, res) => {
try {
const { userId, plan, tokenId, email } = req.body;
// ตรวจสอบแผน
if (!PLANS[plan]) {
return res.status(400).json({ error: 'Invalid plan' });
}
// สร้าง customer
const customer = await omise.customers.create({
email: email,
description: `User ${userId} - ${PLANS[plan].name}`,
card: tokenId,
metadata: {
user_id: userId,
plan: plan
}
});
// การเรียกเก็บเงินครั้งแรก
const charge = await omise.charges.create({
amount: PLANS[plan].amount,
currency: 'THB',
customer: customer.id,
description: `${PLANS[plan].name} - First payment`
});
if (charge.status === 'successful') {
// บันทึก subscription
await db.subscriptions.create({
user_id: userId,
customer_id: customer.id,
plan: plan,
status: 'active',
current_period_start: new Date(),
current_period_end: addMonths(new Date(), 1),
amount: PLANS[plan].amount,
next_billing_date: addMonths(new Date(), 1)
});
res.json({
success: true,
subscription: {
plan: PLANS[plan].name,
amount: PLANS[plan].amount / 100,
next_billing: addMonths(new Date(), 1)
}
});
} else {
res.status(400).json({ error: 'Payment failed' });
}
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// ประมวลผล recurring billing (ทำงานทุกวันเวลาเที่ยงคืน)
cron.schedule('0 0 * * *', async () => {
console.log('Processing recurring billing...');
const today = new Date().toDateString();
const dueSubscriptions = await db.subscriptions.find({
status: 'active',
next_billing_date: today
});
for (const sub of dueSubscriptions) {
try {
const charge = await omise.charges.create({
amount: sub.amount,
currency: 'THB',
customer: sub.customer_id,
description: `${sub.plan} - Monthly subscription`
});
if (charge.status === 'successful') {
// อัปเดต subscription
await db.subscriptions.update({
subscription_id: sub.id,
current_period_start: new Date(),
current_period_end: addMonths(new Date(), 1),
next_billing_date: addMonths(new Date(), 1),
retry_count: 0
});
await sendReceiptEmail(sub.user_id, charge);
console.log(`✓ Billed user ${sub.user_id}`);
} else {
await handleFailedBilling(sub, charge);
}
} catch (error) {
console.error(`✗ Error billing user ${sub.user_id}:`, error);
await handleBillingError(sub, error);
}
}
});
// ยกเลิก subscription
app.post('/cancel-subscription', async (req, res) => {
const { userId } = req.body;
try {
const sub = await db.subscriptions.findOne({
user_id: userId,
status: 'active'
});
// ไม่เรียกเก็บเงินอีก แต่ให้ระยะเวลาปัจจุบันเสร็จสิ้น
await db.subscriptions.update({
subscription_id: sub.id,
status: 'canceled',
canceled_at: new Date(),
access_until: sub.current_period_end
});
res.json({
success: true,
message: 'Subscription canceled',
access_until: sub.current_period_end
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// ฟังก์ชันยูทิลิตี้
function addMonths(date, months) {
const result = new Date(date);
result.setMonth(result.getMonth() + months);
return result;
}
app.listen(3000);