flutter_gb_in_app_purchases 1.1.0 copy "flutter_gb_in_app_purchases: ^1.1.0" to clipboard
flutter_gb_in_app_purchases: ^1.1.0 copied to clipboard

A Flutter package for in-app purchases using BLoC pattern with Geekbears Stack Base.

Flutter GB In-App Purchases #

A comprehensive Flutter package for handling in-app purchases using the BLoC pattern with Geekbears Stack Base. This package provides a robust, platform-agnostic solution for managing consumables, non-consumables, and subscription products across iOS and Android.

Features #

  • πŸ—οΈ Clean Architecture: Built with BLoC pattern and dependency injection
  • πŸ“± Cross-Platform: Full support for iOS (StoreKit) and Android (Google Play)
  • πŸ”„ Product Types: Support for consumables, non-consumables, and subscriptions
  • πŸ›‘οΈ Transaction Verification: Built-in server-side verification with customizable endpoints
  • πŸ”„ Subscription Management: Handle subscription upgrades, downgrades, and changes
  • πŸ“Š Real-time Updates: Stream-based transaction monitoring
  • πŸ”§ Configurable: Highly customizable configuration options
  • πŸ§ͺ Testable: Built with testing in mind using dependency injection

Supported Platforms #

  • βœ… iOS (StoreKit)
  • βœ… Android (Google Play)

Supported Product Types #

  • Consumables: Products that can be purchased multiple times (coins, lives, etc.)
  • Non-Consumables: One-time purchases (premium features, remove ads)
  • Renewable Subscriptions: Auto-renewing subscriptions
  • Non-Renewable Subscriptions: Time-limited subscriptions

Getting Started #

Installation #

Add this to your package's pubspec.yaml file:

dependencies:
  flutter_gb_in_app_purchases: ^1.0.0

Prerequisites #

  • Flutter SDK >= 3.35.0
  • Dart SDK >= 3.9.0
  • iOS 14.0+ / Android API 21+

iOS Setup #

  1. Configure your app in App Store Connect
  2. Add your product identifiers
  3. Ensure your app is signed with a valid provisioning profile

Android Setup #

  1. Configure your app in Google Play Console
  2. Add your product IDs
  3. Ensure your app is signed and uploaded to Play Console

Usage #

1. Basic Setup #

First, configure the dependency injection:

import 'package:flutter_gb_in_app_purchases/flutter_gb_in_app_purchases.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Configure dependency injection
  await configurePurchasesInjection(
    environment: AppEnvironment.production,
    purchasesConfig: InAppPurchasesConfig(
      // Optional: API endpoint to fetch product listings
      getProductsListingApiEndpoint: () => Uri.parse('https://your-api.com/products'),
      
      // Optional: Parse product listings response
      getProductsListingResponseParser: (response) {
        // Parse your API response and return PurchaseProductListing list
        return [];
      },
      
      // Optional: Transaction verification endpoint
      purchaseVerificationApiEndpoint: (transaction, params) {
        return Uri.parse('https://your-api.com/verify-purchase');
      },
      
      // Optional: Custom request mapper for verification
      purchaseVerificationRequestMapper: (request, transaction) {
        return request..body = {'transaction_id': transaction.id};
      },
      
      // Optional: Product type mapper
      productTypeMapper: (item, listings) {
        // Determine product type based on your business logic
        return ProductType.consumable;
      },
      
      // Complete purchases without verification (not recommended)
      completeWithoutVerifying: false,
    ),
  );
  
  runApp(MyApp());
}

2. Using the BLoC #

Wrap your app or specific widgets with the purchases BLoC:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_gb_in_app_purchases/flutter_gb_in_app_purchases.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => PurchasesBloc(
        purchasesBlocConfig: PurchasesBlocConfig(
          autoInitialize: true,
          userReferenceMapper: () async => 'user_123', // Your user ID
          verificationParamBuilder: (transaction) {
            return {'user_id': 'user_123'};
          },
        ),
      ),
      child: MaterialApp(
        home: PurchaseScreen(),
      ),
    );
  }
}

3. Initialize and Get Products #

class PurchaseScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<PurchasesBloc, PurchasesState>(
      builder: (context, state) {
        return Scaffold(
          appBar: AppBar(title: Text('In-App Purchases')),
          body: Column(
            children: [
              // Initialize purchases
              ElevatedButton(
                onPressed: () {
                  context.read<PurchasesBloc>().initialize();
                },
                child: Text('Initialize Purchases'),
              ),
              
              // Get store catalog
              ElevatedButton(
                onPressed: () {
                  context.read<PurchasesBloc>().add(
                    PurchasesEvent.getStoreCatalog(
                      productIds: {'premium_upgrade', 'coins_100'},
                    ),
                  );
                },
                child: Text('Load Products'),
              ),
              
              // Display products
              if (state.storeCatalog.data != null)
                Expanded(
                  child: ListView.builder(
                    itemCount: state.storeCatalog.data!.products.length,
                    itemBuilder: (context, index) {
                      final product = state.storeCatalog.data!.products[index];
                      return ListTile(
                        title: Text(product.title),
                        subtitle: Text(product.description),
                        trailing: Text(product.price),
                        onTap: () {
                          context.read<PurchasesBloc>().purchase(product);
                        },
                      );
                    },
                  ),
                ),
            ],
          ),
        );
      },
    );
  }
}

4. Handle Purchase States #

class PurchaseListener extends StatelessWidget {
  final Widget child;
  
  const PurchaseListener({Key? key, required this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocListener<PurchasesBloc, PurchasesState>(
      listener: (context, state) {
        // Handle purchase completion
        if (state.isPurchasing.data == true) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('Purchase completed!')),
          );
        }
        
        // Handle errors
        if (state.storeCatalog.data?.isLeft() == true) {
          final failure = state.storeCatalog.data?.fold(
            (failure) => failure,
            (success) => null,
          );
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('Error: ${failure?.message}')),
          );
        }
      },
      child: child,
    );
  }
}

5. Restore Purchases #

// Restore purchases (typically called from a settings screen)
context.read<PurchasesBloc>().restorePurchases();

6. Subscription Changes (Android) #

// Change subscription (upgrade/downgrade)
context.read<PurchasesBloc>().changeSubscription(newProduct);

Configuration Options #

InAppPurchasesConfig #

Property Type Description
getProductsListingApiEndpoint Uri Function()? API endpoint to fetch product listings
getProductsListingResponseParser List<PurchaseProductListing> Function(String)? Parse product listings API response
purchaseVerificationApiEndpoint Uri Function(Transaction, Map<String, dynamic>?)? Transaction verification endpoint
purchaseVerificationRequestMapper BaseRequest Function(Request, Transaction)? Customize verification request
completeWithoutVerifying bool Complete purchases without server verification (default: false)
productTypeMapper ProductItemTypeMapper? Map products to their types
logger Logger? Custom logger instance

PurchasesBlocConfig #

Property Type Description
userReferenceMapper UserReferenceMapper? Function to get current user ID
verificationParamBuilder VerificationParamBuilder? Build verification parameters
autoInitialize bool Auto-initialize on bloc creation (default: false)
logEvents bool Enable event logging (default: false)
verifyDebounced Duration Debounce time for verification (default: 3s)
transactionCompleteDebounced Duration Debounce time for completion (default: 3s)

API Integration #

Product Listings API #

Your API should return product information in this format:

{
  "products": [
    {
      "product_id": "premium_upgrade",
      "title": "Premium Upgrade",
      "description": "Unlock premium features",
      "price": "4.99",
      "type": "non-consumable"
    }
  ]
}

Transaction Verification API #

Your verification endpoint will receive transaction details and should return verification results.

State Management #

The package provides comprehensive state management through BLoC:

  • Initialization: Track initialization status
  • Product Loading: Monitor catalog loading states
  • Purchase States: Track individual purchase progress
  • Transaction Updates: Real-time transaction monitoring
  • Verification Status: Track server-side verification

Error Handling #

The package provides detailed error handling for common scenarios:

  • Network failures
  • Invalid products
  • Purchase failures
  • Verification failures
  • Platform-specific errors

Testing #

The package is built with testing in mind. Use the mock configuration for testing:

final mockConfig = InAppPurchasesConfig.mock();

Dependencies #

This package depends on:

  • flutter_gb_stack_base: Geekbears base utilities
  • in_app_purchase: Core in-app purchase functionality
  • flutter_bloc: State management
  • dartz: Functional programming utilities
  • built_value: Immutable data structures
  • rxdart: Reactive extensions

Contributing #

Contributions are welcome! Please feel free to submit a Pull Request.

License #

This project is licensed under the MIT License - see the LICENSE file for details.

Support #

For support, please open an issue in the GitLab repository or contact the Geekbears team.

Changelog #

See CHANGELOG.md for a list of changes and version history.