Home / Blog / Refunds Are Transactions, Not Edits: A Better POS Payment Model
PaymentsAugust 6, 2026·3 min read

Refunds Are Transactions, Not Edits: A Better POS Payment Model

Learn how to model POS refunds as linked financial transactions for safer balances, clearer audits, and reliable payment reconciliation.

A refund button looks simple until money starts moving. If a point-of-sale system handles a refund by changing the original sale, it destroys evidence, complicates reconciliation, and makes partial refunds dangerously ambiguous. The safer model is straightforward: a refund is a new transaction linked to an earlier one.

Keep the original sale immutable

Once a payment has been accepted, treat its financial record as append-only. Correcting a product name or customer note may be harmless, but changing the paid amount rewrites history. Your database would say one thing while the payment provider and bank settlement still preserve the original charge.

Instead, create a refund record containing at least:

This structure preserves the complete chain of events. The sale remains a sale; the refund becomes an offsetting financial event.

A current balance should be calculated from recorded events, not manufactured by rewriting old ones.

Enforce the refundable amount

The most important invariant is that successful refunds must not exceed the captured amount. For a payment of Rp500,000 with one successful Rp100,000 refund, only Rp400,000 remains refundable.

That check must be concurrency-safe. Two cashiers could initiate refunds at nearly the same time, and both might read the same available balance. Use a database transaction with row locking, serializable isolation, or another atomic reservation mechanism. Do not rely on a disabled button in the user interface.

Also distinguish between requested and completed refunds. Payment providers can process asynchronously. Reserve the requested amount while its status is pending so another request cannot spend the same refundable balance. Release that reservation if the provider rejects the refund.

Make retries safe

Network timeouts create an uncomfortable question: did the provider receive the request, or not? Blindly trying again can issue two refunds.

Assign an idempotency key before calling the provider and persist it with the local refund record. Every retry for the same business action must reuse that key. If the provider supports idempotency, it can return the original result instead of processing a duplicate. Your own API should apply the same rule to repeated requests from the POS device.

A robust sequence is:

  1. Validate the refundable balance.
  2. Create a local pending refund with an idempotency key.
  3. Submit it to the payment provider.
  4. Update the status from the response or webhook.
  5. Reconcile unresolved records with the provider later.

Design for operations, not just code

Refunds affect inventory, accounting, staff permissions, and customer communication. A returned item may restock inventory, but a goodwill refund may not. Keep those decisions separate from the payment movement rather than assuming every refund reverses the entire sale.

Require explicit permissions for large or manual refunds, and retain the operator identity and reason. Show staff the original amount, prior refunds, pending refunds, and remaining refundable amount before confirmation. Generate a receipt that references both the refund and original sale.

Finally, include refunds as distinct rows in settlement reports. This makes provider fees, net deposits, and timing differences visible during reconciliation.

The practical takeaway is simple: never “undo” a payment by editing it. Record an immutable, linked refund; protect it against concurrency and retries; then let the ledger tell the complete story.

Build with Abati Technology

We build software that ships — WhatsApp API, developer tools, POS, and mobile apps. Let's talk about your project.

Get in Touch →