Flow
Building a claim-first button
1Pick the key
Environment, store and delivery
2Partial index
Count only requested and created rows
3Claim first
Save the requested row before any outbound call
4One error means taken
Only the uniqueness error becomes a conflict
5Settle the same row
Update it to created or failed
6Ship the way out
Admin-only cancel with a reason and no outbound call
The button I built for turning a pending delivery into a saved draft in the stores' inventory system had a gap in it. The gap was as long as the call to that other system.
It only runs in the sandbox so far. The old code checked for a finished draft, called the other system, waited, and then saved my record. While it waited, nothing on my side said a draft was being made. So a double click, a second tab or a second person could check, find nothing, and send a second create. A duplicate draft in someone else's system is not something I can undo. I never saw it happen. The order of the steps was enough to worry me.
Claim first, then call
Now my code saves a "requested" row first. Then it calls. Then it updates that same row to created or failed. One attempt, one row.
The database enforces the claim with a partial unique index. It covers the environment, the store and the delivery, but only for requested and created rows:
create unique index one_active_claim on claims (env, store_id, delivery_id)
where state in ('requested', 'created');
The old check still runs first, to give a clear message. But if two requests both pass it, only one insert wins. The other gets a uniqueness error. My code treats that one error as "taken", logs it, and returns a conflict without calling out. Any other error still fails as an error. Failed rows sit outside the index, so a person can retry later. The page also says when a draft is still being made, not just when it's done.
A hash that missed the lines
Each attempt also saves a hash of the payload. The old helper passed the top-level keys to the JSON serializer as a filter. That filter hits nested objects too, so the lines came out empty. The new helper sorts keys at every level. The payload looks flat today, so I doubt it ever mattered. It would have the first time a nested field showed up.
A way out for stuck claims
If the process dies after saving the claim but before updating it, the claim blocks that delivery forever. My side can't tell if the call happened. So there's a narrow cancel: admins only, sandbox only, store named, reason required. It must match the delivery, and it can't touch a finished draft. It never calls the other system. It just marks the row cancelled and logs who and why. A person checks the other system before anyone retries.
The part I can't vouch for
My tests fake the database. They prove my code handles the error. They don't prove the real index fires on two real clicks, and I haven't watched it. The call also sends an idempotency key, a tag that could let the other system spot a repeat. Nobody has shown me that it reads it. Until someone does, the person checking the other system before a retry is the real safety net.