Developer dashboard

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:

http
Authorization: Bearer YOUR_JWT
X-Tenant-ID: your-tenant-id
  • A user JWT is required and its tenant must match X-Tenant-ID (401 otherwise). API keys are not accepted on these routes.
  • merchantId is 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 return 500.

Create Payment Plan#

Endpoint#

POST/api/v1/billing/payment-plans
Create Payment Planbash
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

Create Payment Planjson
{
  "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#

POST/api/v1/billing/payment-plans/preview

Response:

Preview Schedulejson
{
  "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#

GET/api/v1/billing/payment-plans/suggestions
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#

GET/api/v1/billing/payment-plans
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
List Payment Plansbash
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:

List Payment Plansjson
{
  "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
}

Payment Plan Overview#

Endpoint#

GET/api/v1/billing/payment-plans/overview

Response:

Payment Plan Overviewjson
{ "success": true, "data": { "plans": 12 } }

Get Payment Plan#

Endpoint#

GET/api/v1/billing/payment-plans/:id
Get Payment Planbash
curl 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"

Returns the full plan with scheduledPayments. 404 if not found.


Get Payment Schedule#

Endpoint#

GET/api/v1/billing/payment-plans/:id/schedule

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#

GET/api/v1/billing/payment-plans/:id/history

Returns the activity log (created, activated, authorized, payment succeeded/failed, retry scheduled, paused, resumed, cancelled, completed, defaulted, payment method linked).


Update Payment Plan#

Endpoint#

PUT/api/v1/billing/payment-plans/:id
Update Payment Planbash
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#

POST/api/v1/billing/payment-plans/:id/activate

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#

POST/api/v1/billing/payment-plans/:id/authorize
Authorize Payment Plan (Merchant)json
{ "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#

POST/api/v1/billing/payment-plans/:id/pause

Resume Payment Plan#

Only paused plans can be resumed.

Endpoint#

POST/api/v1/billing/payment-plans/:id/resume

Cancel Payment Plan#

Cancels the plan and every unpaid installment. completed or cancelled plans cannot be cancelled.

Endpoint#

POST/api/v1/billing/payment-plans/:id/cancel
Cancel Payment Planbash
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#

POST/api/v1/billing/payment-plans/:id/charge-now

Re-send the setup link (valid 7 days) for a pending_approval plan.

Endpoint#

POST/api/v1/billing/payment-plans/:id/resend-auth

Response:

Resend Authorization Linkjson
{ "success": true, "data": { "linkId": "lnk_7Hq2Xv9P", "setupLinkId": "lnk_7Hq2Xv9P" } }

Replace the card on file. Does not charge the new card.

Endpoint#

POST/api/v1/billing/payment-plans/:id/link-payment-method
Link Payment Methodjson
{ "pciToken": "pci_tok_1a2b3c4d5e6f", "last4": "4242", "brand": "visa", "type": "card" }

pciToken is required (400 otherwise).


Email the customer a link (valid 24 hours) to update their card. Returns 400 if the plan has no customer email.

Endpoint#

POST/api/v1/billing/payment-plans/:id/send-method-update-link

Response:

Send Payment Method Update Linkjson
{ "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#

GET/api/v1/billing/payment-plan-templates

Returns active templates for the tenant, plus merchant-specific ones when merchantId is passed in the query string.


Create Plan Template#

Endpoint#

POST/api/v1/billing/payment-plan-templates
Create Plan Templatejson
{
  "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)#

GET/api/v1/public/payment-plans/:planId

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)#

POST/api/v1/public/payment-plans/:planId/authorize
Authorize Plan (Public)bash
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:

  1. The token is promoted to a long-lived card-on-file token.
  2. The first due installment (plus any late fee) is charged.
  3. The plan becomes active and 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 active plans are charged hourly. Plans that are not yet authorized are never charged.
  • A declined installment is retried every retryIntervalDays (default 3) up to maxRetryAttempts (default 3) attempts while retryFailedPayments is on. After the last failed attempt the plan becomes defaulted.
  • Late fees start the day after the due date and accrue per full interval: per_day (each day), per_week (each 7 days) or per_month (each month). A percent fee is a percentage of the installment. The fee is added to the amount charged and recorded on the installment as lateFeeAmount.

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 defaulted plans - Send a method update link and charge again

❌ DON'T:#

  • Don't use activateImmediately without 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