Skip to content

Sales Payments (مدفوعات المبيعات)

The Sales Payments API records customer payments against posted invoices. Supports partial payments, multiple payment methods, installment schedules, and automatic journal entry creation.

Entity Attributes

SalesPayment

FieldTypeRequiredDescription
idintegerautoPrimary key
payment_numberstringautoAuto-generated via SequenceService (e.g., SPAY-00001)
datedateyesPayment date
invoice_idFKyesSource posted invoice
partner_idFKautoCopied from the source invoice
amountdecimal(15,3)yesPayment amount (max = invoice balance_due)
currency_idFKnoPayment currency
exchange_ratedecimal(15,6)noExchange rate (default 1.000000)
payment_methodenumyescash, bank_transfer, check, credit_card
bank_account_idFKnoBank account (for bank_transfer/check)
check_numberstring(50)noCheck number
check_datedatenoCheck date
check_bankstringnoCheck issuing bank
referencestringnoExternal reference number
notes / notes_artextnoNotes in English / Arabic
receiving_account_idFKyesGL account receiving payment (Cash/Bank)
journal_entry_idFKautoCreated on post
statusenumautodraft, posted, cancelled
posted_atdatetimeautoWhen posted
posted_byFKautoUser who posted
cancelled_atdatetimeautoWhen cancelled
cancelled_byFKautoUser who cancelled
cancellation_reasontextnoReason for cancellation

SalesPaymentSchedule (Installment Tracking)

FieldTypeRequiredDescription
idintegerautoPrimary key
invoice_idFKyesSource invoice
installment_numberintegeryesInstallment sequence number
due_datedateyesInstallment due date
amountdecimal(15,3)yesInstallment amount
amount_paiddecimal(15,3)autoAmount paid so far (default 0)
statusenumautopending, partially_paid, paid, overdue

ER Diagram

Payment Lifecycle

Posting Flow (Critical)

When a payment is posted (POST /api/sales/payments/{id}/post), the following happens atomically:

Step 1: Create Journal Entry

DR  Receiving Account (Cash/Bank)    ← payment amount
CR  Accounts Receivable (AR)         ← payment amount (with partner_id)

The AR account is read from sales.receivable_account_id setting.

Step 2: Update Invoice

  • Increments invoice.amount_paid by the payment amount
  • Recalculates invoice.balance_due = total - amount_paid
  • Updates invoice.payment_status:
    • paid — if balance_due = 0
    • partial — if amount_paid > 0
  • Updates invoice.status:
    • paid — if fully paid
    • partially_paid — if partially paid

Step 3: Allocate to Installments

If the invoice has payment schedule installments:

  • Allocates to the oldest unpaid installment first
  • Updates each installment's amount_paid and status
  • Moves to the next installment if the current one is fully paid

Cancellation Flow

When a posted payment is cancelled (POST /api/sales/payments/{id}/cancel):

  1. Reverse JE — creates a reversal journal entry via ReverseJournalEntry
  2. Update invoice — decrements amount_paid, recalculates balance_due and payment_status
  3. Reverse installment allocation — deallocates from the newest paid installment first, reverting status

API Endpoints

MethodEndpointDescriptionPermission
GET/api/sales/paymentsList payments (paginated)sales.payments.view
POST/api/sales/paymentsCreate a paymentsales.payments.create
GET/api/sales/payments/{id}Get payment detailssales.payments.view
PUT/api/sales/payments/{id}Update a draft paymentsales.payments.update
DELETE/api/sales/payments/{id}Delete a draft paymentsales.payments.delete
POST/api/sales/payments/{id}/postPost payment (creates JE)sales.payments.post
POST/api/sales/payments/{id}/cancelCancel payment (reverses JE)sales.payments.cancel
GET/api/sales/invoices/{id}/paymentsList payments for invoicesales.payments.view
GET/api/sales/invoices/{id}/payment-scheduleView installment schedulesales.payments.view

Query Parameters (List)

ParameterTypeDescription
statusstringFilter by payment status
partner_idintegerFilter by customer
invoice_idintegerFilter by source invoice
payment_methodstringFilter by payment method
branch_idintegerFilter by branch
date_fromdatePayments from this date
date_todatePayments up to this date
searchstringSearch in payment_number, reference, check_number

Request / Response Examples

Create Payment

bash
curl -X POST /api/sales/payments \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "invoice_id": 1,
    "date": "2026-02-24",
    "amount": 250.000,
    "payment_method": "cash",
    "receiving_account_id": 5,
    "reference": "REC-001"
  }'
dart
final response = await dio.post('/api/sales/payments', data: {
  'invoice_id': 1,
  'date': '2026-02-24',
  'amount': 250.000,
  'payment_method': 'cash',
  'receiving_account_id': 5,
  'reference': 'REC-001',
});

Create Check Payment

bash
curl -X POST /api/sales/payments \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "invoice_id": 1,
    "date": "2026-02-24",
    "amount": 500.000,
    "payment_method": "check",
    "receiving_account_id": 5,
    "check_number": "CHK-12345",
    "check_date": "2026-03-01",
    "check_bank": "National Bank of Kuwait"
  }'
dart
final response = await dio.post('/api/sales/payments', data: {
  'invoice_id': 1,
  'date': '2026-02-24',
  'amount': 500.000,
  'payment_method': 'check',
  'receiving_account_id': 5,
  'check_number': 'CHK-12345',
  'check_date': '2026-03-01',
  'check_bank': 'National Bank of Kuwait',
});

Post Payment

bash
curl -X POST /api/sales/payments/1/post \
  -H "Authorization: Bearer {token}"
dart
final response = await dio.post('/api/sales/payments/1/post');

Cancel Payment

bash
curl -X POST /api/sales/payments/1/cancel \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"cancellation_reason": "Customer requested refund"}'
dart
final response = await dio.post('/api/sales/payments/1/cancel', data: {
  'cancellation_reason': 'Customer requested refund',
});

View Invoice Payments

bash
curl -X GET /api/sales/invoices/1/payments \
  -H "Authorization: Bearer {token}"
dart
final response = await dio.get('/api/sales/invoices/1/payments');

View Payment Schedule

bash
curl -X GET /api/sales/invoices/1/payment-schedule \
  -H "Authorization: Bearer {token}"
dart
final response = await dio.get('/api/sales/invoices/1/payment-schedule');

Business Rules

  1. Payments can only be created against posted invoices — draft, approved, or cancelled invoices are rejected
  2. Payment amount cannot exceed the invoice balance due — prevents overpayment
  3. Partner and branch are inherited from the source invoice — cannot be manually set
  4. Only draft payments can be edited or deleted
  5. Posting creates a journal entry — DR receiving account (Cash/Bank), CR AR
  6. Cancellation fully reverses — JE is reversed, invoice amounts are updated, installment allocations are reversed
  7. Installment allocation is automatic — oldest unpaid installment is allocated first on post, newest paid is reversed first on cancel
  8. Invoice status auto-updates — posted → partially_paid → paid based on amount_paid vs total

Moon ERP API Documentation