Billing Cycles & Use Cases

Overview of recurrence logic and practical use cases. Review the supported billing intervals and the specific data structures required to automate your customer's payment schedule across daily, weekly, monthly, or yearly cycles.

Billing Cycles

The system calculates next_payment_date automatically based on the interval and initial next_payment_date provided at creation. After each successful payment, the next date advances by the configured interval. Dates falling on weekends (Saturday/Sunday) are automatically adjusted to the next Monday.

IntervalDescriptionDate CalculationNotes
weeklyEvery 7 daysAdds 7 days to last payment dateWeekends adjusted to Monday
monthlyEvery monthAdds 1 month to last payment dateMaintains same day of month
quarterlyEvery 3 monthsAdds 3 months to last payment dateJanuary → April → July → October
semiannualEvery 6 monthsAdds 6 months to last payment dateTwice yearly
yearlyEvery 12 monthsAdds 12 months to last payment dateAnnual billing

Important Date Rules

RuleDescription
Future Dates OnlyCharge/start dates must be in the future (today is not allowed)
Weekend AdjustmentDates on Saturday/Sunday automatically moved to next Monday
End DateFor recurring debits, end_date must be after next_payment_date
Leap Year HandlingFebruary 29 dates adjusted to February 28 in non-leap years



Order Generation Schedule

Orders are generated automatically when the following conditions are met:

  1. Direct debit status is active
  2. is_fixed_amount is true
  3. next_payment_date ≤ today
  4. is_extended_for_retry is not true
  5. No active orders exist (status: created, pending, or in_process)
    After order generation, next_payment_date advances by the configured interval.



Practical Use Cases

Below are common scenarios demonstrating how to configure the API for different business models. See our Direct Debit Management page for further details on how to use each endpoint.

Case 1: Monthly Subscription

Scenario: A gym charges $500 MXN monthly on the 1st of each month.

// Create the direct debit
POST /api/direct-debits
{
  "customer_id": "6944a4946afaf625504595e3",
  "is_fixed_amount": true,
  "is_recurring": true,
  "amount": 500.00,
  "currency": "MXN",
  "interval": "monthly",
  "next_payment_date": "2026-04-01",
  "end_date": "2027-04-01",
  "concept": "Gym Membership"
}
// System calculates next dates: 2026-04-01, 2026-05-01, 2026-06-01...
// Weekends automatically adjusted to Monday

Case 2: Quarterly Payment

Scenario: A software company charges $3,000 MXN each quarter on the 15th.

POST /api/direct-debits
{
  "customer_id": "6944a4946afaf625504595e4",
  "is_fixed_amount": true,
  "is_recurring": true,
  "amount": 3000.00,
  "currency": "MXN",
  "interval": "quarterly",
  "next_payment_date": "2026-01-15",
  "end_date": "2027-01-15",
  "concept": "Software License"
}
// System calculates next dates: 2026-01-15, 2026-04-15, 2026-07-15, 2026-10-15

Case 3: Weekly Payment

Scenario: A streaming service charges $99 MXN every week on Fridays.

POST /api/direct-debits
{
  "customer_id": "6944a4946afaf625504595e5",
  "is_fixed_amount": true,
  "is_recurring": true,
  "amount": 99.00,
  "currency": "MXN",
  "interval": "weekly",
  "next_payment_date": "2026-04-03",  // Friday
  "end_date": "2026-12-31",
  "concept": "Streaming Premium"
}
// System calculates next dates: 2026-04-03, 2026-04-10, 2026-04-17...
// If date falls on weekend, automatically moved to Monday

Case 4: Scheduled One-Time Charge

Scenario: A one-time charge of $10,000 MXN scheduled for March 31, 2026.

POST /api/direct-debits
{
  "customer_id": "6944a4946afaf625504595e6",
  "is_fixed_amount": true,
  "is_recurring": false,
  "amount": 10000.00,
  "currency": "MXN",
  "next_payment_date": "2026-03-31",
  "concept": "Payment for professional services"
}
// Single charge only. After payment, status changes to 'completed'
// No end_date needed for non-recurring debits

Case 5: Variable Amount Charge

Scenario: A utility company charges varying amounts each month based on consumption.

// Step 1: Create variable direct debit (no fixed amount)
POST /api/direct-debits
{
  "customer_id": "6944a4946afaf625504595e7",
  "is_fixed_amount": false,
  "currency": "MXN",
  "concept": "Electric Utility"
}
// Step 2: After activation, create individual charges
POST /api/direct-debits/6944a5366afaf625504595ea/charges
{
  "amount": 1250.00,
  "scheduled_date": "2026-04-15"
}
// Step 3: Next month's charge
POST /api/direct-debits/6944a5366afaf625504595ea/charges
{
  "amount": 1480.00,
  "scheduled_date": "2026-05-15"
}
// Each charge is an independent order with its own amount and date

Case 6: Semiannual Insurance Payment

Scenario: An insurance company charges $5,000 MXN twice a year.

POST /api/direct-debits
{
  "customer_id": "6944a4946afaf625504595e8",
  "is_fixed_amount": true,
  "is_recurring": true,
  "amount": 5000.00,
  "currency": "MXN",
  "interval": "semiannual",
  "next_payment_date": "2026-01-01",
  "end_date": "2028-01-01",
  "concept": "Insurance Premium"
}
// System calculates next dates: 2026-01-01, 2026-07-01, 2027-01-01, 2027-07-01

Case 7: Annual Membership

Scenario: A professional association charges $1,200 MXN yearly.

POST /api/direct-debits
{
  "customer_id": "6944a4946afaf625504595e9",
  "is_fixed_amount": true,
  "is_recurring": true,
  "amount": 1200.00,
  "currency": "MXN",
  "interval": "yearly",
  "next_payment_date": "2026-01-01",
  "concept": "Annual Membership Dues"
}
// System calculates next dates: 2026-01-01, 2027-01-01, 2028-01-01...



Direct Debit Bank Returns

A bank return occurs when Santander reverses funds from a Direct Debit charge that was already collected and whose related order is marked as paid. For example, the bank may return funds because of insufficient funds discovered after settlement, a closed account, a customer dispute, or another bank rejection reason.

Unlike direct_debit.payment_failed, which applies to pending collection attempts, a return applies after a successful collection has been reversed.

What Happens in Ecart Pay

  1. Santander delivers a return file with the affected order_id and return code.
  2. Ecart Pay matches the return to a Direct Debit order with status paid.
  3. Ecart Pay creates a transaction linked to the original order_id with the following values:
    • type: charge
    • reason: direct_debit_return
    • status: paid
  4. The merchant balance is reduced by the returned amount.
  5. Ecart Pay emits the direct_debit.payment_returned webhook.
triangle-exclamation

A bank return does not change the Direct Debit lifecycle status, such as active or pending. It records the bank return against the related paid order only.

Payment Failed vs. Payment Returned

ScenarioWhen It HappensOrder Status at Processing TimeWebhook
Payment failedThe bank rejects a pending collection attempt.pendingdirect_debit.payment_failed
Payment returnedThe bank returns funds after a successful collection.paiddirect_debit.payment_returned

Merchant Impact

  • The activity appears in the dashboard as a Direct Debit return.
  • Your available balance decreases by the returned amount.
  • Use order_id to reconcile the return with the original payment.
  • Subscribe to direct_debit.payment_returned to automate reconciliation, customer notifications, or retry logic in your system.

Webhook Event: direct_debit.payment_returned

This event is emitted when Santander reports that a previously successful Direct Debit payment was returned after collection.

EventDescription
direct_debit.payment_returnedEmitted when a previously successful Direct Debit payment is returned by the bank.

Payload Example

{
  "event": "direct_debit.payment_returned",
  "data": {
    "message": "A direct debit payment was returned by the bank.",
    "direct_debit_id": "682123abc456def789012345",
    "reference": 1234567,
    "status": "active",
    "currency": "MXN",
    "amount": 500.00,
    "is_recurring": true,
    "is_fixed_amount": true,
    "validation_level": 1,
    "customer_id": "64b2c3d4e5f67890abcdef12",
    "payment_method_id": "64c3d4e5f67890abcdef1234",
    "interval": "monthly",
    "order_id": "682abc123def456789012345",
    "order_amount": 500.00,
    "order_currency": "MXN",
    "status_code": "04",
    "status_message": "Insuficiencia de fondos",
    "returned_at": "2026-08-07T18:05:00.000Z"
  }
}

Payload Fields

FieldTypeDescription
order_idstringID of the paid order that was returned.
order_amountnumberReturned amount.
order_currencystringCurrency of the returned order, typically MXN.
referencestring or numberDirect Debit reference.
status_codestringBank return code from Santander.
status_messagestringHuman-readable reason for the return.
returned_atstring (ISO 8601)Timestamp when the return was processed.

Subscribe to direct_debit.payment_returned in your webhook configuration to keep your systems in sync.


Webhook Events

Events dispatched through the notification system during billing cycles:

EventTriggerPayload Includes
direct_debit.createdDirect debit is createddirect_debit_id, reference, status
direct_debit.activatedDirect debit transitions to activedirect_debit_id, status, customer_id
direct_debit.payment_successOrder paid successfullyorder_id, amount, currency, reference
direct_debit.payment_failedOrder failsorder_id, amount, error_code, error_message
direct_debit.payment_returnedBank returns a previously paid orderorder_id, order_amount, status_code, returned_at
direct_debit.cancelledDirect debit is cancelleddirect_debit_id, status, customer_id
direct_debit.completedAll payments processeddirect_debit_id, status, total_payments

Webhook Payload Example

{
  "message": "Direct debit payment processed successfully.",
  "direct_debit_id": "6944a5366afaf625504595ea",
  "reference": 1234567,
  "order_id": "6944a53b6afaf625504595ef",
  "amount": 500.00,
  "currency": "MXN",
  "is_recurring": true,
  "status": "paid"
}



Retry and Extension Scenarios

Single Charge Retry

When a one-time scheduled charge fails:

// After max attempts reached, status becomes 'pending'
// Merchant retries:
POST /api/direct-debits/6944a5366afaf625504595ea/retry
// Response: Status returns to 'active' with new next_payment_date
{
  "status": "active",
  "next_payment_date": "2026-04-05T12:00:00.000Z",
  "is_extended_for_retry": true
}

Variable Charge Retry

When a variable charge fails:

// Retry failed charge
POST /api/direct-debits/6944a5366afaf625504595ea/charges/6944a53a6afaf625504595ee/retry
{
  "scheduled_date": "2026-04-20"
}
// Response: Order reset to 'created' status
{
  "status": "created",
  "attempts": 0,
  "is_retry_order": true,
  "scheduled_date": "2026-04-20T12:00:00.000Z"
}

Extended for Retry Flag

The is_extended_for_retry flag prevents duplicate order generation when retrying recurring debits near cycle boundaries. This ensures only one order exists per billing cycle during retry scenarios.


Did this page help you?