Every offer has three price fields, all computed server-side:
| Field | Formula | Description |
|---|---|---|
offerAmountMinorUnit | Input from buyer | What the talent receives |
zoolyFeeMinorUnit | Math.round(offerAmountMinorUnit × 0.20) | 20% Zooly platform fee |
totalBrandPriceMinorUnit | offerAmountMinorUnit + zoolyFeeMinorUnit | What the brand pays |
The client sends only offerAmountMinorUnit. The server computes and stores the other two values. Client-sent fee fields are ignored.
createOffer() in packages/db/src/access/offers.ts computes zoolyFeeMinorUnit and totalBrandPriceMinorUnit from the provided offerAmountMinorUnit before inserting the row.
updateOfferStatus() in packages/db/src/access/offers.ts recomputes both fee fields whenever a countered amount is applied to the canonical offer_amount_minor_unit column:
→ ADMIN_REVIEW) applies the proposed deltas and recomputes immediately.→ COUNTERED) only stores the proposal; fees are recomputed when the other side accepts (COUNTERED → ACCEPTED).updateOfferDraft() likewise recomputes fees when a draft edit changes the amount.
POST /api/offers/submit only accepts offerAmountMinorUnit (positive integer). The submitOfferBodySchema contract has no fee fields at all, so any client-sent zoolyFeeMinorUnit / totalBrandPriceMinorUnit are dropped at parse time before reaching createOffer.
When a payment intent is created for an offer, getOfferProduct() in packages/srv-stripe-payment/src/getProductByPayForId.ts performs a consistency check:
zoolyFeeMinorUnit matches Math.round(offerAmountMinorUnit × 0.20)totalBrandPriceMinorUnit matches offerAmountMinorUnit + zoolyFeeMinorUnitThis prevents price drift if the fee percentage changes or if stored values are manually edited.
The computePriceData(amountMinorUnit, platformFeeMinorUnit) function in packages/srv-stripe-payment/src/getProductByPayForId.ts produces the full ProductPriceData breakdown used by Stripe:
calculateStripeFees(amount, currency) from @zooly/util — the fixed fee component is 0 for zero-decimal currencies)This function is exported from @zooly/srv-stripe-payment for use in escrow release and other payment flows.
| File | Role |
|---|---|
packages/db/src/access/offers.ts | Fee computation in createOffer and updateOfferStatus |
apps/zooly-app/app/api/offers/submit/route.ts | Input validation, strips client fee fields |
packages/srv-stripe-payment/src/getProductByPayForId.ts | Payment-time consistency assertion, computePriceData |