- Why separate products from variants?
- Because almost everything a store does operates on the variant, not the product. Stock, weight, barcode, and often price belong to the specific size and colour a customer receives, while the title, description, and imagery belong to the product. Collapse them and you end up with a stock column that means nothing and a checkout that cannot tell the warehouse what to pick. Keep options and their values as their own rows too, so adding a third size does not mean an ALTER TABLE.
- Why does an order need to store its own prices?
- Because prices move and orders do not. If an order line references a product row and reads the current price, then every historical order silently re-prices the next time you run a promotion, and your revenue reports change retroactively. Copy the unit price, the title, the tax rate, and the variant details onto the line item when the order is created. The reference to the product stays useful for reporting, but the money on that order is fixed at the moment it was placed.
- Can the checkout total be calculated in the browser?
- It can be displayed there, and it must be calculated again on the server. The client sends variant identifiers and quantities; the Hono route looks up current prices, applies the discount rules itself, computes tax and delivery, and creates the order from its own arithmetic. Anything else means the total is an input, and inputs can be edited. This is the single most common way a generated storefront leaks money, and it costs one route to avoid.
- How should stock be tracked so the store does not oversell?
- Treat stock as a ledger of movements -- received, sold, returned, adjusted -- and derive the on-hand figure, or at minimum write the movement in the same batched statement that creates the order. A read-then-write against a mutable count has a race in the middle, and on a launch day that race is the difference between two customers and one apology. Decide separately whether stock is reserved when checkout opens or only when payment confirms, because both are defensible and silence is not.
- How does payment confirmation reach the store?
- Through the webhook, not the redirect. A customer returning to your success page proves that a browser navigated there; the provider event proves money moved. Mark an order paid when the checkout completion event arrives with a valid signature, keep the order in a pending state until then, and make the handler idempotent so a redelivery does not fulfil twice. The Stripe-enabled template ships the subscriptions and payments tables and a verifying route to build this on.
- Is a store built this way PCI compliant?
- Compliance belongs to the merchant, and it is scoped by where card data goes. The pattern the templates encourage is a hosted checkout: the customer enters card details on the provider domain, your worker never sees them, and your database stores identifiers instead. That keeps the sensitive path out of the code you generated, which is the arrangement most small merchants describe on a self-assessment. It is not a certificate, and nothing in a generated app substitutes for the assessment your acquirer asks for.
- How should tax and delivery appear on an order?
- As their own line rows with their own amounts, not as adjustments folded into a total. When a customer returns one item out of three, someone has to work out how much tax and how much delivery goes back, and that calculation is only possible if each component was recorded separately in the first place. The same structure makes the invoice, the accounting export, and the customer email agree with each other, which is harder than it sounds when a total has been rounded twice.
- Can I add discount codes without creating a loophole?
- Yes, if validation happens in the worker and the limits are enforced by the database. Keep the code, its rule, an active window, a global usage counter, and a per-customer limit, then record each redemption as a row with a unique constraint on the code and customer pair. Checking a limit in application code and incrementing afterwards is exactly the shape that lets a determined shopper apply the same launch discount eleven times in one afternoon.
- How do carts work for shoppers who are not signed in?
- Give the guest cart its own identifier in a cookie and store the cart server side against that token, so it survives a refresh and a different tab. When the shopper signs in, merge rather than replace: combine quantities, keep whichever line is newer where they conflict, and clear the guest token afterwards. A cart held only in browser storage disappears on a device switch, which is where a surprising share of abandoned baskets actually go.
- What does a refund do to the rest of the data?
- More than flip a flag. Model order status as explicit transitions, allow partial refunds against specific line items, and decide per refund whether the goods return to stock -- a damaged return does not. Record the refund as its own row referencing the payment, so the order total and the amount actually settled remain separately visible. Reporting that reads a single status column will misstate revenue the first week you handle a partial return.