| name | knet-development |
| description | Build and work with KNET payment gateway integration, including payment initiation, callback handling, refunds, and event-driven payment flows. |
KNET Payment Development
When to use this skill
Use this skill when:
- Integrating KNET payments into a Laravel application
- Creating payment flows that redirect users to the KNET gateway
- Handling KNET payment callbacks and response processing
- Implementing refunds (full or partial) for KNET transactions
- Querying transaction status from the KNET gateway
- Listening to payment events to trigger business logic
- Working with the
knet_transactions table or KnetTransaction model
Package Overview
asciisd/knet provides a fluent Laravel interface to Kuwait's KNET payment gateway. It handles AES-encrypted communication, payment initiation, callback verification, transaction inquiry, and refunds.
Architecture
The package follows SOLID principles with focused interfaces:
CreatesPayments — payment creation and response handling
InquiresPayments — transaction status inquiry
RefundsPayments — full and partial refunds
TransactionRepository — data access abstraction
EncryptsPayload — encryption abstraction (wraps AES-128-CBC)
KnetPaymentService is a pure delegating facade that implements all three payment interfaces. Prefer depending on the narrower interface when only a subset of functionality is needed.
Setup
Environment Variables
KNET_TRANSPORT_ID=your_transport_id
KNET_TRANSPORT_PASSWORD=your_password
KNET_RESOURCE_KEY=your_resource_key
KNET_REDIRECT_URL=/payment/success
KNET_CURRENCY=414
KNET_LANGUAGE=EN
KNET_DEBUG=false
Add the Trait
Add HasKnet to any model that can make payments (typically User):
use Asciisd\Knet\HasKnet;
class User extends Authenticatable
{
use HasKnet;
}
This adds:
pay(float $amount, array $options = []): KnetTransaction — create a payment
payWithKfast(float $amount, array $options = []): KnetTransaction — KFAST faster checkout (auto-generates 8-digit token)
refund(KnetTransaction $transaction, ?float $amount = null): array — full or partial refund
kfastToken(): string — overridable 8-digit numeric KFAST token generator
knetTransactions(): HasMany — relationship to all transactions
Creating Payments
Via the Trait
$transaction = $user->pay(10.000, [
'udf1' => 'order_123',
'udf2' => 'product_subscription',
]);
return redirect($transaction->url);
Via Service Injection
use Asciisd\Knet\Services\KnetPaymentService;
public function checkout(KnetPaymentService $paymentService)
{
$transaction = $paymentService->createPayment(
user: auth()->user(),
amount: 25.500,
options: [
'udf1' => 'invoice_789',
'trackid' => 'custom-track-id', // optional, auto-generated if omitted
]
);
return redirect($transaction->url);
}
Via Focused Interface
If you only need payment creation, type-hint the narrower interface:
use Asciisd\Knet\Contracts\CreatesPayments;
public function checkout(CreatesPayments $payments)
{
$transaction = $payments->createPayment(auth()->user(), 25.500);
return redirect($transaction->url);
}
With KFAST (Faster Checkout)
$transaction = $user->payWithKfast(25.500, [
'udf1' => 'order_id',
]);
return redirect($transaction->url);
$transaction = $user->payWithKfast(25.500, [
'udf3' => '99887766',
]);
$transaction = $user->pay(25.500, [
'udf3' => '12345678',
]);
Override the token strategy by implementing kfastToken() on your model:
class User extends Authenticatable
{
use HasKnet;
public function kfastToken(): string
{
return str_pad((string) $this->customer_number, 8, '0', STR_PAD_LEFT);
}
}
KFAST token rules:
- Must be exactly 8 numeric digits (validated automatically by
PaymentRequest)
- Must uniquely identify the customer
- KFAST must be enabled on the terminal by the acquirer bank
Important Rules
- Amounts MUST use 3 decimal places (KWD format):
10.000, 25.500, 0.250
- Currency code
414 is the ISO code for Kuwaiti Dinar
- Track IDs must be unique per transaction
- UDF fields (
udf1–udf5) carry custom data through the full payment lifecycle
- The
$transaction->url contains the full KNET gateway URL to redirect the user to
Payment Flow
- Initiate: Call
$user->pay() or KnetPaymentService::createPayment() — returns a KnetTransaction with a url
- Redirect: Send the user to
$transaction->url (the KNET payment page)
- Callback: KNET POSTs encrypted
trandata to /knet/response — middleware decrypts once and passes payload via request attributes
- Events:
KnetPaymentSucceeded or KnetPaymentFailed is dispatched
- Redirect back: User is redirected to
KNET_REDIRECT_URL with the result
Handling Payment Events
use Asciisd\Knet\Events\KnetPaymentSucceeded;
use Asciisd\Knet\Events\KnetPaymentFailed;
use Illuminate\Support\Facades\Event;
Event::listen(KnetPaymentSucceeded::class, function ($event) {
$transaction = $event->transaction;
});
Event::listen(KnetPaymentFailed::class, function ($event) {
$transaction = $event->transaction;
$error = $event->errorMessage;
});
All Available Events
| Event | Payload |
|---|
KnetPaymentSucceeded | KnetTransaction $transaction |
KnetPaymentFailed | KnetTransaction $transaction, ?string $errorMessage |
KnetRefundSucceeded | KnetTransaction $transaction, KnetTransaction $refundTransaction, float $amount |
KnetRefundFailed | KnetTransaction $transaction, KnetTransaction $refundTransaction, string $reason |
KnetResponseReceived | array $payload |
KnetResponseHandled | array $payload |
KnetTransactionCreated | KnetTransaction $transaction |
KnetTransactionUpdated | KnetTransaction $transaction |
KnetTransactionHasErrors | KnetTransaction $transaction |
KnetReceiptSeen | KnetTransaction $transaction |
Working with KnetTransaction
use Asciisd\Knet\KnetTransaction;
$transaction->isCaptured();
$transaction->hasStatus();
$transaction->isRefundable();
$transaction->isRefunded();
$transaction->rawAmount();
$transaction->formattedAmount();
$transaction->owner;
To find by track ID, use the repository:
use Asciisd\Knet\Contracts\TransactionRepository;
$repository = app(TransactionRepository::class);
$transaction = $repository->findByTrackId('track-123');
Key Columns
| Column | Type | Description |
|---|
trackid | string | Unique tracking identifier |
paymentid | string | KNET payment ID |
result | string | Payment result (CAPTURED, FAILED, etc.) |
amt | string | Payment amount |
auth | string | Authorization code |
ref | string | Reference number |
tranid | string | Transaction ID |
udf1–udf5 | string | User-defined fields |
paid | boolean | Whether payment was successful |
refunded | boolean | Whether transaction was refunded |
refund_amount | decimal | Refund amount (if partial) |
Payment Status Enum
use Asciisd\Knet\Enums\PaymentStatus;
$status = PaymentStatus::from($transaction->result);
$status->isSuccessful();
$status->isFailed();
$status->isPending();
$status->displayName();
For presentation (colors, images), use the presenter:
$presenter = $status->presenter();
$presenter->styleColor();
$presenter->textColor();
$presenter->bgColor();
$presenter->imageUrl();
$presenter->toArray();
Inquiry & Refunds
Transaction Inquiry
use Asciisd\Knet\Services\KnetPaymentService;
$transaction = $paymentService->inquireAndUpdateTransaction($transaction);
$result = $paymentService->inquirePayment(10.000, 'track-123');
Refunds
Via Trait
$result = $user->refund($transaction);
$result = $user->refund($transaction, 5.000);
Via Service
$result = $paymentService->refundPayment($transaction);
$result = $paymentService->refundPayment($transaction, 5.000);
Refund Validation
The service automatically validates before processing:
- Transaction must be captured and not already refunded (
isRefundable())
- Refund amount must be greater than zero
- Refund amount must not exceed the original transaction amount
Throws KnetException on validation failure.
Refund Behavior
- On success: original transaction is marked
refunded = true, refunded_at is set, refund_amount is recorded
- A separate
KnetTransaction record is created for the refund (with action = 2 and original_transaction_id linking to the original)
KnetRefundSucceeded event is dispatched on success; KnetRefundFailed on failure
- Refund result is
CAPTURED for success; any other value is a failure
Routes
The package auto-registers these routes (prefix configurable via KNET_PATH):
| Method | URI | Purpose |
|---|
| POST | /knet/response | KNET encrypted callback (with signature verification middleware) |
| POST | /knet/handle | Post-success redirect handler |
| GET/POST | /knet/error | Error callback handler |
Disable auto-registration with Knet::ignoreRoutes() in a service provider.
Customization
Custom Billable Model
use Asciisd\Knet\KnetTransaction;
KnetTransaction::useCustomerModel(\App\Models\Customer::class);
Skip Package Migrations
use Asciisd\Knet\Knet;
Knet::ignoreMigrations();
Skip Package Routes
use Asciisd\Knet\Knet;
Knet::ignoreRoutes();
Artisan Commands
| Command | Purpose |
|---|
php artisan knet:install | Publish config, run migrations, validate setup |
php artisan knet:check | Validate credentials and configuration |
php artisan knet:publish | Publish config (--config) and/or migrations (--migrations) |
Common Patterns
Complete Checkout Controller
use Asciisd\Knet\Services\KnetPaymentService;
use Illuminate\Http\Request;
class CheckoutController extends Controller
{
public function __construct(
private readonly KnetPaymentService $paymentService
) {}
public function pay(Request $request)
{
$request->validate(['amount' => 'required|numeric|min:0.001']);
$transaction = $this->paymentService->createPayment(
$request->user(),
$request->amount,
['udf1' => $request->order_id]
);
return redirect($transaction->url);
}
}
Payment Success Listener
use Asciisd\Knet\Events\KnetPaymentSucceeded;
class HandleSuccessfulPayment
{
public function handle(KnetPaymentSucceeded $event): void
{
$transaction = $event->transaction;
$orderId = $transaction->udf1;
Order::where('id', $orderId)->update(['status' => 'paid']);
}
}
KNET Gateway Protocol (K-064)
This section documents the underlying KNET Payment Gateway protocol (doc K-064 v1.4) that the package abstracts.
Gateway URLs
| Environment | Portal | Transaction Pipe (RAW) |
|---|
| Test | https://www.kpaytest.com.kw/kpg/merchant.htm | https://www.kpaytest.com.kw/kpg/tranPipe.htm?param=tranInit& |
| Production | https://www.kpay.com.kw/portal/merchant.htm | https://www.kpay.com.kw/kpg/tranPipe.htm?param=tranInit& |
Action Codes
| Action | Code | Description |
|---|
| Purchase | 1 | Standard payment transaction |
| Refund (Credit) | 2 | Refund a captured transaction |
| Inquiry | 8 | Query transaction status |
RAW Integration (used by this package)
The package uses RAW-based integration which requires:
- Tran Portal ID (
KNET_TRANSPORT_ID) — unique terminal identifier
- Tran Portal Password (
KNET_TRANSPORT_PASSWORD) — terminal password
- Terminal Resource Key (
KNET_RESOURCE_KEY) — 16-char AES-128-CBC key for encrypting/decrypting trandata
RAW requests use Content-type: application/xml and send XML-formatted parameters to the tranPipe.htm endpoint.
Payment Initialization Variables
| Variable | Type | Length | Required | Description |
|---|
| Tran Portal ID | STRING | 255 | Y | Unique terminal ID |
| Tran Portal Password | STRING | 15 | Y | Terminal password |
| Terminal Resource Key | STRING | 16 | Y | AES encryption key |
| Action | STRING | 1 | Y | 1 (purchase), 2 (refund), 8 (inquiry) |
| Amount | STRING | 10 | Y | 3 decimal places for KWD: 15.750, 10.000 |
| Currency Code | STRING | 3 | Y | 414 for KWD |
| Language | STRING | 3 | Y | EN or AR |
| Response URL | STRING | 255 | Y | URL for KNET to POST transaction response |
| Error URL | STRING | 255 | Y | URL for consumer redirect on error |
| Track ID | STRING | 40 | Y | Unique merchant-generated tracking ID (alphanumeric only) |
| Trans ID | STRING | 19 | Y (Refund/Inquiry) | Identifies the original transaction |
| UDF1–UDF4 | STRING | 255 | N | User-defined fields for custom data |
| UDF3 (KFAST) | STRING | 8 | N | 8-digit numeric customer token for KFAST faster checkout |
| UDF5 | STRING | 255 | N | Purchase: extra data, prefix with "ptlf" for bank reports (max 50 chars). Inquiry: identifier type — "TrackID", "PaymentID", "TransID", or "SeqNum" |
Transaction Response Variables
| Variable | Description |
|---|
| paymentid | Unique order ID from Payment Gateway |
| result | CAPTURED, NOT CAPTURED, CANCELED, HOST TIMEOUT |
| ref | Reference number from KNET |
| trackid | Merchant's original tracking ID |
| tranid | Transaction ID from Payment Gateway |
| postdate | Post date in MMDD format |
| auth | Authorization code from the bank |
| amt | Transaction amount (3 decimal places) |
| udf1–udf5 | User-defined fields echoed back |
| avr | Address verification response |
| authRespCode | Reason code for the transaction result |
| Error | Error code (if unsuccessful) |
| ErrorText | Error description (if unsuccessful) |
Response Notification Contract
When KNET POSTs the response to the responseURL:
- KNET sends an encrypted
trandata parameter via URL-encoded POST
- The merchant must decrypt the response, cross-verify with their database, and save both encrypted and plain text
- The response page must output a single line:
REDIRECT=<Merchant Receipt URL>
- The response page must have no HTML tags, no errors, no redirections — only the
REDIRECT= text
- KNET reads this output and redirects the customer's browser to that URL
Result Values
| Result | Meaning | Context |
|---|
CAPTURED | Transaction approved | Purchase, Inquiry, Refund |
NOT CAPTURED | Transaction declined by bank | Purchase |
CANCELED | Customer canceled on payment page | Purchase |
HOST TIMEOUT | Bank did not respond in time | Purchase |
SUCCESS | Transaction available and captured | Inquiry |
FAILURE | Transaction failed | Inquiry |
SUSPECTED | Transaction suspected | Inquiry |
Inquiry Protocol
Inquiry uses action code 8. The udf5 parameter determines which identifier is used to locate the original transaction:
| Inquire by | transid value | udf5 value | amt | trackid |
|---|
| Transaction ID | Original TransId | Empty or "TransID" | Original Amount | Original TrackId |
| Track ID | Original TrackId | "TrackID" | Original Amount | Original TrackId |
| Payment ID | Original PaymentId | "PaymentID" | Original Amount | Original TrackId |
| Reference ID | Original RefId | "SeqNum" | Original Amount | Original TrackId |
Refund Protocol
Refund uses action code 2. When refunding by Track ID:
- Set
transid = original Track ID value
- Set
trackid = original Track ID value
- Set
udf5 = "TrackID"
- Refund is successful only if result is
CAPTURED; any other value is a failure
KFAST (Faster Checkout)
KFAST allows registered cardholders to skip entering card details. To enable:
- Pass an 8-digit numeric token in
UDF3 that uniquely identifies the customer
- The merchant must ensure the correct token is passed for each customer
- KFAST must be enabled on the terminal by the acquirer bank
- OTP is required for initial card registration
Validation Constraints
Restricted characters:
| Field | Forbidden Characters |
|---|
| UDF1–UDF5 | @, / |
| Track ID | -, =, [, ], /, ?, . |
Track ID must be alphanumeric, max 40 characters, unique per transaction.
Test Environment
- Test URL:
https://www.kpaytest.com.kw/kpg/merchant.htm
- Test Card: Select "KNET Test Card [KNET1]" from the bank dropdown
- CAPTURED result: Set expiration date to
09/2021
- NOT CAPTURED result: Use any other expiration date
- PIN: Any 4-digit numeric value works in the test environment
Error Codes (Common)
Error codes follow the pattern IPAY01xxxxx (validation/processing) and IPAY02xxxxx (system/database). Key codes:
| Code | Description |
|---|
| IPAY0100001 | Missing error URL |
| IPAY0100003 | Missing response URL |
| IPAY0100005 | Missing tranportal ID |
| IPAY0100008 | Terminal not enabled |
| IPAY0100013 | Invalid transaction data |
| IPAY0100015 | Invalid tranportal password |
| IPAY0100017 | Inactive terminal |
| IPAY0100018 | Terminal password expired |
| IPAY0100020 | Invalid action type |
| IPAY0100023 | Missing amount |
| IPAY0100024 | Invalid amount |
| IPAY0100027 | Invalid track id |
| IPAY0100033 | Terminal action not enabled |
| IPAY0100036 | UDF Mismatched |
| IPAY0100042 | Transaction time limit exceeded |
| IPAY0100045 | Denied by Risk |
| IPAY0100048 | Cancelled |
| IPAY0100050 | Invalid terminal key |
| IPAY0100158 | Host timeout |
| IPAY0100176 | Decrypting transaction data failed |
| IPAY0100249 | Merchant response URL is down |
| IPAY0100264 | Signature validation failed |
Full error code list: see K-064 Integration Manual Section 10.
When Adding New Features
- New services go in
src/Services/ and should implement the appropriate focused contract
- For encryption, inject
EncryptsPayload — do not use KPayClient static methods directly
- For data access, inject
TransactionRepository — do not use model static methods directly
- Configuration access should go through the injected
KnetConfig singleton, not bare config() calls
- Verbose logging should be gated behind
$config->isDebugMode(); only error-level logging should be unconditional
- New events go in
src/Events/ with Dispatchable and SerializesModels traits
- New exceptions go in
src/Exceptions/ extending KnetException
- All amounts must use
number_format($amount, 3, '.', '') for 3-decimal KWD formatting