01 · Start here
The three rules that prevent most integration bugs
- Require an address before opening Floward. The shipping address city and country decide which HOI fulfillment center, catalog, prices, stock, and slots apply.
- Use the server-provided cart and price. Never invent a Floward cart ID, send a catalog price, or reuse a price from another address.
- Prepare immediately before payment. HOI availability, price, fulfillment center, and delivery slot can change; the prepare result is the mobile UI’s final checkout gate.
02 · Mental model
Address → fulfillment center → sellable catalog
Floward is not a conventional fixed-price Medusa catalog. Barq stores the product identity and content; HOI supplies the location-specific sellability and binding price.
city + country
resolved by name
current policy
stock + price
When the address changes
Reload the Floward home/cart state and treat previous product prices as stale. The backend re-resolves the city and center; products may disappear, prices may change, and prepare will replace the automatic delivery slot.
03 · Home & browsing
Bootstrap Floward with one request
Creates or retrieves the customer’s persistent Floward cart, resolves its address, warms the fulfillment-center catalog, and returns page-builder sections.
GET {{BASE_URL}}/store/floward/home{
"storefront": {
"cart": {
"id": "cart_01FLOWARD",
"type": "floward",
"item_count": 2,
"requires_shipping_address": true,
"shipping_address": {
"id": "addr_01",
"city": "Dubai",
"country_code": "ae",
"latitude": 25.276987,
"longitude": 55.296233
}
},
"wish_list_id": "wish_01"
},
"page_sections": [
{
"id": "floward_category_collection",
"type": "scrollable_subcategory_section",
"title": "Categories",
"content": []
},
{
"id": "popularFloward",
"type": "product_listing_section",
"title": "Popular",
"config": {
"api_url": "/store/floward/products?..."
},
"styles": { "axis": "horizontal" }
}
],
"config": {
"dependency": { "requires_address": true }
}
}Mobile behavior
- Render page sections using the existing Barq/Nana page-builder contracts.
- Use every action’s supplied
api_url; do not reconstruct category queries. - Keep the returned Floward cart ID in storefront state for add/update/remove operations.
- If the API reports a missing or unsupported address, return the customer to address selection.
04 · Products & pricing
Only show what the current center can fulfill
Supports the standard product-list query parameters used in the page-builder URLs: pagination, category, tags, search, fields, and expansions.
GET {{BASE_URL}}/store/floward/products?category_id[0]=pcat_01&expand=price_range&limit=20&offset=0{
"products": [
{
"id": "prod_01",
"title": "Pink Rose Arrangement",
"thumbnail": "https://...",
"price_range": { "min": 22885, "max": 22885 },
"variants": [
{
"id": "variant_01",
"inventory_quantity": 1,
"calculated_price": 22885,
"original_price": 22885,
"prices": [{ "currency_code": "sar", "amount": 22885 }]
}
]
}
],
"count": 1,
"offset": 0,
"limit": 20
}Open a Floward product detail page
Returns the page-builder PDP, the persistent Floward cart, and a fresh fulfillment-center price. A product that is unavailable at the current address returns 404.
GET {{BASE_URL}}/store/products/prod_01/details{
"config": { "title": "Pink Rose Arrangement" },
"storefront": {
"cart": {
"id": "cart_01FLOWARD",
"type": "floward",
"items_count": 2,
"requires_shipping_address": true
},
"default_address": { "id": "addr_01", "city": "Dubai" }
},
"product": {
"id": "prod_01",
"title": "Pink Rose Arrangement",
"description": "A hand-tied rose arrangement",
"thumbnail": "https://...",
"images": [{ "url": "https://..." }],
"price_range": { "min": 22885, "max": 22885 },
"requires_shipping": true,
"variants": [{
"id": "variant_01",
"purchasable": true,
"calculated_price": 22885,
"original_price": 22885
}],
"capabilities": { "can_buy_now": true, "can_add_to_cart": true }
},
"page_sections": []
}22885 means SAR 228.85. Use the returned currency and the application’s money formatter. Never use an imported variant price as a Floward fallback.| Situation | Expected UI behavior |
|---|---|
| Product is absent from the response | Do not display it. It is not sellable from the selected center. |
| Address changes | Discard the current result set and reload from page 1. |
| Empty result | Show an address-specific empty state, not a global “out of stock” claim. |
| Price changes later | Use the prepare error to refresh the cart and ask the customer to review. |
05 · Cart lifecycle
The Floward cart is persistent
The customer has one persistent Floward cart. Reopening Floward retrieves it; mobile should not create a replacement cart for normal shopping.
Add a single Medusa variant. The backend confirms that it is an HOI dynamically priced product, resolves the current center, applies the trusted price, and records the pricing snapshot.
POST {{BASE_URL}}/store/v2/carts/cart_01FLOWARD/line-items
Idempotency-Key: {{UUID}}
Content-Type: application/json
{
"variant_id": "variant_01",
"quantity": 1
}200 response — updated detailed cart
{
"id": "cart_01FLOWARD",
"type": "floward",
"title": "Floward",
"cart_sections": ["item_listing", "gift_card_messaging", "delivery_details"],
"items": [{
"id": "item_01",
"cart_id": "cart_01FLOWARD",
"quantity": 1,
"title": "Pink Rose Arrangement",
"variant_id": "variant_01",
"thumbnail": "https://...",
"requires_shipping": true,
"type": null,
"unit_price": 22885,
"total": 22885
}],
"subtotal": 22885,
"shipping_total": 0,
"discount_total": 0,
"total": 22885,
"dependencies": { "requires_address": true, "can_checkout": true, "blocked_reason": "" }
}The production response also includes every field documented in the complete DetailedCartV2 contract.
Quantity and removal
Use the existing Medusa cart line-item endpoints. Dynamic Floward pricing is preserved by the backend when quantity changes.
POST /store/v2/carts/:cart_id/line-items/:line_item_id
{ "quantity": 2 }
DELETE /store/v2/carts/:cart_id/line-items/:line_item_id200 response for both calls: the updated DetailedCartV2 directly. After deletion, the removed line is absent from items; if no items remain, dependencies is { "can_checkout": false, "blocked_reason": "CART_EMPTY" }.
Update the cart or shipping address
POST /store/v2/carts/:cart_id
Content-Type: application/json
{
"shipping_address": "addr_01"
}200 response: the updated DetailedCartV2 directly, including the resolved shipping_address. Treat all earlier Floward product results and prices as stale after this call.
Create the checkout cart from the persistent cart
When the customer continues to checkout, send the persistent cart ID and the selected source line-item IDs. The backend copies the address, cart type, metadata, quantities, and trusted pricing provenance. If quantity is omitted, the full source quantity is copied.
POST {{BASE_URL}}/store/v2/carts
Content-Type: application/json
{
"source_cart_id": "cart_01FLOWARD",
"items": [
{ "line_item_id": "item_01", "quantity": 1 },
{ "line_item_id": "item_02" }
]
}200 response: a new checkout cart as DetailedCartV2 directly. Save its returned id; every subsequent checkout mutation and /prepare call must use that ID.
06 · Detailed carts
One aggregate API; one stable detailed DTO
This is the only endpoint that returns all cart summaries. Use type=floward for the persistent cart. During checkout, use id=cart_... to request the exact ephemeral checkout cart. If both are supplied, the type must match the ID.
{
"default_address": { "id": "addr_01", "city": "Dubai" },
"total_item_count": 5,
"carts": [
{ "id": "cart_default", "type": "default", "title": "Digital Vouchers", "item_count": 1 },
{ "id": "cart_floward", "type": "floward", "title": "Floward", "item_count": 4 }
],
"detailed": {
"id": "cart_floward",
"type": "floward",
"title": "Floward",
"cart_sections": ["item_listing", "gift_card_messaging", "delivery_details"],
"items": [{
"id": "item_01",
"cart_id": "cart_floward",
"quantity": 1,
"title": "Pink Rose Arrangement",
"variant_id": "variant_01",
"thumbnail": "https://...",
"requires_shipping": true,
"type": null,
"unit_price": 5000,
"total": 5000
}],
"subtotal": 5000,
"shipping_total": 0,
"discount_total": 0,
"total": 5000,
"allow_discounts": false,
"show_third_party_acknowledgement": false,
"requires_shipping": true,
"required_minimum_spend": null,
"free_delivery": null,
"discounts": [],
"shipping_address": {
"id": "addr_01",
"first_name": "Alex",
"last_name": "Customer",
"phone": "+966500000000",
"address_1": "King Fahd Road",
"city": "Riyadh",
"country_code": "sa",
"latitude": 24.7136,
"longitude": 46.6753
},
"gift_card_messaging": {
"fields": [
{ "id": "to", "title": "label_optional_to", "text": "Bestie", "regex": "^[\\s\\S]{0,100}$", "required": false, "field_type": "text_field" },
{ "id": "message", "title": "label_message", "text": "Congratulations!", "regex": "^[\\s\\S]{0,250}$", "required": false, "field_type": "text_area" },
{ "id": "from", "title": "label_optional_from", "text": "Derek", "regex": "^[\\s\\S]{0,100}$", "required": false, "field_type": "text_field" },
{ "id": "qr_link", "title": "label_qr_link", "text": null, "regex": null, "required": false, "field_type": "qr_link" }
],
"action": { "method": "POST", "api_url": "/store/carts/cart_floward/gift-card-messaging" }
},
"delivery_details": {
"fields": [
{ "id": "recipient_name", "title": "label_optional_to", "text": "Alex", "regex": "^[\\s\\S]{0,100}$", "required": false, "field_type": "text_field" },
{ "id": "recipient_phone", "title": "label_recipient_number", "text": "+966500000000", "regex": null, "required": false, "field_type": "phone_number", "supported_countries": ["SAU"] },
{ "id": "address", "title": "label_delivery_address", "text": null, "regex": null, "required": true, "field_type": "address", "is_editable": false, "address": { "id": "addr_01", "city": "Riyadh", "country_code": "sa" } }
],
"slot": [{
"id": "slot_morning",
"title": { "key": "09:00 - 12:00", "params": null },
"description": { "key": "label_delivery_date", "params": ["2026-08-20"] },
"shipping_total": { "currency": "SAR", "amount": 0 },
"action": "FIXED"
}],
"action": { "method": "POST", "api_url": "/store/carts/cart_floward/gift-card-messaging" }
},
"dependencies": {
"requires_address": true,
"can_checkout": true,
"blocked_reason": ""
}
}
}| Selector | Use |
|---|---|
?type=floward | Shopping cart screen and persistent-cart badge. |
?id=cart_... | Exact checkout cart after the customer chooses a subset of items. |
?id=...&type=floward | Optional defensive assertion; mismatch is rejected. |
Frontend API response checklist
These are all Floward-specific calls needed by the mobile flow. “DetailedCartV2” means the complete detailed object shown above, returned directly without a cart envelope.
| Call | Successful response | Documented in |
|---|---|---|
GET /store/floward/home | { storefront, page_sections, config } | Home & browsing |
GET /store/floward/products | { products, count, offset, limit } | Products & pricing |
GET /store/products/:id/details | { config, storefront, product, page_sections } | Product detail |
GET /store/carts?type=… or ?id=… | { default_address, total_item_count, carts, detailed } | Detailed carts |
POST /store/v2/carts | New checkout DetailedCartV2 | Cart lifecycle |
POST /store/v2/carts/:id | Updated DetailedCartV2 | Cart lifecycle |
POST /store/v2/carts/:id/line-items | Updated DetailedCartV2 | Cart lifecycle |
POST /store/v2/carts/:id/line-items/:line_id | Updated DetailedCartV2 | Cart lifecycle |
DELETE /store/v2/carts/:id/line-items/:line_id | Updated DetailedCartV2 | Cart lifecycle |
POST /store/carts/:id/gift-card-messaging | Updated DetailedCartV2 | Gift messaging |
POST /store/carts/:id/prepare | { valid, errors } | Prepare & errors |
07 · Gift messaging
Write flat; render server-described fields
The generic API name can support another gift-capable cart later. Today, only owned, incomplete Floward carts accept it.
{
"to": "Bestie",
"recipient_name": "Alex",
"recipient_phone": "+966500000000",
"from": "Derek",
"message": "Congratulations!",
"qr_link": "https://example.com/feeling"
}Every field is optional for partial updates. Send null to clear a field. Limits: recipient and sender names 100 characters, phone 100 characters, message 250 characters.
Read the saved values from the detailed cart’s gift_card_messaging.fields. Mobile should render each field using field_type, required, and the returned regex. Submit the field IDs as a flat object to the section’s action. The playful card addressee is to; the delivery contact is recipient_name.
200 response: the complete updated DetailedCartV2 directly. The response updates both gift_card_messaging.fields and delivery_details.fields:
{
"id": "cart_01FLOWARD",
"gift_card_messaging": {
"fields": [
{ "id": "to", "text": "Bestie", "field_type": "text_field" },
{ "id": "message", "text": "Congratulations!", "field_type": "text_area" },
{ "id": "from", "text": "Derek", "field_type": "text_field" },
{ "id": "qr_link", "text": "https://example.com/feeling", "field_type": "qr_link" }
],
"action": { "method": "POST", "api_url": "/store/carts/cart_01FLOWARD/gift-card-messaging" }
},
"delivery_details": {
"fields": [
{ "id": "recipient_name", "text": "Alex", "field_type": "text_field" },
{ "id": "recipient_phone", "text": "+966500000000", "field_type": "phone_number", "supported_countries": ["SAU"] },
{ "id": "address", "field_type": "address", "is_editable": false, "address": { "id": "addr_01" } }
]
}
}08 · Delivery slots
The backend selects the next available slot
Mobile does not request or select HOI slots. During prepare, the backend searches today through the next six days and stores the first slot returned by HOI.
"delivery_details": {
"fields": [],
"slot": [{
"id": "slot_morning",
"title": { "key": "09:00 - 12:00", "params": null },
"description": { "key": "label_delivery_date", "params": ["2026-08-20"] },
"shipping_total": { "currency": "SAR", "amount": 0 },
"action": "FIXED"
}]
}- An empty
slotarray means prepare has not selected a slot. action: FIXEDmeans mobile displays the option without a picker.- If no slot exists in the seven-day window, prepare blocks checkout.
The /prepare response itself is only { valid, errors }. After valid: true, reload the exact checkout cart with GET /store/carts?id=cart_... if the confirmation screen needs to display the selected slot.
09 · Prepare & errors
The final mobile checkout gate
Call immediately before proceeding to payment. Do not infer readiness only from the detailed cart’s can_checkout field.
{ "valid": true, "errors": [] }{
"valid": false,
"errors": [
{
"item_ids": ["item_01"],
"error": {
"code": "PRICE_CHANGED",
"message": "The price of this Floward product has changed",
"details": { "cart_price": 22885, "provider_price": 23500 }
}
}
]
}| Error code | Meaning | Recommended mobile action |
|---|---|---|
| FLOWARD_ADDRESS_REQUIRED | City or country is missing. | Open address selection. |
| FLOWARD_LOCATION_UNSUPPORTED | No HOI city/center supports the address. | Explain that Floward is unavailable at this address. |
| FLOWARD_FULFILLMENT_UNAVAILABLE | No single selected center can fulfill the cart. | Refresh cart; identify affected items. |
| INSUFFICIENT_INVENTORY | An item or requested quantity is unavailable. | Mark the item and offer remove/change quantity. |
| PRICE_CHANGED | HOI’s current price differs from the cart snapshot. | Refresh details and ask the customer to confirm. |
| FLOWARD_DELIVERY_SLOT_UNAVAILABLE | No slot exists in the seven-day search window. | Explain that delivery is unavailable and allow retry. |
| FLOWARD_DELIVERY_SLOT_VALIDATION_FAILED | HOI slot discovery failed. | Show retry UI; do not continue to payment. |
| FLOWARD_AVAILABILITY_FAILED | HOI availability could not be confirmed. | Show retry UI; do not continue to payment. |
Common HTTP error responses
Validation and ownership failures use the standard Medusa error envelope. Treat the HTTP status as authoritative; v2 mutations preserve the original error response rather than returning a cart DTO.
401 Unauthorized
{ "type": "unauthorized", "message": "You must be logged in" }
404 Not Found
{ "type": "not_found", "message": "Cart not found" }
409 Conflict
Failed to create idempotency key
400 Bad Request
{ "type": "invalid_data", "message": "Product is not available for this delivery address" }10 · Checkout sequence
Recommended screen-to-screen orchestration
- Confirm the customer has a selected address with city and country.
- Load
/store/floward/homeand retain its persistent cart ID. - Browse only through
/store/floward/productsURLs supplied by the page builder. - Add products to the returned Floward cart. Render prices from the server response.
- Load
/store/carts?type=flowardfor the cart screen. - Submit gift-card messaging and recipient details.
- Create the exact checkout cart with
POST /store/v2/carts, passingsource_cart_idand the selected persistent-cart line-item IDs. - Call
/prepareon that exact cart. The backend selects the next available slot; resolve every error before payment. - Continue through the existing Barq payment and completion experience only after
valid: true.
id, not type=floward.11 · Mobile test plan
Scenarios worth automating
- Customer without an address is routed to address selection before Floward loads.
- Supported address returns categories, Popular products, and a persistent Floward cart.
- Changing city reloads products and can change price and availability.
- Only products returned by the Floward product API are displayed.
- Add, quantity update, and removal always use server-returned totals.
- Gift fields support partial update and explicit clearing.
- Prepare automatically advances past dates with no slots and blocks checkout when none are available.
- Changing address causes prepare to replace the automatic slot for the new fulfillment center.
- Prepare price change refreshes the cart and asks for confirmation.
- Prepare inventory failure identifies the affected line item.
- Prepare slot failure blocks checkout and asks the customer to retry; mobile never presents slot selection.
- Unselected persistent-cart items survive partial checkout.
12 · Rollout boundaries
What mobile owns—and what remains backend work
| Capability | Status | Owner |
|---|---|---|
| Location-aware home and product browsing | Implemented | Mobile consumes |
| Dynamic cart pricing and prepare validation | Implemented | Backend enforces |
| Gift messaging and recipient details | Implemented | Mobile captures |
| Automatic delivery-slot discovery and prepare validation | Implemented | Backend selects and validates |
| Express-slot capability | Unavailable from HOI; always false | Backend |
HOI order submission from order.process | Upcoming | Backend |
| HOI status synchronization, cancellation, returns, and refunds | Upcoming | Backend |