PHPackages                             qbe-digital/payments - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Payment Processing](/categories/payments)
4. /
5. qbe-digital/payments

ActiveLibrary[Payment Processing](/categories/payments)

qbe-digital/payments
====================

A unified payments package for Laravel supporting Tabby, Tamara and more.

v1.0.2(1mo ago)14MITPHP ^8.2

Since Jun 21Compare

[ Source](https://github.com/qbe-digital/payments)[ Packagist](https://packagist.org/packages/qbe-digital/payments)[ RSS](/packages/qbe-digital-payments/feed)WikiDiscussions Synced 2w ago

READMEChangelogDependencies (18)Versions (4)Used By (0)

Laravel Payments
================

[](#laravel-payments)

A unified payments package for Laravel, supporting **Tabby** and **Tamara** with a clean, driver-based architecture.

Features
--------

[](#features)

- 🔌 **Driver-based architecture** — Switch payment providers with zero code changes
- 🪝 **Webhook handling** — Automatic signature validation and normalized event dispatching
- 💳 **Checkout sessions** — Unified API for creating payment sessions
- ↩️ **Refunds** — Full and partial refund support
- 📦 **Installments** — BNPL support for Tabby &amp; Tamara
- 📋 **Transaction logging** — Built-in migration and model for tracking payments
- 🧩 **Billable trait** — Add `pay()` to any Eloquent model

Installation
------------

[](#installation)

```
composer require qbe-digital/payments
```

Publish the config:

```
php artisan vendor:publish --tag=payments-config
```

Publish the migrations:

```
php artisan vendor:publish --tag=payments-migrations
php artisan migrate
```

Configuration
-------------

[](#configuration)

Add your credentials to `.env`:

```
PAYMENTS_DRIVER=tabby

# Regional defaults (applied to all drivers; override per-driver in config/payments.php)
PAYMENTS_CURRENCY=SAR
PAYMENTS_LOCALE=ar
PAYMENTS_COUNTRY=SA

# Tabby
TABBY_SECRET_KEY=your-secret-key
TABBY_PUBLIC_KEY=your-public-key
TABBY_MERCHANT_CODE=your-merchant-code
TABBY_WEBHOOK_KEY=your-webhook-key
TABBY_SANDBOX=true

# Tamara
TAMARA_API_TOKEN=your-api-token
TAMARA_NOTIFICATION_TOKEN=your-notification-token
TAMARA_SANDBOX=true
```

Usage
-----

[](#usage)

### Basic Checkout

[](#basic-checkout)

```
use QBE\Payments\Facades\Payments;
use QBE\Payments\Support\CheckoutSession;
use QBE\Payments\Support\CustomerInfo;

$session = new CheckoutSession(
    amount: 500.00,
    currency: 'SAR',
    referenceId: 'order-123',
    description: 'Premium Package',
    successUrl: route('payment.success'),
    failureUrl: route('payment.failure'),
    customer: CustomerInfo::make('Ahmed', 'ahmed@example.com', '+966500000000'),
);

// Use default driver
$result = Payments::createCheckout($session);

// Or specify a driver
$result = Payments::driver('tabby')->createCheckout($session);

if ($result->requiresRedirect()) {
    return redirect($result->redirectUrl);
}
```

### Verify Payment

[](#verify-payment)

```
$result = Payments::verify($transactionId);

if ($result->isSuccessful()) {
    // Payment confirmed
}
```

### Refund

[](#refund)

```
$refund = Payments::refund($transactionId, 100.00, 'Customer requested');

// Pass the original transaction currency for multi-currency correctness:
$refund = Payments::refund($transactionId, 100.00, 'Customer requested', 'AED');

if ($refund->isSuccessful()) {
    // Refund processed
}
```

### Billable Trait

[](#billable-trait)

Add the `Billable` trait to any model:

```
use QBE\Payments\Traits\Billable;
use QBE\Payments\Support\CustomerInfo;

class Order extends Model
{
    use Billable;

    public function getPaymentAmount(): float
    {
        return $this->total;
    }

    public function getPaymentReference(): string
    {
        return "order-{$this->id}";
    }

    public function getPaymentCustomer(): ?CustomerInfo
    {
        return CustomerInfo::make(
            $this->customer_name,
            $this->customer_email,
            $this->customer_phone,
        );
    }
}

// Then simply:
$result = $order->pay();            // Default driver
$result = $order->pay('tamara');     // Specific driver
$order->verifyPayment($transactionId);
$order->refundPayment($transactionId, 50.00);
```

### Webhooks

[](#webhooks)

Webhook routes are registered automatically:

```
POST /api/payments/webhook/tabby
POST /api/payments/webhook/tamara

```

Listen for events in your `EventServiceProvider`:

```
use QBE\Payments\Events\PaymentSucceeded;
use QBE\Payments\Events\PaymentFailed;
use QBE\Payments\Events\WebhookReceived;
use QBE\Payments\Events\RefundProcessed;

protected $listen = [
    PaymentSucceeded::class => [
        UpdateOrderStatus::class,
        SendPaymentConfirmation::class,
    ],
    PaymentFailed::class => [
        HandleFailedPayment::class,
    ],
    WebhookReceived::class => [
        LogWebhook::class,
    ],
];
```

### Transaction Records

[](#transaction-records)

When transaction persistence is enabled (default), the package keeps a `payment_transactions` row in sync through the payment lifecycle:

- `$model->pay()` (via the `Billable` trait) records a **pending** row linked to the payable model.
- Payment/refund webhooks update the row to **paid**, **failed**, or **refunded**(and stamp `paid_at`) through built-in event listeners.

```
use QBE\Payments\Models\PaymentTransaction;

$transactions = $order->payments;            // if you add the morph relation
$paid = PaymentTransaction::paid()->get();   // built-in scopes: paid(), pending()
```

Opt out or swap the backing model in `config/payments.php`:

```
'transactions' => [
    'enabled' => env('PAYMENTS_RECORD_TRANSACTIONS', true),
    'model'   => \App\Models\PaymentTransaction::class,
],
```

### Installments (Tabby &amp; Tamara)

[](#installments-tabby--tamara)

```
use QBE\Payments\Contracts\SupportsInstallmentsInterface;

$driver = Payments::driver('tabby');

if ($driver instanceof SupportsInstallmentsInterface) {
    $plans = $driver->getInstallmentPlans(1000.00);
    // Returns available installment plans
}
```

### Feature Detection

[](#feature-detection)

```
use QBE\Payments\Enums\PaymentFeature;

if (Payments::driver('tamara')->supports(PaymentFeature::PARTIAL_REFUNDS->value)) {
    // Tamara supports partial refunds
}
```

Adding a Custom Driver
----------------------

[](#adding-a-custom-driver)

1. Extend `AbstractDriver`:

```
use QBE\Payments\Drivers\AbstractDriver;

class StripeDriver extends AbstractDriver
{
    public function driverName(): string { return 'stripe'; }
    protected function baseUrl(): string { return 'https://api.stripe.com/v1/'; }
    protected function defaultHeaders(): array { /* ... */ }
    protected function supportedFeatures(): array { /* ... */ }
    public function createCheckout(CheckoutSession $session): PaymentResult { /* ... */ }
    public function verify(string $transactionId): PaymentResult { /* ... */ }
    public function refund(string $transactionId, float $amount, string $reason = ''): RefundResult { /* ... */ }
}
```

2. Register in your `AppServiceProvider`:

```
use QBE\Payments\PaymentManager;

app(PaymentManager::class)->extend('stripe', function () {
    return new StripeDriver(config('payments.drivers.stripe'));
});
```

Testing
-------

[](#testing)

```
composer test
```

License
-------

[](#license)

MIT

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance90

Actively maintained with recent releases

Popularity6

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity48

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

3

Last Release

45d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/83adcae12b304bbc5dd5640c4c3f3e7ff76cb587811466f940f913b45c7b9943?d=identicon)[qbe-digital](/maintainers/qbe-digital)

---

Tags

laravelpaymentpaymentstamarabnpltabbysaudi

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/qbe-digital-payments/health.svg)

```
[![Health](https://phpackages.com/badges/qbe-digital-payments/health.svg)](https://phpackages.com/packages/qbe-digital-payments)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3355.4M352](/packages/psalm-plugin-laravel)[fleetbase/core-api

Core Framework and Resources for Fleetbase API

1239.7k25](/packages/fleetbase-core-api)[simplestats-io/laravel-client

Server-side analytics for Laravel that follows the full funnel from visit to registration to payment, attributed to the channel that drove it. Revenue, MRR, churn and ad-spend profit (ROAS/CAC) per channel. GDPR compliant, ad-blocker proof.

5222.6k](/packages/simplestats-io-laravel-client)[forjedio/inertia-table

Backend-driven dynamic tables for Laravel + Inertia.js

272.0k](/packages/forjedio-inertia-table)[api-platform/laravel

API Platform support for Laravel

58174.6k18](/packages/api-platform-laravel)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

255.2k](/packages/aedart-athenaeum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
