BBarq Marketplace · Mobile Integration
Floward v1 · August 2026

Implementation guide · iOS & Android

Floward, from address to prepared cart.

A practical guide to location-aware browsing, dynamic prices, the persistent Floward cart, gift messaging, delivery slots, and checkout preparation in Barq Marketplace.

AuthenticatedEvery Floward route
Address-firstCity selects fulfillment
DynamicPrice and stock from HOI
One centerPer cart and order

01 · Start here

The three rules that prevent most integration bugs

  1. Require an address before opening Floward. The shipping address city and country decide which HOI fulfillment center, catalog, prices, stock, and slots apply.
  2. Use the server-provided cart and price. Never invent a Floward cart ID, send a catalog price, or reuse a price from another address.
  3. 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.
Currency values are minor unitsDates use YYYY-MM-DDCart type is floward
AuthenticationAll examples assume the normal authenticated Barq customer session and the application’s standard headers. A cart is accessible only to its owning customer.

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.

Customer address
city + country
HOI city
resolved by name
First center
current policy
Catalog snapshot
stock + price
Current center policyWhen a city has multiple active fulfillment centers, the backend selects the first returned center. Mobile must not select or display fulfillment centers.

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

GET/store/floward/home

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

GET/store/floward/products

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

GET/store/products/:product_id/details

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": []
}
Price display22885 means SAR 228.85. Use the returned currency and the application’s money formatter. Never use an imported variant price as a Floward fallback.
SituationExpected UI behavior
Product is absent from the responseDo not display it. It is not sellable from the selected center.
Address changesDiscard the current result set and reload from page 1.
Empty resultShow an address-specific empty state, not a global “out of stock” claim.
Price changes laterUse 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.

POST/store/v2/carts/:cart_id/line-items

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
}
Do not send price metadataMobile sends only the variant and quantity. The backend reads HOI’s trusted center-specific price.

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_id

200 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.

Do not rebuild checkout items on mobileFor Floward, variant-based checkout-cart creation is rejected. Source-cart creation revalidates availability and binding prices with HOI, leaves the persistent cart unchanged during checkout, and returns the new detailed checkout cart directly. After successful completion, the backend decrements or removes only the purchased source quantities.
Cart isolationNever add Floward and non-Floward products to the same cart. The product details and Floward home responses provide the correct cart.

06 · Detailed carts

One aggregate API; one stable detailed DTO

GET/store/carts?type=floward

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": ""
    }
  }
}
SelectorUse
?type=flowardShopping cart screen and persistent-cart badge.
?id=cart_...Exact checkout cart after the customer chooses a subset of items.
?id=...&type=flowardOptional 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.

CallSuccessful responseDocumented 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/cartsNew checkout DetailedCartV2Cart lifecycle
POST /store/v2/carts/:idUpdated DetailedCartV2Cart lifecycle
POST /store/v2/carts/:id/line-itemsUpdated DetailedCartV2Cart lifecycle
POST /store/v2/carts/:id/line-items/:line_idUpdated DetailedCartV2Cart lifecycle
DELETE /store/v2/carts/:id/line-items/:line_idUpdated DetailedCartV2Cart lifecycle
POST /store/carts/:id/gift-card-messagingUpdated DetailedCartV2Gift messaging
POST /store/carts/:id/prepare{ valid, errors }Prepare & errors

07 · Gift messaging

Write flat; render server-described fields

POST/store/carts/:id/gift-card-messaging

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

POST/store/carts/:id/prepare

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 slot array means prepare has not selected a slot.
  • action: FIXED means 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.

Address changes are handled server-sideThe selected slot is replaced during the next prepare and is always bound to the current fulfillment center.

09 · Prepare & errors

The final mobile checkout gate

POST/store/carts/:id/prepare

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 codeMeaningRecommended mobile action
FLOWARD_ADDRESS_REQUIREDCity or country is missing.Open address selection.
FLOWARD_LOCATION_UNSUPPORTEDNo HOI city/center supports the address.Explain that Floward is unavailable at this address.
FLOWARD_FULFILLMENT_UNAVAILABLENo single selected center can fulfill the cart.Refresh cart; identify affected items.
INSUFFICIENT_INVENTORYAn item or requested quantity is unavailable.Mark the item and offer remove/change quantity.
PRICE_CHANGEDHOI’s current price differs from the cart snapshot.Refresh details and ask the customer to confirm.
FLOWARD_DELIVERY_SLOT_UNAVAILABLENo slot exists in the seven-day search window.Explain that delivery is unavailable and allow retry.
FLOWARD_DELIVERY_SLOT_VALIDATION_FAILEDHOI slot discovery failed.Show retry UI; do not continue to payment.
FLOWARD_AVAILABILITY_FAILEDHOI 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

  1. Confirm the customer has a selected address with city and country.
  2. Load /store/floward/home and retain its persistent cart ID.
  3. Browse only through /store/floward/products URLs supplied by the page builder.
  4. Add products to the returned Floward cart. Render prices from the server response.
  5. Load /store/carts?type=floward for the cart screen.
  6. Submit gift-card messaging and recipient details.
  7. Create the exact checkout cart with POST /store/v2/carts, passing source_cart_id and the selected persistent-cart line-item IDs.
  8. Call /prepare on that exact cart. The backend selects the next available slot; resolve every error before payment.
  9. Continue through the existing Barq payment and completion experience only after valid: true.
Partial checkoutThe source cart remains unchanged while checkout is in progress, so unselected items remain available. Request checkout details and prepare using the returned ephemeral cart 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

CapabilityStatusOwner
Location-aware home and product browsingImplementedMobile consumes
Dynamic cart pricing and prepare validationImplementedBackend enforces
Gift messaging and recipient detailsImplementedMobile captures
Automatic delivery-slot discovery and prepare validationImplementedBackend selects and validates
Express-slot capabilityUnavailable from HOI; always falseBackend
HOI order submission from order.processUpcomingBackend
HOI status synchronization, cancellation, returns, and refundsUpcomingBackend
Release coordinationDo not enable production Floward payment completion until HOI order submission and post-order status/refund handling are deployed and verified. Mobile does not call HOI directly.