PaymentResult — while your backend owns credentials, session creation, and payment verification.0.1.0-beta.x is a security-driven rewrite — every public API from 0.0.1 was replaced. Validate the flow against the current Bank Alfalah sandbox before going live. Upgrading? See Migration below.12dependencies: bank_alfalah_payment: ^0.1.0-beta.1
webview_flutter; only other dependency is meta). There is nothing to configure in the app — no merchant IDs, no keys, no gateway URLs. Your Bank Alfalah credentials (merchant ID, password, username, hash, store ID, hash keys, registered HTTPS return URL) live in your server's secret store.Flutter app ────► Your backend ────► Bank Alfalah │ │ │ │ 1. create │ 2. gateway │ │ session │ transaction │ ◄─────────────────┘ │ │ 3. open bank-hosted checkout │ ├────────────────────────────────────► │ 4. redirect to return URL │ ◄────────────────────────────────────┤ │ 5. verify │ server-to- │ ├────────────────►│ server ─────►│ │ 6. PaymentCompleted (verified) │
PaymentCompleted.PaymentResult per checkout); the checkout screen owns navigation and pops itself exactly once; a cancellable 5-minute timeout (configurable); non-main-frame resource errors don't abort checkout.12345678910111213class MyBackendSessionProvider implements BankAlfalahSessionProvider { @override Future<CheckoutSession> createSession(CheckoutRequest request) async => CheckoutSession.fromJson( await api.post('/payments/create', request.toJson()), ); @override Future<PaymentVerification> verifyPayment(String transactionId) async => PaymentVerification.fromJson( await api.get('/payments/$transactionId/verify'), ); }
Money.pkr() — validated, minor-units based:12345678910111213final bankAlfalah = BankAlfalahPayment( environment: BankAlfalahEnvironment.sandbox, sessionProvider: MyBackendSessionProvider(), ); final result = await bankAlfalah.startCheckout( context: context, request: CheckoutRequest( amount: Money.pkr(2500), orderId: 'ORDER-123', customer: Customer(email: 'user@example.com', phone: '03001234567'), ), );
123456789switch (result) { case PaymentCompleted(): // verified paid — fulfill the order case PaymentPending(): // wait for backend/webhook confirmation case PaymentFailed(): // gateway declined case PaymentCancelled(): // user closed checkout case PaymentTimedOut(): // checkout took too long case PaymentVerificationFailed(): // redirect said OK, backend said no case PaymentError(): // network / WebView / backend error }
BankAlfalahSessionProvider:POST /payments/create — receives CheckoutRequest.toJson(); creates the Bank Alfalah transaction (hashing, credentials, gateway calls all happen here) and responds:123456{ "transactionId": "TX-123", "checkoutUrl": "https://payments.bankalfalah.com/...", "returnUrl": "https://yourserver.example/payments/return", "formFields": { "optional": "POST form hand-off fields" } }
GET /payments/{transactionId}/verify — checks the transaction server-to-server and responds:12345678{ "status": "verified | pending | failed", "transactionId": "TX-123", "orderId": "ORDER-123", "gatewayReference": "...", "responseCode": "00", "message": "..." }
CheckoutSession.fromJson / PaymentVerification.fromJson parse these shapes directly. status maps to PaymentCompleted / PaymentPending / PaymentVerificationFailed. When formFields is present the SDK performs a POST-form hand-off with every name and value HTML-escaped. Gateway endpoints are chosen by your backend, never hardcoded in the app.| Result | Meaning | What to do |
PaymentCompleted | Redirect + backend verification confirmed | Fulfill the order |
PaymentPending | Verification returned pending | Poll backend / await webhook |
PaymentFailed | Gateway declined | Offer retry |
PaymentCancelled | User closed checkout | Return to cart |
PaymentTimedOut | Exceeded timeout (default 5 min) | Offer retry |
PaymentVerificationFailed | Redirect OK, backend said no | Do not fulfill; investigate |
PaymentError | Network / WebView / backend error | Log the typed exception |
BankAlfalahException subtype: ConfigurationException, NetworkException, GatewayException, RedirectException, VerificationException, TimeoutException (each prefixed BankAlfalah). Exceptions thrown by your provider surface as PaymentError — startCheckout always resolves with exactly one result.PaymentLifecycleObserver(onPaymentStarted:, onCheckoutOpened:, onRedirectReceived:, onVerificationStarted:, onCompleted:). The PaymentLogger is off by default and redacts credential-like keys.environment: BankAlfalahEnvironment.sandbox and point your backend at the Bank Alfalah sandbox gateway — switching to production is a backend config change, not an app release. The bundled example app includes a MockSessionProvider so you can exercise every UI state with no credentials at all.createSession and verifyPayment implemented on your backend.PaymentCompleted (or a server-side webhook), never on a redirect alone.PaymentLogger.enabled is false in release builds.PaymentPending handled (poll your backend / webhook).0.0.1 required merchant credentials in the app and decided success from a URL substring — both security defects, so the API was deliberately broken:| 0.0.1 | 0.1.0 |
BankAlfalahConfig with credentials in the app | Gone — credentials on your backend |
| Client-side gateway request + hash | Backend session via createSession |
Success = URL contains RC-00 | Strict return-URI match + mandatory verifyPayment |
PaymentRequest(amount: "100") (string, unused) | CheckoutRequest(amount: Money.pkr(100), ...) |
| Status enum + nullable fields | Sealed PaymentResult, seven states |
| Double-fire callbacks / double pops | Exactly-once completion |
Full-payload print() logging | Off-by-default redacting PaymentLogger |
BankAlfalahConfig and every credential string (and rotate anything that shipped) → implement the provider → replace initiatePayment with startCheckout → fulfill only on PaymentCompleted. Full guide: MIGRATION.md.12345678910111213141516171819202122232425262728293031323334# bank_alfalah_payment — agent integration instructions Add Bank Alfalah payments to this Flutter app using bank_alfalah_payment ^0.1.0-beta.1 (pub.dev). Follow these rules exactly: 1. NEVER put Bank Alfalah merchant credentials (merchant ID, password, username, hash, store ID, keys) in Flutter code, config, or env files shipped with the app. They belong on the backend only. 2. Add to pubspec.yaml: bank_alfalah_payment: ^0.1.0-beta.1 3. Implement `BankAlfalahSessionProvider` with two backend calls: - createSession(CheckoutRequest) -> POST /payments/create, parse with CheckoutSession.fromJson. Backend returns {transactionId, checkoutUrl, returnUrl, formFields?}. - verifyPayment(transactionId) -> GET /payments/{id}/verify, parse with PaymentVerification.fromJson. Backend returns {status: "verified|pending|failed", transactionId, orderId, ...}. 4. Create the entry point once: BankAlfalahPayment(environment: BankAlfalahEnvironment.sandbox, sessionProvider: MyBackendSessionProvider()) 5. Start checkout with typed money: startCheckout(context: context, request: CheckoutRequest( amount: Money.pkr(2500), orderId: 'ORDER-123', customer: Customer(email: ..., phone: ...))) 6. Handle the sealed PaymentResult exhaustively: PaymentCompleted, PaymentPending, PaymentFailed, PaymentCancelled, PaymentTimedOut, PaymentVerificationFailed, PaymentError. 7. Fulfill orders ONLY on PaymentCompleted (or a server-side webhook), never on a redirect alone. Handle PaymentPending by polling the backend. 8. If the backend endpoints don't exist yet, scaffold them server-side (all hashing/credentials/gateway calls there) before wiring the app. 9. Keep PaymentLogger.enabled = false in release builds. Docs: https://docs.aliarain.com/bank-alfalah-payment
llms.txt index.