moosyl_flutter 2.0.4 copy "moosyl_flutter: ^2.0.4" to clipboard
moosyl_flutter: ^2.0.4 copied to clipboard

The Moosyl Flutter SDK is a powerful tool for integrating payment solutions with Mauritania's popular banking apps, such as Bankily, Sedad, and Masrivi

example/lib/main.dart

// ignore_for_file: avoid_print

import 'package:flutter/material.dart';
import 'package:moosyl_flutter/moosyl.dart';

import 'payment_success_dialog.dart';

const _apiKey = 'your_api_key';
const _transactionId = 'transaction_id';
const _primaryColor = Color(0xFF000000);

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

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

  static const colorScheme = ColorScheme.light(
    primary: _primaryColor,
    onPrimary: Color(0xFFFFFFFF),
    surface: Color(0xFFF7F7F7),
    onSurface: Color(0xFF111111),
    secondary: Color(0xFFEAF1FF),
    onSecondary: Color(0xFF000000),
    error: Color(0xFFCE2C2C),
    tertiary: Color(0xFF01D066),
  );

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Moosyl Demo',
      localizationsDelegates: MoosylLocalization.localizationsDelegates,
      supportedLocales: MoosylLocalization.supportedLocales,
      locale: const Locale('en'),
      theme: ThemeData(
        colorScheme: colorScheme,
        useMaterial3: true,
      ),
      home: const DemoHomePage(),
    );
  }
}

enum DemoMode {
  menu,
  embeddedPlatforms,
  customPlatforms,
}

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

  @override
  State<DemoHomePage> createState() => _DemoHomePageState();
}

class _DemoHomePageState extends State<DemoHomePage> {
  final _embeddedController = MoosylPaymentMethodsController();
  final _customController = MoosylPaymentMethodsController();

  DemoMode _demoMode = DemoMode.menu;
  ConfigurationListDataInner? _selectedEmbeddedMethod;
  ConfigurationListDataInner? _selectedCustomMethod;
  bool _embeddedContinueLoading = false;
  bool _customContinueLoading = false;

  @override
  void dispose() {
    _embeddedController.dispose();
    _customController.dispose();
    super.dispose();
  }

  Future<void> _openMoosylView() async {
    final isSuccess = await MoosylFlutter.show(context,
        publishableApiKey: _apiKey,
        transactionId: _transactionId,
        isFullPage: true,
        items: [
          const MoosylPaymentSummaryItem(amount: 2000, label: 'Subtotal'),
          const MoosylPaymentSummaryItem(amount: 150, label: 'Tax')
        ]);
    if (!mounted) return;
    if (isSuccess != null) {
      await _showSuccess(isSuccess);
    }
  }

  Future<void> _showSuccess(bool isSuccess) async {
    await showPaymentSuccessDialog(context, isSuccess: isSuccess);
    print('Payment was successful! isSuccess=$isSuccess');
  }

  void _showMenu() {
    setState(() {
      _demoMode = DemoMode.menu;
    });
  }

  @override
  Widget build(BuildContext context) {
    return switch (_demoMode) {
      DemoMode.menu => _MenuScreen(
          onOpenMoosylView: _openMoosylView,
          onOpenEmbeddedPlatforms: () {
            setState(() {
              _demoMode = DemoMode.embeddedPlatforms;
            });
          },
          onOpenCustomPlatforms: () {
            setState(() {
              _demoMode = DemoMode.customPlatforms;
            });
          },
        ),
      DemoMode.embeddedPlatforms => _EmbeddedPlatformsScreen(
          controller: _embeddedController,
          selectedMethod: _selectedEmbeddedMethod,
          continueLoading: _embeddedContinueLoading,
          onBack: _showMenu,
          onSelectMethod: (method) {
            setState(() {
              _selectedEmbeddedMethod = method;
            });
          },
          onContinueLoadingChange: (loading) {
            setState(() {
              _embeddedContinueLoading = loading;
            });
          },
          onPaymentSuccess: _showSuccess,
        ),
      DemoMode.customPlatforms => _CustomPlatformsScreen(
          controller: _customController,
          selectedMethod: _selectedCustomMethod,
          continueLoading: _customContinueLoading,
          onBack: _showMenu,
          onSelectMethod: (method) {
            setState(() {
              _selectedCustomMethod = method;
            });
          },
          onContinueLoadingChange: (loading) {
            setState(() {
              _customContinueLoading = loading;
            });
          },
          onPaymentSuccess: _showSuccess,
        ),
    };
  }
}

class _MenuScreen extends StatelessWidget {
  const _MenuScreen({
    required this.onOpenMoosylView,
    required this.onOpenEmbeddedPlatforms,
    required this.onOpenCustomPlatforms,
  });

  final VoidCallback onOpenMoosylView;
  final VoidCallback onOpenEmbeddedPlatforms;
  final VoidCallback onOpenCustomPlatforms;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Moosyl Demo')),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(24),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            spacing: 14,
            children: [
              _DemoButton(
                label: 'Test MoosylView',
                onPressed: onOpenMoosylView,
              ),
              _DemoButton(
                label: 'Test embedded platforms',
                onPressed: onOpenEmbeddedPlatforms,
                outlined: true,
              ),
              _DemoButton(
                label: 'Test custom methods UI',
                onPressed: onOpenCustomPlatforms,
                outlined: true,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _EmbeddedPlatformsScreen extends StatelessWidget {
  const _EmbeddedPlatformsScreen({
    required this.controller,
    required this.selectedMethod,
    required this.continueLoading,
    required this.onBack,
    required this.onSelectMethod,
    required this.onContinueLoadingChange,
    required this.onPaymentSuccess,
  });

  final MoosylPaymentMethodsController controller;
  final ConfigurationListDataInner? selectedMethod;
  final bool continueLoading;
  final VoidCallback onBack;
  final ValueChanged<ConfigurationListDataInner> onSelectMethod;
  final ValueChanged<bool> onContinueLoadingChange;
  final Future<void> Function(bool isSuccess) onPaymentSuccess;

  @override
  Widget build(BuildContext context) {
    final selectedTitle = selectedMethod == null
        ? 'platform'
        : PaymentMethodTypes.fromString(selectedMethod!.type).title(context);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Embedded Platforms'),
        leading: IconButton(
          onPressed: onBack,
          icon: const Icon(Icons.arrow_back),
        ),
      ),
      body: ListView(
        padding: const EdgeInsets.all(20),
        children: [
          Text(
            'This screen owns the checkout UI and only uses Moosyl to load and select the platform.',
            style: Theme.of(context).textTheme.bodyMedium,
          ),
          const SizedBox(height: 20),
          Text(
            'Payment method',
            style: Theme.of(context).textTheme.titleLarge,
          ),
          const SizedBox(height: 12),
          MoosylPaymentMethods(
            controller: controller,
            publishableApiKey: _apiKey,
            transactionId: _transactionId,
            selectedMethodId: selectedMethod?.id,
            onSelectMethod: onSelectMethod,
            onContinueLoadingChange: onContinueLoadingChange,
            onPaymentSuccess: onPaymentSuccess,
            primaryColor: _primaryColor,
          ),
          const SizedBox(height: 20),
          _DemoButton(
            label:
                continueLoading ? 'Loading...' : 'Continue with $selectedTitle',
            onPressed: selectedMethod == null || continueLoading
                ? null
                : controller.continuePayment,
          ),
        ],
      ),
    );
  }
}

class _CustomPlatformsScreen extends StatelessWidget {
  const _CustomPlatformsScreen({
    required this.controller,
    required this.selectedMethod,
    required this.continueLoading,
    required this.onBack,
    required this.onSelectMethod,
    required this.onContinueLoadingChange,
    required this.onPaymentSuccess,
  });

  final MoosylPaymentMethodsController controller;
  final ConfigurationListDataInner? selectedMethod;
  final bool continueLoading;
  final VoidCallback onBack;
  final ValueChanged<ConfigurationListDataInner> onSelectMethod;
  final ValueChanged<bool> onContinueLoadingChange;
  final Future<void> Function(bool isSuccess) onPaymentSuccess;

  @override
  Widget build(BuildContext context) {
    final selectedTitle = selectedMethod == null
        ? 'payment method'
        : PaymentMethodTypes.fromString(selectedMethod!.type).title(context);

    return Scaffold(
      backgroundColor: const Color(0xFFF6F0E8),
      appBar: AppBar(
        backgroundColor: const Color(0xFFF6F0E8),
        title: const Text('Custom Checkout'),
        leading: IconButton(
          onPressed: onBack,
          icon: const Icon(Icons.arrow_back),
        ),
      ),
      body: ListView(
        padding: const EdgeInsets.fromLTRB(16, 12, 16, 120),
        children: [
          const _CheckoutSectionTitle(step: '1', title: 'Delivery address'),
          _CheckoutCard(
            child: ListTile(
              contentPadding: EdgeInsets.zero,
              leading: const Icon(Icons.location_on_outlined),
              title: const Text('Mohamed Ahmed'),
              subtitle:
                  const Text('Tevragh Zeina, Nouakchott\n+222 47 12 34 56'),
              trailing: TextButton(
                onPressed: () {},
                child: const Text('Change'),
              ),
            ),
          ),
          const SizedBox(height: 24),
          const _CheckoutSectionTitle(step: '2', title: 'Products'),
          const _CheckoutCard(
            child: Column(
              children: [
                _ProductRow(
                  title: 'Floral Midi Dress',
                  subtitle: 'Pink • M • 1 x 520 MRU',
                  price: '520 MRU',
                ),
                Divider(height: 28),
                _ProductRow(
                  title: 'Cargo Wide-Leg Pants',
                  subtitle: 'Beige • L • 1 x 680 MRU',
                  price: '680 MRU',
                ),
              ],
            ),
          ),
          const SizedBox(height: 24),
          const _CheckoutSectionTitle(step: '3', title: 'Payment method'),
          MoosylPaymentMethods(
            controller: controller,
            publishableApiKey: _apiKey,
            transactionId: _transactionId,
            selectedMethodId: selectedMethod?.id,
            onSelectMethod: onSelectMethod,
            onContinueLoadingChange: onContinueLoadingChange,
            onPaymentSuccess: onPaymentSuccess,
            primaryColor: _primaryColor,
            renderMethod: (context, data) => _CustomMethodRow(data: data),
          ),
        ],
      ),
      bottomNavigationBar: SafeArea(
        minimum: const EdgeInsets.fromLTRB(16, 12, 16, 16),
        child: SizedBox(
          height: 64,
          child: FilledButton.icon(
            onPressed: selectedMethod == null || continueLoading
                ? null
                : controller.continuePayment,
            icon: selectedMethod == null
                ? const Icon(Icons.payments_outlined)
                : PaymentMethodTypes.fromString(selectedMethod!.type)
                    .icon
                    .apply(size: 32),
            label: Text(
              continueLoading
                  ? 'Loading...'
                  : 'Pay 2,470 MRU with $selectedTitle',
              textAlign: TextAlign.center,
            ),
            style: FilledButton.styleFrom(
              backgroundColor: const Color(0xFF171713),
              foregroundColor: Colors.white,
              disabledBackgroundColor: const Color(0xFF171713).withValues(
                alpha: 0.45,
              ),
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(18),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class _CustomMethodRow extends StatelessWidget {
  const _CustomMethodRow({required this.data});

  final MoosylPaymentMethodRenderData data;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 12),
      child: InkWell(
        borderRadius: BorderRadius.circular(24),
        onTap: data.onSelect,
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 160),
          constraints: const BoxConstraints(minHeight: 78),
          padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(24),
            border: Border.all(
              color: data.isSelected
                  ? const Color(0xFF171713)
                  : const Color(0xFFEEE9E2),
              width: data.isSelected ? 2 : 1,
            ),
            boxShadow: const [
              BoxShadow(
                color: Color(0x12000000),
                blurRadius: 10,
                offset: Offset(0, 4),
              ),
            ],
          ),
          child: Row(
            children: [
              Container(
                width: 34,
                height: 34,
                decoration: BoxDecoration(
                  color:
                      data.isSelected ? const Color(0xFF171713) : Colors.white,
                  border: Border.all(color: const Color(0xFFD1D1CD)),
                  shape: BoxShape.circle,
                ),
                child: data.isSelected
                    ? const Icon(Icons.check, color: Colors.white, size: 20)
                    : null,
              ),
              const SizedBox(width: 14),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.end,
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Text(
                      data.title,
                      textAlign: TextAlign.right,
                      style: Theme.of(context).textTheme.titleMedium?.copyWith(
                            fontWeight: FontWeight.w800,
                          ),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      data.type == PaymentMethodTypes.bankily
                          ? 'Confirm with passcode'
                          : data.type == PaymentMethodTypes.masrivi
                              ? 'Mauritel Money'
                              : 'Payment wallet',
                      textAlign: TextAlign.right,
                      style: Theme.of(context).textTheme.bodySmall,
                    ),
                  ],
                ),
              ),
              const SizedBox(width: 14),
              Container(
                width: 56,
                height: 56,
                decoration: BoxDecoration(
                  color: const Color(0xFFF4F4F1),
                  borderRadius: BorderRadius.circular(16),
                ),
                child: Center(child: data.type.icon.apply(size: 46)),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _DemoButton extends StatelessWidget {
  const _DemoButton({
    required this.label,
    required this.onPressed,
    this.outlined = false,
  });

  final String label;
  final VoidCallback? onPressed;
  final bool outlined;

  @override
  Widget build(BuildContext context) {
    final shape = RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(8),
    );

    if (outlined) {
      return OutlinedButton(
        onPressed: onPressed,
        style: OutlinedButton.styleFrom(
          foregroundColor: _primaryColor,
          side: const BorderSide(color: _primaryColor, width: 2),
          shape: shape,
          padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
        ),
        child: Text(label),
      );
    }

    return FilledButton(
      onPressed: onPressed,
      style: FilledButton.styleFrom(
        backgroundColor: _primaryColor,
        foregroundColor: Colors.white,
        shape: shape,
        padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
      ),
      child: Text(label),
    );
  }
}

class _CheckoutSectionTitle extends StatelessWidget {
  const _CheckoutSectionTitle({
    required this.step,
    required this.title,
  });

  final String step;
  final String title;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 10),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          Text(
            title,
            style: Theme.of(context).textTheme.titleMedium?.copyWith(
                  fontWeight: FontWeight.w800,
                ),
          ),
          const SizedBox(width: 10),
          Container(
            width: 30,
            height: 22,
            alignment: Alignment.center,
            decoration: BoxDecoration(
              color: const Color(0xFFEBE5DC),
              borderRadius: BorderRadius.circular(12),
            ),
            child: Text(
              step,
              style: const TextStyle(fontWeight: FontWeight.w800),
            ),
          ),
        ],
      ),
    );
  }
}

class _CheckoutCard extends StatelessWidget {
  const _CheckoutCard({required this.child});

  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(18),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(22),
        boxShadow: const [
          BoxShadow(
            color: Color(0x12000000),
            blurRadius: 10,
            offset: Offset(0, 4),
          ),
        ],
      ),
      child: child,
    );
  }
}

class _ProductRow extends StatelessWidget {
  const _ProductRow({
    required this.title,
    required this.subtitle,
    required this.price,
  });

  final String title;
  final String subtitle;
  final String price;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Text(
          price,
          style: Theme.of(context).textTheme.titleSmall?.copyWith(
                fontWeight: FontWeight.w800,
              ),
        ),
        const Spacer(),
        Column(
          crossAxisAlignment: CrossAxisAlignment.end,
          children: [
            Text(title, style: Theme.of(context).textTheme.titleSmall),
            const SizedBox(height: 6),
            Text(subtitle, style: Theme.of(context).textTheme.bodySmall),
          ],
        ),
        const SizedBox(width: 14),
        Container(
          width: 58,
          height: 58,
          decoration: BoxDecoration(
            color: const Color(0xFFF2CDD8),
            borderRadius: BorderRadius.circular(16),
          ),
        ),
      ],
    );
  }
}
1
likes
140
points
39
downloads
screenshot

Documentation

API reference

Publisher

verified publishersoftware-savants.com

Weekly Downloads

The Moosyl Flutter SDK is a powerful tool for integrating payment solutions with Mauritania's popular banking apps, such as Bankily, Sedad, and Masrivi

Homepage
Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

file_picker, flutter, flutter_hooks, flutter_localizations, http, intl, mime, modal_bottom_sheet, moosyl, provider, shimmer, webview_flutter

More

Packages that depend on moosyl_flutter