Dart / Flutter server SDK watenga_flutter
Dart 3. Server-side Dart only: never ship a secret key inside an app.
Installation
dart pub add watenga_flutterBefore the first release
# pubspec.yaml
dependencies:
watenga_flutter:
path: ../watenga-payment-network/packages/watenga-flutterTake a test payment
Use a test secret key (sk_test_…) from the developer console. Test keys never move money: use the test cards and wallet numbers.
import 'package:watenga_flutter/watenga.dart';
final client = WatengaClient(Platform.environment['WATENGA_SECRET_KEY']!); // sk_test_..., on your server
final checkout = await client.checkout.create({
'amount': '25.00',
'currency': 'USD',
'merchant_transaction_id': 'ORD-1001',
'return_url': 'https://shop.example/return',
});
print(checkout['payment_url']);Receive the webhook
Verify the Watenga-Signature header against the raw request body before you trust an event. While you rotate the endpoint's signing secret, pass both secrets: either one verifies. On your machine, watenga listen --forward-to <your URL> (the CLI) forwards test events signed with a session secret.
import 'package:watenga_flutter/watenga.dart';
try {
final event = constructEvent(rawBody, request.headers['watenga-signature']!, secret);
if (event['type'] == 'payment.capture.completed') {
// fulfil the order for event['data']['object']
}
} on WebhookSignatureException {
// answer 400
}Errors
Every failed call raises one error type with the stable code, the HTTP status, the request_id to quote to support, the field at fault (param) and the code's page in the error reference.
try {
await client.refunds.create('txn_...', {'amount': '5.00', 'reason': 'customer_request'});
} on WatengaException catch (e) {
print('${e.code} ${e.statusCode} ${e.param} ${e.requestId} ${e.docUrl}');
}Retries and idempotency
Network errors, 429 (after Retry-After) and 5xx answers are retried twice with backoff. Every write carries an Idempotency-Key, kept across those retries, so a retry is answered once and never applied twice; checkout.create uses checkout:<merchant_transaction_id>. See Idempotency.
// Every write gets an Idempotency-Key, kept across the SDK's own retries.
await client.refunds.create('txn_...', {'reason': 'duplicate'}, idempotencyKey: 'refund-ORD-1001');Pagination
Every list answers one page, { object: "list", data, has_more, next_cursor }; the auto-paginating form reads them all. See Pagination.
final page = await client.transactions.list({'status': 'completed', 'limit': 50});
await for (final txn in client.transactions.listAll({'status': 'completed'})) {
final t = Transaction.fromJson(txn);
print('${t.id} ${t.amount} ${t.currency}');
}Resources
The SDK sends Watenga-Version: 2026-10-29; fields use the API's names (snake_case) and money is a decimal string. The same method names in every language.
| Resource | Methods |
|---|---|
checkout | create, getStatus |
checkoutSessions | create, retrieve, expire |
transactions | list, listAll, retrieve, getMobileStatus |
refunds | create |
paymentLinks | create, list, listAll, retrieve, update, deactivate |
invoices | create, list, listAll, retrieve, update, send, voidInvoice |
quotations | create, list, listAll, retrieve, update, issue, convertToInvoice |
plans | create, list, listAll, retrieve, update |
subscriptions | create, list, listAll, retrieve, cancel, pause, resume, upcomingInvoice |
customers | create, list, listAll, retrieve, update |
paymentMethods | create, list, listAll, retrieve, delete, charge |
account | getBalance, getProfile, getApiKey |
constructEvent | webhook verification (a list of secrets while rotating) |
For payments inside a Flutter app, use the native payment sheet (watenga_flutter_ui) with a checkout session's client_secret created by your server.
Typed models (Transaction.fromJson, …) are generated from the OpenAPI document.
Refunds and payouts
refunds scope (Refunds). Payouts to your bank are dashboard-only, with two-step verification: they are not in the API or the SDKs.