Download the PHP package digitaltunnel/jeelpay without Composer
On this page you can find all versions of the php package digitaltunnel/jeelpay. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package jeelpay
JeelPay for Laravel
A typed, testable SDK for JeelPay — Saudi Arabia's "Study Now Pay Later" platform — with first-class Laravel integration. Supports both Items (universities, courses, training centers) and Schooling (K-12) checkout flows out of the box.
Built against the official JeelPay developer docs. Endpoints, payloads, statuses and webhook signatures are verified to match the documented API.
- OAuth 2.1 client credentials with mandatory token caching (Laravel Cache, configurable store)
- Fluent request builders for Items + Schooling checkouts with built-in Saudi-specific validation
- Readonly DTOs (
CheckoutResult,CheckoutStatusResult,RefundResult,WebhookPayload) - Auto-generated idempotency keys (UUID v4) on every checkout POST — reuse on retries to dedupe
tx_idcaptured from response headers on every call for support debugging- Auto-registered webhook endpoint outside the
webmiddleware group — no CSRF dance - HMAC-SHA256 signature verification on incoming webhooks
- Idempotent webhook de-duplication via fingerprint hash
- Optional
jeelpay_checkouts,jeelpay_refunds,jeelpay_webhook_eventstables — auto-mirrored - Test helper
JeelPayFakewith realistic fixtures + signed-webhook builder
Requirements
- PHP 8.2+
- Laravel 11, 12, or 13
Installation
Publish the config:
Publish & run the migrations (opt-in):
Configure your .env:
Verify the webhook route is live:
Quickstart
Items checkout (universities, institutes, training centers)
Schooling checkout (K-12)
Idempotent retries
If your network call timed out, retry with the same idempotency key:
Status & refunds
Webhooks
The package auto-registers POST {config.webhook.path} (default webhooks/jeelpay) outside the
web middleware group, so CSRF is never applied.
Listen for typed events:
Available events:
CheckoutPending·CheckoutSucceeded·CheckoutRejected·CheckoutExpiredWebhookReceived(catch-all, fires for every webhook)
Signature verification
The middleware computes base64(HMAC-SHA256(client_secret, raw_body)) over the raw request body
and compares it (timing-safe) against the X-Jeel-Signature header. The raw body is preserved
through $request->getContent() — never re-serialise the JSON before checking.
Idempotent de-duplication
When jeelpay.persistence.webhook_events.enabled is true (default), the controller stores every
webhook keyed by a fingerprint of (checkout_id|status|raw_body). If JeelPay retries the exact same
event, no duplicate event is dispatched.
Disabling auto-registration
Then register your own route:
Persistence (opt-in)
Three tables ship via --tag=jeelpay-migrations. Each layer has an independent enable flag.
jeelpay_webhook_events
Fingerprint-based audit + idempotency log. Auto-written by the controller when
JEELPAY_LOG_WEBHOOK_EVENTS=true.
jeelpay_checkouts
Local mirror of every checkout (created by createItems()/createSchooling()/find() and
updated by webhooks). Enable with JEELPAY_PERSIST_CHECKOUTS=true. Polymorphic payable_*
columns let you link a checkout to any of your domain models.
jeelpay_refunds
Mirror of every refund call. Enable with JEELPAY_PERSIST_REFUNDS=true.
Token caching
The OAuth endpoint is rate-limited. The package caches tokens in your default Laravel cache store
(override with JEELPAY_CACHE_STORE) under the key jeelpay.access_token, refreshing 30 seconds
before expiry. Tokens are also memoised per request to avoid re-deserialising on every call.
Exception handling
The full hierarchy:
Every ApiException exposes txId() — include it in your support tickets.
Configuration reference
Every option in config/jeelpay.php is environment-driven. The full list:
| Env var | Default | Purpose |
|---|---|---|
JEELPAY_ENV |
sandbox |
sandbox or production — selects the base URLs below. |
JEELPAY_CLIENT_ID |
— | OAuth client id (required). |
JEELPAY_CLIENT_SECRET |
— | OAuth client secret (required). Also used to verify webhook signatures. |
JEELPAY_API_URL |
per-env | Override the API host (e.g. a local proxy/mock). |
JEELPAY_AUTH_URL |
per-env | Override the OAuth host. |
JEELPAY_TIMEOUT |
30 |
HTTP timeout in seconds (auth + API). |
JEELPAY_RETRY_TIMES |
0 |
Automatic retries on transient HTTP failures. |
JEELPAY_RETRY_SLEEP_MS |
200 |
Delay between retries (ms). |
JEELPAY_CACHE_STORE |
default store | Cache store for the access token (redis recommended in prod). |
JEELPAY_CACHE_KEY |
jeelpay.access_token |
Token cache key. |
JEELPAY_TOKEN_REFRESH_BUFFER |
30 |
Seconds before expiry to proactively refresh. |
JEELPAY_REDIRECT_URL |
— | Default redirect_url fallback for checkouts. |
JEELPAY_NOTIFICATION_URL |
— | Default notification_url fallback for checkouts. |
JEELPAY_WEBHOOK_ENABLED |
true |
Auto-register the webhook route. |
JEELPAY_WEBHOOK_PATH |
webhooks/jeelpay |
Webhook route path. |
JEELPAY_WEBHOOK_NAME |
jeelpay.webhook |
Webhook route name. |
JEELPAY_LOG_WEBHOOK_EVENTS |
true |
Persist + de-dupe webhooks in jeelpay_webhook_events. |
JEELPAY_PERSIST_CHECKOUTS |
false |
Mirror checkouts into jeelpay_checkouts. |
JEELPAY_PERSIST_REFUNDS |
false |
Mirror refunds into jeelpay_refunds. |
The base URLs (sandbox → production):
| API host | Auth host | |
|---|---|---|
| Sandbox | https://api.sandbox.jeel.co |
https://auth.sandbox.jeel.co |
| Production | https://api.jeel.co |
https://auth.jeel.co |
Statuses, enums & DTOs
CheckoutStatus (->find()->status, webhook $payload->status):
| Case | Value | Meaning |
|---|---|---|
Pending |
PENDING |
Created, awaiting buyer action. |
Succeeded |
SUCCEEDED |
Down payment paid, installment plan created. |
Rejected |
REJECTED |
Rejected / cancelled / not eligible. |
Expired |
EXPIRED |
No action for 2 hours. |
Helpers: $status->isTerminal(), $status->isPaid().
RefundStatus ($refund->status):
| Case | Value | Meaning |
|---|---|---|
Pending |
PENDING |
Under review. |
Done |
DONE |
Successfully processed. |
Rejected |
REJECTED |
Rejected (see $refund->rejectionReason). |
Helpers: $status->isTerminal(), $status->isDone(), $status->isRejected().
Result DTOs (all readonly):
CheckoutResult→checkoutId,redirectUrl,type,idempotencyKey,referenceId,txId,metadata,rawCheckoutStatusResult→checkoutId,status,type,referenceId,txId,metadata,raw+isPaid()/isPending()/isExpired()/isRejected()RefundResult→id(withdrawalRequestId),status,rejectionReason,referenceId,checkoutId,txId,raw+isDone()/isRejected()WebhookPayload→checkoutId,status,type,referenceId,metadata,rawBody,raw
Testing
The package ships JeelPayFake to stub every JeelPay HTTP call (auth, checkouts, refunds) and
to build signed webhooks — no network access needed.
Available JeelPayFake helpers: fakeAuth(), fakeItemsCheckoutCreated(),
fakeSchoolingCheckoutCreated(), fakeRefundSubmitted(), fakeRefundStatus(),
fakeValidationError(), sign(), signedWebhook(), assertSentTo(),
assertNothingSent(), assertSentCount().
Contributing & quality gate
The suite runs against an in-memory SQLite database via Orchestra Testbench — no setup required.
Sandbox info
- API:
https://api.sandbox.jeel.co - Auth:
https://auth.sandbox.jeel.co - Test card:
4111 1111 1111 1111, exp05/30, CVV123, 3DS codeCheckout1! - Test buyer phone: any valid Saudi mobile (e.g.
512345678), OTP3333, passcode100000
License
MIT.
All versions of jeelpay with dependencies
illuminate/cache Version ^11.0|^12.0|^13.0
illuminate/contracts Version ^11.0|^12.0|^13.0
illuminate/database Version ^11.0|^12.0|^13.0
illuminate/http Version ^11.0|^12.0|^13.0
illuminate/routing Version ^11.0|^12.0|^13.0
illuminate/support Version ^11.0|^12.0|^13.0