Serialize the Critical Path, Not the Product
Payment initiation runs inside a distributed lock keyed by the split group: split:pay:{token}. While holding it, the service reloads the share with its order, verifies that the token matches, rejects paid shares and cancelled orders, and only then asks the establishment's gateway to create a payment.
The lock is deliberately narrow. Guests can still read the shared session and the interface can continue polling; only the state transition that creates a competing charge is serialized. The lock has a bounded TTL and retry policy, and release uses a value-checked Redis Lua script so an expired lock can never be deleted by an older owner. Concurrency control is useful only when the lock itself is safe.
Persist the Intent Before the Return
A gateway redirect divides payment into two different moments: initiation and verification. Sofreh records the gateway authority and the exact initiated amount on the share before sending the guest away. The callback can then compare external evidence with internal intent instead of trusting query parameters or reconstructing history.
This also makes retries understandable. A callback may arrive more than once, but a share already marked paid is a known state rather than a second payment event. The client treats the callback redirect as presentation—it refreshes the authoritative session and removes transient query parameters—while the server remains the only place where money changes state.