shop cart - BevvyTech/BrewskiDocs GitHub Wiki
- Endpoint Group: Public storefront carts & customer auth
- Purpose: Allow logged-in consumers to manage a cart per brewery storefront, supporting product variants and JWT-based sessions.
- Availability: Public APIs guarded by the “public” JWT audience; only accessible to authenticated storefront clients.
| Method | Path | Description |
|---|---|---|
POST |
/public/auth/register |
Register a marketplace consumer account (email/password). |
POST |
/public/auth/login |
Authenticate a consumer using email/password. |
POST |
/public/auth/refresh |
Rotate refresh tokens and issue a new access token. |
POST |
/public/auth/logout |
Revoke a refresh token. |
GET |
/public/teams/:identifier/cart |
Fetch (or lazily create) the consumer’s active cart. |
POST |
/public/teams/:identifier/cart/items |
Add/update a variant or upsell within the cart. |
PATCH |
/public/teams/:identifier/cart/items/:itemId |
Adjust quantity or swap the variant for an existing line. |
DELETE |
/public/teams/:identifier/cart/items/:itemId |
Remove an item from the cart. |
GET |
/public/teams/:identifier/products |
Fetch published storefront products with active variants. |
GET |
/public/teams/:identifier/products/:slug |
Fetch a single published product (with variants). |
GET |
/public/teams/:identifier/wishlist |
Fetch the customer’s wishlist for the team. |
POST |
/public/teams/:identifier/wishlist |
Add a beer to the wishlist ({ "beerId": "<uuid>" }). |
DELETE |
/public/teams/:identifier/wishlist |
Remove a beer from the wishlist ({ "beerId": "<uuid>" }). |
GET |
/public/marketplace/cart |
Marketplace cart spanning multiple breweries. |
POST |
/public/marketplace/cart/items |
Add or replace a line in the marketplace cart ({ "variantId", "quantity" }). |
PATCH |
/public/marketplace/cart/items/:itemId |
Update quantity or swap variant for a marketplace cart line. |
DELETE |
/public/marketplace/cart/items/:itemId |
Remove a marketplace cart line. |
POST |
/public/marketplace/cart/guest |
Persist a lightweight guest basket payload ({ items, token? }) and return a guestToken for future hydrate/merge calls. |
POST |
/public/marketplace/cart/hydrate |
Hydrate a guest basket payload ({ "items": [...], "guestToken": "<token>" }) and return the computed cart snapshot. |
POST |
/public/marketplace/cart/merge |
Authenticated bulk merge of the guest basket payload for the signed-in client; accepts the same lightweight items array and/or a guestToken and returns the updated cart. |
GET |
/public/marketplace/wishlist |
Marketplace wishlist (mixed breweries). |
POST |
/public/marketplace/wishlist |
Add a beer to the marketplace wishlist ({ "beerId" }). |
DELETE |
/public/marketplace/wishlist |
Remove a beer from the marketplace wishlist ({ "beerId" }). |
GET |
/public/beers/latest |
Convenience listing of recently packaged beers (supports limit and packaging_group). |
identifier accepts either the team UUID or public slug. Legacy /public/teams/:identifier/* routes still scope a request to a single brewery storefront. Use the new /public/marketplace/* endpoints when customers need to mix beers from multiple breweries—each line item carries its own teamId metadata, and responses echo the same payload for rendering.
All cart routes require an Authorization: Bearer <token> header containing a “public” audience JWT.
- Creates (or reuses) a
clientsrow withlogin_enabled=true,login_email, hashed password, andis_individual=true. Marketplace accounts are not bound to a single brewery; memberships are attached later when the customer interacts with a team storefront. - Response mirrors the backend auth shape:
{
"client": {
"id": "...",
"teamId": null,
"teamIds": [],
"name": "Jordan Black",
"email": "[email protected]"
},
"token": {
"accessToken": "...",
"tokenType": "Bearer",
"expiresAt": "2025-06-01T12:00:00.000Z"
},
"refresh": {
"refreshToken": "...",
"expiresAt": "2025-06-15T12:00:00.000Z"
}
}-
teamIdremainsnulluntil a primary brewery membership exists;teamIdslists all associated breweries (useful for legacy per-team dashboards).
POST /public/teams/lantern-brewery/cart/items
{
"variantId": "1c00bce6-44e7-45c8-8f5f-4a7c33a3f14d",
"quantity": 2,
"replaceQuantity": false
}-
variantIdmust reference an activestorefront_product_variantsrow belonging to the team and tied to a published product. -
replaceQuantity=trueoverwrites the line quantity instead of incrementing. - Response returns the recomputed cart totals and snapshot metadata captured at add-time.
To add an upsell accessory instead of a beer variant:
POST /public/teams/lantern-brewery/cart/items
{
"upsellId": "55dcf0be-53e3-4d2f-9f68-4f6fb1f126aa",
"quantity": 1
}-
upsellIdreferences an active record instorefront_upsells. The upsell must have aprice_minordefined; the snapshot includes its name, description, coupler metadata, and image URLs.
PATCH /public/teams/lantern-brewery/cart/items/95f0...
{
"quantity": 6,
"variantId": "3e5c..." // optional swap
}- Quantity
0triggers a delete. WhenvariantIdis supplied, the snapshot/price is refreshed from the new variant before totals are recalculated. - Upsell lines support quantity updates only—omit
variantIdwhen patching and send a payload such as{ "quantity": 0 }to remove the accessory.
{
"cart": {
"id": "cart-123",
"teamId": "team-1",
"clientId": "client-9",
"status": "active",
"currency": "GBP",
"subtotalMinor": 1500,
"taxTotalMinor": 200,
"totalMinor": 1700,
"items": [
{
"id": "item-1",
"kind": "variant",
"variantId": "variant-1",
"upsellId": null,
"quantity": 2,
"unitPriceMinor": 500,
"unitTaxRateBps": 2000,
"lineTotalMinor": 1000,
"snapshot": {
"variantId": "variant-1",
"sku": "440-can",
"product": {
"id": "product-1",
"title": "Lantern IPA",
"slug": "lantern-ipa",
"subtitle": "440 ml can"
}
}
},
{
"id": "item-2",
"kind": "upsell",
"variantId": null,
"upsellId": "55dcf0be-53e3-4d2f-9f68-4f6fb1f126aa",
"quantity": 1,
"unitPriceMinor": 12900,
"unitTaxRateBps": null,
"lineTotalMinor": 12900,
"snapshot": {
"upsellId": "55dcf0be-53e3-4d2f-9f68-4f6fb1f126aa",
"slug": "s-type-coupler",
"name": "S-Type Keg Coupler",
"description": "Stainless body with check valve.",
"priceMinor": 12900,
"currencySymbol": "£",
"defaultImageUrl": "https://assets.brewskiapp.com/upsells/s-type/main.webp"
}
}
]
}
}- Guest browsers now store only trimmed basket entries in the cookie (
kind,id,quantity, optionalgroupRef) plus the latestguestTokenissued by/public/marketplace/cart/guest. - Every basket mutation posts the current payload to
/public/marketplace/cart/guest. The API persists the payload for 14 days and returns a token string; the frontend stores the token alongside the cookie. -
/public/marketplace/cart/hydrateaccepts either the inlineitemsarray, theguestToken, or both. Whenitemsis empty butguestTokenis present, the server replays the persisted payload and returns the fullCartSnapshot. -
/public/marketplace/cart/mergeaccepts the same structure. During login the storefront submits{ items, guestToken }, and the API merges any persisted guest rows into the authenticated cart before deleting the guest token. - Tokens are rotated on every successful
/cart/guestrequest and cleared server-side as soon as a merge completes so stale guest baskets cannot be reused.
- Wishlist entries are keyed by
beerId(per team) and de-duplicate automatically. - All wishlist routes require the same “public” JWT auth as the cart endpoints.
POST /public/teams/lantern-brewery/wishlist
{
"beerId": "f778b914-4bf6-4a28-9a91-9bd085249491"
}GET /public/teams/lantern-brewery/wishlist
{
"wishlist": {
"id": "wishlist-123",
"teamId": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7",
"clientId": "client-9",
"count": 2,
"items": [
{
"id": "item-1",
"beerId": "f778b914-4bf6-4a28-9a91-9bd085249491",
"addedAt": "2025-06-11T10:00:00.000Z",
"beer": {
"id": "f778b914-4bf6-4a28-9a91-9bd085249491",
"name": "Lantern Mosaic IPA",
"style": "IPA",
"badgeImageUrl": "https://cdn.brewskiapp.com/assets/beers/lantern-mosaic-ipa.svg",
"badgeIconUrl": "https://cdn.brewskiapp.com/assets/beers/lantern-mosaic-ipa.svg",
"team": {
"id": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7",
"name": "Lantern Brewery",
"slug": "lantern-brewery",
"logoUrl": "https://cdn.brewskiapp.com/assets/logos/lantern-brewery.png"
}
}
}
]
}
}-
DELETE /public/teams/:identifier/wishlistexpects a JSON body{ "beerId": "<uuid>" }and returns204 No Contenteven when the entry is already absent.
- Access tokens for customers reuse the platform JWT secret and set
aud = "public". Marketplace accounts emit tokens without ateamIdclaim; when a primary membership exists the claim is populated so legacy per-team routes keep working. - Refresh tokens live in
storefront_refresh_tokensand rotate on each/public/auth/refreshcall (same TTL as backend refresh tokens).
-
clientstable gains login fields (is_individual,login_enabled,login_email,password_hash,last_login_at,marketing_opt_in,public_profile). - New enums
storefront_product_stateandstorefront_cart_status. - New tables:
-
storefront_products(per-team catalogue wrapper around beers/multipacks). -
storefront_product_variants(price-bearing variants tied to containers/multipacks). -
storefront_cartsandstorefront_cart_items(customer baskets). -
storefront_refresh_tokens(public refresh token storage). -
storefront_wishlistsandstorefront_wishlist_items(per-client, per-team favourite beers).
-
- Currency currently defaults to
GBP; extend once ISO codes arrive inteams. - Only published products/active variants are exposed through
/public/teams/:identifier/products. - Upsell lines appear alongside variants in cart responses with
kind: "upsell"and carryupsellIdrather thanvariantId. - Inventory reservation is not yet implemented—cart totals are optimistic; downstream checkout should revalidate stock.
- Team-scoped cart/wishlist endpoints still enforce brewery ownership by comparing the resolved team identifier with the customer’s memberships. Marketplace accounts without a matching membership receive
403 Forbiddenresponses for those routes and should rely on/public/marketplace/*.
| Date | Author | Change |
|---|---|---|
| 2025-05-31 | Agent | Added public customer auth and cart endpoints with variation support. |
| 2025-10-21 | Agent | Added storefront wishlist schema and /public/teams/:identifier/wishlist endpoints. |
| 2025-10-26 | Agent | Removed teamIdentifier requirement, introduced marketplace auth responses (teamIds) and updated plugin safeguards. |
-
GET /public/beers/latest?packaging_group=smallpackrestricts results to packaged cans/bottles (usekegorcaskfor draught-only views).
POST /public/auth/register { "email": "[email protected]", "password": "Passw0rd!", "name": "Jordan Black", "marketingOptIn": true }