Payment Reconciliation
A PaymentReconciliation is a single payment recorded against an Account, and optionally allocated to one Invoice. It captures how much was paid, by what method, when, and by whom — mirroring the FHIR PaymentReconciliation resource. Reach for it whenever you post a payment, refund, credit note, or adjustment to a patient or insurer account.
Source:
- Model:
care/emr/models/payment_reconciliation.py - Resource spec:
care/emr/resources/payment_reconciliation/spec.py
The model is thin storage: enum-constrained fields are plain CharFields and extensions is an opaque JSONField. The behaviour you integrate against — enum constraints, amount computation, validation, and the read/write schema split — lives in the Pydantic resource specs (care/emr/resources/payment_reconciliation/spec.py), built on EMRResource.
Models
| Model | Purpose |
|---|---|
PaymentReconciliation | A single payment (or credit note / adjustment) recorded against an account or invoice |
The model extends EMRBaseModel, the shared Care EMR base supplying external_id, audit fields, and soft-delete semantics — see Base model.
PaymentReconciliation fields
Relationships
| Field | Type | Notes |
|---|---|---|
facility | FK → Facility (PROTECT) | Facility where the payment is recorded |
target_invoice | FK → Invoice (PROTECT) | The invoice this payment is allocated to. Nullable (default=None); one allocation per record. On write, pass the invoice external_id (UUID) |
account | FK → Account (PROTECT) | The account being paid. Required even when target_invoice is set. On write, pass the account external_id (UUID) |
location | FK → FacilityLocation (PROTECT) | Physical location (e.g. the counter) where payment was taken. Nullable. On write, pass the location external_id (UUID) |
Classification
Six columns map to fixed enums defined in the resource spec. Each is stored as a free-form CharField(100); the enum is enforced at the schema/API layer, not by the database. Full value lists are under enum values.
| Field | Type | Spec enum | Notes |
|---|---|---|---|
reconciliation_type | CharField(100) | PaymentReconciliationTypeOptions | Required. payment | adjustment | advance |
status | CharField(100) | PaymentReconciliationStatusOptions | Required. active | cancelled | draft | entered_in_error |
kind | CharField(100) | PaymentReconciliationKindOptions | Required. deposit | periodic_payment | online | kiosk |
issuer_type | CharField(100) | PaymentReconciliationIssuerTypeOptions | Required. Who paid — patient | insurer |
outcome | CharField(100) | PaymentReconciliationOutcomeOptions | Required. queued | complete | error | partial |
method | CharField(100) | PaymentReconciliationPaymentMethodOptions | Required. HL7 v2-0570 payment-method codes — cash | ccca | cchk | cdac | chck | ddpo | debc |
Payment details
| Field | Type | Notes |
|---|---|---|
payment_datetime | DateTimeField → datetime | None | When the payment was issued. Optional (default None) |
reference_number | CharField(1024) → str | None | Cheque number or payment reference. Optional |
authorization | CharField(1024) → str | None | Authorization number. Optional |
disposition | TextField → str | None | Message describing the outcome. Optional |
note | TextField → str | None | Free-text note. Optional |
Amounts
Every amount is a DecimalField(max_digits=20, decimal_places=6). Six decimal places allow sub-currency precision.
| Field | Type | Notes |
|---|---|---|
tendered_amount | Decimal(20, 6) | Required on write. Amount offered by the issuer |
returned_amount | Decimal(20, 6) | Required on write. Amount returned to the issuer (e.g. change). Must be strictly less than tendered_amount, or the write is rejected |
amount | Decimal(20, 6) | Net payment, computed server-side as tendered_amount - returned_amount. A client-supplied value is always overwritten |
Flags & extensions
| Field | Type | Notes |
|---|---|---|
is_credit_note | BooleanField → bool | Default False. Marks the record as a credit note rather than an inbound payment |
extensions | JSONField → dict | Default {}. Open extension bag. On write, each key is validated against the registered handler for resource type payment_reconciliation — unknown keys are currently dropped. On retrieve, rendered through those handlers |
Enum values
All enums are str, Enum classes in spec.py.
PaymentReconciliationTypeOptions (reconciliation_type)
| Value |
|---|
payment |
adjustment |
advance |
PaymentReconciliationStatusOptions (status)
| Value |
|---|
active |
cancelled |
draft |
entered_in_error |
PaymentReconciliationKindOptions (kind)
| Value |
|---|
deposit |
periodic_payment |
online |
kiosk |
PaymentReconciliationIssuerTypeOptions (issuer_type)
| Value |
|---|
patient |
insurer |
PaymentReconciliationOutcomeOptions (outcome)
| Value |
|---|
queued |
complete |
error |
partial |
PaymentReconciliationPaymentMethodOptions (method)
HL7 v2-0570 payment-method codes.
| Value | HL7 meaning |
|---|---|
cash | Cash |
ccca | Credit card |
cchk | Cashier's check |
cdac | Credit/debit account |
chck | Check |
ddpo | Direct deposit |
debc | Debit card |
Resource specs (API schema)
Defined in care/emr/resources/payment_reconciliation/spec.py. All extend EMRResource (serialize / de_serialize, with perform_extra_serialization / perform_extra_deserialization hooks). See Base model.
| Spec class | Role | Notes |
|---|---|---|
BasePaymentReconciliationSpec | shared base | Holds id, the six enum fields, disposition, payment_datetime, method, reference_number, authorization, note. __exclude__ = ["target_invoice", "account"] (FKs handled in hooks). ___extension_resource_type__ = payment_reconciliation |
PaymentReconciliationWriteSpec | write · create & update | Adds target_invoice (UUID, optional), account (UUID, required), tendered_amount, returned_amount, amount (optional), is_credit_note (default False), location (UUID, optional). Mixes in ExtensionValidator |
PaymentReconciliationMinimalReadSpec | read · list | Adds amount, tendered_amount, returned_amount, is_credit_note, created_date, modified_date |
PaymentReconciliationReadSpec | read · detail (nested) | Extends minimal read; adds account: dict (serialized via AccountReadSpec) and target_invoice: dict | None (serialized via InvoiceReadSpec) |
PaymentReconciliationRetrieveSpec | read · full retrieve | Extends read; adds location: dict | None (via FacilityLocationListSpec), extensions: dict, created_by / updated_by. Also injects account.patient via PatientRetrieveSpec |
Write validation & side effects
PaymentReconciliationWriteSpec runs three steps:
- The amount validator (
model_validator(mode="after"),check_amount_or_factor) raises"Returned amount cannot be greater than tendered amount"whenreturned_amount >= tendered_amount, then setsamount = tendered_amount - returned_amount. The spec'samountfield is advisory and always recomputed. perform_extra_deserialization(is_update, obj)resolves the FK UUIDs to model instances:target_invoice(if provided) →Invoice.objects.get(external_id=...)account(required) →Account.objects.get(external_id=...)location(if provided) →FacilityLocation.objects.get(external_id=...)
ExtensionValidator.validate_extensionschecksextensionsagainst thepayment_reconciliationextension registry before save.
Read serialization side effects
PaymentReconciliationMinimalReadSpec.perform_extra_serializationmapsid←obj.external_id.PaymentReconciliationReadSpecserializes the relatedaccount(AccountReadSpec) and, when present,target_invoice(InvoiceReadSpec) into nested objects.PaymentReconciliationRetrieveSpecadditionally nests the account'spatient(PatientRetrieveSpec, scoped to the account's facility), thelocation(FacilityLocationListSpec), the rawextensions, and audit users (created_by/updated_byviaserialize_audit_users).
Related models
PaymentReconciliation is self-contained; its foreign keys reach into the billing and facility domains:
facility → FK Facility (PROTECT)
target_invoice → FK Invoice (PROTECT, nullable)
account → FK Account (PROTECT)
location → FK FacilityLocation (PROTECT, nullable)
Each foreign key uses on_delete=PROTECT, so a payment record blocks deletion of the facility, invoice, account, or location it references.
Methods & save behaviour
PaymentReconciliation defines no custom save(); it inherits EMRBaseModel behaviour (audit fields, external_id, soft delete). Everything below happens in the write spec, not the model:
amountis derived, not stored as sent. On every create and update the write spec computesamount = tendered_amount - returned_amountand rejectsreturned_amount >= tendered_amount.- FK resolution happens in
perform_extra_deserialization:external_idUUIDs foraccount,target_invoice, andlocationresolve to instances at de-serialization time. extensionsare validated against thepayment_reconciliationregistry on write and rendered through registered handlers on retrieve.
API integration notes
- Create and update use
PaymentReconciliationWriteSpec; list responses usePaymentReconciliationMinimalReadSpec; detail and retrieve responses usePaymentReconciliationReadSpec/PaymentReconciliationRetrieveSpec. accountis always required;target_invoiceis optional and allocates the payment to one invoice. Passaccount,target_invoice, andlocationasexternal_idUUIDs.- The Pydantic schema constrains the six classification fields (
reconciliation_type,status,kind,issuer_type,outcome,method) to the enums above and rejects invalid values, even though each column is a plainCharField. - Send
tendered_amountandreturned_amount(both required); the server derivesamount, so any value you send for it is ignored. extensionsholds custom key-value data without a schema migration, validated against registered handlers.
Related
- Reference: Account
- Reference: Invoice
- Reference: Charge item
- Reference: Base model
- Source (model): payment_reconciliation.py on GitHub
- Source (spec): payment_reconciliation/spec.py on GitHub