khaime_flutter 0.1.0
khaime_flutter: ^0.1.0 copied to clipboard
Khaime Checkout SDK for Flutter. Accept payments globally with a single widget.
khaime_flutter #
Khaime Checkout SDK for Flutter. Accept payments globally with a single widget.
The Flutter counterpart to @khaime/react. Same token, same
callbacks, same "your app never branches on gateways" contract.
Installation #
dependencies:
khaime_flutter: ^0.1.0
Then run the platform setup below — Stripe's native PaymentSheet needs it.
Quick start #
import 'package:khaime_flutter/khaime_flutter.dart';
KhaimeCheckout(
token: token,
onSuccess: (result) {
debugPrint('Payment successful: ${result.reference ?? result.paymentIntentId}');
Navigator.of(context).pushReplacementNamed('/success');
},
onError: (error) => debugPrint('Payment failed: ${error.message}'),
onClose: () => debugPrint('Checkout dismissed'),
)
How it works #
- Your backend calls the Khaime API to create a payment intent.
- Khaime returns a
tokencontaining gateway configuration. - Pass the
tokentoKhaimeCheckout. - The widget renders the correct payment UI automatically.
Your Backend Khaime API
│ │
│ POST /payment/intent │
│ { amount, currency, ... } │
│ ─────────────────────────────►
│ │
│ { token: "eyJ..." } │
│ ◄─────────────────────────────
│ │
▼
KhaimeCheckout(token: token)
Create the intent on your server. The Khaime secret key must never ship inside the app bundle.
Supported payment gateways #
| Currency | Gateway | UI on Flutter |
|---|---|---|
| USD, EUR, GBP, CAD | Stripe | Native PaymentSheet (flutter_stripe) |
| NGN, ZAR, KES | Paystack | Full-screen in-app WebView |
| GHS | StartButton | Redirect to the system browser |
Routing comes from the token's payment_gateway field, so Khaime can move a
merchant between gateways without an app release.
Differences from the React SDK #
- Stripe presents a native modal PaymentSheet instead of an inline
PaymentElement. That is the supported mobile surface and it is what enables Apple Pay and Google Pay. - Paystack opens a full-screen route rather than a browser popup. The
hosted
authorization_urlis used when present; a token carrying only apublic_keyfalls back to Paystack's inline script inside the WebView. - Redirect gateways hand off to the system browser. The app cannot observe the result — confirm these payments from your backend via webhook.
Platform setup #
Android #
android/app/build.gradle:
android {
compileSdk 34
defaultConfig {
minSdk 21 // required by flutter_stripe
}
}
flutter_stripe requires the host activity to extend FlutterFragmentActivity.
In MainActivity.kt:
import io.flutter.embedding.android.FlutterFragmentActivity
class MainActivity : FlutterFragmentActivity()
Use an AppCompat theme in android/app/src/main/res/values/styles.xml:
<style name="LaunchTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
android/app/src/main/AndroidManifest.xml needs internet access, plus a
<queries> entry so url_launcher can resolve a browser on Android 11+:
<uses-permission android:name="android.permission.INTERNET" />
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
</queries>
iOS #
Set the platform to iOS 13 or newer in ios/Podfile:
platform :ios, '13.0'
Then cd ios && pod install.
For Apple Pay, add the Apple Pay capability in Xcode and pass your merchant identifier through Stripe's own configuration.
Return URL (required for redirect payment methods) #
If your PaymentIntent allows a method that leaves the app — Link, Cash App Pay, iDEAL, Klarna — Stripe needs a URL to come back to. Without one the PaymentSheet fails to present and the widget sits on "Processing...".
Register a custom scheme in ios/Runner/Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.example.myapp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
On Android, add the matching intent filter to the .MainActivity activity in
AndroidManifest.xml:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" />
</intent-filter>
Then pass it:
KhaimeCheckout(
token: token,
returnUrl: 'myapp://stripe-redirect',
)
Card-only PaymentIntents work without this. Check your intent's
payment_method_types — if it is anything beyond card, set a return URL.
Flutter 3.27+ with UIScene lifecycle (iOS) #
Recent Flutter iOS templates adopt the UIScene lifecycle: Info.plist carries
a UIApplicationSceneManifest and a SceneDelegate. Under UIScene the window
belongs to the scene, so UIApplication.shared.delegate.window is nil.
flutter_stripe still presents the PaymentSheet from there — Flutter logs
Plugin StripeIosPlugin uses deprecated application lifecycle events at
startup. The sheet is created and Stripe reports it as shown, but it lands on a
window that is not on screen. Nothing is visible, nothing is dismissed, and
presentPaymentSheet never returns, so checkout sits on "Processing..."
forever.
Until flutter_stripe supports UIScene, remove the manifest from
ios/Runner/Info.plist to fall back to the application lifecycle:
/usr/libexec/PlistBuddy -c "Delete :UIApplicationSceneManifest" ios/Runner/Info.plist
Check UIMainStoryboardFile is set to Main afterwards, and do a full
rebuild — plist changes do not survive hot restart. Flutter still supports the
application lifecycle, but it will not forever, so track the plugin's UIScene
support before relying on this long term.
Running the example #
cd example
flutter run # developer mode: paste a token by hand
With a backend that exposes POST /checkout returning { "token": "..." }:
flutter run --dart-define=MERCHANT_BACKEND_URL=https://your-store.example.com
The example walks the real shape of an integration — catalogue, buyer details,
a token minted server-side, then KhaimeCheckout — and falls back to a pasted
token when no backend is configured. To mint tokens for that fallback:
dart run tool/mint_test_token.dart --gateway stripe
dart run tool/mint_test_token.dart --gateway stripe \
--publishable-key pk_test_xxx --client-secret pi_xxx_secret_yyy
The first exercises routing and UI only; the second completes a real test payment.
API #
KhaimeCheckout #
| Parameter | Type | Default | Description |
|---|---|---|---|
token |
String |
required | Payment token from the Khaime API |
onSuccess |
void Function(PaymentResult)? |
— | Called on successful payment |
onError |
void Function(PaymentError)? |
— | Called on payment error |
onClose |
VoidCallback? |
— | Called when checkout is dismissed |
onReady |
VoidCallback? |
— | Called when checkout is ready for input |
productName |
String? |
token value | Overrides the product name |
productImage |
String? |
token value | Overrides the product image |
showOrderSummary |
bool |
true |
Show or hide the order summary card |
submitButtonText |
String? |
— | Custom button label |
returnUrl |
String? |
— | URL the gateway returns to after payment |
autoPresent |
bool |
false |
Open Stripe's PaymentSheet without a tap |
appearance |
KhaimeAppearance |
defaults | Colour and shape overrides |
invalidTokenBuilder |
WidgetBuilder? |
error text | UI shown for an undecodable token |
PaymentResult #
| Field | Type | Description |
|---|---|---|
success |
bool |
Whether the payment succeeded |
gateway |
PaymentGateway |
The gateway that processed it |
paymentIntentId |
String? |
Stripe PaymentIntent id |
reference |
String? |
Paystack transaction reference |
error |
PaymentError? |
Failure detail when unsuccessful |
KhaimeAppearance #
KhaimeCheckout(
token: token,
appearance: const KhaimeAppearance(
primaryColor: Color(0xFF6C2BD9),
surfaceColor: Color(0xFFF4F4F5),
borderRadius: 12,
buttonBorderRadius: 999,
),
)
Unset colours fall back to the active gateway's brand colour.
Advanced use #
The gateway widgets are exported for callers that decode the token themselves:
final data = decodePaymentToken(token);
if (data?.paymentGateway == PaymentGateway.paystack) {
return PaystackPayment(data: data!, onSuccess: handle);
}
decodePaymentToken only decodes the payload and checks exp. The signature is
verified server-side by the Khaime API, so a token that decodes locally is not
proof of anything — always confirm payments from your backend.
Verifying payments #
Client callbacks tell you what the payer saw, not what settled. Treat
onSuccess as a UI signal and confirm the payment from your backend against the
Khaime webhook before fulfilling an order. For redirect gateways the webhook is
the only signal you get.
License #
MIT
For AI agents #
llms.txt in this repository is a condensed integration reference: the
rules that prevent the common mistakes, the platform setup, the error-to-fix
table, and the backend calls that produce a token — including where the
published docs are wrong.