Invoice Payments#
Collect, record and track invoice payments, including partial payments and payment history.
Overview#
There are two ways money reaches an invoice:
- 💳 Customer pays the pay link - card, ACH or a saved card on the hosted pay page (
/api/v1/public/invoices/...). The charge and the invoice update happen together. - 📝 Merchant records a payment - money already collected elsewhere (portal collect, terminal, cash, check) is recorded against the invoice with
/paymentor/external-payment. These endpoints never charge anything.
Every payment is appended to the invoice's paymentHistory, and amountPaid, amountDue and status move with it:
- 📊 Partial payments - only when
allowPartialPaymentistrue(optionally with a minimum payment) - ✅ Fully paid - status becomes
paidandpaidAtis stamped whenamountDuereaches 0
Admin endpoints use Authorization: Bearer YOUR_TOKEN + X-Tenant-ID (or x-api-key).
Record Payment#
Record money that has already been collected (full or partial). Do not send card data — this endpoint does not charge.
Endpoint#
POST /api/v1/invoices/:invoiceId/record-payment is an alias.
curl -X POST https://uat.auxcore.net/api/v1/invoices/0b8e6a3c-5d2f-4c71-9e1a-7f3b2c9d4e10/payment \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Tenant-ID: your-tenant-id" \
-H "Content-Type: application/json" \
-d '{
"amount": 500.00,
"paymentMethod": "card",
"transactionId": "TXN-20261001-8F3A2C",
"notes": "Collected at the counter"
}'| Parameter | Type | Required | Description |
|---|---|---|---|
amount |
number | Yes | Amount collected |
paymentMethod |
string | Yes | Free text, e.g. card, ach, cash |
transactionId |
string | No | The transaction that collected the money |
notes |
string | No | Shown in the activity log |
isExternal |
boolean | No | Marks the entry as recorded outside AuxCORE |
Response: the updated invoice.
{
"success": true,
"data": {
"id": "0b8e6a3c-5d2f-4c71-9e1a-7f3b2c9d4e10",
"status": "partial",
"totalAmount": "1000.00",
"amountPaid": 500.00,
"amountDue": 500.00,
"paymentHistory": [
{
"transactionId": "TXN-20261001-8F3A2C",
"amount": 500.00,
"paymentMethod": "card",
"paidAt": "2026-10-01T14:00:00.000Z",
"notes": "Collected at the counter",
"isExternal": false,
"balanceAfter": 500.00,
"actorId": "a4c2e9d1-7b3f-4e8a-9c21-5d6e7f8a9b0c",
"actorEmail": "jane@merchant.example"
}
]
}
}Validation#
- The invoice must be
sent,viewed,partialoroverdue(INV-014); otherwise400. - An amount below
amountDueis rejected whenallowPartialPaymentisfalse. /paymentis a settlement endpoint and does not check the minimum payment rule or the remaining balance — run Payment Preflight first when you decide the amount yourself.
Payment Preflight#
Check an amount against the invoice's rules before charging it elsewhere (e.g. a saved card or ACH collect). Nothing is recorded.
Endpoint#
curl -X POST https://uat.auxcore.net/api/v1/invoices/0b8e6a3c-5d2f-4c71-9e1a-7f3b2c9d4e10/payment-preflight \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Tenant-ID: your-tenant-id" \
-H "Content-Type: application/json" \
-d '{ "amount": 250.00 }'Response:
{
"success": true,
"data": { "amount": 250.00, "amountDue": 500.00, "minimumPayment": 125.00 }
}400 VALIDATION_ERROR when the invoice is not collectable, the amount is not greater than zero, exceeds the remaining balance, is partial on a full-payment-only invoice, or is below the minimum payment.
Record External Payment#
Record merchant-reported money received outside AuxCORE.
Endpoint#
Request#
Requires a user token with the invoices.manage permission (API-key calls are rejected with 403).
curl -X POST https://uat.auxcore.net/api/v1/invoices/0b8e6a3c-5d2f-4c71-9e1a-7f3b2c9d4e10/external-payment \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Tenant-ID: your-tenant-id" \
-H "Content-Type: application/json" \
-d '{
"amount": 500.00,
"paymentMethod": "Check",
"paymentDate": "2026-10-01",
"recordedBy": "Jane Doe",
"reference": "CHK-1024",
"internalNote": "Deposited to bank"
}'| Parameter | Required | Description |
|---|---|---|
amount |
Yes | Greater than 0 and not above the remaining balance |
paymentMethod |
Yes | One of the allowed methods below (exact spelling) |
paymentDate |
Yes | YYYY-MM-DD |
recordedBy |
No | Display label ("Received by") |
reference |
No | e.g. check number |
internalNote |
No | Internal note |
Allowed Payment Methods#
Cash
ACH
Check
Wire Transfer
External Card
OtherBehavior#
- Updates
amountPaid,amountDue, and invoicestatususing the existing invoice balance transition. - Appends a
paymentHistoryitem:amount,paymentMethod,paidAt,isExternal: true,balanceAfter,recordedBy, optionalnotesand actor fields. - Combines
referenceandinternalNoteintonotes("CHK-1024 · Deposited to bank"). - Stores
recordedByas a display-only label, and stampsactorId/actorEmailfrom the authenticated token — the request body can never set the audit actor. - Stamps
paidAtat midday UTC on the given date, so the date renders unchanged in every viewer timezone. - Creates a matching transaction record (provider
EXT) so the payment appears in transaction reporting. This is best-effort: if it fails, the invoice is still updated. - Does not charge anything and sends no notifications, receipts or webhooks.
Validation#
- Rejects any invoice that is not collectable — only
sent,viewed,partialandoverdueare accepted (INV-014). - Rejects no-balance invoices.
- Rejects invalid payment methods or dates.
- Rejects non-finite, zero, negative, or over-balance amounts.
- Rejects partial amounts when
allowPartialPaymentisfalse, unless the invoice is already partial. - Enforces the invoice's minimum payment rule.
- Requires
paymentDateinYYYY-MM-DDformat and not before the invoice issue date. The future-date bound allows one day past UTC today, so a merchant east of UTC can record the current local date.
Response#
Returns the updated invoice.
{
"success": true,
"data": {
"id": "0b8e6a3c-5d2f-4c71-9e1a-7f3b2c9d4e10",
"status": "partial",
"amountPaid": 500.00,
"amountDue": 1000.00,
"paymentHistory": [
{
"amount": 500.00,
"paymentMethod": "Check",
"paidAt": "2026-10-01T12:00:00.000Z",
"notes": "CHK-1024 · Deposited to bank",
"isExternal": true,
"balanceAfter": 1000.00,
"recordedBy": "Jane Doe",
"actorId": "a4c2e9d1-7b3f-4e8a-9c21-5d6e7f8a9b0c",
"actorEmail": "jane@merchant.example"
}
]
}
}Payment History#
There is no separate history endpoint. GET /api/v1/invoices/:invoiceId returns paymentHistory (one entry per payment) together with amountPaid, amountDue, status, transactionId (primary transaction) and paidAt.
Payment Status#
Payment progress is reflected in the invoice status:
| Status | Description |
|---|---|
sent / viewed / overdue |
No payments received yet |
partial |
Some payments received |
pending |
An ACH payment is processing |
paid |
Fully paid |
Customer Payments (Pay Link)#
The hosted pay page uses these public endpoints. They need no API key: possession of the invoice's linkId authorises them. A full edit rotates the linkId; the old one returns 410 LINK_ROTATED.
Get Invoice for Payment#
Returns the invoice plus merchant branding, allowedPaymentMethods (from metadata.acceptCards / metadata.acceptAch), hasSavedCard and hasSavedAch. The first open moves a sent invoice to viewed.
GET /api/v1/public/invoices/link/:linkId/fee-preview returns the customer-facing fee for an amount.
Pay by Card#
{
"pciToken": "pci_tok_9f8e7d6c5b4a3210",
"amount": 250.00,
"email": "john@example.com",
"customerName": "John Doe",
"billingAddress": "100 Main St",
"billingCity": "Austin",
"billingState": "TX",
"billingPostalCode": "78701",
"billingCountry": "US"
}pciToken(pci_tok_…, from the AuxCORE SDK hosted fields) orcardTokenis required. Raw card fields (cardNumber,CardNumber,CVV) are rejected with400 VALIDATION_ERRORin every environment.- Optional
last4/cardBrandare shown on the receipt; when missing they are looked up from the PCI vault. amountis only honoured when the invoice allows partial payments (capped atamountDue, and at least the minimum payment); otherwise the full balance is charged.- The invoice must be
sent,viewed,partialoroverdue, and card payments must be enabled for it.
Response: { "success": true, "data": { "invoice": { ... }, "transaction": { ... } } }
Pay by ACH#
Body: accountHolderName, routingNumber, accountNumber, accountType, customerType, businessName, email, phone, billing address fields, consentAccepted, eSignature, optional amount, customerName and saveAch. routingNumber must be 9 digits, accountNumber 4–17 digits, accountType checking or savings, customerType personal or business (businessName required for business); consentAccepted and eSignature are required. Field errors return 400 with error.fields. While the ACH transaction is processing the invoice is pending.
Other Pay-Link Endpoints#
| Method | Path | Purpose |
|---|---|---|
POST |
/api/v1/public/invoices/:linkId/send-receipt |
Send a receipt: { "channel": "email" | "sms", "recipient": "...", "transactionId": "..." } |
PATCH |
/api/v1/public/invoices/:linkId/next-payment |
Payer moves the next automatic instalment: { "nextPaymentDate": "YYYY-MM-DD" } (after tomorrow, not after dueDate; only when allowRescheduleNextPayment) |
POST |
/api/v1/public/invoices/:linkId/saved-card/request-otp |
Start saved-card verification (OTP) |
POST |
/api/v1/public/invoices/:linkId/saved-card/verify-otp |
Verify the OTP |
POST |
/api/v1/public/invoices/:linkId/saved-card/resend-otp |
Resend the OTP |
POST |
/api/v1/public/invoices/:linkId/pay-with-saved-card |
Charge a verified saved card |
PUT |
/api/v1/public/invoices/:linkId/saved-card/:paymentMethodId/set-default |
Make a saved card the default |
DELETE |
/api/v1/public/invoices/:linkId/saved-card/:paymentMethodId |
Remove a saved card |
The saved-card OTP endpoints are limited to 10 requests per 15 minutes.
Payment Reminders#
Sends a reminder email now. See Sending & Delivery.
Note: a configurable automatic reminder schedule is not available via API.
Failed Payments#
A declined card payment on the pay link returns 400 with success: false and a message such as "Payment declined: Insufficient funds", and the invoice is left unchanged so the customer can try again. Nothing is recorded in paymentHistory.
Webhooks#
When the invoice has a webhookUrl, a successful pay-link card or ACH payment POSTs invoice.paid to it:
{
"event": "invoice.paid",
"invoice": { "id": "0b8e6a3c-...", "status": "partial", "amountPaid": 250.00, "amountDue": 250.00 },
"transaction": { "transactionId": "TXN-20261002-9C1D4E", "amount": 250.00 }
}The event is also sent for partial payments — check invoice.status. Payments recorded with /payment or /external-payment do not send it.
Best Practices#
✅ DO:#
- Allow partial payments - Increases payment rate
- Preflight before charging - when your integration decides the amount
- Record every off-platform payment with
/external-payment - Enable reminders - Reduce overdue invoices
- Offer multiple methods - Card and ACH
❌ DON'T:#
- Don't send card data to
/payment- it records, it does not charge - Don't record more than the balance -
/paymentdoes not stop you - Don't record the same payment twice - pass
transactionId
Common Scenarios#
Scenario 1: Payment Plan#
Customer wants to pay a $500 invoice in 5 installments (the invoice needs allowPartialPayment: true):
# Payment 1, after collecting $100
curl -X POST https://uat.auxcore.net/api/v1/invoices/0b8e6a3c-.../payment \
-H "Authorization: Bearer YOUR_TOKEN" -H "X-Tenant-ID: your-tenant-id" \
-H "Content-Type: application/json" \
-d '{"amount": 100.00, "paymentMethod": "card", "transactionId": "TXN-20261001-1A2B3C"}'
# Continue until paid...Scenario 2: External Payment#
Customer paid by check outside AuxCORE:
curl -X POST https://uat.auxcore.net/api/v1/invoices/0b8e6a3c-.../external-payment \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Tenant-ID: your-tenant-id" \
-H "Content-Type: application/json" \
-d '{
"amount": 500.00,
"paymentMethod": "Check",
"paymentDate": "2026-10-01",
"reference": "CHK-12345",
"internalNote": "Deposited to bank"
}'Next Steps#
Need help? Contact support@auxcore.net