Online Direct Debit (Bill Payment)
Accept recurring payments by debiting directly from customer bank accounts in Thailand with pre-authorized mandates for subscriptions and automated billing.
Overview
Online Direct Debit (Bill Payment) allows you to automatically charge customers' bank accounts on a recurring basis. Customers authorize your business once, and you can then charge their account automatically for subscriptions, installments, or recurring services.
Key Features:
- ✅ Recurring payments - Automatic billing on schedule
- ✅ Pre-authorized - Customer authorizes once
- ✅ High success rate - Direct from bank account
- ✅ High limits - Up to ฿5,000,000+ per transaction
- ✅ Refund support - Full and partial refunds
- ✅ Multiple banks - All major Thai banks supported
Use Cases
Perfect For:
- Subscriptions - Monthly SaaS, streaming services, memberships
- Installment payments - Loan repayments, purchase plans
- Utility bills - Recurring service charges
- Insurance premiums - Regular premium payments
- Donations - Recurring charitable contributions
Not Suitable For:
- One-time payments (use mobile or internet banking instead)
- Guest checkouts (requires authorization setup)
- Instant payments (initial setup takes time)
Supported Region
| Region | Currency | Min Amount | Max Amount | Refundable |
|---|---|---|---|---|
| Thailand | THB | ฿20.00 | ฿5,000,000+ | ✅ Yes |
How It Works
Initial Setup Flow
Recurring Charge Flow
Implementation
Step 1: Create Customer
- Node.js
- PHP
const omise = require('omise')({
secretKey: 'skey_test_YOUR_SECRET_KEY'
});
// Create customer
const customer = await omise.customers.create({
email: 'customer@example.com',
description: 'Customer for subscription',
metadata: {
user_id: '12345'
}
});
console.log('Customer created:', customer.id);
<?php
$customer = OmiseCustomer::create(array(
'email' => 'customer@example.com',
'description' => 'Customer for subscription'
));
?>
Step 2: Create Direct Debit Source
// Create direct debit source (requires customer authorization)
const source = await omise.sources.create({
type: 'pay_with_bill_payment',
amount: 50000, // Initial authorization amount
currency: 'THB',
customer: customer.id
});
// Redirect customer to authorize
res.redirect(source.authorize_uri);
Step 3: Handle Authorization Callback
app.get('/debit/callback', async (req, res) => {
const sourceId = req.query.source_id;
const source = await omise.sources.retrieve(sourceId);
if (source.flow_status === 'successful') {
// Source is authorized and can be charged
// Attach to customer (if not already)
await omise.customers.update(customer.id, {
default_source: source.id
});
res.redirect('/subscription-success');
} else {
res.redirect('/authorization-failed');
}
});
Step 4: Create Recurring Charges
// Charge customer on recurring basis
async function chargeSubscription(customerId, amount) {
try {
const charge = await omise.charges.create({
customer: customerId,
amount: amount,
currency: 'THB',
description: 'Monthly subscription',
metadata: {
billing_period: '2024-02',
subscription_id: 'sub_12345'
}
});
return charge;
} catch (error) {
console.error('Charge failed:', error);
// Handle failure (retry, notify customer, etc.)
}
}
// Schedule recurring charges
setInterval(async () => {
const subscriptions = await getActiveSubscriptions();
for (const sub of subscriptions) {
if (sub.next_billing_date === today()) {
await chargeSubscription(sub.customer_id, sub.amount);
}
}
}, 24 * 60 * 60 * 1000); // Check daily
Complete Implementation Example
const express = require('express');
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY
});
const app = express();
app.use(express.json());
// Step 1: Subscribe customer
app.post('/subscribe', async (req, res) => {
try {
const { email, plan_id, amount } = req.body;
// Create customer
const customer = await omise.customers.create({
email: email,
description: `Subscription: ${plan_id}`
});
// Create direct debit source
const source = await omise.sources.create({
type: 'pay_with_bill_payment',
amount: amount,
currency: 'THB',
customer: customer.id
});
// Save subscription in database
await saveSubscription({
customer_id: customer.id,
source_id: source.id,
plan_id: plan_id,
amount: amount,
status: 'pending_authorization'
});
// Redirect to bank authorization
res.json({
authorize_uri: source.authorize_uri,
customer_id: customer.id
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Step 2: Handle authorization callback
app.get('/debit/callback', async (req, res) => {
try {
const sourceId = req.query.source_id;
const source = await omise.sources.retrieve(sourceId);
if (source.flow_status === 'successful') {
// Update subscription status
await updateSubscription(source.id, {
status: 'active',
authorized_at: new Date()
});
// Create first charge
const charge = await omise.charges.create({
customer: source.customer,
amount: source.amount,
currency: 'THB',
description: 'Initial subscription payment'
});
res.redirect('/subscription-active');
} else {
await updateSubscription(source.id, {
status: 'authorization_failed'
});
res.redirect('/authorization-failed');
}
} catch (error) {
res.redirect('/error');
}
});
// Step 3: Process recurring charges (cron job)
app.post('/cron/process-subscriptions', async (req, res) => {
try {
const dueSubscriptions = await getDueSubscriptions();
for (const subscription of dueSubscriptions) {
try {
const charge = await omise.charges.create({
customer: subscription.customer_id,
amount: subscription.amount,
currency: 'THB',
description: `Recurring: ${subscription.plan_id}`,
metadata: {
subscription_id: subscription.id,
billing_period: getCurrentPeriod()
}
});
await recordPayment(subscription.id, charge.id);
} catch (error) {
await handleFailedPayment(subscription.id, error);
}
}
res.sendStatus(200);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Webhook handler
app.post('/webhooks/omise', async (req, res) => {
const event = req.body;
if (event.key === 'charge.complete') {
const charge = event.data;
if (charge.status === 'successful') {
await updateSubscriptionPayment(charge.metadata.subscription_id, 'paid');
await sendReceiptEmail(charge);
} else {
await handleFailedPayment(charge.metadata.subscription_id);
await notifyCustomer(charge);
}
}
res.sendStatus(200);
});
app.listen(3000);
Refund Support
// Full or partial refund
const refund = await omise.charges.refund('chrg_test_...', {
amount: 50000 // Full or partial
});
Best Practices
1. Clear Authorization Flow
<div class="direct-debit-setup">
<h3>ตั้งค่าการหักบัญชีอัตโนมัติ</h3>
<div class="benefits">
<p>✅ ชำระอัตโนมัติทุกเดือน</p>
<p>✅ ไม่ต้องกังวลเรื่องลืมจ่าย</p>
<p>✅ ยกเลิกได้ทุกเมื่อ</p>
</div>
<p class="amount">จำนวนเงิน: <strong>฿500/เดือน</strong></p>
<button>อนุมัติการหักบัญชี</button>
</div>
2. Handle Failed Payments
async function handleFailedPayment(subscriptionId, error) {
// Log failure
await logPaymentFailure(subscriptionId, error);
// Retry logic
const subscription = await getSubscription(subscriptionId);
subscription.retry_count++;
if (subscription.retry_count < 3) {
// Retry after 24 hours
await scheduleRetry(subscriptionId, 24);
} else {
// Suspend subscription
await suspendSubscription(subscriptionId);
await notifyCustomerSuspension(subscription.customer_id);
}
}
3. Customer Notifications
// Notify before charging
async function sendUpcomingChargeNotification(customerId, amount, dueDate) {
await sendEmail(customerId, {
subject: 'การแจ้งเตือนการหักบัญชี',
body: `บัญชีของคุณจะถูกหัก ฿${amount} ในวันที่ ${dueDate}`
});
}
// Notify after successful charge
async function sendChargeConfirmation(customerId, charge) {
await sendEmail(customerId, {
subject: 'ยืนยันการห ักบัญชี',
body: `การหักบัญชีสำเร็จ ฿${charge.amount / 100}`
});
}
4. Allow Cancellation
app.post('/subscription/cancel', async (req, res) => {
const { subscription_id } = req.body;
// Mark as canceled (don't charge next billing)
await updateSubscription(subscription_id, {
status: 'canceled',
canceled_at: new Date()
});
// Optionally delete source
// await omise.sources.destroy(source_id);
res.json({ message: 'Subscription canceled' });
});
FAQ
What is Online Direct Debit?
Online Direct Debit allows you to automatically charge customer bank accounts on a recurring basis after they authorize your business once. Perfect for subscriptions and automated billing.
How is it different from Mobile/Internet Banking?
- Mobile/Internet Banking: One-time payments, customer initiates each payment
- Direct Debit: Recurring payments, merchant initiates charges after authorization
Can customers cancel authorization?
Yes, customers can cancel the direct debit mandate through their bank or through your platform. Always provide an easy cancellation option.
What happens if a charge fails?
Common reasons: insufficient funds, account closed, mandate canceled. Implement retry logic (2-3 attempts) and notify customers to update payment method.
Are refunds supported?
Yes, direct debit supports both full and partial refunds.
How long does authorization take?
Initial authorization takes 2-5 minutes. Once authorized, recurring charges process within 1-2 business days.
Related Resources
- Bank Transfers Overview - All bank payment methods
- Mobile Banking - One-time bank payments
- Customers API - Managing customers
- Schedules - Automated billing schedules
- Refunds - Refund policies
- Testing - Test your integration
Next Steps
- Create customer object
- Create direct debit source
- Handle authorization callback
- Implement recurring charge logic
- Add retry and failure handling
- Test full subscription flow
- Go live