Flow
What goes into a dedupe key
1Random key on every try
Never brings back an old answer, but every retry can make a duplicate.
2Group ID only (customer create)
Retries and doubled events land on one customer; a retry with a changed email may be rejected.
3Customer plus price (subscription create)
Retries collapse; cancelling and quickly re-signing for the same price may return the old subscription.
4Group ID plus a counter
Would allow a real second signup, but means storing and bumping the counter. I haven't built this.
Looking up a group's email address at the payment provider and taking the first match was how the billing code checked if it already had a paying-customer record. Some of those emails were made up by my own code.
When a new group signs up, my app makes a customer at the payment provider, then saves the group in my own database. A customer needs an email, and sometimes I didn't have one yet. So the code guessed an admin address from the group's web domain, or made up a placeholder. Later checks looked up that guess.
How one group could grab another's record
If the email lookup found a customer that belonged to a different group, the old code just relabeled it for the new group. Two groups run by the same person, or two groups on the same domain, could end up sharing one billing record, with the first group losing its link. I haven't checked whether it ever happened. The code let it happen, and that was enough for me.
One ID that never changes
Lookup now uses the group's ID from the identity provider, stored on the customer when it's made. That ID is given out once and never reused. The email is just a way to reach someone now.
The same ID also builds a dedupe key, a label you send with the request. If the create call times out after the provider already made the customer, a retry with the same key gets the first customer back instead of a second one.
The key is just the organization's ID with a short prefix, so the same organization always sends the same label.
Subscriptions got a key made from the customer and the price.
My app saves its own record after the customer exists, so it goes back and writes its own ID onto the customer. If that step fails, it logs a warning. If the app crashes right there, nothing notices.
Fixed keys have their own catches, as far as I can tell from the provider's docs:
- the provider only remembers a key for about a day
- a retry with a changed email may be rejected
- cancelling and quickly signing up again for the same price may return the old subscription
Ignored retry advice, wording-based delete, an empty seat counter
The commit came with a cheerful document calling the work production-ready. I'm fairly sure an agent wrote it, and I didn't trim it.
A new wrapper labels each error and says whether to retry, but the callers throw that away and keep only the message, apart from one metadata update that just logs a warning. Delete now decides "already gone" by searching the error text for a phrase, which breaks if the provider changes its wording. The seat counter now returns nothing.
There are no tests. If I did this again, I'd write the "same group, created twice, one customer" test before touching the error wrapper.