esaalat 0.1.0
esaalat: ^0.1.0 copied to clipboard
Esaalat hosted checkout for Flutter — create a checkout, let the buyer pay in a WebView, and receive a verified result.
esaalat #
Flutter SDK for the Esaalat hosted checkout. Create a checkout, let the buyer
pick a wallet and enter their transfer reference in a WebView, and receive the
same terminal result the web SDK (esaalat) resolves.
final checkout = EsaalatCheckout(publishableKey: 'pk_live_...');
final result = await checkout.open(
context,
amount: '5000.00',
currency: 'YER',
externalOrderId: 'order-123',
);
if (result.isVerified) {
// Show the buyer a success state. Confirm the order server-side first.
}
Install #
flutter pub add esaalat
Until the package is on pub.dev, use a path dependency:
dependencies:
esaalat:
path: ../sdk/flutter
Android needs the android.permission.INTERNET permission (any app that talks
to an API already declares it). The package uses webview_flutter; see its
platform requirements (Android 5.0+, iOS 12+).
Open a checkout #
final checkout = EsaalatCheckout(
publishableKey: 'pk_live_...', // Dashboard → API Keys
locale: EsaalatLocale.ar, // ar (default) or en
);
final result = await checkout.open(
context, // used to push the checkout route
amount: '5000.00',
externalOrderId: 'order-123',
);
open() returns a CheckoutResult:
status |
Meaning |
|---|---|
CheckoutResultStatus.verified |
A wallet alert matched the order and the amount was verified. |
CheckoutResultStatus.wrongAmount |
A payment matched but the amount differs; paidAmount says what arrived. |
CheckoutResultStatus.expired |
The buyer did not finish before the payment window closed. |
CheckoutResultStatus.cancelled |
The buyer closed the checkout, or the intent was cancelled. |
CheckoutResultStatus.failed |
The portal could not load a valid session. |
reason carries the web SDK's reasons (dismissed, intent_cancelled,
session_expired, session_not_found, ...).
Session creation, timeout and API failures throw EsaalatCheckoutError
with a code of configuration, network, api, session, unavailable or
aborted; a checkout that starts but ends badly is a result, not an exception.
Constructor #
| Option | Default | Notes |
|---|---|---|
publishableKey |
— | Required. Safe to embed: it can only create sessions. |
locale |
EsaalatLocale.ar |
Portal language. |
apiBaseUrl |
https://api.esaalat.com/api/v1 |
Override for sandbox/local. |
portalBaseUrl |
https://pay.esaalat.com |
Fallback only; the API's portal_url wins. |
httpClient |
http.Client() |
Custom transport (tests, proxies). |
dispose() |
— | Releases the SDK's HTTP client. |
open() options #
| Option | Notes |
|---|---|
amount |
Order total (decimal string). Required unless items or sessionToken is used. |
items |
List<CheckoutItem>; determines the amount. |
currency |
Defaults to YER server-side. |
externalOrderId |
Your order reference, echoed back in the result. |
notes |
Internal merchant notes. |
wallet |
Optional preselected receiving wallet id. |
sessionToken |
Open a server-created session instead of creating one. |
portalUrl |
Portal URL for that session (optional; the SDK builds it). |
idempotencyKey |
Stable retry key; generated per call when omitted. |
locale |
Overrides the client locale for this checkout. |
simulateResult |
Demo-only outcome; refused unless the key allows simulation. |
pollInterval |
Result polling cadence. Defaults to 4 seconds. |
open() needs a mounted BuildContext and pushes a full-screen route. The
future completes when the route closes; if the buyer closes it, the result is
cancelled / dismissed.
Lock the amount on your server #
The publishable key can start a session with any amount because the amount lives in the app. If it must be authoritative, create the session from your server with the client JWT and hand only its token to the app:
POST /api/v1/checkout/merchant/sessions/
Authorization: Bearer <access_token>
Content-Type: application/json
{"amount": "5000.00", "external_order_id": "order-123"}
await checkout.open(
context,
sessionToken: session.sessionToken,
portalUrl: session.portalUrl,
);
The token is a capability: do not log it, and give it only to the buyer who is paying that order.
How the WebView is resolved #
The portal is loaded top-level, exactly as a standalone visitor sees it. A
WebView has no window.parent / window.opener, so the web SDK's iframe
handshake does not apply — and a wrapper iframe or an injected shim is not
reliable (iOS gives loadHtmlString an opaque origin, and webview_flutter
has no document-start injection). The SDK therefore uses the documented native
path: it polls GET /checkout/public/sessions/{token} and applies the same
result mapping as the portal. See CONTRACT.md §7 for the full
decision and its consequences.
The route closes after a verified payment (1.8 s, so the buyer sees the success
screen) or immediately for other terminal states. Network hiccups while polling
are ignored; a session the backend does not know settles as
failed / session_not_found.
Local development #
# backend
CHECKOUT_PORTAL_URL=http://localhost:3000
# web app
cd fe/esaalat && npm run dev
EsaalatCheckout(
publishableKey: 'pk_...',
apiBaseUrl: 'http://10.0.2.2:8000/api/v1', // Android emulator
portalBaseUrl: 'http://10.0.2.2:3000',
)
Cleartext HTTP is blocked by default on Android 9+; enable it for the debug
build (android:usesCleartextTraffic="true") or run the API behind HTTPS.
Demo mode #
simulateResult asks the backend to resolve the session with a chosen outcome
(verified, underpaid, overpaid, cancelled, expired) as soon as a
wallet is selected. It only works for publishable keys whose client has
allow_checkout_simulation enabled — a normal production key cannot create a
simulated session.
Fulfilment #
The result is for the buyer's UI. Confirm the order with the signed
payment.verified webhook (or GET /api/v1/merchant/intents/{id}/) before
shipping anything.
Example #
See example/ for a complete app with an amount field and a result
card. Generate its platform folders once with flutter create . inside
example/, then run it with your publishable key:
cd example
flutter create . --platforms=android,ios
flutter run --dart-define=ESAALAT_PUBLISHABLE_KEY=pk_...
License #
MIT © Esaalat