Moosyl logo

Flutter SDK

Accept Bankily, Sedad, Masrvi and other Mauritanian bank app payments in your Flutter app with the Moosyl Flutter SDK.

moosyl_flutter shows the Moosyl payment flow inside your Flutter app: your customer picks their bank app, pays, and your app gets the result. It supports Bankily, Sedad, Masrvi, BimBank, BCI Pay and Amanty, in English, French and Arabic.

The SDK only handles the customer's side. Creating the payment and confirming it happen on your server.

How it fits together

Your server creates a payment request

Call POST /payment-request with your secret key, the amount and your own transactionId. See Quickstart.

Your app opens the payment flow

Pass your publishable key and the same transactionId to the SDK.

Your server confirms the payment

Treat the in-app result as a hint for the UI. Fulfil the order when your server receives the webhook with a completed status.

Install

flutter pub add moosyl_flutter
pubspec.yaml
dependencies:
  moosyl_flutter: ^2.0.4

Requires Dart >=3.4.4.

import 'package:moosyl_flutter/moosyl.dart';

Register the translations

Add the Moosyl localization delegates to your MaterialApp. Supported locales are ar, en and fr.

MaterialApp(
  localizationsDelegates: MoosylLocalization.localizationsDelegates,
  supportedLocales: MoosylLocalization.supportedLocales,
  home: const PaymentScreen(),
);

If your app has its own delegates, combine both lists.

Open the payment flow

MoosylFlutter.show opens the full flow and returns when it closes:

  • true or false when the payment finished;
  • null when the customer closed it without paying.
class PaymentScreen extends StatelessWidget {
  const PaymentScreen({super.key, required this.transactionId});

  final String transactionId; // from your server

  @override
  Widget build(BuildContext context) {
    return FilledButton(
      onPressed: () async {
        final isSuccess = await MoosylFlutter.show(
          context,
          publishableApiKey: 'YOUR_PUBLISHABLE_KEY',
          transactionId: transactionId,
          isFullPage: true, // false opens it as a bottom sheet
        );

        if (isSuccess == true) {
          // Show a confirmation. Fulfil the order from your webhook.
        }
      },
      child: const Text('Pay'),
    );
  }
}

Options

OptionTypeDefaultDescription
publishableApiKeyStringrequiredYour publishable key.
transactionIdStringrequiredThe transactionId of the payment request your server created.
isFullPagebooltruetrue pushes a full page, false opens a bottom sheet.
itemsList<MoosylPaymentSummaryItem>?nullSummary rows shown under the payment methods. Their total must equal the payment request amount.
isMasriviInBottomSheetboolfalseOpens the Masrvi payment page inside a bottom sheet instead of a full page.
masriviPhoneNumberString?nullPrefills the Masrvi page. Accepts 22222222 or +22222222222. Defaults to the payment request's phone number.

Summary rows

items shows a breakdown under the payment methods. Amounts are in MRU. The labels amountToPay, tax, total and totalAmount are translated; any other label is shown as written.

items: const [
  MoosylPaymentSummaryItem(label: 'amountToPay', amount: 2000),
  MoosylPaymentSummaryItem(label: 'tax', amount: 150),
],

Use your own checkout screen

To keep your own layout and button, use MoosylPaymentMethods with a MoosylPaymentMethodsController. Moosyl loads and shows the payment methods, and your button calls continuePayment().

class CheckoutScreen extends StatefulWidget {
  const CheckoutScreen({super.key, required this.transactionId});

  final String transactionId;

  @override
  State<CheckoutScreen> createState() => _CheckoutScreenState();
}

class _CheckoutScreenState extends State<CheckoutScreen> {
  final _controller = MoosylPaymentMethodsController();
  ConfigurationListDataInner? _selected;
  bool _loading = false;

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        MoosylPaymentMethods(
          controller: _controller,
          publishableApiKey: 'YOUR_PUBLISHABLE_KEY',
          transactionId: widget.transactionId,
          selectedMethodId: _selected?.id,
          onSelectMethod: (method) => setState(() => _selected = method),
          onContinueLoadingChange: (loading) => setState(() => _loading = loading),
          onPaymentSuccess: (isSuccess) async {
            // Show a confirmation. Fulfil the order from your webhook.
          },
          onPaymentError: (error) {
            // Show an error message.
          },
        ),
        const SizedBox(height: 16),
        FilledButton(
          onPressed: _selected == null || _loading ? null : _controller.continuePayment,
          child: Text(_loading ? 'Loading…' : 'Continue'),
        ),
      ],
    );
  }
}
OptionDescription
publishableApiKeyRequired. Your publishable key.
transactionIdThe payment request's transactionId, used by continuePayment().
controllerLets you call continuePayment() and read selectedMethod. Dispose it with your state.
selectedMethodId, onSelectMethodThe selected method, controlled by your state.
onPaymentSuccessFutureOr<void> Function(bool isSuccess), called when the payment finishes.
onPaymentErrorCalled when loading methods or continuing fails.
onContinueLoadingChangeCalled when continuePayment() starts and finishes.
amountToPay, tax, totalAmountExpected amounts, checked against the payment request before paying.
primaryColorAccent color for the default rows and dialogs. Defaults to your theme's primary color.
renderMethodBuilds each method row yourself (see below).

Custom method rows

Pass renderMethod to draw each row with your own widgets. Call data.onSelect when the row is tapped.

renderMethod: (context, data) {
  return ListTile(
    onTap: data.onSelect,
    leading: data.type.icon.apply(size: 36),
    title: Text(data.title),
    trailing: data.isSelected ? const Icon(Icons.check_circle) : null,
  );
},

data has method (the raw method), type (a PaymentMethodTypes value), title (translated), isSelected, selectedMethodId, isRTL and onSelect.

PaymentMethodTypes values are bankily, sedad, masrivi, bimBank, bCIpay and amanty.

Test in the sandbox

Use a publishable key from your Sandbox environment, and create the payment request with a sandbox secret key. The test phone numbers decide whether the payment completes or fails.

Never put your secret key in the app. Anyone can extract it from the binary. The app only needs the publishable key and a transactionId.

Troubleshooting

  • The payment view shows an error right away. Check that the payment request exists for this transactionId, and that the publishable key and the secret key that created it belong to the same environment.
  • An error says the amount should match the payment request. The total of items (or totalAmount) must equal the payment request amount.
  • Texts show as keys or the app throws a localization error. Add MoosylLocalization.localizationsDelegates to your MaterialApp.

Next steps