Every offer has three price fields, all computed server-side:
| Field | Formula | Description |
|---|---|---|
offerAmountCent | Input from buyer | What the talent receives |
zoolyFeeCent | Math.round(offerAmountCent × 0.20) | 20% Zooly platform fee |
totalBrandPriceCent | offerAmountCent + zoolyFeeCent | What the brand pays |
The client sends only offerAmountCent. The server computes and stores the other two values. Client-sent fee fields are ignored.
createOffer() in packages/db/src/access/offers.ts computes zoolyFeeCent and totalBrandPriceCent from the provided offerAmountCent before inserting the row.
When a counter-offer is submitted with a new counterAmountCent, updateOfferStatus() in packages/db/src/access/offers.ts recalculates the fees if the offer re-enters ADMIN_REVIEW status.
POST /api/offers/submit in apps/zooly-app/app/api/offers/submit/route.ts validates that offerAmountCent is a finite positive number. Any client-sent zoolyFeeCent or totalBrandPriceCent fields are stripped 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:
zoolyFeeCent matches Math.round(offerAmountCent × 0.20)totalBrandPriceCent matches offerAmountCent + zoolyFeeCentThis prevents price drift if the fee percentage changes or if stored values are manually edited.
The computePriceData(amountCent, platformFeeCent) function in packages/srv-stripe-payment/src/getProductByPayForId.ts produces the full ProductPriceData breakdown used by Stripe:
calculateStripeFees from @zooly/util)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 |