Flutter SDK
One Dart package for Android and iOS. It wraps the native YallaPay SDKs, so you get the same sheet, the same results and the same recovery behaviour without writing Kotlin or Swift. Three calls: configure, pay, and a check on launch for interrupted payments.
On this page
Before you start, your server must be able to create sessions and your dashboard must have a return target registered. If you have not done that yet, read Dashboard setup and Server integration first.
Requirements
| Flutter / Dart | Flutter 3.19 or newer, Dart 3.3 or newer (the result types use sealed classes and pattern matching). |
| Android | minSdk 24, compileSdk 34, Kotlin 2.0, Android Gradle Plugin 8.x. |
| iOS | 15.0 or newer, Swift 5.9, CocoaPods. |
| Web, desktop | Not supported. The calls return a failed result on those platforms; use the hosted checkout URL directly there. |
The plugin wraps the native iOS SDK, which relies on system web-authentication sessions. If your app currently targets iOS 13 or 14, pod install will refuse: raise platform :ios in your Podfile and the deployment target in Xcode to 15.0.
Install
# pubspec.yaml
dependencies:
yallapay_flutter: ^0.1.0
Then flutter pub get. The native SDKs are pulled in by the plugin's own Gradle and CocoaPods configuration; you add nothing else.
While the SDK is in early access the package is provided directly by YallaPay rather than from pub.dev. Email [email protected] for repository access, then depend on it with a git: or path: dependency; the package name stays yallapay_flutter, so nothing in your code changes later.
Declare your return scheme
Checkout returns the customer to your app through a custom URL scheme. Choose one unique to your brand and declare it on each platform. This is the only platform-specific step.
Android — android/app/build.gradle:
android {
defaultConfig {
manifestPlaceholders = [yallapayReturnScheme: "myapp"]
}
}
Kotlin DSL build files use manifestPlaceholders["yallapayReturnScheme"] = "myapp". The native SDK's manifest registers the receiving activity; you do not edit AndroidManifest.xml.
iOS — ios/Runner/Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
Register the same scheme against your merchant account under Apps & Return URLs, and use myapp://yallapay/return as the return URL when your server creates sessions. Unregistered schemes are rejected at session creation. The host part is always yallapay.
Configure
Await configure once, before the first payment. The natural place is main():
import 'package:flutter/foundation.dart';
import 'package:yallapay_flutter/yallapay_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await YallaPay.configure(
publishableKey: kReleaseMode ? 'pk_live_…' : 'pk_test_…',
returnUrl: 'myapp://yallapay/return',
environment: kReleaseMode ? YallaPayEnvironment.live : YallaPayEnvironment.sandbox,
);
runApp(const MyApp());
}
| publishableKey | Your pk_live_ or pk_test_ key. A key beginning with sk_ throws an ArgumentError. |
| returnUrl | Your scheme plus ://yallapay/return. |
| environment | YallaPayEnvironment.live (default) or YallaPayEnvironment.sandbox. |
| baseUrl | Optional host override for debug builds against your own deployment. Leave null in anything you ship. |
Take a payment
Ask your server for a session id, then hand it to the SDK. The future completes when the customer finishes, cancels, or something fails.
Future<void> payOrder(String orderId) async {
final sessionId = await api.createPaymentSession(orderId); // your backend
final result = await YallaPay.pay(sessionId);
switch (result) {
case PaymentCompleted(:final transactionId, :final paymentMethod):
showPaid(transactionId, paymentMethod);
case PaymentCanceled():
showCancelled();
case PaymentFailed(:final error):
showError(error);
}
}
What pay does, in order:
- Reads the session with your publishable key. If it is already paid, expired or failed, returns straight away without opening anything.
- Remembers the session id on the device, so an interrupted payment can be recovered on the next launch.
- Opens checkout: a bottom-sheet Custom Tab on Android, a system web sheet on iOS.
- Waits for the return deep link or for the customer to close the sheet.
- Reads the session again and returns a result based on what the API says, never on the redirect.
- Forgets the remembered session id.
Call it from a widget that is mounted. Disable your Pay button while the future is pending; a second call during a payment is not supported.
Handling the result
| PaymentCompleted | YallaPay confirmed the session is paid. Fields: sessionId, transactionId, paymentMethod (PaymentMethod.card, .applePay, .googlePay, .unknown). Show a receipt and refresh the order from your server. |
| PaymentCanceled | The customer closed the sheet without paying. Nothing was charged. The session stays open until it expires, so the customer can try again with the same id. |
| PaymentFailed | Carries a YallaPayError with a code and a message safe to show. Act on the code, see the table below. |
void showError(YallaPayError error) {
if (error.isIndeterminate) {
// A network error after checkout: the payment may have succeeded. Re-check, don't say "failed".
scheduleRecheck();
return;
}
switch (error.code) {
case YallaPayErrorCode.paymentFailed: showSnack('Your card was declined. Please try another.');
case YallaPayErrorCode.sessionExpired: startNewSession();
default: showSnack(error.message);
}
}
Whatever the app shows, ship the order from your server on the webhook. See Webhooks & fulfilment.
Interrupted payments
Both operating systems can terminate your app while the customer is in the checkout sheet. When that happens the future returned by pay is gone, and the customer may have paid. The plugin keeps the in-flight session id on the device for this case. On every cold start, check for one and resolve it:
// After configure(), on every launch final pending = await YallaPay.pendingSessionId(); if (pending != null) { final result = await YallaPay.sessionStatus(pending); if (result case PaymentCompleted(:final transactionId)) { showPaid(transactionId); // and refresh the order from your server } }
pendingSessionId()returns the interrupted session id, or null on almost every launch. It never throws, even on platforms without the plugin.sessionStatusreads the session from the API and returns the same three results aspay, without opening anything. It also clears the pending id.
Four lines that run in milliseconds when nothing is pending, and that turn a lost payment into a receipt. Skipping this loses payments.
What the customer sees
Checkout is a YallaPay-hosted page. On Android it appears as a Chrome Custom Tab in bottom-sheet form, coloured with your app's primary colour, draggable to full height. On iOS it appears as a system web-authentication sheet. In both cases the customer sees your business name, the purpose, the amount, Apple Pay or Google Pay where supported, and a card form. Card numbers are entered on the YallaPay page, never in your app, and 3-D Secure happens inside the same sheet. A small line shows the YallaPay domain; that is a platform security requirement.
Error codes
| Code | Meaning and what to do |
|---|---|
| invalidKey | The publishable key was missing, malformed, for the wrong environment, or not accepted. Fix the key or the environment. |
| sessionNotFound | No such session for this account. Usually created on the other environment. |
| sessionExpired | The session passed its expiry before the customer paid. Ask your server for a new one. |
| paymentFailed | The customer reached checkout and the payment did not succeed. Offer to try again. |
| presentationFailed | Checkout could not be presented: no browser on Android, or no window yet on iOS. |
| network | Could not reach YallaPay. The payment may still have succeeded. error.isIndeterminate is true for exactly this code. Re-check with sessionStatus. |
| unknown | YallaPay returned something the SDK does not recognise, or the plugin is not available on this platform. Show the message. |
Reference
| YallaPay.configure({publishableKey, returnUrl, environment, baseUrl}) | Once, before the first payment. Throws on a secret key. |
| YallaPay.pay(sessionId) → Future<PaymentResult> | Presents checkout and resolves the outcome from the API. |
| YallaPay.sessionStatus(sessionId) → Future<PaymentResult> | Reads a session without presenting anything. Clears the pending id. |
| YallaPay.pendingSessionId() → Future<String?> | The session id of an interrupted payment, or null. Never throws. |
| PaymentResult | PaymentCompleted(sessionId, transactionId, paymentMethod), PaymentCanceled(), PaymentFailed(error) |
| PaymentMethod | card, applePay, googlePay, unknown |
| YallaPayEnvironment | live, sandbox |
| YallaPayError | code, message, isIndeterminate |
There is deliberately no method to create a session. Creating one needs your secret key, and a secret key inside an app bundle is a secret key in everyone's hands.
Supported platforms
| Android | minSdk 24. Bottom-sheet Custom Tab. |
| iOS | 15.0 or newer. System web-authentication sheet. |
| Web, macOS, Windows, Linux | Not supported. pay returns PaymentFailed(unknown) with a message saying so; open the session's checkout URL in a browser instead. |
Sample app
The package includes a one-screen example under example/: a text field for a session id, a Pay button and the launch-time recovery check. Set your publishable key in example/lib/main.dart, run flutter run on an emulator or simulator, paste a session id created against the same environment, and press Pay.
Troubleshooting
| Android build fails mentioning yallapayReturnScheme | The manifest placeholder is missing from android/app/build.gradle. |
| pod install fails on the deployment target | Raise platform :ios in ios/Podfile and the deployment target in Xcode to 15.0, then run pod install again. |
| pay returns PaymentFailed(unknown) saying YallaPay is not available | You are running on web or desktop, or the plugin was added without a full rebuild. Stop the app and run flutter run again; hot reload does not load native plugins. |
| An assertion says YallaPay is not configured | Await configure before calling pay. Put it in main() before runApp. |
| The customer paid but pay returned PaymentCanceled | The scheme in the Android placeholder, the iOS Info.plist, the dashboard and the session's return URL are not all identical. |
General problems, test cards and the go-live checklist are on Sandbox & go-live.