Dart SDK
Use the Moosyl Dart client on your Dart server: create payment requests and checkout sessions, and read payment status.
moosyl is a typed Dart client for the Moosyl API, generated from the API reference and built on Dio. Use it on your server, where your secret key is safe.
Building the payment screen in a Flutter app? Use the Flutter SDK instead. It uses this client under the hood with your publishable key.
Install
dart pub add moosyl one_of dioone_of builds the amount values and dio gives you DioException for error handling. Requires Dart >=2.18.0.
Set up the client
Always pass basePathOverride: the client's built-in default is http://localhost.
import 'dart:io';
import 'package:moosyl/moosyl.dart';
final moosyl = Moosyl(basePathOverride: 'https://api.moosyl.com')
..setApiKey('ApiKey', Platform.environment['MOOSYL_SECRET_KEY']!);setApiKey('ApiKey', …) sends your key in the Authorization header. Keys from your Sandbox environment create sandbox payments; keys from Production create real ones. See API keys.
The client times out after 5 seconds to connect and 3 seconds to receive. To change that, pass your own Dio:
import 'package:dio/dio.dart';
final moosyl = Moosyl(
dio: Dio(BaseOptions(
baseUrl: 'https://api.moosyl.com',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 15),
)),
)..setApiKey('ApiKey', Platform.environment['MOOSYL_SECRET_KEY']!);Create a payment request
A payment request is the amount you want to collect, identified by your own transactionId (for example your order ID). It must be unique per environment.
import 'package:one_of/any_of.dart';
PaymentRequestCreateAmount mru(num value) => PaymentRequestCreateAmount(
(b) => b.anyOf = AnyOf2<String, num>(values: {1: value}),
);
final created = await moosyl.getPaymentRequestApi().postPaymentRequest(
paymentRequestCreate: PaymentRequestCreate(
(b) => b
..transactionId = 'order_123'
..amount.replace(mru(1000)), // in MRU
),
);
final request = created.data!.data;
print(request.id); // UUID of the payment requestThen pass order_123 to your app's Flutter or React Native payment view, or send the customer to a hosted checkout.
Reuse a request on retry
Creating a request with a transactionId that already exists fails. To make retries safe, look the request up instead:
import 'package:dio/dio.dart';
Future<PaymentRequestGetData> ensurePaymentRequest(String transactionId, num amount) async {
final api = moosyl.getPaymentRequestApi();
try {
final created = await api.postPaymentRequest(
paymentRequestCreate: PaymentRequestCreate(
(b) => b
..transactionId = transactionId
..amount.replace(mru(amount)),
),
);
return created.data!.data;
} on DioException {
final existing = await api.getPaymentRequestByTransactionByTransactionId(
transactionId: transactionId,
);
return existing.data!.data;
}
}Create a hosted checkout
A checkout session gives you a Moosyl-hosted payment page to redirect the customer to. Create it from an existing payment request:
final session = await moosyl.getCheckoutSessionApi().postCheckoutSession(
checkoutSessionCreateBody: CheckoutSessionCreateBody(
(b) => b
..paymentRequestId = request.id
..successUrl = 'https://example.com/success'
..cancelUrl = 'https://example.com/cancel'
..expiresInMinutes = 30,
),
);
final checkoutUrl = session.data!.checkoutUrl;Or skip the payment request and pass transactionId and amount directly:
CheckoutSessionCreateBody(
(b) => b
..transactionId = 'order_124'
..amount.replace(mru(1000))
..successUrl = 'https://example.com/success'
..cancelUrl = 'https://example.com/cancel',
)When paymentRequestId is set, transactionId and amount are ignored.
Check a payment
Payment requests don't have a status; payments do. The reliable way to learn the outcome is the webhook. To check on demand, read a payment by its ID (from the webhook's data.id):
final payment = await moosyl.getPaymentApi().getPaymentById(id: paymentId);
if (payment.data!.data.status == PaymentGetDataStatusEnum.completed) {
// Fulfil the order.
}A payment's status is pending, completed, failed or cancelled.
To ask Moosyl to re-check a request with the bank, call patchPaymentRequestByTransactionIdRefreshStatus(transactionId: 'order_123').
What the client covers
| API | Methods |
|---|---|
getPaymentRequestApi() | postPaymentRequest, getPaymentRequestById, getPaymentRequestByTransactionByTransactionId, patchPaymentRequestByTransactionIdRefreshStatus |
getCheckoutSessionApi() | postCheckoutSession |
getPaymentApi() | getPaymentById |
getConfigurationApi() | getConfiguration (the payment methods enabled for the key's environment) |
Products, prices, customers and subscriptions aren't in the Dart client yet. Call those endpoints over HTTP with the same Authorization header, or use the TypeScript SDK.
Handle errors
Every method throws DioException for network errors and error responses:
try {
await moosyl.getPaymentRequestApi().getPaymentRequestByTransactionByTransactionId(
transactionId: 'order_123',
);
} on DioException catch (e) {
print(e.response?.statusCode);
print(e.response?.data);
}Verify webhooks
The client has no webhook helper. Verify the signature yourself: compute an HMAC-SHA256 of the raw request body with your webhook secret and compare it to the x-webhook-signature header, which looks like sha256=<hex>.
dart pub add cryptoimport 'dart:convert';
import 'package:crypto/crypto.dart';
bool verifyMoosylWebhook({
required String rawBody,
required String? signatureHeader,
required String webhookSecret,
}) {
if (signatureHeader == null || !signatureHeader.startsWith('sha256=')) return false;
final received = signatureHeader.substring('sha256='.length);
final expected = Hmac(sha256, utf8.encode(webhookSecret))
.convert(utf8.encode(rawBody))
.toString();
// Constant-time comparison.
if (received.length != expected.length) return false;
var diff = 0;
for (var i = 0; i < expected.length; i++) {
diff |= received.codeUnitAt(i) ^ expected.codeUnitAt(i);
}
return diff == 0;
}Read the body as a string before parsing it as JSON. Re-encoding parsed JSON changes the bytes and breaks the signature. See Webhooks for the events and payloads.