Complete iOS Shopping App
Learn how to build a full-featured iOS shopping app using the Reachu Swift SDK. This comprehensive example covers product browsing, cart management, and checkout flows with real-world best practices.
App Overview
We'll build a complete shopping app with the following features:
- ✅ Product Catalog with search and filtering
- ✅ Shopping Cart with quantity management
- ✅ Checkout Flow with address and payment
- ✅ User Profile and order history
- ✅ Real-time Updates and error handling
Project Setup
1. Create New iOS Project
Create a new iOS project in Xcode and add the Reachu Swift SDK:
dependencies: [
.package(url: "https://github.com/ReachuDevteam/ReachuSwiftSDK.git", from: "1.0.0")
],
targets: [
.target(
name: "ShoppingApp",
dependencies: [
.product(name: "ReachuComplete", package: "ReachuSwiftSDK"),
]
),
]
2. App Configuration
import SwiftUI
import ReachuCore
@main
struct ShoppingApp: App {
init() {
configureReachuSDK()
}
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(AppState())
}
}
private func configureReachuSDK() {
#if DEBUG
ReachuSDK.configure(
baseURL: "https://graph-ql-dev.reachu.io",
apiKey: "dev-api-key",
environment: .development
)
#else
ReachuSDK.configure(
baseURL: "https://graph-ql.reachu.io",
apiKey: "prod-api-key",
environment: .production
)
#endif
}
}
App State Management
Global App State
import Foundation
import ReachuCore
import Combine
@MainActor
class AppState: ObservableObject {
// Navigation
@Published var selectedTab: Tab = .home
@Published var navigationPath = NavigationPath()
// Cart state
@Published var cart: Cart?
@Published var cartItemCount: Int = 0
// User state
@Published var isLoggedIn: Bool = false
@Published var userProfile: UserProfile?
// Global loading and error states
@Published var isLoading: Bool = false
@Published var errorMessage: String?
private let sdk = ReachuSDK.shared
private var cancellables = Set<AnyCancellable>()
init() {
setupObservers()
loadInitialData()
}
private func setupObservers() {
// Update cart item count when cart changes
$cart
.map { cart in
cart?.lineItems.reduce(0) { $0 + $1.quantity } ?? 0
}
.assign(to: &$cartItemCount)
}
private func loadInitialData() {
Task {
await loadCart()
}
}
func loadCart() async {
// Load existing cart or create new one
do {
// Try to load from stored cart ID
if let cartId = UserDefaults.standard.string(forKey: "cartId") {
self.cart = try await sdk.cart.getById(cartId)
} else {
// Create new cart
self.cart = try await sdk.cart.create(
customerSessionId: UUID().uuidString,
currency: "USD"
)
UserDefaults.standard.set(cart?.id, forKey: "cartId")
}
} catch {
print("Failed to load cart: \(error)")
}
}
}
enum Tab {
case home, search, cart, profile
}
Product Catalog
Product List View
import SwiftUI
import ReachuUI
import ReachuCore
struct ProductListView: View {
@StateObject private var viewModel = ProductListViewModel()
@EnvironmentObject private var appState: AppState
@State private var selectedLayout: LayoutVariant = .grid
var body: some View {
NavigationStack(path: $appState.navigationPath) {
VStack(spacing: 0) {
// Search and filter bar
SearchFilterBar(
searchText: $viewModel.searchText,
selectedCategory: $viewModel.selectedCategory,
selectedLayout: $selectedLayout,
categories: viewModel.categories
)
// Product grid/list
ScrollView {
if viewModel.isLoading && viewModel.products.isEmpty {
LoadingView()
} else {
ProductGrid(
products: viewModel.products,
layout: selectedLayout,
onProductTap: { product in
appState.navigationPath.append(ProductDetailRoute(product: product))
},
onAddToCart: { product in
await viewModel.addToCart(product, appState: appState)
}
)
}
// Load more indicator
if viewModel.hasMoreProducts {
LoadMoreView()
.onAppear {
Task { await viewModel.loadMoreProducts() }
}
}
}
.refreshable {
await viewModel.refresh()
}
}
.navigationTitle("Products")
.navigationDestination(for: ProductDetailRoute.self) { route in
ProductDetailView(product: route.product)
}
.task {
await viewModel.loadInitialProducts()
}
.alert("Error", isPresented: .constant(viewModel.errorMessage != nil)) {
Button("OK") { viewModel.errorMessage = nil }
} message: {
if let error = viewModel.errorMessage {
Text(error)
}
}
}
}
}
Product Grid Component
import SwiftUI
import ReachuUI
struct ProductGrid: View {
let products: [Product]
let layout: LayoutVariant
let onProductTap: (Product) -> Void
let onAddToCart: (Product) async -> Void
var body: some View {
LazyVGrid(columns: gridColumns, spacing: 16) {
ForEach(products) { product in
RProductCard(
product: product,
variant: layout.cardVariant,
showDescription: layout == .hero,
onTap: { onProductTap(product) },
onAddToCart: {
Task { await onAddToCart(product) }
}
)
.animation(.easeInOut, value: layout)
}
}
.padding()
}
private var gridColumns: [GridItem] {
switch layout {
case .grid:
return Array(repeating: GridItem(.flexible()), count: 2)
case .list, .hero:
return [GridItem(.flexible())]
}
}
}
enum LayoutVariant: CaseIterable {
case grid, list, hero
var cardVariant: RProductCard.Variant {
switch self {
case .grid: return .grid
case .list: return .list
case .hero: return .hero
}
}
var icon: String {
switch self {
case .grid: return "square.grid.2x2"
case .list: return "list.bullet"
case .hero: return "rectangle.stack"
}
}
}
Product ViewModel
import Foundation
import ReachuCore
import Combine
@MainActor
class ProductListViewModel: ObservableObject {
@Published var products: [Product] = []
@Published var filteredProducts: [Product] = []
@Published var categories: [Category] = []
@Published var searchText: String = ""
@Published var selectedCategory: Category?
@Published var isLoading: Bool = false
@Published var errorMessage: String?
@Published var hasMoreProducts: Bool = true
private let sdk = ReachuSDK.shared
private var currentPage = 0
private let pageSize = 20
private var cancellables = Set<AnyCancellable>()
init() {
setupSearchObserver()
}
private func setupSearchObserver() {
// Debounce search input
$searchText
.debounce(for: .milliseconds(500), scheduler: RunLoop.main)
.removeDuplicates()
.sink { [weak self] searchText in
Task { await self?.performSearch(searchText) }
}
.store(in: &cancellables)
}
func loadInitialProducts() async {
guard products.isEmpty else { return }
isLoading = true
errorMessage = nil
do {
// Load categories
async let categoriesTask = sdk.channel.category.getAll()
// Load initial products
async let productsTask = sdk.channel.product.getAll(
currency: "USD",
limit: pageSize,
offset: 0
)
let (loadedCategories, loadedProducts) = try await (categoriesTask, productsTask)
self.categories = loadedCategories
self.products = loadedProducts
self.hasMoreProducts = loadedProducts.count == pageSize
self.currentPage = 1
} catch {
self.errorMessage = error.localizedDescription
}
isLoading = false
}
func loadMoreProducts() async {
guard hasMoreProducts && !isLoading else { return }
isLoading = true
do {
let newProducts = try await sdk.channel.product.getAll(
currency: "USD",
limit: pageSize,
offset: currentPage * pageSize,
categoryId: selectedCategory?.id
)
self.products.append(contentsOf: newProducts)
self.hasMoreProducts = newProducts.count == pageSize
self.currentPage += 1
} catch {
self.errorMessage = error.localizedDescription
}
isLoading = false
}
func refresh() async {
currentPage = 0
products.removeAll()
hasMoreProducts = true
await loadInitialProducts()
}
private func performSearch(_ query: String) async {
guard !query.isEmpty else {
await refresh()
return
}
isLoading = true
do {
let searchResults = try await sdk.channel.product.search(
query: query,
currency: "USD",
limit: 50
)
self.products = searchResults
self.hasMoreProducts = false // Search doesn't support pagination
} catch {
self.errorMessage = error.localizedDescription
}
isLoading = false
}
func addToCart(_ product: Product, appState: AppState) async {
guard let cart = appState.cart else { return }
do {
let updatedCart = try await sdk.cart.addItem(
cartId: cart.id,
productId: product.id,
quantity: 1
)
appState.cart = updatedCart
// Show success feedback
HapticFeedback.success()
} catch {
self.errorMessage = "Failed to add item to cart: \(error.localizedDescription)"
HapticFeedback.error()
}
}
}
Shopping Cart
Cart View
import SwiftUI
import ReachuUI
import ReachuCore
struct CartView: View {
@EnvironmentObject private var appState: AppState
@StateObject private var viewModel = CartViewModel()
var body: some View {
NavigationView {
VStack {
if let cart = appState.cart, !cart.lineItems.isEmpty {
// Cart items list
List {
ForEach(cart.lineItems) { item in
CartItemRow(
item: item,
onQuantityChange: { newQuantity in
await viewModel.updateQuantity(
lineItemId: item.id,
quantity: newQuantity,
appState: appState
)
},
onRemove: {
await viewModel.removeItem(
lineItemId: item.id,
appState: appState
)
}
)
}
.onDelete { indexSet in
Task {
for index in indexSet {
await viewModel.removeItem(
lineItemId: cart.lineItems[index].id,
appState: appState
)
}
}
}
}
// Cart summary and checkout
VStack(spacing: ReachuSpacing.md) {
CartSummary(totals: cart.totals)
RButton(
title: "Proceed to Checkout",
style: .primary,
size: .large,
isLoading: viewModel.isLoading
) {
await viewModel.proceedToCheckout(appState: appState)
}
.disabled(cart.lineItems.isEmpty)
}
.padding()
.background(ReachuColors.surface)
} else {
// Empty cart state
EmptyCartView()
}
}
.navigationTitle("Shopping Cart")
.toolbar {
if let cart = appState.cart, !cart.lineItems.isEmpty {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Clear") {
Task { await viewModel.clearCart(appState: appState) }
}
.foregroundColor(.red)
}
}
}
}
}
}
Cart Item Row
import SwiftUI
import ReachuUI
import ReachuCore
struct CartItemRow: View {
let item: LineItem
let onQuantityChange: (Int) async -> Void
let onRemove: () async -> Void
@State private var isUpdating = false
var body: some View {
HStack(spacing: ReachuSpacing.md) {
// Product image
AsyncImage(url: URL(string: item.product.images.first?.url ?? "")) { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
} placeholder: {
Rectangle()
.fill(ReachuColors.background)
.overlay(
Image(systemName: "photo")
.foregroundColor(ReachuColors.textSecondary)
)
}
.frame(width: 60, height: 60)
.clipShape(RoundedRectangle(cornerRadius: ReachuBorderRadius.medium))
// Product details
VStack(alignment: .leading, spacing: ReachuSpacing.xs) {
Text(item.product.title)
.font(ReachuTypography.body)
.foregroundColor(ReachuColors.textPrimary)
.lineLimit(2)
if let brand = item.product.brand {
Text(brand)
.font(ReachuTypography.caption1)
.foregroundColor(ReachuColors.textSecondary)
}
Text(item.price.displayAmount)
.font(ReachuTypography.bodyBold)
.foregroundColor(ReachuColors.primary)
}
Spacer()
// Quantity controls
VStack(spacing: ReachuSpacing.sm) {
HStack(spacing: ReachuSpacing.xs) {
Button {
Task {
isUpdating = true
await onQuantityChange(max(1, item.quantity - 1))
isUpdating = false
}
} label: {
Image(systemName: "minus.circle.fill")
.foregroundColor(ReachuColors.textSecondary)
}
.disabled(item.quantity <= 1 || isUpdating)
Text("\(item.quantity)")
.font(ReachuTypography.bodyBold)
.frame(minWidth: 30)
Button {
Task {
isUpdating = true
await onQuantityChange(item.quantity + 1)
isUpdating = false
}
} label: {
Image(systemName: "plus.circle.fill")
.foregroundColor(ReachuColors.primary)
}
.disabled(isUpdating)
}
Button("Remove") {
Task { await onRemove() }
}
.font(ReachuTypography.caption1)
.foregroundColor(.red)
}
}
.opacity(isUpdating ? 0.6 : 1.0)
.animation(.easeInOut(duration: 0.2), value: isUpdating)
}
}
Global Checkout System
Modern Approach: RCheckoutOverlay + CartManager
The new Reachu Swift SDK includes a complete global checkout system that works across your entire app. This modern approach provides better user experience and easier implementation.
import SwiftUI
import ReachuCore
import ReachuUI
@main
struct ShoppingApp: App {
init() {
configureReachuSDK()
}
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(CartManager()) // Global cart state
}
}
private func configureReachuSDK() {
ReachuSDK.configure(
baseURL: "https://graph-ql.reachu.io",
apiKey: "your-api-key",
environment: .production
)
}
}
Main Content View with Global Checkout
import SwiftUI
import ReachuCore
import ReachuUI
struct ContentView: View {
@EnvironmentObject var cartManager: CartManager
var body: some View {
TabView {
ProductCatalogView()
.tabItem {
Image(systemName: "bag.fill")
Text("Products")
}
ShoppingCartView()
.tabItem {
Image(systemName: "cart.fill")
Text("Cart")
}
.badge(cartManager.itemCount > 0 ? cartManager.itemCount : nil)
ProfileView()
.tabItem {
Image(systemName: "person.fill")
Text("Profile")
}
}
// Global checkout overlay - works from anywhere in the app
.sheet(isPresented: $cartManager.isCheckoutPresented) {
RCheckoutOverlay()
.environmentObject(cartManager)
}
}
}
Product Catalog with Global Cart Integration
import SwiftUI
import ReachuUI
import ReachuCore
struct ProductCatalogView: View {
@EnvironmentObject var cartManager: CartManager
@StateObject private var viewModel = ProductCatalogViewModel()
var body: some View {
NavigationView {
ScrollView {
LazyVGrid(columns: [
GridItem(.flexible()),
GridItem(.flexible())
], spacing: ReachuSpacing.md) {
ForEach(viewModel.products) { product in
RProductCard(
product: product,
variant: .grid,
onTap: {
viewModel.showProductDetail(product)
},
onAddToCart: {
Task {
await cartManager.addProduct(product)
}
}
)
}
}
.padding(ReachuSpacing.lg)
}
.navigationTitle("Products")
.task {
await viewModel.loadProducts()
}
}
}
}
Shopping Cart View with Modern Controls
import SwiftUI
import ReachuUI
import ReachuDesignSystem
struct ShoppingCartView: View {
@EnvironmentObject var cartManager: CartManager
var body: some View {
NavigationView {
VStack(spacing: 0) {
if cartManager.items.isEmpty {
// Empty cart state
VStack(spacing: ReachuSpacing.lg) {
Spacer()
Image(systemName: "cart")
.font(.system(size: 48))
.foregroundColor(ReachuColors.textSecondary)
Text("Your cart is empty")
.font(ReachuTypography.headline)
.foregroundColor(ReachuColors.textSecondary)
Text("Add some products to get started")
.font(ReachuTypography.body)
.foregroundColor(ReachuColors.textTertiary)
Spacer()
}
} else {
// Cart items list
ScrollView {
LazyVStack(spacing: ReachuSpacing.md) {
ForEach(cartManager.items) { item in
CartItemRowView(item: item)
.environmentObject(cartManager)
}
}
.padding(ReachuSpacing.lg)
}
Spacer()
// Checkout section with total
VStack(spacing: 0) {
Divider()
VStack(spacing: ReachuSpacing.md) {
// Cart total
HStack {
Text("Total")
.font(ReachuTypography.headline)
Spacer()
Text("\(cartManager.currency) \(String(format: "%.2f", cartManager.cartTotal))")
.font(ReachuTypography.headline)
.foregroundColor(ReachuColors.primary)
}
// Global checkout button
RButton(
title: "Proceed to Checkout",
style: .primary,
size: .large,
isLoading: cartManager.isLoading
) {
cartManager.showCheckout() // Opens global overlay
}
}
.padding(ReachuSpacing.lg)
}
.background(ReachuColors.surface)
}
}
.navigationTitle("Cart (\(cartManager.itemCount))")
.toolbar {
if !cartManager.items.isEmpty {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Clear") {
Task {
await cartManager.clearCart()
}
}
.foregroundColor(ReachuColors.error)
}
}
}
}
}
}
Cart Item Row with Real-time Updates
import SwiftUI
import ReachuUI
import ReachuDesignSystem
struct CartItemRowView: View {
let item: CartManager.CartItem
@EnvironmentObject var cartManager: CartManager
var body: some View {
HStack(spacing: ReachuSpacing.md) {
// Product image with AsyncImage
AsyncImage(url: URL(string: item.imageUrl ?? "")) { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
} placeholder: {
Rectangle()
.fill(ReachuColors.background)
.overlay {
Image(systemName: "photo")
.foregroundColor(ReachuColors.textSecondary)
}
}
.frame(width: 60, height: 60)
.cornerRadius(ReachuBorderRadius.medium)
// Product information
VStack(alignment: .leading, spacing: ReachuSpacing.xs) {
Text(item.title)
.font(ReachuTypography.bodyBold)
.lineLimit(2)
if let brand = item.brand {
Text(brand)
.font(ReachuTypography.caption1)
.foregroundColor(ReachuColors.textSecondary)
}
Text("\(item.currency) \(String(format: "%.2f", item.price))")
.font(ReachuTypography.body)
.foregroundColor(ReachuColors.primary)
}
Spacer()
// Quantity controls with real-time updates
VStack(spacing: ReachuSpacing.xs) {
HStack(spacing: ReachuSpacing.xs) {
// Decrease quantity
Button("-") {
if item.quantity > 1 {
Task {
await cartManager.updateQuantity(for: item, to: item.quantity - 1)
}
}
}
.frame(width: 32, height: 32)
.background(ReachuColors.background)
.cornerRadius(ReachuBorderRadius.small)
// Current quantity
Text("\(item.quantity)")
.font(ReachuTypography.bodyBold)
.frame(minWidth: 24)
// Increase quantity
Button("+") {
Task {
await cartManager.updateQuantity(for: item, to: item.quantity + 1)
}
}
.frame(width: 32, height: 32)
.background(ReachuColors.background)
.cornerRadius(ReachuBorderRadius.small)
}
// Remove item
Button("Remove") {
Task {
await cartManager.removeItem(item)
}
}
.font(ReachuTypography.caption1)
.foregroundColor(ReachuColors.error)
}
}
.padding(ReachuSpacing.md)
.background(ReachuColors.surface)
.cornerRadius(ReachuBorderRadius.medium)
.shadow(color: ReachuColors.textPrimary.opacity(0.05), radius: 2, x: 0, y: 1)
}
}
Floating Cart Button (Optional)
import SwiftUI
import ReachuUI
import ReachuDesignSystem
struct FloatingCartButton: View {
@EnvironmentObject var cartManager: CartManager
var body: some View {
if cartManager.itemCount > 0 {
VStack {
Spacer()
HStack {
Spacer()
Button(action: {
cartManager.showCheckout() // Opens checkout from anywhere
}) {
HStack {
Image(systemName: "cart.fill")
Text("\(cartManager.itemCount)")
Text("•")
Text("\(cartManager.currency) \(String(format: "%.0f", cartManager.cartTotal))")
}
.padding(.horizontal, ReachuSpacing.lg)
.padding(.vertical, ReachuSpacing.md)
.background(ReachuColors.primary)
.foregroundColor(.white)
.cornerRadius(ReachuBorderRadius.circle)
.shadow(radius: 4)
}
Spacer()
}
.padding(.bottom, ReachuSpacing.xl)
}
}
}
}
Benefits of the Global Checkout System
- ✅ Works Everywhere: Checkout can be triggered from any screen in your app
- ✅ Real-time Updates: Cart state updates instantly across all views
- ✅ Modal Experience: Checkout appears as overlay without disrupting navigation
- ✅ Production Ready: Handles errors, loading states, and edge cases
- ✅ Cross-Platform: Works on iOS, macOS, tvOS, and watchOS
- ✅ Easy Integration: Just add
CartManager
as environment object
Key Features Comparison
Feature | Traditional Checkout | Global Checkout System |
---|---|---|
Trigger Location | Only from cart screen | From anywhere in app |
Navigation | Push/present new screen | Modal overlay |
State Management | Local to checkout flow | Global reactive state |
User Experience | Interrupts navigation | Maintains context |
Integration Effort | High (custom implementation) | Low (built-in components) |
Real-time Updates | Manual implementation | Automatic via @Published |
Error Handling | Custom implementation | Built-in comprehensive handling |
Navigation & Routing
Navigation Routes
import Foundation
import ReachuCore
// Route definitions for type-safe navigation
struct ProductDetailRoute: Hashable {
let product: Product
}
struct CategoryRoute: Hashable {
let category: Category
}
struct CheckoutRoute: Hashable {
let cartId: String
}
struct OrderDetailRoute: Hashable {
let orderId: String
}
Main Tab View
import SwiftUI
import ReachuUI
struct MainTabView: View {
@EnvironmentObject private var appState: AppState
var body: some View {
TabView(selection: $appState.selectedTab) {
ProductListView()
.tabItem {
Image(systemName: "house.fill")
Text("Home")
}
.tag(Tab.home)
SearchView()
.tabItem {
Image(systemName: "magnifyingglass")
Text("Search")
}
.tag(Tab.search)
CartView()
.tabItem {
Image(systemName: "cart.fill")
Text("Cart")
}
.badge(appState.cartItemCount > 0 ? appState.cartItemCount : nil)
.tag(Tab.cart)
ProfileView()
.tabItem {
Image(systemName: "person.fill")
Text("Profile")
}
.tag(Tab.profile)
}
.accentColor(ReachuColors.primary)
}
}
Performance Optimizations
Image Caching
import Foundation
import UIKit
actor ImageCache {
static let shared = ImageCache()
private var cache: [URL: UIImage] = [:]
private let maxCacheSize = 100
private init() {}
func image(for url: URL) -> UIImage? {
return cache[url]
}
func setImage(_ image: UIImage, for url: URL) {
if cache.count >= maxCacheSize {
// Remove oldest entries
let keysToRemove = Array(cache.keys.prefix(10))
keysToRemove.forEach { cache.removeValue(forKey: $0) }
}
cache[url] = image
}
}
Data Loading Strategy
import Foundation
import ReachuCore
@MainActor
class DataLoader: ObservableObject {
@Published var isLoading = false
private var loadingTasks: [String: Task<Void, Never>] = [:]
func loadData<T>(
id: String,
operation: @escaping () async throws -> T,
completion: @escaping (Result<T, Error>) -> Void
) {
// Cancel existing task with same ID
loadingTasks[id]?.cancel()
loadingTasks[id] = Task {
isLoading = true
defer { isLoading = false }
do {
let result = try await operation()
completion(.success(result))
} catch {
completion(.failure(error))
}
loadingTasks.removeValue(forKey: id)
}
}
func cancelAll() {
loadingTasks.values.forEach { $0.cancel() }
loadingTasks.removeAll()
}
}
Testing
Unit Tests
import XCTest
@testable import ShoppingApp
import ReachuTesting
final class ProductViewModelTests: XCTestCase {
var viewModel: ProductListViewModel!
override func setUp() {
super.setUp()
// Configure SDK for testing
ReachuSDK.configure(
baseURL: "https://mock.reachu.io",
apiKey: "test-key",
environment: .development
)
viewModel = ProductListViewModel()
}
func testLoadInitialProducts() async {
// Given
XCTAssertTrue(viewModel.products.isEmpty)
// When
await viewModel.loadInitialProducts()
// Then
XCTAssertFalse(viewModel.products.isEmpty)
XCTAssertFalse(viewModel.isLoading)
}
func testSearchProducts() async {
// Given
await viewModel.loadInitialProducts()
let initialCount = viewModel.products.count
// When
viewModel.searchText = "headphones"
// Wait for debounced search
try? await Task.sleep(nanoseconds: 600_000_000)
// Then
XCTAssertNotEqual(viewModel.products.count, initialCount)
}
}
SwiftUI Previews
#if DEBUG
import SwiftUI
import ReachuTesting
struct ProductListView_Previews: PreviewProvider {
static var previews: some View {
ProductListView()
.environmentObject(AppState())
.previewDisplayName("Product List")
}
}
#endif
This complete example demonstrates how to build a production-ready iOS shopping app using the Reachu Swift SDK. The modular architecture, proper state management, and comprehensive error handling ensure a robust and maintainable application.
This example includes production-ready patterns like proper error handling, loading states, image caching, and comprehensive testing. Use it as a foundation for your own ecommerce applications.