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_flutterdependencies:
moosyl_flutter: ^2.0.4Requires 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:
trueorfalsewhen the payment finished;nullwhen 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
| Option | Type | Default | Description |
|---|---|---|---|
publishableApiKey | String | required | Your publishable key. |
transactionId | String | required | The transactionId of the payment request your server created. |
isFullPage | bool | true | true pushes a full page, false opens a bottom sheet. |
items | List<MoosylPaymentSummaryItem>? | null | Summary rows shown under the payment methods. Their total must equal the payment request amount. |
isMasriviInBottomSheet | bool | false | Opens the Masrvi payment page inside a bottom sheet instead of a full page. |
masriviPhoneNumber | String? | null | Prefills 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'),
),
],
);
}
}| Option | Description |
|---|---|
publishableApiKey | Required. Your publishable key. |
transactionId | The payment request's transactionId, used by continuePayment(). |
controller | Lets you call continuePayment() and read selectedMethod. Dispose it with your state. |
selectedMethodId, onSelectMethod | The selected method, controlled by your state. |
onPaymentSuccess | FutureOr<void> Function(bool isSuccess), called when the payment finishes. |
onPaymentError | Called when loading methods or continuing fails. |
onContinueLoadingChange | Called when continuePayment() starts and finishes. |
amountToPay, tax, totalAmount | Expected amounts, checked against the payment request before paying. |
primaryColor | Accent color for the default rows and dialogs. Defaults to your theme's primary color. |
renderMethod | Builds 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(ortotalAmount) must equal the payment request amount. - Texts show as keys or the app throws a localization error. Add
MoosylLocalization.localizationsDelegatesto yourMaterialApp.
Next steps
- Webhooks: confirm payments on your server
- Testing: test phone numbers and outcomes
- API keys: publishable and secret keys
- Package on pub.dev
Platform Connect
Let merchants connect their Moosyl account to your platform in a few clicks. You receive their API keys and a webhook, so you can take payments on their behalf.
React Native SDK
Accept Bankily, Sedad, Masrvi and other Mauritanian bank app payments in your React Native app with the Moosyl SDK.