Page 1 of 20
System Requirements Document
BusinessOnline — Customer Mobile Application
1. Introduction
1.1 Purpose
This System Requirements Document (SRD) defines the required behavior, structure, constraints, and quality bar for BusinessOnline, a production-grade, customer-facing Flutter mobile application that acts as the mobile client for an existing multi-vendor B2B / e-commerce marketplace.
This SRD is synthesized from:
- The source document
BusinessOnline_Master_Prompt.txt (production Flutter agent master prompt).
- The project owner's instruction to take that prompt file and begin the work.
- The supplied brand asset
logo.png, which is the authoritative source for the layered logo geometry and the exact brand hex values.
1.2 Objective
Deliver a production-ready mobile application — not a tutorial, not a conceptual example, and not a demo or prototype — that integrates with a documented, pre-existing REST API, and that is architected, secured, localized, accessible, tested, and documented to a releasable standard.
Page 2 of 20
1.3 Scope
In scope
- The Flutter customer mobile application (
customer flavor).
- Integration with the existing backend REST API.
- Local persistence and caching.
- Authentication and token lifecycle.
- Localization (English / Arabic), accessibility, and theming.
- Automated tests, CI/CD, release documentation, and engineering status documentation.
- The layered brand splash animation and the brand asset pipeline derived from the supplied
logo.png.
Out of scope
- Any modification of the backend.
- Vendor-side functionality (a
vendor flavor is reserved architecturally but must not be implemented).
- The existing web admin.
- Any API endpoint, DTO property, business rule, payment state, order state, discount calculation, or server-side validation rule that is not documented by the backend contract.
1.4 Reader Guidance
Rules in this SRD are derived strictly from the source material. Where the backend contract does not supply information required to implement a behavior, the requirement is to document the gap and build an abstraction, never to invent behavior.
2. System Overview
Page 3 of 20
2.1 Product
BusinessOnline is the customer-facing Flutter mobile client for an existing multi-vendor B2B / e-commerce marketplace. The marketplace backend already exists as a separate ASP.NET Core 8 REST API. The backend is authoritative for authentication, authorization, pricing, discounts, offers, wholesale eligibility, order totals, payment status, order status, vendor acceptance, and validation.
The mobile application is a client, not a source of truth. It must not modify the backend.
2.2 Application Identity
| Item | Value |
|---|
| Application name | BusinessOnline |
| Package identifier | com.businessonline.app |
| Primary language | English (en) |
| Secondary language | Arabic (ar) |
| Default display currency | KWD |
Page 4 of 20
2.3 Platform Targets
| Platform | Minimum |
|---|
| Android | Android 8.0+, minSdk 24 |
| iOS | iOS 13+ |
Minimum platform targets must not be raised silently. If a dependency technically forces a newer OS, the requirement is to (1) document it, (2) evaluate alternatives, and (3) avoid silently raising the minimum.
2.4 Delivery Shape
The product is a single mobile application binary (customer flavor) that talks to a remote authoritative REST API, holds a local cache, and renders a Material 3, bilingual, accessibility-compliant interface. There is no separate web frontend, no server component authored by this project, and no administrative interface in scope.
Page 5 of 20
2.5 Documented Backend Interface (Authoritative API Surface)
The following endpoints are the complete documented API surface available to the mobile client:
GET /api/MobileAppVersion
POST /api/Auth/login
POST /api/Auth/register
POST /api/Auth/forgot-password
POST /api/Auth/refresh
GET /api/Products
GET /api/Products/{id}
GET /api/Categories/tree
GET /api/Vendors/{id}
POST /api/Contact
GET /api/Orders
POST /api/Orders
GET /api/Orders/{id}
POST /api/Orders/{id}/cancel
GET /api/Addresses
GET /api/PaymentMethods
GET /api/Offers/active
GET /api/Discounts/validate?code=
POST /api/Reviews
GET /api/Customers/me
No undocumented endpoint may be invented. If the UI requires an endpoint not in this list, it must be recorded in docs/API_GAPS.md.
Page 6 of 20
2.6 System Actors and External Recipients
| Actor / System | Role |
|---|
| ASP.NET Core 8 REST API | Authoritative backend; source of all business state. System actor. |
| Stripe (PaymentSheet-compatible integration) | Payment instrument handling; must follow the actual backend contract. System actor. |
| Firebase Cloud Messaging | Push delivery. System actor / outbound provider. |
| Firebase Analytics | Consent-gated analytics sink. Outbound-only recipient. |
| Platform secure storage | Token storage provider. System actor. |
| Platform local notifications | Notification rendering. System actor. |
| Existing web admin | Out of scope for this project. External system. |
3. Functional Requirements
All functional requirements are expressed as user stories. Sub-capabilities listed in the source material are preserved as distinct, independently testable stories.
3.1 Application Bootstrap and Startup
- FR-1.1 — As a returning customer, I want the app to initialize secure storage at splash so that my session credentials are available before any authenticated screen is shown.
- FR-1.2 — As a returning customer, I want the app to initialize the local database at splash so that cached catalog and cart data are available immediately.
- FR-1.3 — As a returning customer, I want the app to inspect my authentication state at splash so that I am routed to the correct destination without a flicker of unauthenticated UI.
- FR-1.4 — As a customer, I want the app to call
GET /api/MobileAppVersion during splash so that version and availability policy is evaluated before entering the main application.
- FR-1.5 — As a customer, I want the app to enforce the minimum supported version so that I cannot use an incompatible build.
- FR-1.6 — As a customer, I want the app to handle kill-switch and update states so that I am directed to the correct blocking or dismissible screen.
- FR-1.7 — As a first-time customer, I want the app to determine onboarding state at splash so that I see onboarding only when I have not completed it.
- FR-1.8 — As a customer, I want the app to route me to the correct destination after bootstrap so that I land on onboarding, login, the main application, or a force-update/kill-switch screen as appropriate.
Page 7 of 20
3.2 Splash Brand Animation
- FR-2.1 — As a customer, I want the splash screen to play a deterministic brand animation built from the layered BusinessOnline logo so that the cold start feels intentional and branded.
- FR-2.2 — As a customer, I want the cart glyph to slide in from the left edge of the screen to its resting position (roughly horizontal center-left, where it sits relative to the wordmark in the source logo) as step 1 of the sequence.
- FR-2.3 — As a customer, I want the globe + signal-arc glyph to drop from above the cart's resting position down onto/into the cart with a small settle/bounce on arrival (slight overshoot then ease back) as step 2 of the sequence.
- FR-2.4 — As a customer, I want the "BusinessOnline" wordmark to fade/slide in underneath the cart + globe group immediately after the globe settles — a combined fade-in plus upward slide, or a staggered letter/word reveal ("BUSINESS" then "ONLINE") — as step 3 of the sequence.
- FR-2.5 — As a customer, I want the fully assembled logo held briefly and then transitioned into the app via cross-fade or shared-axis transition once bootstrap and the version check have both completed, as step 4 of the sequence.
- FR-2.6 — As a customer, I want the brand animation to run concurrently with real bootstrap work (secure storage init, database init, auth-state check,
/api/MobileAppVersion call) so that the animation never blocks or waits on the network call, and the network call is never delayed by the animation.
- FR-2.7 — As a product owner, I want the animation authored as a Rive or Lottie asset built from the layered assets so that motion is smooth and designer-tunable.
- FR-2.8 — As a product owner, I want a composed Flutter animation fallback (
AnimationController + Tween/Curves, or flutter_animate) driving three independently positioned Image/SvgPicture layers (cart, globe, wordmark) when no Rive/Lottie file is provided.
- FR-2.9 — As a product owner, I want the splash animation to not be implemented as a single animated GIF or a pre-baked video, so the layers remain independently controllable and timing/easing can be tuned without re-exporting a flattened asset.
- FR-2.10 — As a product owner, I want the total animation duration to target roughly 1200–1800 ms end-to-end (cart slide + globe drop + wordmark reveal + short hold), consistent with the cold-start budget.
- FR-2.11 — As a product owner, I want the actual measured animation duration documented in
docs/IMPLEMENTATION_STATUS.md if it cannot be kept under the cold-start budget on the baseline device.
- FR-2.12 — As a customer using reduce-motion / "disable animations", I want the app to skip directly to the static assembled logo (the flattened
logo.png fallback) with at most a simple cross-fade.
- FR-2.13 — As a screen reader user, I want the animation wrapped so that it is announced as a single meaningful label (e.g. "BusinessOnline, loading") rather than exposing each glyph layer as a separate focusable/semantic element.
- FR-2.14 — As a product owner, I want the animation direction (cart entering from the left) treated as a fixed brand-identity motion that is not mirrored for Arabic/RTL by default.
- FR-2.15 — As a product owner, I want any decision to mirror the cart entrance to the reading-start side per locale to be an explicitly confirmed product decision recorded in
docs/ARCHITECTURE.md, never silently assumed.
- FR-2.16 — As a customer, I want the "BusinessOnline" wordmark displayed identically in both
en and ar locales, with only any surrounding screen-reader label localized.
- FR-2.17 — As a customer, I want the animation to be interruptible/skippable by the app once bootstrap and version-check resolve, so I do not wait through the full animation if the app is ready sooner.
- FR-2.18 — As a product owner, I want no visible "Skip" button added unless requested; the transition simply proceeds once both the animation's minimum hold and the bootstrap work are complete, whichever finishes last.
- FR-2.19 — As a developer, I want all splash timing constants (durations, delays, curve choices) kept in one named constants file/class local to the splash feature (e.g.
SplashAnimationSpec) rather than scattered as magic numbers.
- FR-2.20 — As a customer, I want the splash animation to play correctly at cold start on both light and dark theme backgrounds, with sufficient contrast for the gold/silver glyph colors against the themed background.
- FR-2.21 — As a customer, I want the splash animation to not regress the cold-start / kill-switch / force-update flow: if
/api/MobileAppVersion indicates forceUpdate or a kill-switch state, the animation still completes (or is skipped per reduce-motion) and the app then routes to the force-update/kill-switch screen instead of the main app.
- FR-2.22 — As a developer, I want a widget test (or golden test) covering the initial frame, a mid-sequence frame, the final assembled-logo frame, and the reduce-motion static fallback path.
- FR-2.23 — As a customer, I want the splash to fall back gracefully to the static flattened
logo.png if the Rive/Lottie asset fails to load, without throwing or leaving a blank screen.
Page 8 of 20
3.3 Brand Asset Handling
- FR-3.1 — As a product owner, I want the brand logo treated as three separable animation layers — the cart glyph (gold/bronze shopping-cart silhouette: body + wheels), the globe + signal arc (gold/bronze globe with a Wi-Fi/signal arc positioned above-right of the cart, representing "online"), and the wordmark ("BUSINESS" in brushed silver/grey, "ONLINE" in gold, stacked two lines, set to the right of the cart glyph) — rather than as a single flattened bitmap, wherever the asset is used for animated placements (splash screen, animated app icon previews, loading states).
- FR-3.2 — As a product owner, I want a layered source asset (SVG, or separated PNG/Lottie/Rive layers) exporting the cart glyph, the globe + signal glyph, and the wordmark as independent elements before Phase 1 can fully close out the splash screen.
- FR-3.3 — As a customer on a very low-end device, I want the flattened
logo.png used as the static fallback when reduce motion is enabled, on very low-end devices, or if animation assets fail to load.
- FR-3.4 — As a product owner, I want separated layers requested/derived (via a Rive/Lottie asset built by design, or by re-cutting the PNG into cart/globe/wordmark sub-images) for the animated splash when only the flattened
logo.png is available.
- FR-3.5 — As a product owner, I want layered source asset unavailability documented in a renamed
docs/ASSET_GAPS.md (or an "Asset Gaps" subsection of IMPLEMENTATION_STATUS.md) and only the static fallback shipped until the layered assets are provided.
- FR-3.6 — As a product owner, I want brand colors never fabricated: exact hex values must be extracted from the supplied
logo.png (bronze/gold, silver/grey, background) and centralized in AppColors rather than hardcoded per widget.
3.4 Onboarding
- FR-4.1 — As a first-time customer, I want a three-slide onboarding sequence so that I understand the marketplace before I use it.
- FR-4.2 — As a first-time customer, I want onboarding shown only on first run so that I am not interrupted on subsequent launches.
- FR-4.3 — As a first-time customer, I want onboarding to be dismissible so that I can proceed immediately if I choose.
- FR-4.4 — As a returning customer, I want my onboarding completion state persisted locally so that onboarding does not reappear.
- FR-4.5 — As an Arabic-speaking customer, I want onboarding content available in English and Arabic, with correct RTL behavior.
- FR-4.6 — As a customer using assistive technology, I want onboarding to be accessible.
- FR-4.7 — As a first-time customer, I want a skip button on onboarding so that I can bypass the slides.
Page 9 of 20
3.5 Authentication and Session
- FR-5.1 — As a customer, I want a login screen with an email field and a password field so that I can authenticate.
- FR-5.2 — As a customer, I want a login action that calls
POST /api/Auth/login so that I obtain a session.
- FR-5.3 — As a customer, I want a "forgot password" action on the login screen so that I can recover access.
- FR-5.4 — As a customer, I want a "register" action on the login screen so that I can create an account.
- FR-5.5 — As a product owner, I want social login to remain unavailable where backend support does not exist, and I want no social endpoints invented.
- FR-5.6 — As a new customer, I want a registration form with first name, last name, email, phone, country code, password, confirm password, and Terms acceptance so that I can create an account.
- FR-5.7 — As a new customer, I want client-side validation that complements, but never replaces, server validation.
- FR-5.8 — As a customer, I want a forgot-password request flow so that I can request a reset.
- FR-5.9 — As a customer, I want a reset-password flow so that I can set a new password.
- FR-5.10 — As a customer, I want deep link handling for password reset so that a reset link opens the correct in-app screen.
- FR-5.11 — As a customer, I want only the backend contract that actually exists to be used for password recovery.
- FR-5.12 — As a customer, I want authentication to use
Authorization: Bearer <access-token>.
- FR-5.13 — As a security-conscious customer, I want my access token and refresh token stored only in
flutter_secure_storage.
- FR-5.14 — As a customer, I want a single-flight refresh mechanism so that multiple simultaneous 401 responses do not trigger multiple refresh requests.
- FR-5.15 — As a customer, I want only one refresh request to be active at any time.
- FR-5.16 — As a customer, I want concurrent requests during a refresh to await the same refresh operation.
- FR-5.17 — As a customer, I want a successful refresh to update secure storage and retry the original request exactly once.
- FR-5.18 — As a customer, I want a failed refresh to clear credentials, invalidate authenticated state, and redirect me to login.
- FR-5.19 — As a developer, I want the app to never recursively refresh the refresh endpoint.
- FR-5.20 — As a developer, I want the app to never retry an authentication request through the authentication interceptor.
- FR-5.21 — As a developer, I want infinite retry loops prevented.
- FR-5.22 — As a developer, I want generic network retry to never retry HTTP 401.
- FR-5.23 — As a developer, I want unsafe POST mutations not automatically retried unless explicitly safe/idempotent.
- FR-5.24 — As a customer, I want
GET /api/Customers/me used to resolve my authenticated customer identity.
Page 10 of 20
3.6 Error Handling
- FR-6.1 — As a developer, I want all API calls represented through
Result<T, ApiError> or an equivalent sealed-result abstraction.
- FR-6.2 — As a developer, I want widgets to never receive raw Dio exceptions.
- FR-6.3 — As a customer, I want a distinct "network unavailable" error state so that I understand connectivity is the problem.
- FR-6.4 — As a customer, I want a distinct "timeout" error state so that I understand the request took too long.
- FR-6.5 — As a customer, I want a distinct "unauthorized" error state so that I understand my session is no longer valid.
- FR-6.6 — As a customer, I want a distinct "forbidden" error state so that I understand I lack permission for the action.
- FR-6.7 — As a customer, I want a distinct "not found" error state so that I understand the requested resource does not exist.
- FR-6.8 — As a customer, I want a distinct "validation error" state so that I understand which input the server rejected.
- FR-6.9 — As a customer, I want a distinct "conflict" error state so that I understand the request conflicted with current state.
- FR-6.10 — As a customer, I want a distinct "rate limited" error state so that I understand I must slow down or wait.
- FR-6.11 — As a customer, I want a distinct "server error" state so that I understand the problem is on the service side.
- FR-6.12 — As a customer, I want a distinct "unknown error" state so that I always receive a defined outcome rather than an unhandled exception.
- FR-6.13 — As a customer, I want a distinct "serialization error" state so that malformed server payloads are handled without crashing.
- FR-6.14 — As a customer, I want a distinct "offline/cache error" state so that cache-layer failures are surfaced meaningfully.
- FR-6.15 — As a customer, I want backend validation errors mapped into user-readable localized messages.
- FR-6.16 — As a customer, I want stack traces never exposed to production users.
Page 11 of 20
3.7 Money and Currency
- FR-7.1 — As a customer, I want money never represented with
double so that displayed amounts are always exact.
- FR-7.2 — As a customer, I want decimal monetary values returned by the API as strings preserved exactly (e.g.
"123.450" remains exact).
- FR-7.3 — As a developer, I want a decimal arithmetic package/type used for all monetary handling.
- FR-7.4 — As a customer, I want
KWD as the default currency.
- FR-7.5 — As a customer, I want to switch my display currency to
USD or EUR.
- FR-7.6 — As a customer, I want the server to remain authoritative for product price, discounts, offers, order totals, taxes, and final prices.
- FR-7.7 — As a customer, I want currency conversion to never alter the server-side transaction amount unless explicitly supported by the API.
- FR-7.8 — As a customer, I want money formatting handled in the presentation layer using locale-aware formatting.
- FR-7.9 — As a developer, I want monetary calculations never performed with floating point.
3.8 Navigation and Information Architecture
- FR-8.1 — As a customer, I want bottom navigation with exactly four primary sections — Home, Categories, Cart, Profile — so that the main destinations are always one tap away.
- FR-8.2 — As a developer, I want the four primary sections implemented with
ShellRoute.
- FR-8.3 — As a customer, I want the following additional modal/pushed routes: Search, Product Detail, Checkout, Login, Register, Forgot Password, Reset Password, Contact Us.
- FR-8.4 — As a developer, I want all navigation centralized through the router, with no other navigation framework introduced.
- FR-8.5 — As a customer, I want
ShellRoute, nested routes, modal routes where appropriate, and deep links supported by the router.
Page 12 of 20
3.9 Home
- FR-9.1 — As a customer, I want hero banners on Home so that I see current merchandising.
- FR-9.2 — As a customer, I want hero banner variants so that different promotional layouts are supported.
- FR-9.3 — As a customer, I want featured categories on Home so that I can jump into key catalog areas.
- FR-9.4 — As a customer, I want a statistics section on Home where the backend provides it.
- FR-9.5 — As a customer, I want a testimonials section on Home where the backend provides it.
- FR-9.6 — As a customer, I want a "today's deals" section on Home.
- FR-9.7 — As a customer, I want a sticky promotional banner on Home.
- FR-9.8 — As an authorized wholesale customer, I want wholesale messaging on Home where applicable.
- FR-9.9 — As a developer, I want Home-specific endpoints not currently documented to be recorded in
docs/API_GAPS.md rather than invented.
- FR-9.10 — As a developer, I want repository abstractions created for Home sections so that these APIs can be connected later without rewriting the UI.
3.10 Categories
- FR-10.1 — As a customer, I want a nested category tree from
GET /api/Categories/tree so that I can browse the catalog hierarchy.
- FR-10.2 — As a customer, I want category navigation so that I can move into subcategories.
- FR-10.3 — As a customer, I want search within categories.
- FR-10.4 — As a customer, I want search input debounced so that typing does not trigger excessive requests.
- FR-10.5 — As a customer, I want recent searches so that I can repeat a previous search.
- FR-10.6 — As a customer, I want product filtering.
- FR-10.7 — As a customer, I want a price slider filter.
- FR-10.8 — As a customer, I want a rating filter.
- FR-10.9 — As a customer, I want vendor flags displayed where supported.
- FR-10.10 — As an authorized wholesale customer, I want a wholesale-only toggle where authorized.
- FR-10.11 — As a customer, I want category trees cached locally so that browsing does not require repeated network calls.
- FR-10.12 — As a performance-conscious customer, I want unnecessary network calls avoided during category browsing.
Page 13 of 20
3.11 Product List
- FR-11.1 — As a customer, I want paginated product lists so that large catalogs load incrementally.
- FR-11.2 — As a customer, I want infinite scrolling so that more products load as I scroll.
- FR-11.3 — As a customer, I want loading placeholders while products load.
- FR-11.4 — As a customer, I want pull-to-refresh on product lists.
- FR-11.5 — As a customer, I want a clear empty state when no products match.
- FR-11.6 — As a customer, I want a clear error state when the product list fails to load.
- FR-11.7 — As a customer, I want cached fallback content shown when the network is unavailable and a cache exists.
- FR-11.8 — As a customer, I want a back-to-top floating action button on long product lists.
- FR-11.9 — As a customer, I want my filter state preserved so that returning to the list keeps my filters.
- FR-11.10 — As a customer, I want sorting on product lists where the API supports it.
- FR-11.11 — As a performance-conscious customer, I want N+1 requests avoided in product listing.
- FR-11.12 — As a performance-conscious customer, I want duplicate pagination requests prevented.
- FR-11.13 — As a performance-conscious customer, I want repeated requests caused by widget rebuilds prevented.
- FR-11.14 — As a performance-conscious customer, I want the entire catalog never loaded into memory.
Page 14 of 20
3.12 Product Detail
- FR-12.1 — As a customer, I want an image gallery on product detail where supported.
- FR-12.2 — As a customer, I want the product title displayed.
- FR-12.3 — As a customer, I want the product description displayed.
- FR-12.4 — As a customer, I want product variants displayed and selectable where supported.
- FR-12.5 — As a customer, I want a quantity selector on product detail.
- FR-12.6 — As a customer, I want pricing displayed, with the server authoritative for the final price.
- FR-12.7 — As a customer, I want applicable offers displayed on product detail.
- FR-12.8 — As a customer, I want applicable bundles displayed on product detail.
- FR-12.9 — As a customer, I want applicable discounts displayed on product detail.
- FR-12.10 — As a customer, I want product reviews displayed on product detail.
- FR-12.11 — As an authorized wholesale customer, I want a wholesale call-to-action on product detail.
- FR-12.12 — As a customer, I want vendor information displayed on product detail, sourced from
GET /api/Vendors/{id} where supported.
3.13 Search
- FR-13.1 — As a customer, I want a full-screen search experience.
- FR-13.2 — As a customer, I want search available from every tab.
- FR-13.3 — As a customer, I want search input debounced.
- FR-13.4 — As a customer, I want recent searches retained.
- FR-13.5 — As a customer, I want the ability to clear my search history.
- FR-13.6 — As an Arabic-speaking customer, I want search fully localized.
- FR-13.7 — As a performance-conscious customer, I want the API not called for every keystroke.
- FR-13.8 — As a customer, I want obsolete search requests cancelled so that stale results do not overwrite newer ones.
Page 15 of 20
3.14 Cart
- FR-14.1 — As a customer, I want cart line items displayed.
- FR-14.2 — As a customer, I want cart items grouped by vendor where appropriate.
- FR-14.3 — As a customer, I want to update line item quantities.
- FR-14.4 — As a customer, I want to remove line items.
- FR-14.5 — As a customer, I want pull-to-refresh on the cart.
- FR-14.6 — As a customer, I want to apply a coupon in the cart.
- FR-14.7 — As a customer, I want applicable offers reflected in the cart.
- FR-14.8 — As a customer, I want my wholesale eligibility reflected in the cart.
- FR-14.9 — As a customer, I want to select a delivery address from the cart.
- FR-14.10 — As a customer, I want to select a payment method from the cart.
- FR-14.11 — As a customer, I want a checkout call-to-action from the cart.
- FR-14.12 — As a customer, I want free bundled products introduced by offer bundles represented as non-removable where the backend identifies them as server-controlled bundle items.
3.15 Wholesale
- FR-15.1 — As an authorized wholesale customer, I want wholesale pricing available to me only when
IsWholesaleApproved == true.
- FR-15.2 — As a customer, I want wholesale pricing never exposed to unauthorized customers.
- FR-15.3 — As a product owner, I want UI gating treated as presentation only, never as security.
- FR-15.4 — As a product owner, I want the backend to remain authoritative for wholesale eligibility.
- FR-15.5 — As a customer, I want my wholesale state sourced from the API.
- FR-15.6 — As a customer, I want the wholesale toggle to never be enabled merely because I changed a local preference.
Page 16 of 20
3.16 Discounts and Offers
- FR-16.1 — As a customer, I want the server to calculate the final price in all cases.
- FR-16.2 — As a customer, I want offers to be able to stack with discounts as defined by the backend.
- FR-16.3 — As a customer, I want the last-applied discount to win per line according to the backend rule.
- FR-16.4 — As a developer, I want the mobile app to never attempt to reproduce authoritative pricing logic.
- FR-16.5 — As a customer, I want coupon validation through
GET /api/Discounts/validate?code=.
- FR-16.6 — As a customer, I want active offers retrieved through
GET /api/Offers/active.
3.17 Addresses
- FR-17.1 — As a customer, I want to list my addresses from
GET /api/Addresses.
- FR-17.2 — As a customer, I want to add an address.
- FR-17.3 — As a customer, I want to edit an address.
- FR-17.4 — As a customer, I want to delete an address if supported by the backend.
- FR-17.5 — As a customer, I want to designate a default address.
- FR-17.6 — As a customer, I want a country field on addresses.
- FR-17.7 — As a customer, I want a phone country code field on addresses.
- FR-17.8 — As a customer, I want address input validation.
- FR-17.9 — As a developer, I want to never assume address fields that are not supported by the backend contract.
Page 17 of 20
3.18 Payment Methods
- FR-18.1 — As a customer, I want my payment methods listed from
GET /api/PaymentMethods with masked card numbers.
- FR-18.2 — As a customer, I want the card brand displayed.
- FR-18.3 — As a customer, I want the card expiry displayed where available.
- FR-18.4 — As a customer, I want my default payment method indicated.
- FR-18.5 — As a customer, I want PAN never displayed or persisted.
- FR-18.6 — As a customer, I want CVV never displayed or persisted.
- FR-18.7 — As a customer, I want adding a card to use the backend plus Stripe flow supported by the actual API.
3.19 Checkout
- FR-19.1 — As a customer, I want checkout to display my cart items.
- FR-19.2 — As a customer, I want checkout to show vendor grouping where relevant.
- FR-19.3 — As a customer, I want checkout to show the selected address.
- FR-19.4 — As a customer, I want checkout to show the selected payment method.
- FR-19.5 — As a customer, I want checkout to show the applied coupon.
- FR-19.6 — As a customer, I want checkout to show applied discounts.
- FR-19.7 — As a customer, I want checkout to show the order totals as determined by the server.
- FR-19.8 — As a customer, I want to place an order through
POST /api/Orders with a payload of items[], addressId, paymentMethodId, and optional couponCode?.
- FR-19.9 — As a customer, I want the server to determine the final order state.
- FR-19.10 — As a customer in a multi-vendor cart, I want partial vendor rejection / multi-status-style responses handled explicitly.
- FR-19.11 — As a customer, I want the app to never assume that an HTTP success status means every line was accepted.
Page 18 of 20
3.20 Payments (Stripe)
- FR-20.1 — As a customer, I want payments handled through Stripe's Flutter SDK / PaymentSheet-compatible integration appropriate to the existing backend contract.
- FR-20.2 — As a product owner, I want
pay not used as a substitute if the backend is specifically built around Stripe PaymentSheet.
- FR-20.3 — As a developer, I want the existing API contract inspected before implementation to determine how PaymentIntent / SetupIntent / client secrets are provided.
- FR-20.4 — As a developer, I want any missing payment API requirements documented.
- FR-20.5 — As a customer, I want my mobile app to never store PAN.
- FR-20.6 — As a customer, I want my mobile app to never store CVV.
- FR-20.7 — As a customer, I want payment credentials never logged.
- FR-20.8 — As a customer, I want the mobile app to never decide that an order is paid.
- FR-20.9 — As a customer, I want server/webhook state to be authoritative for payment status.
- FR-20.10 — As a customer, I want a client-side payment success flag never trusted as proof of payment.
- FR-20.11 — As a security-conscious customer, I want payment tokens/client secrets handled securely.
- FR-20.12 — As a security-conscious customer, I want payment secrets not persisted unnecessarily.
Page 19 of 20
3.21 Orders
- FR-21.1 — As a customer, I want an order list from
GET /api/Orders.
- FR-21.2 — As a customer, I want order statuses represented as Pending, Accepted, Shipped, Delivered, and Returned.
- FR-21.3 — As a customer, I want order detail from
GET /api/Orders/{id} showing order metadata.
- FR-21.4 — As a customer, I want order detail to show line items.
- FR-21.5 — As a customer, I want order detail to show the vendor for each line where applicable.
- FR-21.6 — As a customer, I want order detail to show line status.
- FR-21.7 — As a customer, I want order detail to show accepted/rejected state per line.
- FR-21.8 — As a customer, I want order detail to show an ETA where provided.
- FR-21.9 — As a customer, I want order detail to show a tracking link if available.
- FR-21.10 — As a customer, I want cancellation available where permitted.
- FR-21.11 — As a customer, I want cancellation performed through
POST /api/Orders/{id}/cancel.
- FR-21.12 — As a customer, I want cancellation never displayed as successful until the server confirms it.
- FR-21.13 — As a customer, I want Google Maps integration for order tracking where tracking data exists.
3.22 Reviews
- FR-22.1 — As a customer, I want to submit a review through
POST /api/Reviews.
- FR-22.2 — As a customer, I want a rating scale of 1–5.
- FR-22.3 — As a customer, I want a comment field with a minimum of 12 characters.
- FR-22.4 — As a customer, I want to be prevented from reviewing myself.
- FR-22.5 — As a customer, I want server validation to remain authoritative for reviews.
- FR-22.6 — As a developer, I want no client-side assumptions about purchase eligibility for reviews.
Page 20 of 20
3.23 Contact Us
- FR-23.1 — As a customer, I want a Contact Us screen that submits through
POST /api/Contact.
- FR-23.2 — As a customer, I want the contact form to include name, email, phone, subject (maximum 200 characters), and message (maximum 2000 characters).
- FR-23.3 — As a customer, I want a loading state shown while the contact message is submitting.
- FR-23.4 — As a customer, I want a success state shown after the contact message is accepted.
- FR-23.5 — As a customer, I want a validation error state shown when my contact input is rejected.
- FR-23.6 — As a customer, I want a network error state shown
No comments yet. Be the first!