stripe_smart_checkout 0.1.1 copy "stripe_smart_checkout: ^0.1.1" to clipboard
stripe_smart_checkout: ^0.1.1 copied to clipboard

Modern Flutter package for Stripe Checkout integration with support for one-time payments and subscriptions. Clean architecture, type-safe, cross-platform.

example/lib/main.dart

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

void main() {
  runApp(const StripeCheckoutExampleApp());
}

class StripeCheckoutExampleApp extends StatelessWidget {
  const StripeCheckoutExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Stripe Smart Checkout Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const CheckoutExampleScreen(),
    );
  }
}

class CheckoutExampleScreen extends StatefulWidget {
  const CheckoutExampleScreen({super.key});

  @override
  State<CheckoutExampleScreen> createState() => _CheckoutExampleScreenState();
}

class _CheckoutExampleScreenState extends State<CheckoutExampleScreen> {
  // Configure your Stripe keys here
  late final StripeCheckoutConfig _stripeConfig;

  @override
  void initState() {
    super.initState();
    _stripeConfig = const StripeCheckoutConfig(
      publicKey:
          'pk_test_51SdSchSjB5swWNpegdfh2fsOYxy9VDFpDdT0UwOHjOvfUDggpDJz8Mz1RdvkqDz4e6m1850DKMJoExkJHsGwHQcJ00bDmrZwHT',
      secretKey:
          'sk_test_51SdSchSjB5swWNpeJMcojb4Tchbw41J1MWfbtgdH7Ip0E3u3gB4U0DimOh0X2Qia27oublEjZcHBSMhANyHYXIBR00yxbdFjv8',
      isTestMode: true,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Stripe Smart Checkout Demo'),
        centerTitle: true,
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          // Package Info Card
          Card(
            color: Colors.blue.shade50,
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    '๐Ÿš€ Stripe Smart Checkout',
                    style: Theme.of(context).textTheme.headlineSmall?.copyWith(
                          fontWeight: FontWeight.bold,
                        ),
                  ),
                  const SizedBox(height: 8),
                  const Text(
                    'A complete Flutter package for Stripe Checkout with support for both one-time payments and recurring subscriptions.',
                  ),
                  const SizedBox(height: 16),
                  const Text(
                    'โš™๏ธ Setup Required:',
                    style: TextStyle(fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 4),
                  const Text(
                    '1. Get your Stripe API keys from dashboard.stripe.com/apikeys\n'
                    '2. Replace the placeholder keys in main.dart (lines 37-38)\n'
                    '3. Tap any product below to start a checkout!',
                    style: TextStyle(fontSize: 12),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 24),

          // One-Time Payments Section
          Text(
            '๐Ÿ’ณ One-Time Payments',
            style: Theme.of(context).textTheme.titleLarge?.copyWith(
                  fontWeight: FontWeight.bold,
                ),
          ),
          const SizedBox(height: 8),
          const Text(
            'Single transaction checkout for products and services',
            style: TextStyle(color: Colors.grey),
          ),
          const SizedBox(height: 16),

          _buildProductCard(
            context,
            icon: Icons.school,
            name: 'Flutter Course',
            description: 'Advanced Flutter Development Masterclass',
            price: 49.99,
            isSubscription: false,
          ),
          _buildProductCard(
            context,
            icon: Icons.book,
            name: 'Clean Architecture Book',
            description: 'Comprehensive Guide to Software Architecture',
            price: 29.99,
            isSubscription: false,
          ),
          _buildProductCard(
            context,
            icon: Icons.laptop,
            name: 'Premium Development Laptop',
            description: 'High-Performance Laptop for Developers',
            price: 1499.99,
            isSubscription: false,
          ),

          const SizedBox(height: 32),

          // Subscriptions Section
          Text(
            '๐Ÿ”„ Subscription Plans',
            style: Theme.of(context).textTheme.titleLarge?.copyWith(
                  fontWeight: FontWeight.bold,
                ),
          ),
          const SizedBox(height: 8),
          const Text(
            'Recurring payments with flexible billing intervals',
            style: TextStyle(color: Colors.grey),
          ),
          const SizedBox(height: 16),

          _buildProductCard(
            context,
            icon: Icons.star_outline,
            name: 'Starter Plan',
            description: 'Weekly subscription - Perfect for trying out',
            price: 9.99,
            isSubscription: true,
            billingPeriod: 'week',
            trialDays: 7,
          ),
          _buildProductCard(
            context,
            icon: Icons.star_half,
            name: 'Pro Plan',
            description: 'Monthly subscription with advanced features',
            price: 29.99,
            isSubscription: true,
            billingPeriod: 'month',
          ),
          _buildProductCard(
            context,
            icon: Icons.star,
            name: 'Premium Plan',
            description: 'Annual subscription - Save 20%!',
            price: 299.99,
            isSubscription: true,
            billingPeriod: 'year',
            trialDays: 14,
          ),

          const SizedBox(height: 32),

          // Test Cards Info
          Card(
            color: Colors.amber.shade50,
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    '๐Ÿงช Test Cards',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      fontSize: 16,
                    ),
                  ),
                  const SizedBox(height: 8),
                  const Text(
                    'Use these Stripe test cards:',
                    style: TextStyle(fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 4),
                  _buildTestCardRow('4242 4242 4242 4242', 'Success'),
                  _buildTestCardRow(
                      '4000 0000 0000 9995', 'Insufficient funds'),
                  _buildTestCardRow('4000 0000 0000 0002', 'Card declined'),
                  const SizedBox(height: 8),
                  const Text(
                    'โ€ข Use any future expiration date (e.g., 12/34)\n'
                    'โ€ข Use any 3-digit CVC\n'
                    'โ€ข Use any billing ZIP code',
                    style: TextStyle(fontSize: 12),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildProductCard(
    BuildContext context, {
    required IconData icon,
    required String name,
    required String description,
    required double price,
    required bool isSubscription,
    String? billingPeriod,
    int? trialDays,
  }) {
    final priceLabel = isSubscription
        ? '\$${price.toStringAsFixed(2)}/${billingPeriod ?? 'month'}'
        : '\$${price.toStringAsFixed(2)}';

    final trialLabel = trialDays != null ? ' โ€ข $trialDays-day trial' : '';

    return Card(
      margin: const EdgeInsets.only(bottom: 12),
      child: InkWell(
        onTap: () => _handleCheckout(
          context,
          name: name,
          description: description,
          price: price,
          isSubscription: isSubscription,
          billingPeriod: billingPeriod,
          trialDays: trialDays,
        ),
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Row(
            children: [
              Container(
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: isSubscription
                      ? Colors.purple.shade50
                      : Colors.blue.shade50,
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Icon(
                  icon,
                  color: isSubscription ? Colors.purple : Colors.blue,
                  size: 32,
                ),
              ),
              const SizedBox(width: 16),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      name,
                      style: const TextStyle(
                        fontWeight: FontWeight.bold,
                        fontSize: 16,
                      ),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      description,
                      style: TextStyle(
                        color: Colors.grey.shade600,
                        fontSize: 13,
                      ),
                    ),
                    const SizedBox(height: 8),
                    Row(
                      children: [
                        Text(
                          priceLabel,
                          style: TextStyle(
                            color: Colors.green.shade700,
                            fontWeight: FontWeight.bold,
                            fontSize: 16,
                          ),
                        ),
                        if (trialLabel.isNotEmpty)
                          Text(
                            trialLabel,
                            style: TextStyle(
                              color: Colors.orange.shade700,
                              fontSize: 12,
                              fontWeight: FontWeight.w500,
                            ),
                          ),
                      ],
                    ),
                  ],
                ),
              ),
              Icon(
                Icons.arrow_forward_ios,
                size: 16,
                color: Colors.grey.shade400,
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildTestCardRow(String cardNumber, String result) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 2),
      child: Row(
        children: [
          const Text('โ€ข ', style: TextStyle(fontSize: 12)),
          Text(
            cardNumber,
            style: const TextStyle(
              fontFamily: 'monospace',
              fontSize: 12,
              fontWeight: FontWeight.bold,
            ),
          ),
          Text(
            ' โ†’ $result',
            style: const TextStyle(fontSize: 12),
          ),
        ],
      ),
    );
  }

  void _handleCheckout(
    BuildContext context, {
    required String name,
    required String description,
    required double price,
    required bool isSubscription,
    String? billingPeriod,
    int? trialDays,
  }) {
    // Determine checkout mode
    final mode =
        isSubscription ? CheckoutMode.subscription : CheckoutMode.payment;

    // Create checkout item
    final CheckoutItem item;
    if (isSubscription) {
      // Convert billing period to interval
      BillingInterval interval = BillingInterval.month;
      if (billingPeriod == 'week') {
        interval = BillingInterval.week;
      } else if (billingPeriod == 'year') {
        interval = BillingInterval.year;
      } else if (billingPeriod == 'day') {
        interval = BillingInterval.day;
      }

      item = CheckoutItem.subscription(
        name: name,
        price: price,
        description: description,
        billingInterval: interval,
        trialPeriodDays: trialDays,
      );
    } else {
      item = CheckoutItem.payment(
        name: name,
        price: price,
        quantity: 1,
        description: description,
      );
    }

    // Navigate to checkout
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => StripeCheckoutWidget(
          config: _stripeConfig,
          amount: price,
          currency: 'usd',
          mode: mode,
          items: [item],
          customerEmail: '[email protected]',
          onSuccess: (session) {
            // Pop the checkout screen first
            //Navigator.of(context).pop();
            // Add a small delay to ensure navigation animation completes
            Future.delayed(const Duration(milliseconds: 300), () {
              if (context.mounted) {
                _showSuccessDialog(
                    context, session, name, price, isSubscription);
              }
            });
          },
          onError: (error) {
            // Pop the checkout screen first
            Navigator.of(context).pop();
            // Add a small delay to ensure navigation animation completes
            Future.delayed(const Duration(milliseconds: 300), () {
              if (context.mounted) {
                _showErrorDialog(context, error);
              }
            });
          },
        ),
      ),
    );
  }

  void _showSuccessDialog(
    BuildContext context,
    CheckoutSession session,
    String productName,
    double amount,
    bool isSubscription,
  ) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Row(
          children: [
            Icon(Icons.check_circle, color: Colors.green.shade600, size: 28),
            const SizedBox(width: 8),
            const Text('Success!'),
          ],
        ),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              isSubscription
                  ? 'Your subscription has been activated successfully!'
                  : 'Payment completed successfully!',
              style: const TextStyle(fontSize: 16),
            ),
            const SizedBox(height: 16),
            _buildInfoRow('Product', productName),
            _buildInfoRow('Amount', '\$${amount.toStringAsFixed(2)}'),
            _buildInfoRow('Session ID', session.id),
            _buildInfoRow('Status', session.paid ? 'Paid' : 'Pending'),
            if (isSubscription)
              Padding(
                padding: const EdgeInsets.only(top: 8),
                child: Text(
                  'You can manage your subscription in the Stripe Dashboard.',
                  style: TextStyle(
                    fontSize: 12,
                    color: Colors.grey.shade600,
                  ),
                ),
              ),
          ],
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('Done'),
          ),
        ],
      ),
    );
  }

  void _showErrorDialog(BuildContext context, StripeCheckoutException error) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Row(
          children: [
            Icon(Icons.error, color: Colors.red.shade600, size: 28),
            const SizedBox(width: 8),
            const Text('Payment Failed'),
          ],
        ),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              'Unable to process your payment.',
              style: TextStyle(fontSize: 16),
            ),
            const SizedBox(height: 16),
            _buildInfoRow('Error', error.message),
            if (error is StripePaymentException && error.errorCode != null)
              _buildInfoRow('Code', error.errorCode!),
            const SizedBox(height: 16),
            Container(
              padding: const EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: Colors.amber.shade50,
                borderRadius: BorderRadius.circular(8),
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    '๐Ÿ’ก Troubleshooting:',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      color: Colors.amber.shade900,
                    ),
                  ),
                  const SizedBox(height: 4),
                  Text(
                    _getErrorSuggestion(error),
                    style: TextStyle(
                      fontSize: 12,
                      color: Colors.amber.shade900,
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('Close'),
          ),
        ],
      ),
    );
  }

  Widget _buildInfoRow(String label, String value) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 8),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SizedBox(
            width: 80,
            child: Text(
              '$label:',
              style: const TextStyle(
                fontWeight: FontWeight.bold,
                fontSize: 13,
              ),
            ),
          ),
          Expanded(
            child: Text(
              value,
              style: const TextStyle(fontSize: 13),
            ),
          ),
        ],
      ),
    );
  }

  String _getErrorSuggestion(StripeCheckoutException error) {
    if (error is StripeNetworkException) {
      return 'Check your internet connection and API keys configuration.';
    } else if (error is StripePaymentException) {
      if (error.errorCode?.contains('insufficient') ?? false) {
        return 'The card has insufficient funds. Try a different card.';
      } else if (error.errorCode?.contains('declined') ?? false) {
        return 'The card was declined. Please use a different payment method.';
      }
      return 'Please check your card details and try again.';
    }
    return 'Please try again or contact support if the issue persists.';
  }
}
1
likes
130
points
147
downloads

Publisher

verified publishersnippetcoder.com

Weekly Downloads

Modern Flutter package for Stripe Checkout integration with support for one-time payments and subscriptions. Clean architecture, type-safe, cross-platform.

Homepage

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

dio, flutter, flutter_riverpod, freezed_annotation, json_annotation, url_launcher, webview_flutter

More

Packages that depend on stripe_smart_checkout