Skip to main content

Automated Transfer Schedules

Transfer schedules allow you to automate recurring transfers to recipients. This guide covers creating, managing, and monitoring automated transfer schedules.

Overviewโ€‹

Transfer schedules enable:

  • Automated Payouts: Set up recurring transfers without manual intervention
  • Flexible Frequency: Daily, weekly, or monthly schedules
  • Multiple Recipients: Schedule transfers to multiple recipients
  • Balance Management: Automatic handling of insufficient balance scenarios
  • Status Tracking: Monitor scheduled transfers and their execution
  • Easy Management: Update or pause schedules as needed

Key Featuresโ€‹

  • Recurring Transfers: Automate regular payouts
  • Multiple Schedules: Create schedules for different recipients
  • Flexible Timing: Choose frequency and start dates
  • Automatic Retry: Retry failed transfers automatically
  • Notifications: Webhook events for schedule executions
  • Pause/Resume: Temporarily stop schedules without deleting

Creating Transfer Schedulesโ€‹

Basic Schedule Creationโ€‹

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

// Create a weekly transfer schedule
async function createWeeklySchedule(recipientId, amount) {
try {
const schedule = await omise.schedules.create({
every: 1,
period: 'week',
on: {
weekday_of_month: 'second_monday' // Every second Monday
},
start_date: '2024-02-01',
end_date: '2024-12-31',
charge: {
recipient: recipientId,
amount: amount,
metadata: {
schedule_type: 'weekly_payout',
purpose: 'contractor_payment'
}
}
});

console.log('Schedule created:', schedule.id);
console.log('Status:', schedule.status);
console.log('Next occurrence:', schedule.next_occurrence_dates[0]);

return schedule;
} catch (error) {
console.error('Failed to create schedule:', error.message);
throw error;
}
}

// Create monthly salary schedule
async function createMonthlySalarySchedule(recipientId, amount) {
const schedule = await omise.schedules.create({
every: 1,
period: 'month',
on: {
day_of_month: 25 // 25th of every month
},
start_date: '2024-01-01',
charge: {
recipient: recipientId,
amount: amount,
metadata: {
schedule_type: 'monthly_salary',
payment_type: 'payroll'
}
}
});

return schedule;
}

// Create daily schedule
async function createDailySchedule(recipientId, amount) {
const schedule = await omise.schedules.create({
every: 1,
period: 'day',
start_date: '2024-01-01',
charge: {
recipient: recipientId,
amount: amount,
metadata: {
schedule_type: 'daily_settlement'
}
}
});

return schedule;
}

// Example usage
createWeeklySchedule('recp_test_123456789', 500000);
createMonthlySalarySchedule('recp_test_123456789', 5000000);

Managing Schedulesโ€‹

List All Schedulesโ€‹

async function listSchedules() {
const schedules = await omise.schedules.list();

for (const schedule of schedules.data) {
console.log(`${schedule.id}: ${schedule.status}`);
console.log(` Period: Every ${schedule.every} ${schedule.period}(s)`);
console.log(` Next: ${schedule.next_occurrence_dates[0]}`);
}

return schedules;
}

Update Scheduleโ€‹

def update_schedule(schedule_id, updates):
"""Update schedule details"""
schedule = omise.Schedule.retrieve(schedule_id)
schedule.update(**updates)

print(f"Schedule {schedule_id} updated")
return schedule

# Pause schedule
update_schedule('schd_test_123', {'status': 'suspended'})

# Resume schedule
update_schedule('schd_test_123', {'status': 'active'})

# Update end date
update_schedule('schd_test_123', {'end_date': '2025-12-31'})

Delete Scheduleโ€‹

def delete_schedule(schedule_id)
schedule = Omise::Schedule.retrieve(schedule_id)
schedule.destroy

puts "Schedule #{schedule_id} deleted"
end

Common Use Casesโ€‹

1. Payroll Automationโ€‹

class PayrollAutomation {
async setupEmployeeSchedules(employees) {
const schedules = [];

for (const employee of employees) {
const schedule = await omise.schedules.create({
every: 1,
period: 'month',
on: { day_of_month: 25 },
start_date: employee.start_date,
charge: {
recipient: employee.recipient_id,
amount: employee.monthly_salary,
metadata: {
employee_id: employee.id,
employee_name: employee.name,
department: employee.department,
schedule_type: 'monthly_payroll'
}
}
});

schedules.push({
employee_id: employee.id,
schedule_id: schedule.id
});

console.log(`โœ“ Payroll schedule created for ${employee.name}`);
}

return schedules;
}

async updateSalary(employeeId, newSalary) {
// Find schedule
const scheduleId = this.getScheduleForEmployee(employeeId);

// Update schedule amount
const schedule = await omise.schedules.retrieve(scheduleId);
await schedule.update({
charge: {
...schedule.charge,
amount: newSalary
}
});

console.log(`Salary updated for employee ${employeeId}`);
}
}

2. Subscription Vendor Paymentsโ€‹

class SubscriptionPayments:
def __init__(self):
self.schedules = {}

def setup_vendor_schedule(self, vendor_id, recipient_id, amount, period):
"""Set up recurring vendor payments"""

schedule = omise.Schedule.create(
every=1,
period=period, # 'month', 'week', etc.
start_date=date.today().isoformat(),
charge={
'recipient': recipient_id,
'amount': amount,
'metadata': {
'vendor_id': vendor_id,
'payment_type': 'subscription',
'period': period
}
}
)

self.schedules[vendor_id] = schedule.id
print(f"โœ“ Schedule created for vendor {vendor_id}")

return schedule

def pause_vendor_payments(self, vendor_id):
"""Temporarily pause vendor payments"""
schedule_id = self.schedules.get(vendor_id)
if schedule_id:
schedule = omise.Schedule.retrieve(schedule_id)
schedule.update(status='suspended')
print(f"Payments paused for vendor {vendor_id}")

def resume_vendor_payments(self, vendor_id):
"""Resume vendor payments"""
schedule_id = self.schedules.get(vendor_id)
if schedule_id:
schedule = omise.Schedule.retrieve(schedule_id)
schedule.update(status='active')
print(f"Payments resumed for vendor {vendor_id}")

3. Marketplace Seller Payoutsโ€‹

class MarketplacePayoutSchedules
def setup_seller_schedule(seller_id, recipient_id, payout_frequency)
# Determine schedule parameters
schedule_config = case payout_frequency
when 'daily'
{ every: 1, period: 'day' }
when 'weekly'
{ every: 1, period: 'week', on: { weekday: 'monday' } }
when 'monthly'
{ every: 1, period: 'month', on: { day_of_month: 1 } }
end

schedule = Omise::Schedule.create(
**schedule_config,
start_date: Date.today.to_s,
charge: {
recipient: recipient_id,
# Amount will be calculated based on sales
metadata: {
seller_id: seller_id,
payout_type: 'marketplace_earnings',
frequency: payout_frequency
}
}
)

puts "โœ“ Payout schedule created for seller #{seller_id}"
puts " Frequency: #{payout_frequency}"
schedule
end

def adjust_payout_frequency(seller_id, new_frequency)
# Delete old schedule and create new one
old_schedule_id = get_schedule_for_seller(seller_id)
Omise::Schedule.retrieve(old_schedule_id).destroy

recipient_id = get_recipient_for_seller(seller_id)
setup_seller_schedule(seller_id, recipient_id, new_frequency)
end
end

Schedule Monitoringโ€‹

Track Schedule Executionsโ€‹

class ScheduleMonitor {
public function checkScheduleStatus($scheduleId) {
$schedule = OmiseSchedule::retrieve($scheduleId);

$status = [
'id' => $schedule['id'],
'status' => $schedule['status'],
'next_occurrences' => $schedule['next_occurrence_dates'],
'last_execution' => $this->getLastExecution($scheduleId)
];

if ($schedule['status'] === 'suspended') {
echo "โš  Schedule is suspended\n";
} elseif ($schedule['status'] === 'active') {
echo "โœ“ Schedule is active\n";
echo "Next run: {$schedule['next_occurrence_dates'][0]}\n";
}

return $status;
}

public function getFailedExecutions($scheduleId) {
// Get schedule occurrences
$schedule = OmiseSchedule::retrieve($scheduleId);
$occurrences = $schedule['occurrences']['data'];

$failed = array_filter($occurrences, function($occ) {
return $occ['status'] === 'failed';
});

return $failed;
}

private function getLastExecution($scheduleId) {
$schedule = OmiseSchedule::retrieve($scheduleId);
$occurrences = $schedule['occurrences']['data'];

return !empty($occurrences) ? $occurrences[0] : null;
}
}

Best Practicesโ€‹

1. Handle Insufficient Balanceโ€‹

// Set up webhook to handle schedule execution failures
app.post('/webhooks/omise', async (req, res) => {
const event = req.body;

if (event.key === 'schedule.occurrence.failed') {
const occurrence = event.data;

if (occurrence.failure_code === 'insufficient_balance') {
console.log(`Schedule ${occurrence.schedule} failed: insufficient balance`);

// Notify finance team
await notifyFinanceTeam({
schedule_id: occurrence.schedule,
required_amount: occurrence.charge.amount,
failure_reason: occurrence.failure_message
});

// Queue for retry when balance is available
await queueScheduleRetry(occurrence.schedule);
}
}

res.sendStatus(200);
});

2. Monitor Schedule Healthโ€‹

def monitor_schedule_health():
"""Monitor all schedules for issues"""
schedules = omise.Schedule.list()

issues = []

for schedule in schedules.data:
if schedule.status == 'suspended':
issues.append({
'schedule_id': schedule.id,
'issue': 'suspended',
'action': 'Review and resume if needed'
})

# Check recent failures
occurrences = schedule.occurrences.data
recent_failures = [o for o in occurrences[:5] if o.status == 'failed']

if len(recent_failures) >= 3:
issues.append({
'schedule_id': schedule.id,
'issue': 'multiple_failures',
'failure_count': len(recent_failures),
'action': 'Investigate recipient or balance issues'
})

if issues:
print(f"โš  Found {len(issues)} schedule issues:")
for issue in issues:
print(f" - {issue['schedule_id']}: {issue['issue']}")

return issues

3. Audit Trailโ€‹

class ScheduleAuditLog
def self.log_schedule_event(event_type, schedule_id, details = {})
log_entry = {
event: event_type,
schedule_id: schedule_id,
timestamp: Time.now.iso8601,
details: details
}

File.open('schedule_audit.log', 'a') do |f|
f.puts JSON.generate(log_entry)
end
end
end

# Usage
ScheduleAuditLog.log_schedule_event('schedule_created', schedule.id, {
recipient: schedule.charge.recipient,
amount: schedule.charge.amount,
frequency: "#{schedule.every} #{schedule.period}"
})

FAQโ€‹

Can I change the amount for scheduled transfers?โ€‹

Yes, you can update the schedule to change the transfer amount. However, changes only affect future transfers, not past or pending ones.

What happens if I don't have sufficient balance?โ€‹

If your balance is insufficient when a scheduled transfer is due, the transfer will fail with an insufficient_balance error. You can set up webhooks to be notified and handle this scenario.

Can I create schedules for multiple recipients?โ€‹

Yes, you need to create a separate schedule for each recipient. Each schedule represents transfers to one recipient only.

How do I stop scheduled transfers temporarily?โ€‹

Update the schedule status to suspended. This pauses the schedule without deleting it. You can resume by setting the status back to active.

Can I backdate a schedule?โ€‹

No, you cannot create schedules with a start date in the past. The start date must be today or a future date.

What timezone are schedules executed in?โ€‹

Schedules are executed in the timezone configured for your Omise account, typically Thailand timezone (UTC+7).

How far in advance can I see upcoming scheduled transfers?โ€‹

The schedule object includes a next_occurrence_dates array showing upcoming execution dates. Typically shows the next 5-10 occurrences.

What happens when a schedule ends?โ€‹

When a schedule reaches its end_date, it automatically becomes inactive and no longer executes. You can create a new schedule if you want to continue transfers.

Next Stepsโ€‹