You’ve been in that sprint planning meeting. The marketing team wants five new point-mall gameplays: pure points for physical swag, points plus cash, virtual vouchers, a lottery, and an auction. They all look exactly the same on the frontend: click a button, watch points drop, get a result.
So, someone in engineering says the magic words: “We can just reuse the same order table and toggle a few configs.”
That is the exact moment your system starts dying.
It looks like one unified flow, but it’s five completely different beasts. You aren’t just “selling a product”; you’re trading abstract concepts. The most dangerous architectural debt isn’t a missing feature—it’s compressing five different business outcomes into a single word called “Success.”
Let’s look at the reality behind the UI.
Pure Points for Physical Goods: Deducting points is just the start of the transaction. If the item is out of stock or shipping fails, you have to reverse it. “Order Created” doesn’t mean the user got the item.
Points + Cash: You now have two value streams in one transaction. If the cash payment fails, your system needs to release the points. If the cash succeeds but the points processing times out, what happens? You can’t just slap a “Payment Success” label on it.
Virtual Rights: You aren’t delivering a product ID; you’re delivering an instance, a code, a token. An API returning 200 OK doesn’t mean the user got their stuff. It just means the API stopped talking. If it times out, do you retry? What if you retry and send two vouchers?
Lottery: Users aren’t buying a prize. They are buying a chance. “No win” is a normal state, not a system failure. If you treat “no win” as an error and refund the points, you’ve just broken your own game.
Auction: Users are buying a temporary lead. Being outbid isn’t a system failure; it’s a normal state transition. When they are outbid, you release the frozen points. When they win, you trigger fulfillment.
If you jam all these into one order chain, your failure exits become a disaster. You can’t just apply a generic “refund” button to everything. If a user loses an auction, you don’t refund their points—they were spent on the bid. If they don’t win a lottery, you don’t refund—they bought the chance.
Stop listing UI fields and start asking four questions for every new gameplay:
1. What is the actual transaction object? (A good, a mixed value, an instance, a chance, a bid?)
2. How are points handled? (Checked, frozen, deducted, released?)
3. What truly defines success? (Order creation, API acceptance, or actual fulfillment?)
4. What is the exact failure exit path? (Where do the points, cash, and inventory go?)
When your status names don’t map to real business outcomes, your customer service team will be left staring at “Deducted points but unknown result” tickets, completely paralyzed.
If your failure exit is just a generic “refund points” button, your architecture is already on fire.
Point malls can have endless gameplays, but they should never make the user guess where their hard-earned points actually went.
FAQ
Q: If the UI flow is identical, why can't we just use config flags to handle the differences?
A: Because the UI is a mask. Underneath, you're trading completely different abstract concepts (a physical good vs. a bidding right). Config flags don't change state machines; they just overload them, leading to unmanageable edge cases when failures occur.
Q: What's the practical first step to untangling a messy point-mall backend?
A: Stop writing code and map out the four dimensions for each gameplay: transaction object, point handling lifecycle, true success definition, and failure exit paths. If those four answers differ, you need a separate order chain.
Q: Isn't building separate order chains for each gameplay over-engineering?
A: No, it's the only way to scale. Trying to force a lottery state machine into a standard e-commerce order table is what actually creates technical debt. You're trading short-term dev speed for long-term customer service chaos.