Refund a payment
Mark an existing ticket payment as refunded from the POS payment screen, then refresh the ticket so paid totals, outstanding balance and item payment states use only effective payments.
In this article
Use this flow when an operator needs to correct a local POS payment state on a paid or partially paid ticket.
The action updates Antylogic's ticket payment record. It does not send money back through a card terminal, bank, payment processor or local adapter.
Before you start
- The operator is working in the correct organisation and location.
- The ticket opens in the POS payment screen and belongs to the current location.
- The operator has `tpv:use` plus either `tpv:refund` or `tickets:manage`.
- The ticket is not void, the payment belongs to the ticket, and the payment is not already `refunded`.
- The payment is not linked to an issued invoice; in the current UI, the ticket detail must not include any invoice before Refund is enabled.
Instructions
- 1.
Open the ticket payment screen
Open the advanced POS payment screen for the paid or partially paid ticket. The page loads the ticket, methods and payment operations only when the location context and `tpv:use` permission are valid.
- 2.
Find the refundable payment
In Payments, review each payment row. A refundable payment shows its method, status, amount, allocations and a Refund action. Payments already marked `refunded` keep their status and no longer show the action.
- 3.
Open the confirmation
Select Refund on the eligible row. The app opens the Refund payment confirmation and explains that the payment will be marked as refunded and the ticket will show the outstanding amount again. Cancel closes the modal without changing data.
- 4.
Confirm the refund
Confirm Refund payment. The app calls `POST /api/app/{orgId}/{locationId}/tickets/{ticketId}/payments/{paymentId}/refund` with the current UI payload `{}` and refreshes the ticket detail on success.
- 5.
Check the updated ticket
After the refresh, the payment row should show Refunded, the Refund action should be gone for that row, and the ticket totals and item payment statuses should reflect only non-refunded effective payments.
Validations and limits
- The backend requires ticket access and then `tpv:refund` or `tickets:manage` for the refund mutation.
- The refund endpoint accepts only POST; malformed JSON returns an invalid-payload error, while an empty body or `{}` is valid.
- `refunded_by_staff_id` is optional at the API layer, but the current POS screen does not send the route `staffId` in the refund payload.
- The backend rejects void tickets, payments that do not belong to the ticket, already-refunded payments and payments linked through `invoice_ticket_payment_links`.
- The UI disables Refund when the ticket detail contains any invoice; the backend blocks the specific payment when that payment has invoice links.
- A refunded payment stops contributing to paid totals; the ticket can remain paid, become partially paid or return to pending depending on the remaining effective payments.
- Payment allocations are not deleted; they remain attached and are reread when recalculating item balances.
- The refund does not create a negative payment, void the ticket, print a receipt or create a `tpv_output_job`.
Expected result
The existing `ticket_payment` is marked `refunded`, the ticket detail is returned refreshed, and the operator sees the updated local payment state and recalculated balance inside the POS.