Scheduled payments example

Build a monthly card scheduled payments flow with configuration, recurrence, and API provider submission code.

A simple monthly card module uses an addMonths date helper. It bills the premium only; see Create and manage schedules for adding arrears with outstanding_balance.


Configure billing settings

{
  "billingSettings": {
    "batching": {
      "enabled": true,
      "submitPaymentsFunction": "submitPayments",
      "submitBatchSize": 100,
      "scheduleTimeUtc": "05:00",
      "submissionLeadTime": 2,
      "latestSubmissionTimeUtc": "20:00"
    },
    "retry": { "maxAttempts": 3, "backoffDays": 1, "backoffMultiplier": 2 }
  }
}

Add lifecycle hooks

export const afterPolicyIssued = async ({ policy }) => {
  const billingPeriodEnd = addMonths(policy.first_debit_date, 1);

  return [
    {
      name: 'schedule_payment',
      scheduled_for: policy.first_debit_date,
      expected_amount: policy.premium_amount,
      currency: policy.currency,
      premium_type: 'recurring',
      billing_period_start: policy.first_debit_date,
      billing_period_end: billingPeriodEnd,
    },
  ];
};

export const afterPaymentSucceeded = async ({ policy, payment }) => {
  const next = addMonths(payment.billing_period_start, 1);
  const nextPeriodEnd = addMonths(next, 1);

  return [
    {
      name: 'schedule_payment',
      scheduled_for: next,
      expected_amount: policy.premium_amount,
      currency: policy.currency,
      premium_type: 'recurring',
      billing_period_start: next,
      billing_period_end: nextPeriodEnd,
    },
  ];
};

afterPolicyIssued schedules the first payment at issue. afterPaymentSucceeded schedules the next month after each success.


Add the submission hook

export const submitPayments = async ({ payments }) => {
  const results = await Promise.all(
    payments.map(async (p) => {
      try {
        // `provider` is your payment provider's client.
        const charge = await provider.charge({
          amount: p.amount,
          currency: p.currency,
          idempotencyKey: p.payment_id,
        });

        return {
          payment_id: p.payment_id,
          status: 'submitted',
          provider_reference: charge.id,
        };
      } catch (error) {
        return {
          payment_id: p.payment_id,
          status: 'failed',
          failure_reason: error instanceof Error ? error.message : 'provider_submission_failed',
        };
      }
    }),
  );

  return { results };
};

This module schedules the first payment at issue, charges it through its provider on the due date, and schedules the next month after each success.


Related guides