# Billing Cycles

How recurring schedules are billed: cadence, the payment schedule, skipping and changing cycles.

---

## Overview

Every subscription (recurring schedule) has a `period` and a `nextPaymentDate`. AuxCORE uses them to:

- 📅 **Generate a payment schedule** - One row per expected payment
- ⏱️ **Charge due payments** - An hourly scheduler charges every schedule whose `nextPaymentDate` has arrived
- ⏭️ **Skip a cycle** - Move the next charge forward one period
- 🔄 **Change the cadence** - Update the period or amount; unpaid rows are rebuilt
- 📊 **Track each cycle** - Paid, failed and upcoming rows with transaction references

All endpoints on this page use admin authentication (`Authorization: Bearer YOUR_JWT` + `X-Tenant-ID`, or `x-api-key`). See [Subscriptions](./subscriptions.md).

---

## Billing Cycle Basics

### Start Date and First Charge

- `recurringDate1` is the first charge date (cannot be in the past).
- `nextPaymentDate` is the date the scheduler will charge next. If you do not send it on create, it is set to **one period after** `recurringDate1`; send `nextPaymentDate` equal to `recurringDate1` to charge on the start date.
- One-time scheduled payments (`requestOrigin: "scheduled"` or `maximumOccurrence: "1"`) are charged exactly on `recurringDate1`.

### Next Billing Date

Calculated from the period:

| Period | Cycle Length | Example |
|--------|--------------|---------|
| `Weekly` | 7 days | 2026-11-01 → 2026-11-08 |
| `BiWeekly` | 14 days | 2026-11-01 → 2026-11-15 |
| `SemiMonthly` | 15 days | 2026-11-01 → 2026-11-16 |
| `Monthly` | 1 month | 2026-11-01 → 2026-12-01 |
| `Quarterly` | 3 months | 2026-11-01 → 2027-02-01 |
| `Annually` | 1 year | 2026-11-01 → 2027-11-01 |
| `Custom` | 1 month (day clamped to month end) | Used for one-time scheduled payments; if it recurs, the next date is the same day next month |

After a successful charge, the next date is calculated from the date the charge was **scheduled** for (not when it actually ran), so a delayed run does not shift the series. If the result is already in the past (for example after a long pause), it is rolled forward whole periods until it is today or later, so no backlog of catch-up charges is taken. `Custom` schedules are never rolled forward.

### End of Schedule

| Setting | Number of cycles |
|---------|------------------|
| `maximumOccurrence: "12"` | 12 payments, then the schedule becomes `completed` |
| `recurringDate2` (end date) | Every cycle that falls on or before the end date |
| `maximumOccurrence: "NoExpire"` (or omitted) with no end date | Runs until cancelled |

---

## Get Payment Schedule

Returns the cycle rows for a schedule, ordered by payment number.

### Endpoint

```http
GET /api/v1/recurring/:id/schedule
```

```bash
curl https://uat.auxcore.net/api/v1/recurring/8d2f1c4b-6a3e-4f7d-b1c9-0e5a7d3f2b61/schedule \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-Tenant-ID: your-tenant-id"
```

**Response:**
```json
{
  "success": true,
  "data": [
    {
      "id": "b7e3a1d9-4c2f-4e8a-9d1b-3f6c0a2e5d71",
      "recurringMasterId": "8d2f1c4b-6a3e-4f7d-b1c9-0e5a7d3f2b61",
      "paymentNumber": 1,
      "scheduledDate": "2026-11-01",
      "scheduledAmount": 29.99,
      "status": "paid",
      "actualAmount": 29.99,
      "actualDate": "2026-11-01T05:00:12.000Z",
      "transactionId": "5b9e2d7a-1c3f-4a8e-b6d0-2f4c8a1e7b93",
      "label": "Payment 1"
    },
    {
      "id": "c2d4f6a8-1b3e-4c5d-8e7f-9a0b1c2d3e4f",
      "recurringMasterId": "8d2f1c4b-6a3e-4f7d-b1c9-0e5a7d3f2b61",
      "paymentNumber": 2,
      "scheduledDate": "2026-12-01",
      "scheduledAmount": 29.99,
      "status": "next_charge",
      "label": "Payment 2"
    }
  ]
}
```

### Cycle Statuses

| Status | Description |
|--------|-------------|
| `waiting` | First payment, not yet charged |
| `scheduled` | Future payment |
| `next_charge` | The payment that will be charged next |
| `paid` | Collected (`actualAmount`, `actualDate`, `transactionId` set) |
| `failed` | Charge declined |
| `cancelled` | Schedule was cancelled before this payment |
| `never_ends` | Placeholder row ("Until cancelled") for open-ended schedules |

> **Note:** For open-ended schedules, the first 24 payments are listed as concrete rows, followed by one `never_ends` placeholder. With an end date, there is one row per cycle up to that date; otherwise the row count equals `maximumOccurrence` (12 if it is not a number).

---

## Skip Next Billing Cycle

Skip the next charge but keep the schedule active. `nextPaymentDate` moves forward one period. Only `active` schedules can skip. No request body.

### Endpoint

```http
POST /api/v1/recurring/:id/skip-next
```

```bash
curl -X POST https://uat.auxcore.net/api/v1/recurring/8d2f1c4b-6a3e-4f7d-b1c9-0e5a7d3f2b61/skip-next \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-Tenant-ID: your-tenant-id"
```

**Response:**
```json
{
  "success": true,
  "data": {
    "id": "8d2f1c4b-6a3e-4f7d-b1c9-0e5a7d3f2b61",
    "status": "active",
    "period": "Monthly",
    "nextPaymentDate": "2027-01-01"
  },
  "message": "Next payment skipped successfully"
}
```

**Use cases:**
- Customer vacations
- Seasonal businesses
- Goodwill gestures
- Service interruptions

---

## Change Cadence or Amount

Use the update endpoint to change the period, amount or dates of a schedule.

### Endpoint

```http
PUT /api/v1/recurring/:id
```

```bash
curl -X PUT https://uat.auxcore.net/api/v1/recurring/8d2f1c4b-6a3e-4f7d-b1c9-0e5a7d3f2b61 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-Tenant-ID: your-tenant-id" \
  -H "Content-Type: application/json" \
  -d '{
    "period": "Quarterly",
    "recurringAmount": 79.99
  }'
```

- Changing only `recurringAmount` updates every unpaid row in place.
- Changing `period` deletes the unpaid rows and rebuilds them; `paid` rows are kept.
- Changes take effect from the next charge. There is no proration: the new amount is charged in full on the next cycle.

---

## Billing Time

Due schedules are charged by a scheduler that runs **every hour**, so a payment due on a given date is charged early that day. There is no per-schedule billing time or timezone setting.

> **Note:** Proration, extending a cycle, moving the billing day of month, per-schedule timezones and billing-cycle analytics are not available via API.

---

## Cycle Notifications

AuxCORE does not emit billing-cycle webhook events. Each charged cycle creates an ordinary transaction (`requestOrigin: "recurring"`), and the customer receives a payment success or failure email when notifications are enabled.

---

## Common Scenarios

### Scenario 1: Annual to Monthly Switch

```bash
# 1. Cancel the annual schedule
curl -X POST https://uat.auxcore.net/api/v1/recurring/8d2f1c4b-6a3e-4f7d-b1c9-0e5a7d3f2b61/cancel \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-Tenant-ID: your-tenant-id" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Switching to monthly"}'

# 2. Create a new monthly schedule
curl -X POST https://uat.auxcore.net/api/v1/recurring \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-Tenant-ID: your-tenant-id" \
  -H "Content-Type: application/json" \
  -d '{"recurringAmount": 29.99, "period": "Monthly", "recurringDate1": "2026-11-01", "nextPaymentDate": "2026-11-01", "suggestedMode": "CARD", "customerId": "3f6c2a1e-8b4d-4c7a-9e21-5d0b7f1a2c34", "children": [{"pciToken": "pci_tok_9f8e7d6c5b4a", "isDefault": true}]}'
```

Any refund of unused time must be issued separately as a refund on the original transaction.

### Scenario 2: Free Month for Service Issue

```bash
curl -X POST https://uat.auxcore.net/api/v1/recurring/8d2f1c4b-6a3e-4f7d-b1c9-0e5a7d3f2b61/skip-next \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-Tenant-ID: your-tenant-id"
```

---

## Best Practices

### ✅ DO:
- **Send `nextPaymentDate` explicitly** - Avoid surprises about when the first automatic charge runs
- **Communicate changes** - Email the customer before changing amount or period
- **Allow skips** - Reduces churn
- **Check the schedule** - Use the schedule endpoint for customer support questions

### ❌ DON'T:
- **Don't expect proration** - Amount changes apply in full from the next cycle
- **Don't allow unlimited skips** - Set your own limits
- **Don't ignore failed cycles** - See [Dunning](./dunning.md)

---

## Next Steps

- [Create subscriptions →](./subscriptions.md)
- [Payment plans →](./payment-plans.md)
- [Handle failures →](./dunning.md)

---

**Need help?** Contact support@auxcore.net
