Skip to main content

Payments

Payments represent money received from customers to settle their invoices. The payment system tracks incoming funds, allocates them to specific invoices, and integrates with the accounting system through journal entries.

Overview

The payment workflow consists of three main stages:

  1. Payment Creation - Recording a payment received from a customer
  2. Payment Allocation - Assigning the payment amount to specific invoices
  3. Payment Posting - Creating journal entries and finalizing the payment

Payments can be partially allocated, fully allocated, or over-allocated (creating a credit balance). The system automatically tracks allocated and unallocated amounts.

Business Rationale: Why Allocations Are Independent

The Problem with Status-Dependent Allocations

Traditional payment systems often tie allocations to payment status, creating artificial constraints:

  • Payments must be in a specific status to allocate
  • Once posted, allocations cannot be modified
  • Corrections require complex reversal processes
  • Real-world scenarios don't fit rigid status flows

Real-World Payment Scenarios

Scenario 1: Late-Applied Payments A customer makes a payment, it's posted to the general ledger, but later the accounting team realizes it should be allocated to different invoices. With status-dependent allocations, this requires reversing the entire payment and reposting.

Scenario 2: Partial Payments Over Time A customer makes a partial payment today, it's posted, then makes another payment next week. The second payment should be allocated to the same invoices. Status-dependent systems make this unnecessarily complex.

Scenario 3: Payment Corrections After posting, you discover an allocation error. With status-dependent allocations, you must reverse, correct, and repost - creating audit trail confusion.

Our Solution: Status-Independent Allocations

Key Principle: Allocations are business operations, not status transitions.

  • Allocations = "Which invoices does this payment apply to?"
  • Status = "What is the accounting state of this payment?"

These are orthogonal concerns that should not be coupled.

Benefits

  1. Flexibility - Allocate and reallocate at any time
  2. Simplicity - No complex status transition rules
  3. Real-World Fit - Handles actual business scenarios naturally
  4. Audit Trail - Clear separation between allocation changes and accounting events
  5. Error Recovery - Easy to correct allocation mistakes without reversing accounting entries

Payment Lifecycle & Allocation Flow

Status Lifecycle (Simple)

Allocation Flow (Independent from Status)

Complete Payment Workflow

Real-World Scenario: Payment Correction

Payment Status

  • Draft - Payment created and ready for processing. Allocations can be made at any time.
  • Posted - Payment has been posted to the general ledger with journal entries. Allocations can still be added or modified.
  • Canceled - Payment has been canceled (reversal journal created if previously posted)

Key Insight: Notice how allocations are operations that happen on a payment, not transitions of the payment status.

Payment Methods

The system supports the following payment methods:

  • BankTransfer - Bank wire transfers
  • CreditCard - Credit card payments
  • DebitCard - Debit card payments
  • Cash - Cash payments
  • Check - Check payments
  • DirectDebit - Direct debit/SEPA payments
  • PayPal - PayPal payments
  • Other - Other payment methods

Endpoints

List Payments

GET /payments

Returns a list of all payments. Supports OData query parameters for filtering and sorting.

Query Parameters:

  • $filter - OData filter expression (e.g., customerId eq 'guid')
  • $orderby - Sort order (e.g., paymentDate desc)
  • $top - Number of records to return
  • $skip - Number of records to skip
  • $count - Include total count in response
  • $expand - Expand related entities (e.g., allocations)

Example Request:

GET /payments?$filter=customerId eq '3fa85f64-5717-4562-b3fc-2c963f66afa6'&$orderby=paymentDate desc

Example Response:

{
"value": [
{
"id": "8e9b7c2a-1234-5678-9abc-def012345678",
"paymentNumber": "PAY-2024-00001",
"customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"paymentSourceId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"paymentSourceName": "Main Bank Account",
"amount": 1500.00,
"allocatedAmount": 1500.00,
"unallocatedAmount": 0.00,
"paymentMethod": "BankTransfer",
"status": "Posted",
"paymentDate": "2024-01-20T00:00:00Z",
"externalReference": "TXN-123456",
"notes": "Payment for semester fees",
"journalId": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
"createdAt": "2024-01-20T10:30:00Z",
"createdById": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
"updatedAt": "2024-01-20T11:15:00Z",
"updatedById": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
"postedAt": "2024-01-20T11:15:00Z",
"postedById": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
"allocations": null
}
],
"@odata.count": 1
}

Get Payment by ID

GET /payments/{id}

Retrieves a single payment by its unique identifier, including all payment allocations.

Path Parameters:

  • id (UUID, required) - The payment ID

Example Request:

GET /payments/8e9b7c2a-1234-5678-9abc-def012345678

Example Response:

{
"id": "8e9b7c2a-1234-5678-9abc-def012345678",
"paymentNumber": "PAY-2024-00001",
"customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"paymentSourceId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"paymentSourceName": "Main Bank Account",
"amount": 1500.00,
"allocatedAmount": 1500.00,
"unallocatedAmount": 0.00,
"paymentMethod": "BankTransfer",
"status": "Posted",
"paymentDate": "2024-01-20T00:00:00Z",
"externalReference": "TXN-123456",
"notes": "Payment for semester fees",
"journalId": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
"createdAt": "2024-01-20T10:30:00Z",
"createdById": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
"updatedAt": "2024-01-20T11:15:00Z",
"updatedById": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
"postedAt": "2024-01-20T11:15:00Z",
"postedById": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
"allocations": [
{
"id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"paymentId": "8e9b7c2a-1234-5678-9abc-def012345678",
"invoiceId": "f1e2d3c4-b5a6-9788-0011-223344556677",
"invoiceNumber": "INV-2024-00123",
"allocatedAmount": 1000.00,
"allocatedAt": "2024-01-20T10:45:00Z",
"allocatedById": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
},
{
"id": "b2c3d4e5-6789-01bc-def0-234567890abc",
"paymentId": "8e9b7c2a-1234-5678-9abc-def012345678",
"invoiceId": "f2e3d4c5-b6a7-9899-0022-334455667788",
"invoiceNumber": "INV-2024-00124",
"allocatedAmount": 500.00,
"allocatedAt": "2024-01-20T10:45:00Z",
"allocatedById": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
}
]
}

Create Payment

POST /payments

Creates a new payment record.

Request Body:

{
"customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"paymentSourceId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"amount": 1500.00,
"paymentMethod": "BankTransfer",
"paymentDate": "2024-01-20T00:00:00Z",
"externalReference": "TXN-123456",
"notes": "Payment for semester fees"
}

Response: 201 Created

{
"id": "8e9b7c2a-1234-5678-9abc-def012345678",
"paymentNumber": "PAY-2024-00001",
"customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"paymentSourceId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"paymentSourceName": "Main Bank Account",
"amount": 1500.00,
"allocatedAmount": 0.00,
"unallocatedAmount": 1500.00,
"paymentMethod": "BankTransfer",
"status": "Draft",
"paymentDate": "2024-01-20T00:00:00Z",
"externalReference": "TXN-123456",
"notes": "Payment for semester fees",
"journalId": null,
"createdAt": "2024-01-20T10:30:00Z",
"createdById": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
"updatedAt": null,
"updatedById": null,
"postedAt": null,
"postedById": null,
"allocations": []
}

Allocate Payment

POST /payments/{id}/allocate

Allocates a payment to one or more invoices. Allocations can be made regardless of payment status - you can allocate Draft, Allocated, or even Posted payments. This allows for flexible payment management including adjustments after posting.

The system automatically validates that:

  • The total allocation amount (including existing allocations) does not exceed the payment amount
  • All invoices belong to the same customer as the payment
  • Invoices are in Issued status

Path Parameters:

  • id (UUID, required) - The payment ID

Request Body:

{
"allocations": [
{
"invoiceId": "f1e2d3c4-b5a6-9788-0011-223344556677",
"amount": 1000.00
},
{
"invoiceId": "f2e3d4c5-b6a7-9899-0022-334455667788",
"amount": 500.00
}
]
}

Response: 200 OK

The system will:

  1. Create payment allocation records
  2. Update invoice payment status
  3. Update payment allocated/unallocated amounts
  4. Payment status remains unchanged (Draft or Posted)

Post Payment

POST /payments/{id}/post

Posts a payment to the general ledger by creating journal entries. This action:

  • Creates a journal with debit and credit entries
  • Debits the payment source account (cash/bank)
  • Credits customer receivable accounts for each allocated invoice
  • Marks the payment as Posted
  • Updates invoice payment status

Path Parameters:

  • id (UUID, required) - The payment ID

Response: 200 OK

Business Rules:

  • Payment must be in Allocated status
  • Payment must have at least one allocation
  • All allocations must be to valid, issued invoices

Cancel Payment

POST /payments/{id}/cancel

Cancels a payment. If the payment was posted, a reversal journal entry is created.

Path Parameters:

  • id (UUID, required) - The payment ID

Response: 200 OK

Response Schema

PaymentResponse

FieldTypeDescription
idUUIDUnique identifier
paymentNumberstringAuto-generated payment number (e.g., "PAY-2024-00001")
customerIdUUIDCustomer who made the payment
paymentSourceIdUUIDPayment source used
paymentSourceNamestringName of the payment source
amountdecimalTotal payment amount
allocatedAmountdecimalAmount allocated to invoices
unallocatedAmountdecimalRemaining unallocated amount
paymentMethodenumPayment method used
statusenumPayment status (Draft, Allocated, Posted, Canceled)
paymentDatedatetimeDate payment was received
externalReferencestringExternal reference number (e.g., bank transaction ID)
notesstringOptional notes
journalIdUUIDAssociated journal entry ID (when posted)
createdAtdatetimeCreation timestamp
createdByIdUUIDID of user who created the payment
updatedAtdatetimeLast update timestamp
updatedByIdUUIDID of user who last updated the payment
postedAtdatetimePosting timestamp
postedByIdUUIDID of user who posted the payment
allocationsarrayPayment allocations (see PaymentAllocationResponse)

PaymentAllocationResponse

FieldTypeDescription
idUUIDUnique identifier
paymentIdUUIDAssociated payment ID
invoiceIdUUIDInvoice being paid
invoiceNumberstringInvoice number for reference
allocatedAmountdecimalAmount allocated to this invoice
allocatedAtdatetimeAllocation timestamp
allocatedByIdUUIDID of user who created the allocation

Business Rules

  1. Customer Matching: All allocated invoices must belong to the same customer as the payment
  2. Amount Validation: Total allocations (including existing allocations) cannot exceed the payment amount
  3. Invoice Status: Only issued invoices can receive payment allocations
  4. Payment Numbering: Payment numbers are auto-generated in sequence (PAY-YYYY-NNNNN)
  5. Status-Independent Allocations: Allocations can be made on payments in any status (Draft, Posted, or Canceled)
  6. Status Preservation: Payment status is never changed by allocation operations
  7. Posting Requirements: Payments must have at least one allocation to be posted
  8. Cancellation: Posted payments create reversal journal entries when canceled
  9. Audit Trail: All operations are tracked with user IDs and timestamps

Common Use Cases

Finding Unallocated Payments

GET /payments?$filter=unallocatedAmount gt 0 and status ne 'Canceled'

Getting Customer Payment History

GET /payments?$filter=customerId eq '3fa85f64-5717-4562-b3fc-2c963f66afa6'&$orderby=paymentDate desc

Finding Payments by External Reference

GET /payments?$filter=contains(externalReference, 'TXN-123')

Getting Posted Payments for a Date Range

GET /payments?$filter=postedAt ge 2024-01-01T00:00:00Z and postedAt le 2024-01-31T23:59:59Z

Integration Notes

  • Payments are automatically numbered using the format PAY-YYYY-NNNNN
  • Flexible Allocation: Allocations can be added to payments at any time, even after posting, enabling payment adjustments and corrections
  • Payment allocations update invoice payment status and outstanding amounts
  • Posted payments create journal entries that integrate with the accounting system
  • The system prevents over-allocation and validates customer/invoice relationships
  • Canceled payments maintain audit history and create reversal entries if previously posted
  • Status-independent allocations allow for real-world scenarios like partial payments, payment corrections, and late-applied payments