Confirming an appointment was supposed to create the patient's record and its
session, and PatientService already knew how. Only two of the five paths that
confirm an appointment ever called it, and the one that mattered most did not:
a booking paid for online was confirmed inside the payment callback, which
never ran the side-effects. Every Nobat724 booking therefore went unfiled — 7
confirmed appointments in dev had no session at all.
The side-effects now run through AppointmentConfirmationService, which every
path calls: the payment callback, both PATCH endpoints, and panel/admin
bookings. Creating the record can no longer roll back a confirmation or a
payment; a failure is logged and can be repaired with the new
app:appointment:backfill-sessions command.
Two related defects fixed along the way:
- A doctor working at a clinic got two records for one appointment, one under
the doctor and one under the clinic, so a single visit's revenue was counted
twice. The booking context now decides, and it decides once.
- That context was inferred from address_id, falling back to "the doctor's only
clinic" — a guess that files an appointment under the wrong practice now that
schedules are per-context. It is stored as appointments.clinic_id instead.
Panel and admin bookings were left pending forever: nothing confirmed them and
no payment was expected. They are created confirmed.
Repeat confirmations no longer duplicate the session; an archived one still
counts as filed, so archiving a mistaken visit does not resurrect it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(appointment): enhance appointment detail page with time formatting and additional info
fix(payment): update payment query to fetch from the correct endpoint and adjust response structure
docs(api): add search parameter to payments API documentation and detail response structure
test(payment): add unit test for MellatGateway to verify null credentials handling
reference_id (the gateway's settled-transaction ref) was not unique, so the
same successful callback — or a RefNum replayed onto another order — could
credit twice. Add a unique index (NULL until success, so pending/failed rows
don't collide) and an application-level pre-check in the callback that fails the
payment if the reference already belongs to another order. The unique index is
the hard backstop behind the check.
Regression: PaymentCallbackAmountTest::testReplayedGatewayReferenceIsRejected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The callback marked an order success on any verify-ok result without comparing
the gateway-settled amount to the amount charged. SEP returns AffectiveAmount;
an underpayment or a replayed RefNum from a cheaper order would confirm the
expensive order. Now reject (status=failed, no activation) when the gateway
reports an amount that mismatches the stored amount_rials. Gateways that don't
report a settled amount (Mellat binds it server-side) skip the check.
MockGateway now echoes mock_amount so the guard is exercisable in tests.
Regression: tests/Payment/PaymentCallbackAmountTest (underpayment rejected,
matching amount succeeds).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>