sabitahmad/laravel-bkash
Integrate bKash payments into Laravel with full API v1.2.0 support: tokenized and regular checkout, agreements, refunds, queries, token refresh, transaction logging, events, and sandbox/production modes.
Architecture fit
The package follows Laravel's standard package conventions: a service provider with auto-discovery, a Bkash facade, a publishable config file, publishable migrations for transaction logging, and domain events (PaymentCompleted, PaymentFailed, AgreementCreated). That is the idiomatic shape for a Laravel integration and means it can be adopted without architectural surgery in a conventional Laravel monolith or modular monolith.
It is a thin SDK wrapper, not a payment orchestration layer. It maps 1:1 onto bKash's Tokenized Checkout API v1.2.0 (create/execute/query/refund/search for payments; create/execute/query/cancel for agreements; token grant/refresh), and returns typed response objects (PaymentResponse, AgreementResponse, RefundResponse, QueryResponse, SearchResponse) with a consistent isSuccess()/hasError()/getRawData() surface. The response-method tables in the README are the strongest part of the artifact — they make the contract with bKash legible without reading source.
What it deliberately does not provide: an order/cart abstraction, idempotency keys, webhook signature verification, reconciliation jobs, ledgering, multi-tenant credential resolution, or a state machine for the create→redirect→callback→execute lifecycle. Those are ours to build. The helpers/BkashHelper and the examples/BkashController.php cover the happy path only.
Integration feasibility
Low friction to a running sandbox: composer require, publish config + migrations, set six env vars, get a redirect URL back. A spike should be achievable in 1–3 engineering days including a sandbox merchant account.
Caveats we must verify before committing:
illuminate/support constraints and whether the service provider uses anything deprecated.https://tokenized.pay.bka.sh/v1.2.0-beta/... as the production base URL. bKash's own naming may legitimately retain the -beta segment in the tokenized product line, but a TPM cannot accept that on faith — this must be confirmed against current bKash merchant documentation and, ideally, bKash merchant support in writing.callbackURL posted back with paymentID). There is no signature/HMAC verification in the package. Correctness must be established by calling queryPayment/executePayment server-side before trusting any status — the README's callback example does call executePayment, which is the right instinct, but it is a documentation pattern, not an enforced guarantee.Technical risk
BKASH_RETRY_ATTEMPTS and BKASH_RETRY_DELAY; we need to understand which calls are retried and confirm bKash's idempotency behaviour per endpoint.BKASH_MIN_AMOUNT, BKASH_MAX_AMOUNT, BKASH_CURRENCY=BDT). Enforcement at the package layer is not a substitute for enforcement at the order layer.Key questions for the maintainer / our own engineering
-beta in the production base URL correct and current?Stack fit
Good fit if we are on Laravel 8–10 with PHP 8.0–8.2 and a single bKash merchant account for a Bangladesh-facing storefront or subscription product. The package's two headline flows map cleanly to two of our likely use cases:
Poor fit if we need multi-merchant routing, marketplace/split settlement, non-BDT presentment, or full payment abstraction across multiple gateways. In that case the package should be treated as a reference implementation and a source of bKash API semantics, not as a dependency.
Migration path
Do not call Bkash:: from controllers or domain services. First move:
PaymentGateway port in our domain: createIntent, confirm, refund, query, cancel, plus our own value objects. This is where idempotency keys, correlation IDs, and status normalisation live.BkashGatewayAdapter that delegates to the package facade and translates its response objects into our value objects. All package-specific types stay behind this boundary.payments and payment_attempts tables with our own state machine. Use the package's transaction log table as a diagnostic/audit side-channel, not as the system of record.queryPayment (or executePayment exactly once, guarded), and transitions state idempotently.If the adapter boundary is in place, replacing the package with direct Guzzle calls or a fork is a contained change — days, not a quarter.
Compatibility
composer.json ("sabitahmad/laravel-bkash": "x.y.z"), not a caret range. With 0 dependents and no visible CI, unvetted patch releases are not a risk worth accepting.illuminate/* constraints resolve against our Laravel version in a scratch branch before any product work.php artisan config:cache) — packages that read env() outside the config file break in cached deploys.BKASH_SANDBOX is driven strictly from config and defaults to true, and add a deploy-time assertion that production cannot boot with sandbox credentials.Sequencing
PaymentGateway port and adapter; add idempotency, callback verification via query, structured logging with correlation IDs, and sandbox integration tests in CI.searchTransaction / queryPayment sweep over unsettled attempts), the refund runbook, and the token-refresh failure path. Deliberately test: expired token, timeout after bKash accepted, lost callback, duplicate callback, refund partial failure.executePayment latency, and callback-to-settlement lag.Maintenance
Support
Scaling
BKASH_TOKEN_CACHE_TTL=3300, ~55 min) and auto-refreshed. With multiple app instances on a shared cache store this is fine; with file/array cache per instance it means N token grants per hour and avoidable 401s. Mandate Redis or a shared cache in production.Failure modes to instrument and runbook
| Failure | Symptom | Mitigation |
|---|---|---|
| Token expiry / refresh failure | 401s on all operations | Shared cache; alert on refresh failures; retry with backoff |
| Timeout after bKash accepted | Order stuck pending, money possibly taken |
Reconciliation sweep via queryPayment; never blind-retry createPayment |
| Lost callback (user closes tab) | Order never transitions | Reconciliation job; show "check status" UX, not a hard failure |
| Duplicate callback delivery | Double execution / double fulfilment | Idempotent handler keyed on paymentID; fulfil exactly once |
| Retry-induced duplicate charge | Two trxIds for one order |
Idempotency at our order layer; verify BKASH_RETRY_ATTEMPTS semantics |
| Amount/currency mismatch | Rejected or wrong-charge | Validate at order layer, not only package config |
| Refund partial failure | Refunded in our system, not in bKash | Two-phase refund with refundStatus confirmation before closing the ticket |
| Sandbox credentials in production | Test transactions on live traffic | Deploy-time assertion; alert on BKASH_SANDBOX=true in prod |
Ramp-up
Overall TPM read: acceptable as an accelerator behind a strict adapter boundary for a single-merchant BDT launch; not acceptable as an unexamined direct dependency for a money-critical, high-volume, or multi-tenant payments platform. The decision is defensible only if we fund the fork, the adapter layer, and the sandbox test suite as part of the initial integration — roughly 2–4 engineer-weeks beyond the spike.
How can I help you explore Laravel packages today?