Recurring Payment Schedules
Learn how to create and manage recurring payment schedules for automated subscription billing, memberships, and periodic charges. Schedules automate the process of charging customers at regular intervals.
Overviewโ
The Schedules API enables you to automate recurring charges without manual intervention. Perfect for:
- Subscription services (monthly, yearly)
- Membership dues
- Installment payments
- Usage-based billing cycles
- Recurring donations
- License renewals
Key Benefitsโ
- Automated Billing - Set it once, charges happen automatically
- Flexible Schedules - Daily, weekly, monthly, or custom periods
- Retry Logic - Built-in failed payment handling
- Easy Management - Pause, resume, or cancel schedules
- Comprehensive Tracking - Monitor all scheduled charges
- Customer Retention - Reduce churn with seamless renewals
How Schedules Workโ
- Create schedule with customer and charge details
- Schedule generates charges automatically on specified dates
- System attempts to charge customer's default payment method
- Webhooks notify you of successful/failed charges
- Schedule continues until ended or cancelled
Creating Schedulesโ
Basic Schedule Creationโ
curl https://api.omise.co/schedules \
-u skey_test_123: \
-d "every=1" \
-d "period=month" \
-d "start_date=2024-02-01" \
-d "end_date=2024-12-31" \
-d "charge[customer]=cust_test_123456" \
-d "charge[amount]=100000" \
-d "charge[currency]=THB" \
-d "charge[description]=Monthly Premium Subscription"
const omise = require('omise')({
secretKey: 'skey_test_123'
});
const schedule = await omise.schedules.create({
every: 1,
period: 'month',
start_date: '2024-02-01',
end_date: '2024-12-31',
charge: {
customer: 'cust_test_123456',
amount: 100000,
currency: 'THB',
description: 'Monthly Premium Subscription'
}
});
console.log('Schedule ID:', schedule.id);
console.log('Status:', schedule.status);
console.log('Next occurrence:', schedule.next_occurrences_on[0]);
import omise
from datetime import datetime, timedelta
omise.api_secret = 'skey_test_123'
schedule = omise.Schedule.create(
every=1,
period='month',
start_date='2024-02-01',
end_date='2024-12-31',
charge={
'customer': 'cust_test_123456',
'amount': 100000,
'currency': 'THB',
'description': 'Monthly Premium Subscription'
}
)
print(f'Schedule ID: {schedule.id}')
print(f'Status: {schedule.status}')
print(f'Next occurrence: {schedule.next_occurrences_on[0]}')
<?php
$omise = new Omise(['secretKey' => 'skey_test_123']);
$schedule = $omise['schedules']->create([
'every' => 1,
'period' => 'month',
'start_date' => '2024-02-01',
'end_date' => '2024-12-31',
'charge' => [
'customer' => 'cust_test_123456',
'amount' => 100000,
'currency' => 'THB',
'description' => 'Monthly Premium Subscription'
]
]);
echo "Schedule ID: " . $schedule['id'] . "\n";
echo "Status: " . $schedule['status'];
require 'omise'
Omise.api_key = 'skey_test_123'
schedule = Omise::Schedule.create(
every: 1,
period: 'month',
start_date: '2024-02-01',
end_date: '2024-12-31',
charge: {
customer: 'cust_test_123456',
amount: 100000,
currency: 'THB',
description: 'Monthly Premium Subscription'
}
)
puts "Schedule ID: #{schedule.id}"
puts "Status: #{schedule.status}"
Schedule Periodsโ
// Monthly billing
const monthly = await omise.schedules.create({
every: 1,
period: 'month',
start_date: '2024-02-01',
charge: { /* ... */ }
});
// Weekly billing
const weekly = await omise.schedules.create({
every: 1,
period: 'week',
start_date: '2024-02-01',
charge: { /* ... */ }
});
// Daily billing
const daily = await omise.schedules.create({
every: 1,
period: 'day',
start_date: '2024-02-01',
charge: { /* ... */ }
});
// Quarterly (every 3 months)
const quarterly = await omise.schedules.create({
every: 3,
period: 'month',
start_date: '2024-02-01',
charge: { /* ... */ }
});
// Annual billing
const annual = await omise.schedules.create({
every: 1,
period: 'year',
start_date: '2024-02-01',
charge: { /* ... */ }
});
Schedule Responseโ
{
"object": "schedule",
"id": "schd_test_123456",
"livemode": false,
"location": "/schedules/schd_test_123456",
"status": "active",
"every": 1,
"period": "month",
"on": {
"days_of_month": [1]
},
"in_words": "Every 1 month(s) on the 1st",
"start_date": "2024-02-01",
"end_date": "2024-12-31",
"charge": {
"customer": "cust_test_123456",
"amount": 100000,
"currency": "THB",
"description": "Monthly Premium Subscription"
},
"occurrences": {
"object": "list",
"data": [],
"limit": 20,
"offset": 0,
"total": 0
},
"next_occurrences_on": [
"2024-02-01",
"2024-03-01",
"2024-04-01"
],
"created_at": "2024-01-15T10:00:00Z"
}
Managing Schedulesโ
Retrieve Scheduleโ
const schedule = await omise.schedules.retrieve('schd_test_123456');
console.log('Schedule:', schedule.in_words);
console.log('Status:', schedule.status);
console.log('Next charges:', schedule.next_occurrences_on);
console.log('Completed charges:', schedule.occurrences.total);
schedule = omise.Schedule.retrieve('schd_test_123456')
print(f'Schedule: {schedule.in_words}')
print(f'Status: {schedule.status}')
print(f'Next charges: {schedule.next_occurrences_on}')
print(f'Completed charges: {schedule.occurrences.total}')
curl https://api.omise.co/schedules/schd_test_123456 \
-u skey_test_123:
List All Schedulesโ
// List with filters
const schedules = await omise.schedules.list({
limit: 20,
offset: 0,
order: 'reverse_chronological'
});
schedules.data.forEach(schedule => {
console.log(`${schedule.id} - ${schedule.status} - ${schedule.in_words}`);
});
// Filter by status
const activeSchedules = schedules.data.filter(
s => s.status === 'active'
);
# List with filters
schedules = omise.Schedule.list(
limit=20,
offset=0,
order='reverse_chronological'
)
for schedule in schedules.data:
print(f'{schedule.id} - {schedule.status} - {schedule.in_words}')
# Filter by status
active_schedules = [s for s in schedules.data if s.status == 'active']
Pause Scheduleโ
// Suspend schedule temporarily
await omise.schedules.destroy('schd_test_123456');
// Verify suspension
const schedule = await omise.schedules.retrieve('schd_test_123456');
console.log('Status:', schedule.status); // 'deleted' or 'suspended'
# Suspend schedule temporarily
schedule = omise.Schedule.retrieve('schd_test_123456')
schedule.destroy()
# Verify suspension
schedule.reload()
print(f'Status: {schedule.status}')
curl https://api.omise.co/schedules/schd_test_123456 \
-u skey_test_123: \
-X DELETE
View Schedule Occurrencesโ
const schedule = await omise.schedules.retrieve('schd_test_123456');
const occurrences = schedule.occurrences.data;
occurrences.forEach(occurrence => {
console.log('Date:', occurrence.schedule_date);
console.log('Status:', occurrence.status);
console.log('Charge:', occurrence.result?.id);
console.log('---');
});
schedule = omise.Schedule.retrieve('schd_test_123456')
occurrences = schedule.occurrences.data
for occurrence in occurrences:
print(f'Date: {occurrence.schedule_date}')
print(f'Status: {occurrence.status}')
print(f'Charge: {occurrence.result.id if occurrence.result else None}')
print('---')
Advanced Schedule Configurationโ
On Specific Daysโ
// Charge on specific day of month
const schedule = await omise.schedules.create({
every: 1,
period: 'month',
on: {
days_of_month: [15] // Charge on 15th of each month
},
start_date: '2024-02-15',
charge: {
customer: 'cust_test_123456',
amount: 100000,
currency: 'THB'
}
});
// Multiple days per month
const bimonthly = await omise.schedules.create({
every: 1,
period: 'month',
on: {
days_of_month: [1, 15] // Charge on 1st and 15th
},
start_date: '2024-02-01',
charge: { /* ... */ }
});
End Date Strategiesโ
// End on specific date
const fixedEnd = await omise.schedules.create({
every: 1,
period: 'month',
start_date: '2024-02-01',
end_date: '2024-12-31', // Ends December 31, 2024
charge: { /* ... */ }
});
// No end date (indefinite)
const indefinite = await omise.schedules.create({
every: 1,
period: 'month',
start_date: '2024-02-01',
// No end_date = runs indefinitely
charge: { /* ... */ }
});
// End after specific occurrences
async function createLimitedSchedule(occurrences) {
const startDate = new Date('2024-02-01');
const endDate = new Date(startDate);
endDate.setMonth(endDate.getMonth() + occurrences);
return await omise.schedules.create({
every: 1,
period: 'month',
start_date: startDate.toISOString().split('T')[0],
end_date: endDate.toISOString().split('T')[0],
charge: { /* ... */ }
});
}
Schedule with Metadataโ
const schedule = await omise.schedules.create({
every: 1,
period: 'month',
start_date: '2024-02-01',
charge: {
customer: 'cust_test_123456',
amount: 100000,
currency: 'THB',
description: 'Premium Subscription',
metadata: {
subscription_id: 'sub_12345',
plan: 'premium',
user_id: '67890',
billing_cycle: 'monthly'
}
}
});
Handling Schedule Eventsโ
Webhook Integrationโ
const express = require('express');
const app = express();
app.post('/webhooks/omise', express.json(), async (req, res) => {
const event = req.body;
switch (event.key) {
case 'schedule.occurrence.success':
await handleSuccessfulCharge(event.data);
break;
case 'schedule.occurrence.failure':
await handleFailedCharge(event.data);
break;
case 'schedule.expiring':
await handleExpiringSchedule(event.data);
break;
}
res.sendStatus(200);
});
async function handleSuccessfulCharge(occurrence) {
console.log('Successful charge:', occurrence.result.id);
// Update your database
await updateSubscriptionStatus(
occurrence.charge.metadata.subscription_id,
'active',
occurrence.schedule_date
);
// Send receipt to customer
await sendReceipt(occurrence);
}
async function handleFailedCharge(occurrence) {
console.log('Failed charge:', occurrence.message);
// Get failure reason
const charge = occurrence.result;
const failureCode = charge?.failure_code;
// Handle based on failure type
switch (failureCode) {
case 'insufficient_funds':
await scheduleRetry(occurrence);
await notifyCustomer(occurrence, 'payment_failed');
break;
case 'expired_card':
await requestCardUpdate(occurrence);
await pauseSchedule(occurrence.schedule);
break;
default:
await notifyAdmin(occurrence);
}
}
Monitor Schedule Healthโ
class ScheduleMonitor {
async checkScheduleHealth(scheduleId) {
const schedule = await omise.schedules.retrieve(scheduleId);
const occurrences = schedule.occurrences.data;
// Calculate success rate
const total = occurrences.length;
const successful = occurrences.filter(
o => o.status === 'success'
).length;
const successRate = total > 0 ? (successful / total) * 100 : 100;
// Get recent failures
const recentFailures = occurrences
.filter(o => o.status === 'failed')
.slice(0, 3);
// Check for concerning patterns
const alerts = [];
if (successRate < 80) {
alerts.push({
level: 'warning',
message: `Low success rate: ${successRate.toFixed(1)}%`
});
}
if (recentFailures.length >= 2) {
alerts.push({
level: 'critical',
message: 'Multiple recent failures'
});
}
return {
scheduleId,
status: schedule.status,
successRate,
totalCharges: total,
recentFailures: recentFailures.length,
alerts,
nextCharge: schedule.next_occurrences_on[0]
};
}
async monitorAllSchedules() {
const schedules = await omise.schedules.list({ limit: 100 });
const activeSchedules = schedules.data.filter(
s => s.status === 'active'
);
const reports = await Promise.all(
activeSchedules.map(s => this.checkScheduleHealth(s.id))
);
// Filter schedules needing attention
const needsAttention = reports.filter(r => r.alerts.length > 0);
return { reports, needsAttention };
}
}
Common Use Casesโ
Subscription Serviceโ
class SubscriptionService {
async createSubscription(userId, plan, customerId) {
const planConfig = this.getPlanConfig(plan);
// Create schedule
const schedule = await omise.schedules.create({
every: planConfig.billingCycle,
period: planConfig.period,
start_date: this.getNextBillingDate(),
charge: {
customer: customerId,
amount: planConfig.amount,
currency: 'THB',
description: `${plan} Subscription`,
metadata: {
user_id: userId,
plan: plan,
subscription_type: 'recurring'
}
}
});
// Store in database
await this.saveSubscription({
userId,
plan,
scheduleId: schedule.id,
status: 'active',
startDate: schedule.start_date,
nextBillingDate: schedule.next_occurrences_on[0]
});
return schedule;
}
getPlanConfig(plan) {
const plans = {
basic: {
amount: 29900,
billingCycle: 1,
period: 'month'
},
premium: {
amount: 59900,
billingCycle: 1,
period: 'month'
},
annual: {
amount: 599900,
billingCycle: 1,
period: 'year'
}
};
return plans[plan];
}
getNextBillingDate() {
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
return tomorrow.toISOString().split('T')[0];
}
async upgradeSubscription(subscriptionId, newPlan) {
const subscription = await this.getSubscription(subscriptionId);
// Cancel old schedule
await omise.schedules.destroy(subscription.scheduleId);
// Create new schedule with prorated start
const newSchedule = await this.createSubscription(
subscription.userId,
newPlan,
subscription.customerId
);
// Calculate and charge prorated amount
const proratedAmount = this.calculateProration(
subscription,
newPlan
);
if (proratedAmount > 0) {
await omise.charges.create({
customer: subscription.customerId,
amount: proratedAmount,
currency: 'THB',
description: 'Prorated upgrade charge'
});
}
return newSchedule;
}
async cancelSubscription(subscriptionId, immediately = false) {
const subscription = await this.getSubscription(subscriptionId);
if (immediately) {
// Cancel schedule now
await omise.schedules.destroy(subscription.scheduleId);
await this.updateSubscriptionStatus(subscriptionId, 'cancelled');
} else {
// Cancel at end of billing period
const schedule = await omise.schedules.retrieve(
subscription.scheduleId
);
// Set end date to next billing date
const nextBilling = schedule.next_occurrences_on[0];
await this.updateSubscription(subscriptionId, {
status: 'cancelling',
cancelDate: nextBilling
});
}
}
}
Installment Plansโ
class InstallmentService {
async createInstallmentPlan(customerId, totalAmount, installments) {
const installmentAmount = Math.floor(totalAmount / installments);
const startDate = new Date();
startDate.setMonth(startDate.getMonth() + 1);
const endDate = new Date(startDate);
endDate.setMonth(endDate.getMonth() + installments - 1);
const schedule = await omise.schedules.create({
every: 1,
period: 'month',
start_date: startDate.toISOString().split('T')[0],
end_date: endDate.toISOString().split('T')[0],
charge: {
customer: customerId,
amount: installmentAmount,
currency: 'THB',
description: `Installment ${1} of ${installments}`,
metadata: {
payment_plan: 'installment',
total_amount: totalAmount,
installment_count: installments
}
}
});
return {
scheduleId: schedule.id,
installmentAmount,
totalAmount,
installments,
startDate: schedule.start_date,
endDate: schedule.end_date,
nextPayment: schedule.next_occurrences_on[0]
};
}
async getInstallmentProgress(scheduleId) {
const schedule = await omise.schedules.retrieve(scheduleId);
const occurrences = schedule.occurrences.data;
const completed = occurrences.filter(
o => o.status === 'success'
).length;
const totalInstallments = schedule.charge.metadata.installment_count;
const remaining = totalInstallments - completed;
const progress = (completed / totalInstallments) * 100;
return {
completed,
remaining,
total: totalInstallments,
progress,
nextPaymentDate: schedule.next_occurrences_on[0]
};
}
}
Membership Renewalsโ
class MembershipService {
async createMembership(userId, membershipType, customerId) {
const config = this.getMembershipConfig(membershipType);
const schedule = await omise.schedules.create({
every: 1,
period: 'year',
start_date: this.getAnniversaryDate(),
charge: {
customer: customerId,
amount: config.annualFee,
currency: 'THB',
description: `${membershipType} Membership Renewal`,
metadata: {
user_id: userId,
membership_type: membershipType,
renewal_type: 'automatic'
}
}
});
return {
scheduleId: schedule.id,
membershipType,
nextRenewal: schedule.next_occurrences_on[0],
annualFee: config.annualFee
};
}
async sendRenewalReminders() {
const schedules = await omise.schedules.list({ limit: 100 });
const activeSchedules = schedules.data.filter(
s => s.status === 'active' &&
s.charge.metadata?.renewal_type === 'automatic'
);
for (const schedule of activeSchedules) {
const nextRenewal = new Date(schedule.next_occurrences_on[0]);
const daysUntilRenewal = Math.floor(
(nextRenewal - new Date()) / (1000 * 60 * 60 * 24)
);
if (daysUntilRenewal === 30 || daysUntilRenewal === 7) {
await this.sendRenewalReminder(
schedule.charge.customer,
daysUntilRenewal,
schedule.charge.amount
);
}
}
}
}
Usage-Based Billing Cyclesโ
class UsageBillingService {
async createUsageSchedule(customerId) {
// Create monthly schedule for usage billing
const schedule = await omise.schedules.create({
every: 1,
period: 'month',
on: {
days_of_month: [1] // Bill on first of month
},
start_date: this.getFirstOfNextMonth(),
charge: {
customer: customerId,
amount: 0, // Will be calculated based on usage
currency: 'THB',
description: 'Monthly usage billing',
metadata: {
billing_type: 'usage_based'
}
}
});
return schedule;
}
async processMonthlyUsage(customerId) {
// Get usage for past month
const usage = await this.getMonthlyUsage(customerId);
const amount = this.calculateUsageAmount(usage);
if (amount > 0) {
// Create one-time charge for usage
const charge = await omise.charges.create({
customer: customerId,
amount,
currency: 'THB',
description: `Usage billing - ${usage.units} units`,
metadata: {
billing_period: this.getLastMonth(),
units_used: usage.units,
rate: usage.rate
}
});
return charge;
}
}
}
Best Practicesโ
Schedule Creationโ
// 1. Validate customer before creating schedule
async function createScheduleSafely(customerId, scheduleData) {
// Verify customer exists and has payment method
const customer = await omise.customers.retrieve(customerId);
if (!customer.default_card) {
throw new Error('Customer has no payment method');
}
// Verify card is not expired
const card = customer.cards.data.find(
c => c.id === customer.default_card
);
const expiry = new Date(
card.expiration_year,
card.expiration_month - 1,
1
);
if (expiry <= new Date()) {
throw new Error('Payment method has expired');
}
// Create schedule
return await omise.schedules.create({
...scheduleData,
charge: {
...scheduleData.charge,
customer: customerId
}
});
}
// 2. Set reasonable start dates
function getAppropriateStartDate() {
// Start at least 1 day in the future
const startDate = new Date();
startDate.setDate(startDate.getDate() + 1);
startDate.setHours(0, 0, 0, 0);
return startDate.toISOString().split('T')[0];
}
// 3. Use descriptive charge descriptions
const schedule = await omise.schedules.create({
every: 1,
period: 'month',
start_date: getAppropriateStartDate(),
charge: {
customer: customerId,
amount: 100000,
currency: 'THB',
description: `Premium Plan - Monthly Subscription - ${new Date().getFullYear()}`,
metadata: {
plan: 'premium',
user_id: userId,
created_by: 'subscription_service'
}
}
});
Error Handlingโ
class ScheduleErrorHandler {
async createScheduleWithRetry(scheduleData, maxRetries = 3) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await omise.schedules.create(scheduleData);
} catch (error) {
lastError = error;
console.log(`Attempt ${attempt} failed:`, error.message);
// Handle specific errors
if (error.code === 'invalid_charge') {
// Fix charge data and retry
scheduleData = await this.fixChargeData(scheduleData);
} else if (error.code === 'customer_not_found') {
// Don't retry if customer doesn't exist
throw error;
}
// Wait before retry
if (attempt < maxRetries) {
await this.delay(1000 * attempt);
}
}
}
throw lastError;
}
async handleScheduleFailure(occurrence) {
const charge = occurrence.result;
const customer = await omise.customers.retrieve(
occurrence.charge.customer
);
// Log failure
await this.logFailure({
scheduleId: occurrence.schedule,
occurrenceId: occurrence.id,
failureCode: charge?.failure_code,
failureMessage: charge?.failure_message,
customerEmail: customer.email
});
// Take action based on failure
if (charge?.failure_code === 'insufficient_funds') {
// Retry in 3 days
await this.scheduleRetry(occurrence, 3);
} else if (charge?.failure_code === 'expired_card') {
// Request card update
await this.requestCardUpdate(customer);
// Pause schedule
await omise.schedules.destroy(occurrence.schedule);
}
}
}
Monitoringโ
class ScheduleMonitoring {
async dailyHealthCheck() {
console.log('Running daily schedule health check...');
const schedules = await omise.schedules.list({ limit: 100 });
const active = schedules.data.filter(s => s.status === 'active');
const issues = [];
for (const schedule of active) {
// Check for repeated failures
const recentFailures = schedule.occurrences.data
.filter(o => o.status === 'failed')
.slice(0, 3);
if (recentFailures.length >= 2) {
issues.push({
type: 'repeated_failures',
scheduleId: schedule.id,
customerId: schedule.charge.customer,
failures: recentFailures.length
});
}
// Check for expiring payment methods
const customer = await omise.customers.retrieve(
schedule.charge.customer
);
const card = customer.cards.data.find(
c => c.id === customer.default_card
);
if (card) {
const expiryDate = new Date(
card.expiration_year,
card.expiration_month - 1,
1
);
const daysUntilExpiry = Math.floor(
(expiryDate - new Date()) / (1000 * 60 * 60 * 24)
);
if (daysUntilExpiry <= 30) {
issues.push({
type: 'expiring_card',
scheduleId: schedule.id,
customerId: customer.id,
daysUntilExpiry
});
}
}
}
// Report issues
if (issues.length > 0) {
await this.reportIssues(issues);
}
return {
totalSchedules: active.length,
issuesFound: issues.length,
issues
};
}
}
Troubleshootingโ
Schedule Not Creating Chargesโ
async function diagnoseSchedule(scheduleId) {
const schedule = await omise.schedules.retrieve(scheduleId);
console.log('Schedule Status:', schedule.status);
if (schedule.status !== 'active') {
return 'Schedule is not active';
}
// Check dates
const startDate = new Date(schedule.start_date);
const today = new Date();
if (startDate > today) {
return `Schedule starts in the future: ${schedule.start_date}`;
}
// Check customer
try {
const customer = await omise.customers.retrieve(
schedule.charge.customer
);
if (!customer.default_card) {
return 'Customer has no payment method';
}
} catch (error) {
return 'Customer not found or deleted';
}
// Check recent occurrences
const recentOccurrences = schedule.occurrences.data.slice(0, 5);
const allFailed = recentOccurrences.every(o => o.status === 'failed');
if (allFailed && recentOccurrences.length > 0) {
return 'All recent charges failed - check payment method';
}
return 'Schedule appears healthy';
}
Failed Recurring Chargesโ
async function analyzeFailures(scheduleId) {
const schedule = await omise.schedules.retrieve(scheduleId);
const failures = schedule.occurrences.data.filter(
o => o.status === 'failed'
);
// Group by failure reason
const failuresByReason = failures.reduce((acc, failure) => {
const code = failure.result?.failure_code || 'unknown';
acc[code] = (acc[code] || 0) + 1;
return acc;
}, {});
console.log('Failure analysis:');
Object.entries(failuresByReason).forEach(([code, count]) => {
console.log(` ${code}: ${count} occurrences`);
});
// Suggest actions
const suggestions = [];
if (failuresByReason.insufficient_funds) {
suggestions.push('Consider retry logic with different timing');
}
if (failuresByReason.expired_card) {
suggestions.push('Request customer to update payment method');
}
if (failuresByReason.stolen_or_lost_card) {
suggestions.push('Pause schedule and require new payment method');
}
return { failuresByReason, suggestions };
}
FAQโ
General Questionsโ
Q: When does the first charge occur?
A: The first charge occurs on the start_date you specify. Make sure it's at least 1 day in the future.
Q: Can I change a schedule after creation?
A: No, schedules are immutable. To change parameters, you must delete the old schedule and create a new one.
Q: What happens if a scheduled charge fails?
A: You'll receive a webhook notification. The schedule continues attempting future charges. Implement retry logic in your application.
Q: How far in advance are charges scheduled?
A: Schedules show the next 3 upcoming charge dates in next_occurrences_on.
Q: Can I manually trigger a scheduled charge?
A: No, scheduled charges run automatically. For manual charges, use the Charges API directly.
Q: What timezone are schedules in?
A: All schedule dates use UTC timezone. Plan your start dates accordingly.
Technical Questionsโ
Q: Can I create schedules for different currencies?
A: Yes, specify the currency in the charge parameters. The customer must have a payment method that supports that currency.
Q: How do I handle prorated charges?
A: Schedules don't automatically prorate. Create a one-time charge for prorated amounts, then start the schedule for full periods.
Q: Can I charge different amounts each period?
A: No, schedules charge the same amount each time. For variable amounts, use webhooks to modify the charge or create one-time charges.
Q: What's the maximum schedule duration?
A: There's no maximum duration. Schedules can run indefinitely if no end_date is specified.
Q: Can I get notified before charges occur?
A: Not directly. Implement monitoring to check next_occurrences_on and send notifications from your application.
Q: How do I pause a schedule temporarily?
A: Delete the schedule to pause it. Create a new schedule when ready to resume. Store schedule parameters in your database for recreation.
Best Practices Questionsโ
Q: Should I create schedules during customer signup?
A: Yes, but ensure the customer has a valid payment method first. Consider starting the schedule after a successful initial charge.
Q: How should I handle failed subscription payments?
A: Implement retry logic (with backoff), notify customers, and provide easy payment method update flows. Consider grace periods.
Q: What metadata should I include?
A: Store your internal IDs (user_id, subscription_id), plan details, and any information needed for reconciliation and customer support.
Q: How do I test schedules?
A: Use test mode with start dates in the near future. You can also use the API to simulate time passage for testing.
Related Resourcesโ
- Customer Management
- Saved Payment Methods
- Schedules API Reference
- Charges API Reference
- Webhooks
- Testing Schedules
- Subscription Management Guide