Building E-commerce Applications with Flutter and Stripe

Building an e-commerce application involves integrating various functionalities like product listings, shopping carts, user authentication, and, most importantly, payment processing. Flutter, Google’s UI toolkit, is an excellent choice for building cross-platform e-commerce apps. When it comes to handling payments securely, Stripe is a popular and reliable payment gateway. In this guide, we’ll explore how to build an e-commerce application with Flutter and Stripe, covering essential aspects and providing practical code examples.

Introduction to Flutter E-commerce Apps

Flutter allows you to create native-looking apps for iOS and Android from a single codebase, which significantly speeds up the development process. Combined with a robust payment processing system like Stripe, you can create a feature-rich e-commerce app that’s both user-friendly and secure.

Why Use Flutter for E-commerce?

  • Cross-Platform Development: Build apps for both iOS and Android with a single codebase.
  • Rich UI: Create visually appealing and engaging user interfaces with Flutter’s widgets.
  • Hot Reload: See changes instantly during development, enhancing productivity.
  • Large Community: Access a wealth of resources, libraries, and support from the Flutter community.

Why Use Stripe for Payment Processing?

  • Security: Stripe handles sensitive payment information securely.
  • Ease of Use: Provides straightforward APIs and SDKs for seamless integration.
  • Global Reach: Supports various payment methods and currencies worldwide.
  • Comprehensive Features: Offers tools for fraud prevention, subscription management, and more.

Setting Up Flutter Environment

Before you begin, ensure that you have Flutter installed on your machine. You can follow the official Flutter installation guide:

Flutter Installation Guide

Also, set up an emulator (Android) or simulator (iOS) to test your application.

Step 1: Creating a New Flutter Project

Open your terminal and run the following command to create a new Flutter project:

flutter create ecommerce_app

Navigate to the project directory:

cd ecommerce_app

Step 2: Adding Dependencies

Add the necessary dependencies to your pubspec.yaml file. These dependencies include packages for HTTP requests, state management, and, of course, Stripe.

dependencies:
  flutter:
    sdk: flutter
  http: ^0.13.5
  provider: ^6.0.5
  flutter_stripe: ^7.0.0

dev_dependencies:
  flutter_test:
    sdk: flutter

Run flutter pub get to install the dependencies.

Step 3: Setting Up Stripe

Before using Stripe, you’ll need to create an account on the Stripe website:

Stripe Official Website

After setting up your account, retrieve your publishable key and secret key from the Stripe dashboard.

Initialize the Stripe SDK in your Flutter app’s main.dart file:

import 'package:flutter/material.dart';
import 'package:flutter_stripe/flutter_stripe.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  Stripe.publishableKey = 'YOUR_STRIPE_PUBLISHABLE_KEY';

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'E-commerce App',
      home: Scaffold(
        appBar: AppBar(
          title: Text('E-commerce App'),
        ),
        body: Center(
          child: Text('Welcome to our e-commerce app!'),
        ),
      ),
    );
  }
}

Replace 'YOUR_STRIPE_PUBLISHABLE_KEY' with your actual Stripe publishable key.

Step 4: Building Product Listing UI

Create a basic UI to display a list of products. This UI can be expanded with more features later. First, define a Product model:

class Product {
  final String id;
  final String name;
  final double price;
  final String imageUrl;

  Product({required this.id, required this.name, required this.price, required this.imageUrl});
}

Create a ProductProvider to manage product data. Using Provider is a straightforward approach to state management:

import 'package:flutter/material.dart';

class ProductProvider extends ChangeNotifier {
  List<Product> _products = [
    Product(
      id: '1',
      name: 'Flutter T-Shirt',
      price: 20.00,
      imageUrl: 'https://example.com/flutter_tshirt.png',
    ),
    Product(
      id: '2',
      name: 'Dart Mug',
      price: 15.00,
      imageUrl: 'https://example.com/dart_mug.png',
    ),
  ];

  List<Product> get products => _products;
}

Display the product list in a widget:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'product.dart';
import 'product_provider.dart';

class ProductList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final productProvider = Provider.of<ProductProvider>(context);
    final products = productProvider.products;

    return ListView.builder(
      itemCount: products.length,
      itemBuilder: (context, index) {
        final product = products[index];
        return Card(
          margin: EdgeInsets.all(8.0),
          child: Padding(
            padding: EdgeInsets.all(8.0),
            child: Row(
              children: [
                Image.network(
                  product.imageUrl,
                  width: 50,
                  height: 50,
                  fit: BoxFit.cover,
                ),
                SizedBox(width: 10),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(product.name, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
                      Text('\$${product.price.toStringAsFixed(2)}'),
                    ],
                  ),
                ),
                ElevatedButton(
                  onPressed: () {
                    // TODO: Add to cart functionality
                  },
                  child: Text('Add to Cart'),
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

Don’t forget to integrate the ProductProvider in main.dart using ChangeNotifierProvider:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:ecommerce_app/product_list.dart'; // Import the product list widget
import 'package:ecommerce_app/product_provider.dart'; // Import the product provider

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => ProductProvider(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'E-commerce App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('E-commerce App'),
        ),
        body: ProductList(), // Use the product list widget
      ),
    );
  }
}

Step 5: Implementing the Shopping Cart

Next, create a shopping cart functionality. Define a CartItem model:

class CartItem {
  final String productId;
  final String name;
  final double price;
  int quantity;

  CartItem({
    required this.productId,
    required this.name,
    required this.price,
    this.quantity = 1,
  });
}

Create a CartProvider to manage cart items:

import 'package:flutter/material.dart';
import 'cart_item.dart';

class CartProvider extends ChangeNotifier {
  Map<String, CartItem> _items = {};

  Map<String, CartItem> get items => _items;

  void addItem(String productId, String name, double price) {
    if (_items.containsKey(productId)) {
      _items.update(
        productId,
        (existingCartItem) => CartItem(
          productId: existingCartItem.productId,
          name: existingCartItem.name,
          price: existingCartItem.price,
          quantity: existingCartItem.quantity + 1,
        ),
      );
    } else {
      _items[productId] = CartItem(
        productId: productId,
        name: name,
        price: price,
        quantity: 1,
      );
    }
    notifyListeners();
  }

  void removeItem(String productId) {
    _items.remove(productId);
    notifyListeners();
  }

  void clearCart() {
    _items = {};
    notifyListeners();
  }

  double get totalAmount {
    var total = 0.0;
    _items.forEach((key, cartItem) {
      total += cartItem.price * cartItem.quantity;
    });
    return total;
  }
}

Display the cart items in a widget:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'cart_provider.dart';

class CartScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final cartProvider = Provider.of<CartProvider>(context);
    final cartItems = cartProvider.items;

    return Scaffold(
      appBar: AppBar(
        title: Text('Shopping Cart'),
      ),
      body: ListView.builder(
        itemCount: cartItems.length,
        itemBuilder: (context, index) {
          final cartItem = cartItems.values.toList()[index];
          return Card(
            margin: EdgeInsets.all(8.0),
            child: Padding(
              padding: EdgeInsets.all(8.0),
              child: Row(
                children: [
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(cartItem.name, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
                        Text('\$${cartItem.price.toStringAsFixed(2)} x ${cartItem.quantity}'),
                      ],
                    ),
                  ),
                  IconButton(
                    icon: Icon(Icons.remove_shopping_cart),
                    onPressed: () {
                      cartProvider.removeItem(cartItem.productId);
                    },
                  ),
                ],
              ),
            ),
          );
        },
      ),
      bottomNavigationBar: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text('Total: \$${cartProvider.totalAmount.toStringAsFixed(2)}', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
            ElevatedButton(
              onPressed: () {
                // TODO: Implement checkout
              },
              child: Text('Checkout'),
            ),
          ],
        ),
      ),
    );
  }
}

Update your main.dart to include the CartProvider:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'product_list.dart';
import 'product_provider.dart';
import 'cart_provider.dart'; // Import CartProvider
import 'cart_screen.dart';     // Import CartScreen

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (context) => ProductProvider()),
        ChangeNotifierProvider(create: (context) => CartProvider()), // Add CartProvider
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'E-commerce App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('E-commerce App'),
          actions: [
            IconButton(
              icon: Icon(Icons.shopping_cart),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => CartScreen()),
                );
              },
            ),
          ],
        ),
        body: ProductList(),
      ),
    );
  }
}

To enable users to add items to the cart from the ProductList, update the ElevatedButton within the ProductList widget:

ElevatedButton(
  onPressed: () {
    final cartProvider = Provider.of<CartProvider>(context, listen: false);
    cartProvider.addItem(product.id, product.name, product.price);
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('${product.name} added to cart!')),
    );
  },
  child: Text('Add to Cart'),
),

Step 6: Integrating Stripe for Payment Processing

First, install the flutter_stripe package:

flutter pub add flutter_stripe

Then, you’ll need to set up the payment intent on the server side. For simplicity, this example uses a mock server setup. You should implement a real server with appropriate security measures for a production environment.
For this Example use Server setup from Stripe Documentation.

Below Code is intended to serve as an example of usage, it can be set in the Application without usage of real endpoints or without being Hosted.

The server creates a payment intent, which the client side (Flutter app) uses to complete the payment.

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
import 'package:http/http.dart' as http;

class StripePaymentService {

  Future<Map<String, dynamic>> createPaymentIntent(String amount, String currency) async {
    try {
      // This is a simplified mock endpoint - Replace with your actual server endpoint.
      // Ideally you should use an actual server-side endpoint.

      final Uri paymentIntentUrl = Uri.parse('https://us-central1-function-app.cloudfunctions.net/stripePaymentIntentRequest');

      //Replace hardcoded key by payment key that match current amount (or by another suitable value).
       Map body = {
      'amount': amount,
      'currency': currency,
    };
       

      // Create the PaymentIntent on Stripe
      final http.Response response = await http.post(
        paymentIntentUrl,
        headers: {'Content-Type': 'application/json'},
        body: json.encode(body),
      );

        return json.decode(response.body);
      } catch (e) {
         print('Exception during payment intent creation: $e');
          return {}; 
    }
  }

  Future<void> makePayment(BuildContext context, String amount) async {
    try {
      // Create Payment Intent
      final paymentIntentData = await createPaymentIntent(amount, 'USD');

      if (paymentIntentData.isNotEmpty) {
        // Initialize Payment Sheet
        await Stripe.instance.initPaymentSheet(
          paymentSheetParameters: SetupPaymentSheetParameters(
            paymentIntentClientSecret: paymentIntentData['client_secret'],
            style: ThemeMode.light,
            merchantDisplayName: 'Flutter E-Commerce App',
          ),
        );

        // Display Payment Sheet
        await displayPaymentSheet(context);
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Failed to create payment intent!')),
        );
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Payment failed: $e')),
      );
    }
  }

  Future<void> displayPaymentSheet(BuildContext context) async {
    try {
      await Stripe.instance.presentPaymentSheet();

      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Payment successful!')),
      );
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Payment failed: $e')),
      );
    }
  }
}

In your CartScreen:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'cart_provider.dart';
import 'stripe_payment_service.dart'; // Import the payment service

class CartScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final cartProvider = Provider.of<CartProvider>(context);
    final cartItems = cartProvider.items;

    return Scaffold(
      appBar: AppBar(
        title: Text('Shopping Cart'),
      ),
      body: ListView.builder(
        itemCount: cartItems.length,
        itemBuilder: (context, index) {
          final cartItem = cartItems.values.toList()[index];
          return Card(
            margin: EdgeInsets.all(8.0),
            child: Padding(
              padding: EdgeInsets.all(8.0),
              child: Row(
                children: [
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(cartItem.name, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
                        Text('\$${cartItem.price.toStringAsFixed(2)} x ${cartItem.quantity}'),
                      ],
                    ),
                  ),
                  IconButton(
                    icon: Icon(Icons.remove_shopping_cart),
                    onPressed: () {
                      cartProvider.removeItem(cartItem.productId);
                    },
                  ),
                ],
              ),
            ),
          );
        },
      ),
      bottomNavigationBar: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text('Total: \$${cartProvider.totalAmount.toStringAsFixed(2)}', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
            ElevatedButton(
              onPressed: () async {
                 final paymentService = StripePaymentService();
                await paymentService.makePayment(context, cartProvider.totalAmount.toStringAsFixed(2));
                 cartProvider.clearCart(); // Clear the cart after successful payment
              },
              child: Text('Checkout'),
            ),
          ],
        ),
      ),
    );
  }
}

Step 7: Run Your Application

Now, run your Flutter application:

flutter run

Test the product listing, add items to the cart, and then proceed with the payment using Stripe.

Conclusion

This guide provides a foundational understanding of building an e-commerce application with Flutter and integrating Stripe for payment processing. You can expand on this foundation by adding more advanced features such as user authentication, order tracking, wish lists, and advanced UI designs. Remember to always implement secure practices for handling sensitive payment information and to adhere to Stripe’s best practices.