mustafataj/tabby-laravel

Laravel package for Tabby Buy Now Pay Later checkout, payments, and webhooks

Maintainers

Package info

github.com/MustafaTaj/tabby-laravel

pkg:composer/mustafataj/tabby-laravel

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.1.0 2026-08-06 10:46 UTC

This package is auto-updated.

Last update: 2026-08-06 10:48:22 UTC


README

Laravel package for Tabby Buy Now Pay Later: checkout sessions, payment capture/refund, and webhooks. Supports sandbox and production, plus regional APIs for UAE/Kuwait (api.tabby.ai) and KSA (api.tabby.sa).

Requirements

  • PHP 8.2+
  • Laravel 10, 11, 12, or 13

Installation

composer require mustafataj/tabby-laravel

Publish the config:

php artisan vendor:publish --tag=tabby-config

Configuration

Add credentials to .env:

TABBY_ENV=sandbox
TABBY_REGION=ai

TABBY_SANDBOX_PUBLIC_KEY=pk_test_xxx
TABBY_SANDBOX_SECRET_KEY=sk_test_xxx
TABBY_SANDBOX_MERCHANT_CODE=AE

TABBY_PRODUCTION_PUBLIC_KEY=pk_xxx
TABBY_PRODUCTION_SECRET_KEY=sk_xxx
TABBY_PRODUCTION_MERCHANT_CODE=AE

TABBY_CURRENCY=AED
TABBY_CAPTURE_ON_AUTHORIZE=true

TABBY_WEBHOOK_AUTH_HEADER=replace-with-a-long-random-secret
TABBY_WEBHOOK_AUTH_HEADER_NAME=X-Tabby-Webhook-Token
TABBY_WEBHOOK_REQUIRE_AUTH=true
# TABBY_WEBHOOK_ENFORCE_IP=true
Variable Description
TABBY_ENV sandbox or production — selects which key pair is used
TABBY_REGION ai (UAE/KW) or sa (KSA)
TABBY_CAPTURE_ON_AUTHORIZE Auto-capture after verify/webhook when payment is AUTHORIZED with no prior captures
TABBY_WEBHOOK_ENABLED Register the incoming webhook route (default true)
TABBY_WEBHOOK_PATH Incoming webhook path (default tabby/webhook)
TABBY_WEBHOOK_URL Absolute URL registered with Tabby (optional)
TABBY_WEBHOOK_AUTH_HEADER Shared secret Tabby sends on webhook POSTs (required when REQUIRE_AUTH=true)
TABBY_WEBHOOK_AUTH_HEADER_NAME Header name registered with Tabby (default X-Tabby-Webhook-Token)
TABBY_WEBHOOK_REQUIRE_AUTH Reject unauthenticated webhooks (default true)
TABBY_WEBHOOK_ENFORCE_IP Restrict webhooks to Tabby IP allowlist
TABBY_WEBHOOK_THROTTLE Laravel throttle string, e.g. 60,1 (set empty to disable)

Environment is determined by the API keys you send (sk_test_… vs sk_…). Base URLs do not change between sandbox and production.

Usage

Eligibility check

use MustafaTaj\Tabby\Facades\Tabby;
use MustafaTaj\Tabby\Data\Buyer;
use MustafaTaj\Tabby\Data\SessionRequest;

$result = Tabby::checkout()->checkEligibility(new SessionRequest(
    amount: '340.00',
    currency: 'SAR',
    buyer: new Buyer(email: 'customer@example.com', phone: '500000001'),
));

if ($result->isEligible()) {
    // Show Tabby at checkout
}

Create session and redirect

use MustafaTaj\Tabby\Data\Buyer;
use MustafaTaj\Tabby\Data\BuyerHistory;
use MustafaTaj\Tabby\Data\MerchantUrls;
use MustafaTaj\Tabby\Data\Order;
use MustafaTaj\Tabby\Data\OrderHistory;
use MustafaTaj\Tabby\Data\OrderItem;
use MustafaTaj\Tabby\Data\SessionRequest;
use MustafaTaj\Tabby\Facades\Tabby;

$session = Tabby::checkout()->createSession(new SessionRequest(
    amount: '340.00',
    currency: 'SAR',
    buyer: new Buyer(
        email: 'customer@example.com',
        phone: '500000001',
        name: 'Jane Doe',
    ),
    order: new Order(
        referenceId: (string) $order->id,
        items: [
            new OrderItem(title: 'Product', quantity: 1, unitPrice: '340.00', category: 'general'),
        ],
    ),
    merchantUrls: new MerchantUrls(
        success: route('checkout.success'),
        cancel: route('checkout.cancel'),
        failure: route('checkout.failure'),
    ),
    // Optional — improves approval rates when available
    buyerHistory: new BuyerHistory(
        registeredSince: $customer->created_at->toIso8601String(),
        loyaltyLevel: 1,
        isPhoneNumberVerified: true,
        isEmailVerified: true,
    ),
    orderHistory: [
        new OrderHistory(
            purchasedAt: '2025-01-15T10:00:00+03:00',
            amount: '150.00',
            status: 'complete',
            paymentMethod: 'card',
            buyer: new Buyer(email: 'customer@example.com', phone: '500000001', name: 'Jane Doe'),
            items: [
                new OrderItem(title: 'Previous item', quantity: 1, unitPrice: '150.00'),
            ],
        ),
    ],
));

// Persist $session->paymentId against your order, then redirect:
return redirect()->away($session->webUrl);

Verify and capture (return URL)

Never trust the redirect alone — always verify server-side:

$payment = Tabby::payments()->verifyAndCapture($paymentId);

if ($payment->isClosed() || $payment->isAuthorized()) {
    // Fulfill order
}

Refund

Tabby::payments()->refund($paymentId, '50.00', 'refund-'.$orderId, 'Customer request');

Promo snippet

<x-tabby::promo :price="$product->price" currency="SAR" lang="en" />

Ensure your layout yields @stack('scripts').

Official payment method name: Pay later with Tabby / ادفع لاحقًا مع تابي.

Webhooks

Incoming route: POST /tabby/webhook (no web middleware / CSRF; protected by tabby.webhook auth + optional IP allowlist + throttle).

Auth is required by default. Set TABBY_WEBHOOK_AUTH_HEADER, then register so Tabby sends that header:

php artisan tabby:webhooks:register
# or
php artisan tabby:webhooks:register --url=https://your-app.test/tabby/webhook

Registration includes header.title / header.value from your config so Tabby signs each POST.

Listen for events in your app:

use MustafaTaj\Tabby\Events\PaymentAuthorized;
use MustafaTaj\Tabby\Events\PaymentClosed;
use MustafaTaj\Tabby\Events\PaymentRejected;
use MustafaTaj\Tabby\Events\PaymentExpired;
use MustafaTaj\Tabby\Events\PaymentRefunded;

Event behaviour:

  • PaymentAuthorized — first authorize notification (no captures yet). With TABBY_CAPTURE_ON_AUTHORIZE=true, the package also POSTs a capture (idempotent reference_id). Fulfill on this event or on PaymentClosed.
  • Capture notifications (authorized + captures[]) are ignored for re-dispatch / re-capture.
  • PaymentClosed — Tabby close webhook after capture settles.
  • PaymentRefunded — payload includes a non-empty refunds array.
  • Processing failures return HTTP 500 so Tabby retries.

Make fulfillment handlers idempotent — Tabby may deliver duplicates.

Going live

  1. Complete Tabby’s testing checklist
  2. Set TABBY_ENV=production and live keys
  3. For KSA merchants set TABBY_REGION=sa
  4. Re-register webhooks with the production secret key

Docs

License

MIT