asciisd/knet
Laravel package for integrating KNET payments into PHP applications. It simplifies payment initiation, callback handling, and response verification, giving developers a straightforward way to accept Kuwaiti debit card payments.
Architecture fit
KnetPaymentService is a delegating facade over four narrow contracts (CreatesPayments, InquiresPayments, RefundsPayments, TransactionRepository) plus an EncryptsPayload abstraction for AES-128-CBC. That is a genuinely Laravel-native shape — service-container bindings, a HasKnet trait for the billable model, Eloquent-backed persistence, and event-driven post-payment hooks. It will slot into an existing Laravel monolith or modular domain without architectural surgery.trandata payload. For a payments TPM this is the single most valuable property of the package.knet_transactions row per attempt, result/paid state, UDF1–UDF5 as a passthrough correlation channel, and a distinct refund row (action = 2, original_transaction_id) rather than mutating the original. That gives us an audit trail we can reconcile against the acquirer.Integration feasibility
composer require, php artisan knet:install, set six env vars, add the trait to User, and the routes are auto-registered. The knet:check command gives us a credential-validation gate before we write any business logic.kpaytest.com.kw) and is reachable, but it is a bank-run environment with its own availability and behavior quirks. We should budget for test-harness friction — declined-card matrices, error-code coverage, and callback timing are not fully documented in the package.10.000), currency is fixed to 414, trackid must be unique per transaction, and KFAST tokens are exactly 8 numeric digits with uniqueness per customer. These are all silently wrong if violated, so they belong in our request validation, not in the package.Technical risk
/knet/response sits behind signature-verification middleware. We must verify exactly what is verified — decryption success is not the same as origin authentication or replay protection. If the resource key is symmetric and shared, a captured payload is replayable; idempotency must be enforced by us on trackid and paymentid.KnetPaymentSucceeded is dispatched inside the callback request cycle. If our listener throws, we lose fulfillment while the user is being redirected. Anything financially material (order state, entitlements, ledger entries) must be idempotent and re-drivable, ideally via a queued listener plus an inquireAndUpdateTransaction reconciliation job.knet_transactions with action = 2. Every downstream query, report, and finance export must filter on action or it will double-count. That is an easy, expensive mistake./knet/* can collide with application routing and must be reviewed for CSRF exemption, rate limiting, and WAF allowlisting. Knet::ignoreRoutes() exists but then we own the wiring.Key questions
trackid uniqueness enforced by a database unique index in the shipped migration, or only by convention?KnetException?Stack fit
User-as-billable model but supports KnetTransaction::useCustomerModel() for a custom payer entity — relevant if we bill organizations or accounts rather than users.inquireAndUpdateTransaction for any transaction stuck in INITIATED/PENDING past a threshold.TransactionRepository contract is our seam for read paths; keeping reporting and finance queries behind it prevents them from coupling to knet_transactions columns directly.Migration path
payments table, we introduce KnetTransaction as a provider-specific detail and project it into our canonical ledger via the events — do not let KNET column names (amt, auth, ref, tranid, result) leak into the domain model.CreatesPayments) let us swap or decorate without touching controllers. We should type-hint the narrowest interface we need from day one to keep that door open.Compatibility
KNET_TRANSPORT_ID, KNET_TRANSPORT_PASSWORD, KNET_RESOURCE_KEY, KNET_REDIRECT_URL, KNET_CURRENCY, KNET_LANGUAGE, KNET_DEBUG, plus KNET_PATH). All must live in secrets management, not .env in the repo, and must be rotated on a schedule with a documented procedure.414) and amounts are 3-decimal. Any multi-currency ambition requires a separate gateway decision; treat this package as Kuwait-only and say so in the product spec.payWithKfast will fail at the gateway, not in the package. Gate the feature behind a config flag and a business decision, not a code path.Sequencing
knet:install, exercise a full sandbox payment and callback end-to-end, capture the real request/response payloads, and document actual gateway behavior against the docs.trackid, 8-digit KFAST token, currency 414) and build fixtures for success/decline/abandon/refund/inquiry.KnetPaymentSucceeded/KnetPaymentFailed listeners, idempotency keys on trackid + paymentid.customer_number or a stable external ID).KnetResponseReceived/KnetResponseHandled/KnetTransactionHasErrors, define alerting on failed callbacks, then production terminal switch with a small canary cohort.Maintenance
Support
Scaling
knet_transactions grows monotonically and is never pruned by the package. Plan indexes on trackid (unique), paymentid, result, paid, and created_at, and a retention/archival policy — finance and audit typically want multi-year retention, so archive to cold storage rather than delete.trackid can legitimately arrive more than once. Enforce idempotency with a database-level constraint and an upsert-style handler, not a read-then-write check.Failure modes
trackid/paymentid plus a state-machine guard that only transitions from a pending state.action filtering in every report and a ledger projection that separates debits from credits.414 currency reaching the gateway. Mitigation: validation at the boundary plus a lint-style test.INITIATED rows indefinitely. Mitigation: age-based alerting and an operator runbook to force inquiry.Ramp-up
How can I help you explore Laravel packages today?