Using Packages Like flutter_stripe and in_app_purchase for Payments in Flutter

Flutter offers a versatile platform for building cross-platform mobile applications, and handling payments is a crucial aspect of many apps. In Flutter, you can integrate payment functionalities using packages like flutter_stripe and in_app_purchase. This blog post will explore how to use these packages effectively for implementing payment solutions in your Flutter apps.

Overview of Payment Packages in Flutter

  • flutter_stripe: Provides direct integration with Stripe, allowing you to process credit card payments, use Stripe Connect, and more.
  • in_app_purchase: Enables you to implement in-app purchases through Google Play Store (Android) and App Store (iOS).

Why Choose These Packages?

  • Secure Transactions: Both Stripe and in-app purchase mechanisms provide secure methods for handling transactions.
  • Cross-Platform: Flutter allows you to implement payments across both iOS and Android platforms with a single codebase.
  • Ease of Integration: These packages offer well-documented APIs that simplify the integration process.

Integrating Stripe Payments with flutter_stripe

The flutter_stripe package allows you to easily integrate Stripe payment processing into your Flutter application. Below are the steps to set up and use this package.

Step 1: Add the flutter_stripe Dependency

Include the flutter_stripe package in your pubspec.yaml file:

dependencies:
  flutter_stripe: ^9.1.0  # Use the latest version

Run flutter pub get to install the package.

Step 2: Configure Stripe in your Flutter App

Initialize Stripe by setting your publishable key. This should be done at the start of your app, ideally in the main function:

import 'package:flutter_stripe/flutter_stripe.dart';

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

Replace 'YOUR_STRIPE_PUBLISHABLE_KEY' with your actual Stripe publishable key.

Step 3: Create a Payment Method

Use Stripe.instance.createPaymentMethod to create a payment method. This typically involves collecting credit card details from the user. Always ensure this is done over a secure (HTTPS) connection.

import 'package:flutter_stripe/flutter_stripe.dart';

Future createStripePaymentMethod({
  required String cardNumber,
  required String expMonth,
  required String expYear,
  required String cvc,
}) async {
  try {
    final PaymentMethod paymentMethod = await Stripe.instance.createPaymentMethod(
      PaymentMethodParams.card(
        CardDetails(
          number: cardNumber,
          expirationMonth: int.parse(expMonth),
          expirationYear: int.parse(expYear),
          cvc: cvc,
        ),
      ),
    );
    return paymentMethod;
  } catch (e) {
    print('Error creating payment method: $e');
    rethrow;
  }
}

Step 4: Create a Payment Intent on Your Server

For security reasons, the actual payment processing should occur on your server. You’ll need to create a Payment Intent using the Stripe API. This Payment Intent is then confirmed by your Flutter app.

Here’s a simplified example of how you might create a Payment Intent on the server using Node.js:

const stripe = require('stripe')('YOUR_STRIPE_SECRET_KEY');

exports.createPaymentIntent = async (req, res) => {
  try {
    const paymentIntent = await stripe.paymentIntents.create({
      amount: req.body.amount, // Amount in cents
      currency: 'usd',
      automatic_payment_methods: {
        enabled: true,
      },
    });
    res.json({ clientSecret: paymentIntent.client_secret });
  } catch (e) {
    console.log('Error creating payment intent: ', e);
    res.status(500).json({ error: e.message });
  }
};

Replace 'YOUR_STRIPE_SECRET_KEY' with your actual Stripe secret key.

Step 5: Confirm the Payment Intent on the Client Side

After creating the Payment Intent on the server, you confirm it on the client side using the clientSecret from the Payment Intent. This is done using Stripe.instance.confirmPayment.

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

Future confirmPayment({
  required String paymentMethodId,
  required int amount,
  required Function(String) onSuccess,
  required Function(String) onError,
}) async {
  final url = Uri.parse('YOUR_SERVER_URL/create-payment-intent');
  final response = await http.post(
    url,
    headers: {'Content-Type': 'application/json'},
    body: json.encode({'amount': amount}),
  );

  final body = json.decode(response.body);
  final clientSecret = body['clientSecret'];

  try {
    await Stripe.instance.confirmPayment(
      clientSecret,
      PaymentMethodParams(
        card: paymentMethodId,
      ),
    );
    onSuccess('Payment successful!');
  } catch (e) {
    onError('Payment failed: $e');
  }
}

Ensure to replace 'YOUR_SERVER_URL/create-payment-intent' with your actual server endpoint.

Implementing In-App Purchases with in_app_purchase

For digital products and subscriptions, using in_app_purchase ensures compliance with platform policies and leverages the built-in billing systems of the app stores.

Step 1: Add the in_app_purchase Dependency

Include the in_app_purchase package in your pubspec.yaml file:

dependencies:
  in_app_purchase: ^3.1.6 # Use the latest version

Run flutter pub get to install the package.

Step 2: Initialize the InAppPurchase Instance

Initialize the InAppPurchase instance and listen for purchase updates:

import 'package:in_app_purchase/in_app_purchase.dart';

class InAppPurchaseService {
  final InAppPurchase _inAppPurchase = InAppPurchase.instance;
  late StreamSubscription> _subscription;
  List products = [];
  List purchases = [];

  Future init() async {
    final Stream> purchaseUpdates = _inAppPurchase.purchaseStream;
    _subscription = purchaseUpdates.listen((purchases) {
      this.purchases = purchases;
      _handlePurchaseUpdates(purchases);
    });
    await _getProducts();
    await _verifyPastPurchases();
  }

  Future _getProducts() async {
    Set ids = {'premium_access'}; // Replace with your product IDs
    final ProductDetailsResponse response = await _inAppPurchase.queryProductDetails(ids);
    products = response.productDetails;
  }

  Future _handlePurchaseUpdates(List purchaseDetailsList) async {
    for (var purchaseDetails in purchaseDetailsList) {
      if (purchaseDetails.status == PurchaseStatus.pending) {
        // Show a loading indicator
      } else {
        if (purchaseDetails.status == PurchaseStatus.granted) {
          // Grant access to the product
        } else if (purchaseDetails.status == PurchaseStatus.error) {
          print('Purchase error: ${purchaseDetails.error}');
        }
      }
      if (purchaseDetails.pendingCompletePurchase) {
        await _inAppPurchase.completePurchase(purchaseDetails);
      }
    }
  }

  Future _verifyPastPurchases() async {
    final QueryPurchaseDetailsResponse response = await _inAppPurchase.queryPastPurchases();
    for (var purchase in response.pastPurchases) {
      if (purchase.status == PurchaseStatus.purchased) {
        // Grant access to the product
      }
    }
  }

  void dispose() {
    _subscription.cancel();
  }
}

Step 3: Query Available Products

Fetch available products from the store using their IDs:

Future _getProducts() async {
  Set ids = {'your_product_id'}; // Replace with your product IDs
  final ProductDetailsResponse response = await _inAppPurchase.queryProductDetails(ids);
  products = response.productDetails;
}

Replace {'your_product_id'} with the actual product IDs configured in Google Play Console or App Store Connect.

Step 4: Initiate a Purchase

Initiate a purchase flow using _inAppPurchase.buyProduct:

void purchaseProduct(ProductDetails productDetails) {
  final PurchaseParam purchaseParam = PurchaseParam(productDetails: productDetails);
  _inAppPurchase.buyProduct(purchaseParam);
}

Step 5: Handle Purchase Updates

Implement a listener to handle purchase updates. This includes verifying the purchase, granting the user access to the content, and completing the purchase:

Future _handlePurchaseUpdates(List purchaseDetailsList) async {
  for (var purchaseDetails in purchaseDetailsList) {
    if (purchaseDetails.status == PurchaseStatus.pending) {
      // Show a loading indicator
    } else {
      if (purchaseDetails.status == PurchaseStatus.granted) {
        // Grant access to the product
      } else if (purchaseDetails.status == PurchaseStatus.error) {
        print('Purchase error: ${purchaseDetails.error}');
      }
    }
    if (purchaseDetails.pendingCompletePurchase) {
      await _inAppPurchase.completePurchase(purchaseDetails);
    }
  }
}

Best Practices for Implementing Payments in Flutter

  • Secure Your Server: Ensure your server is secure and uses HTTPS.
  • Handle Errors Gracefully: Implement error handling to manage transaction failures.
  • Validate Purchases: Always validate purchases to prevent fraud.
  • Inform Users: Provide clear and informative messages throughout the purchase process.
  • Stay Updated: Keep your payment packages updated to leverage the latest features and security enhancements.

Conclusion

Implementing payments in Flutter using packages like flutter_stripe and in_app_purchase can enhance your app’s monetization strategy. By following the steps outlined above, you can create secure and user-friendly payment experiences on both iOS and Android platforms. Whether you choose to integrate direct credit card processing with Stripe or offer in-app purchases through the app stores, Flutter provides the tools you need to build successful payment solutions.