Payment Plans#
Split a fixed total into installments, collect customer authorization, and charge each installment automatically.
Overview#
A payment plan is an installment agreement for a fixed totalAmount with one customer:
- 💰 Fixed total - Split into equal installments, a down payment plus installments, or a custom schedule
- 📅 Payment frequency - One-time, weekly, bi-weekly, monthly, quarterly, or every N days
- ✍️ Customer authorization - The customer accepts the terms and provides a card on a hosted page
- 🔄 Automatic retries - Declined installments are retried; the plan defaults after the last attempt
- ⏰ Late fees - Optional fixed or percentage fee per day, week or month overdue
Note: Payment plans are installment agreements, not subscription "price plans". For open-ended subscriptions use Subscriptions.
Authentication#
Payment plan management endpoints live under /api/v1/billing/payment-plans:
Authorization: Bearer YOUR_JWT
X-Tenant-ID: your-tenant-id- A user JWT is required and its tenant must match
X-Tenant-ID(401otherwise). API keys are not accepted on these routes. merchantIdis read from the request body on create and from the query string on list.- Most errors are
{ "success": false, "error": "message" }(a string). Invalid state transitions (pause, resume, cancel, activate) currently return500.
Create Payment Plan#
Endpoint#
curl -X POST https://uat.auxcore.net/api/v1/billing/payment-plans \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Tenant-ID: your-tenant-id" \
-H "Content-Type: application/json" \
-d '{
"merchantId": "c1a7e5d2-3b4f-4e6a-9c8d-7f0b1a2e3d45",
"planName": "Dental treatment plan",
"customerName": "Jane Smith",
"customerEmail": "jane@example.com",
"customerPhone": "5551234567",
"totalAmount": 1200.00,
"timeframeType": "months_6",
"paymentStructure": "fixed_down_equal",
"structureConfig": { "down_payment_amount": 200, "num_remaining_payments": 5 },
"paymentFrequency": "monthly",
"firstPaymentDate": "2026-11-01",
"preferredDayOfMonth": 1,
"lateFeeType": "fixed",
"lateFeeValue": 10,
"lateFeeFrequency": "per_month"
}'Response: 201 Created
{
"success": true,
"data": {
"id": "6e1b9c3a-2d4f-4a7e-8b5c-1f0a9d2e3c47",
"planNumber": "PP-2026-00042",
"planName": "Dental treatment plan",
"merchantId": "c1a7e5d2-3b4f-4e6a-9c8d-7f0b1a2e3d45",
"customerName": "Jane Smith",
"customerEmail": "jane@example.com",
"totalAmount": 1200.00,
"remainingAmount": 1200.00,
"currency": "USD",
"timeframeType": "months_6",
"paymentStructure": "fixed_down_equal",
"paymentFrequency": "monthly",
"totalPaymentsCount": 6,
"nextPaymentDate": "2026-11-01",
"nextPaymentAmount": 200.00,
"status": "pending_approval",
"createdAt": "2026-10-01T12:00:00.000Z"
}
}When customerEmail or customerPhone is present, a setup link (valid 7 days) is sent to the customer by email (or SMS when there is no email) so they can authorize the plan.
Request Parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
merchantId |
uuid | Yes | Merchant the plan belongs to |
planName |
string | Yes | Plan name |
customerName |
string | Yes | Customer name |
customerEmail |
string | Yes | Customer email |
customerPhone |
string | No | Customer phone |
customerId |
uuid | No | Existing customer. If omitted, a customer is found or created from the name/email |
totalAmount |
decimal | Yes | Total to collect (must be > 0) |
currency |
string | No | Default USD |
timeframeType |
string | Yes | days_30, days_60, days_90, months_3, months_6, months_12, custom |
customDurationDays |
integer | If custom |
Plan length in days (≥ 1) |
paymentStructure |
string | Yes | equal_split, fixed_down_equal, percent_down_equal, custom_schedule |
structureConfig |
object | No | See below. Missing counts are derived from timeframe ÷ frequency |
paymentFrequency |
string | Yes | one_time, weekly, bi_weekly (biweekly accepted), monthly, quarterly, custom_days |
customFrequencyDays |
integer | If custom_days |
Days between payments (≥ 1) |
firstPaymentDate |
date | Yes | Date of the first payment |
preferredDayOfMonth |
integer | No | 1-28 |
paymentMethodType |
string | No | card (default), ach, manual |
paymentMethodToken |
string | No | PCI vault token, if the card is already collected |
paymentMethodLast4 / paymentMethodBrand |
string | No | Card display details |
lateFeeType |
string | No | none (default), fixed, percent |
lateFeeValue |
decimal | No | Fixed amount, or percent of the installment |
lateFeeFrequency |
string | No | per_day (default), per_week, per_month |
additionalFeeType / additionalFeeValue |
string / decimal | No | none, fixed, percent — stored on the plan |
defaultedAfterDays |
integer | No | Stored on the plan |
retryFailedPayments |
boolean | No | Default true |
sendPaymentReminders |
boolean | No | Default true |
reminderDaysBefore / reminderDaysAfter |
integer | No | Defaults 3 / 1 |
sendPaymentReceipts |
boolean | No | Default true |
activateImmediately |
boolean | No | true creates the plan as active without customer authorization (default false → pending_approval) |
description |
string | No | Stored as plan notes |
structureConfig by structure:
| Structure | Fields | Rules |
|---|---|---|
equal_split |
num_payments |
At least 2 payments |
fixed_down_equal |
down_payment_amount, num_remaining_payments |
Down payment > 0 and < total; ≥ 1 remaining payment |
percent_down_equal |
down_payment_percent, num_remaining_payments |
Percent between 1 and 99; ≥ 1 remaining payment |
custom_schedule |
payments (array of amounts) |
At least 2 payments; amounts must sum to totalAmount |
Validation failures return 400 with the messages joined in error.
Preview Schedule#
Calculate the installment schedule without saving anything. Takes the same schedule fields as create (totalAmount, timeframeType, paymentStructure, structureConfig, paymentFrequency, firstPaymentDate, ...).
Endpoint#
Response:
{
"success": true,
"data": {
"payments": [
{ "paymentNumber": 1, "scheduledDate": "2026-11-01", "amount": 200.00, "isDownPayment": true, "label": "Down Payment" },
{ "paymentNumber": 2, "scheduledDate": "2026-12-01", "amount": 200.00, "isDownPayment": false, "label": "Payment 1 of 5" }
],
"totalAmount": 1200.00,
"numberOfPayments": 6,
"startDate": "2026-11-01",
"endDate": "2027-04-01",
"timeframeDays": 180
}
}Get Structure Suggestions#
Endpoint#
| Parameter | Type | Required | Description |
|---|---|---|---|
totalAmount |
decimal | Yes | Total amount |
timeframeType |
string | Yes | Timeframe (see above) |
Returns a list of { structure, config, description, previewPayments } options (e.g. equal split, 50% down).
List Payment Plans#
Endpoint#
| Parameter | Type | Description |
|---|---|---|
page |
integer | Default 1 |
limit |
integer | Default 20 |
status |
string | One status (see Plan Statuses) |
merchantId |
uuid | Filter by merchant |
q |
string | Search customer name/email, plan name or plan number |
curl "https://uat.auxcore.net/api/v1/billing/payment-plans?status=active&limit=50" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Tenant-ID: your-tenant-id"Response:
{
"success": true,
"data": [
{
"id": "6e1b9c3a-2d4f-4a7e-8b5c-1f0a9d2e3c47",
"planNumber": "PP-2026-00042",
"planName": "Dental treatment plan",
"customerName": "Jane Smith",
"totalAmount": 1200.00,
"remainingAmount": 1000.00,
"totalPaidAmount": 200.00,
"status": "active",
"nextPaymentDate": "2026-12-01",
"nextPaymentAmount": 200.00,
"completedPayments": 1,
"totalPayments": 6,
"lateFeeType": "fixed",
"lateFeeAmount": 10,
"lateFeeFrequency": "per_month",
"scheduledPayments": [
{ "id": "a4c2...", "paymentNumber": 1, "scheduledDate": "2026-11-01", "scheduledAmount": 200.00, "status": "paid", "lateFeeAmount": 0 }
]
}
],
"total": 1,
"page": 1,
"limit": 50
}Get Payment Schedule#
Endpoint#
Returns the scheduled installments ordered by date, each with paymentNumber, scheduledDate, scheduledAmount, actualAmount, lateFeeAmount, status, attemptCount, nextRetryAt, failureReason, transactionId and isDownPayment.
Installment statuses: waiting, scheduled, next_charge, pending (retry scheduled), processing, paid, completed, failed, cancelled, skipped, refunded.
Get Plan History#
Endpoint#
Returns the activity log (created, activated, authorized, payment succeeded/failed, retry scheduled, paused, resumed, cancelled, completed, defaulted, payment method linked).
Update Payment Plan#
Endpoint#
curl -X PUT https://uat.auxcore.net/api/v1/billing/payment-plans/6e1b9c3a-2d4f-4a7e-8b5c-1f0a9d2e3c47 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Tenant-ID: your-tenant-id" \
-H "Content-Type: application/json" \
-d '{
"planName": "Dental treatment plan (updated)",
"customerEmail": "jane.smith@example.com",
"description": "Updated notes"
}'Only planName, customerName, customerEmail, customerPhone, description (notes) and metadata are applied. Sending totalAmount or paymentStructure on a plan that is not draft returns 400; the schedule itself cannot be changed through this endpoint.
Note: There is no delete endpoint. Cancel a plan instead.
Activate Payment Plan#
Activate a draft or pending_approval plan without customer authorization.
Endpoint#
Authorize Payment Plan (Merchant)#
Attach a card collected by the merchant and charge the first installment as a customer-initiated card-on-file transaction. Behaves like the public authorize endpoint below, but the body uses the paymentMethod* names only (no pciToken / last4 / cardBrand aliases).
Endpoint#
{ "paymentMethodToken": "pci_tok_9f8e7d6c5b4a", "paymentMethodLast4": "1111", "paymentMethodBrand": "visa", "consentAccepted": true }consentAccepted must be true (400 CONSENT_REQUIRED) and paymentMethodToken is required (400 PAYMENT_REQUIRED). A plan that is not draft or pending_approval returns 400 with a plain string error (no INVALID_STATUS code). Declines return 402 PAYMENT_DECLINED.
Pause Payment Plan#
Only active plans can be paused.
Endpoint#
Resume Payment Plan#
Only paused plans can be resumed.
Endpoint#
Cancel Payment Plan#
Cancels the plan and every unpaid installment. completed or cancelled plans cannot be cancelled.
Endpoint#
curl -X POST https://uat.auxcore.net/api/v1/billing/payment-plans/6e1b9c3a-2d4f-4a7e-8b5c-1f0a9d2e3c47/cancel \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Tenant-ID: your-tenant-id" \
-H "Content-Type: application/json" \
-d '{ "reason": "Paid in full by check" }'Charge Next Installment#
Charge the next due installment now (plus any late fee). The plan must be active and have a payment method. A decline returns 402; other failures return 400.
Endpoint#
Send Payment Method Update Link#
Email the customer a link (valid 24 hours) to update their card. Returns 400 if the plan has no customer email.
Endpoint#
Response:
{ "success": true, "data": { "token": "0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0", "updateUrl": "https://uat.auxcore.net/update-payment-method?token=0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0&type=plan&id=6e1b9c3a-2d4f-4a7e-8b5c-1f0a9d2e3c47" } }List Plan Templates#
Endpoint#
Returns active templates for the tenant, plus merchant-specific ones when merchantId is passed in the query string.
Create Plan Template#
Endpoint#
{
"name": "6 months, 20% down",
"merchantId": "c1a7e5d2-3b4f-4e6a-9c8d-7f0b1a2e3d45",
"timeframeType": "months_6",
"paymentStructure": "percent_down_equal",
"structureConfig": { "down_payment_percent": 20, "num_remaining_payments": 5 },
"paymentFrequency": "monthly",
"preferredDayOfMonth": 1
}Public Plan Authorization#
The customer opens the setup link and authorizes the plan on a hosted page. These two endpoints need no API key: the plan ID is the capability.
Get Plan (Public)#
Returns the plan with its scheduledPayments (id, paymentNumber, scheduledDate, scheduledAmount, status, isDownPayment) and merchant branding (merchantName, merchantLogo, primaryColor, displaySaveCard). 404 if not found.
Authorize Plan (Public)#
curl -X POST https://uat.auxcore.net/api/v1/public/payment-plans/6e1b9c3a-2d4f-4a7e-8b5c-1f0a9d2e3c47/authorize \
-H "Content-Type: application/json" \
-d '{
"pciToken": "pci_tok_9f8e7d6c5b4a",
"last4": "1111",
"cardBrand": "visa",
"customerName": "Jane Smith",
"customerEmail": "jane@example.com",
"consentAccepted": true
}'| Field | Type | Required | Description |
|---|---|---|---|
pciToken |
string | Yes | PCI vault card token from the hosted card fields (alias paymentMethodToken) |
consentAccepted |
boolean | Yes | Must be true |
last4 |
string | No | Alias paymentMethodLast4 |
cardBrand |
string | No | Alias paymentMethodBrand |
customerName, customerEmail, customerPhone |
string | No | Override the customer details on the plan |
What happens:
- The token is promoted to a long-lived card-on-file token.
- The first due installment (plus any late fee) is charged.
- The plan becomes
activeand the first installment is marked paid.
| Status | Code | Meaning |
|---|---|---|
| 400 | CONSENT_REQUIRED |
consentAccepted is not true |
| 400 | PAYMENT_REQUIRED |
No card token |
| 400 | INVALID_STATUS |
Plan is not draft or pending_approval |
| 402 | PAYMENT_DECLINED |
The first installment was declined |
| 404 | NOT_FOUND |
Plan not found |
| 502 | TOKEN_PROMOTION_FAILED |
The card could not be stored for recurring use |
The gateway allows up to 50 seconds for this call.
Plan Statuses#
| Status | Description |
|---|---|
draft |
Created, not yet sent |
pending_approval |
Waiting for customer authorization |
active |
Installments are being charged |
paused |
Charging suspended |
completed |
All installments paid |
cancelled |
Cancelled; unpaid installments cancelled |
defaulted |
An installment failed on its last retry attempt |
Automatic Charging, Retries and Late Fees#
- Due installments on
activeplans are charged hourly. Plans that are not yet authorized are never charged. - A declined installment is retried every
retryIntervalDays(default 3) up tomaxRetryAttempts(default 3) attempts whileretryFailedPaymentsis on. After the last failed attempt the plan becomesdefaulted. - Late fees start the day after the due date and accrue per full interval:
per_day(each day),per_week(each 7 days) orper_month(each month). Apercentfee is a percentage of the installment. The fee is added to the amount charged and recorded on the installment aslateFeeAmount.
Best Practices#
✅ DO:#
- Preview first - Call the preview endpoint before creating a plan
- Collect authorization - Let the customer accept the terms through the setup link
- Use a down payment - Reduces risk on longer plans
- Watch for
defaultedplans - Send a method update link and charge again
❌ DON'T:#
- Don't use
activateImmediatelywithout consent - The customer has not accepted the terms - Don't change amounts after creation - Cancel and create a new plan instead
Next Steps#
Need help? Contact support@auxcore.net