PHPackages                             ahmed-abd0/laravel-paymob - 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. ahmed-abd0/laravel-paymob

ActiveLibrary

ahmed-abd0/laravel-paymob
=========================

A Cashier-inspired Laravel package for Paymob payments, saved cards, subscriptions, refunds, webhooks, transaction inquiry, and Quick Links.

v1.0.0(1mo ago)011MITPHPPHP ^8.2CI failing

Since Jul 16Pushed 1mo agoCompare

[ Source](https://github.com/ahmed-abd0/laravel-paymob)[ Packagist](https://packagist.org/packages/ahmed-abd0/laravel-paymob)[ Docs](https://github.com/ahmed-abd0/laravel-paymob)[ RSS](/packages/ahmed-abd0-laravel-paymob/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (13)Versions (2)Used By (0)

Laravel Paymob
==============

[](#laravel-paymob)

A community-built, Cashier-inspired Paymob package for Laravel 11, 12, and 13. It wraps the current Paymob Intention, saved-card, subscription, refund/void/capture, transaction inquiry, and Quick Link APIs while keeping a reliable local billing state.

> This package is not an official Paymob package. Replace the Composer vendor name before publishing under your own organization.

Why this design
---------------

[](#why-this-design)

The package has two layers:

1. **Raw API resources** for direct access to every endpoint.
2. **Billing models and workflows** for subscriptions, idempotent callbacks, local access checks, trials, grace periods, payment methods, and synchronization.

Redirect responses are never treated as proof of payment. Signed backend callbacks and Paymob inquiry APIs are the source of truth.

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

[](#installation)

```
composer require ahmed-abd0/laravel-paymob
php artisan vendor:publish --tag=paymob-config
php artisan migrate
```

Add `Paymob\Laravel\Concerns\Billable` to the model that owns subscriptions:

```
use Paymob\Laravel\Concerns\Billable;

class User extends Authenticatable
{
    use Billable;
}
```

Configure `.env`:

```
PAYMOB_REGION=egypt
PAYMOB_CURRENCY=EGP
PAYMOB_SECRET_KEY=
PAYMOB_PUBLIC_KEY=
PAYMOB_API_KEY=
PAYMOB_HMAC_SECRET=
PAYMOB_CARD_3DS_INTEGRATION_ID=
PAYMOB_MOTO_INTEGRATION_ID=
PAYMOB_SUBSCRIPTION_WEBHOOK_SECRET=a-long-random-secret
PAYMOB_REDIRECTION_URL=https://app.example.com/billing/return
```

Supported regions: `egypt`, `ksa`, `uae`, and `oman`.

Create a Paymob subscription plan
---------------------------------

[](#create-a-paymob-subscription-plan)

```
use Paymob\Laravel\Data\SubscriptionPlanData;
use Paymob\Laravel\Enums\PlanFrequency;
use Paymob\Laravel\Facades\Paymob;

$plan = Paymob::plans()->create(new SubscriptionPlanData(
    name: 'Clinic Pro Monthly',
    amountCents: 49900,
    integration: (int) config('paymob.integrations.moto'),
    frequency: PlanFrequency::MONTHLY,
    reminderDays: 2,
    retrialDays: 2,
    webhookUrl: route('paymob.webhooks.subscription', [
        'secret' => config('paymob.webhooks.subscription_secret')
    ])
));
```

Plans are stored locally in `paymob_plans` and keep the full remote response for diagnostics.

Start a subscription checkout
-----------------------------

[](#start-a-subscription-checkout)

```
$checkout = $user->newSubscription('default', $plan)
    ->billing([
        'first_name' => 'Ahmed',
        'last_name' => 'Abdo',
        'email' => 'ahmed@example.com',
        'phone_number' => '+201000000000',
        'country' => 'EG'
    ])
    ->metadata(['tenant_id' => $tenant->id])
    ->checkout();

return $checkout->redirect();
```

For a trial:

```
$checkout = $user->newSubscription('default', $plan)
    ->trialDays(14)
    ->amount(100)
    ->checkout();
```

When Paymob provides a dedicated 3DS verification integration, pass it explicitly for verification-only enrollment:

```
$checkout = $user->newSubscription('default', $plan)
    ->trialDays(14)
    ->amount(100)
    ->paymentMethods([(int) config('paymob.integrations.verification_3ds')])
    ->checkout();
```

The local subscription starts as `incomplete`. It becomes `active` only after a verified successful callback.

Subscription checks
-------------------

[](#subscription-checks)

```
$user->subscribed('default');
$user->subscription('default')?->active();
$user->subscription('default')?->onTrial();
$user->subscription('default')?->onGracePeriod();
$user->subscription('default')?->pastDue();
```

`valid()` returns true while active, on trial, or in a cancellation/suspension grace period.

Manage a subscription
---------------------

[](#manage-a-subscription)

```
$subscription = $user->subscription('default');

$subscription->suspend();
$subscription->resume(nextBilling: '2026-08-15');
$subscription->updateBilling([
    'amount_cents' => 59900,
    'ends_at' => '2027-07-15',
    'next_billing' => '2026-08-15'
]);
$subscription->cancel();
$subscription->sync();
$subscription->sync(withRelations: true);
```

Paymob cancellation is permanent. Creating a fresh subscription is required after cancellation.

Manage cards
------------

[](#manage-cards)

```
$cards = Paymob::subscriptions()->cards($subscription->paymob_id);
Paymob::subscriptions()->changePrimaryCard($subscription->paymob_id, $cardId);
Paymob::subscriptions()->deleteCard($subscription->paymob_id, $cardId);
```

To add a secondary card, create an intention with `subscriptionv2_id`:

```
use Paymob\Laravel\Data\BillingData;
use Paymob\Laravel\Data\IntentionData;
use Paymob\Laravel\Data\Item;

$data = new IntentionData(
    amount: 100,
    billingData: BillingData::fromArray($user->paymobBillingData()),
    paymentMethods: [(int) config('paymob.integrations.card_3ds')],
    subscriptionId: (int) $subscription->paymob_id
);

$data->items(new Item('Card verification', 100));
$response = Paymob::subscriptions()->addCard($data);
```

Card tokens are encrypted using Laravel's application key. Webhook payloads are also encrypted at rest.

One-off intentions
------------------

[](#one-off-intentions)

```
$data = new IntentionData(
    amount: 10000,
    billingData: BillingData::fromArray($user->paymobBillingData()),
    paymentMethods: [(int) config('paymob.integrations.default')],
    specialReference: 'ORDER-1001'
);

$data->items(new Item('Order 1001', 10000));
$response = Paymob::intentions()->create($data);
$url = Paymob::intentions()->checkoutUrl($response['client_secret']);
```

Saved cards
-----------

[](#saved-cards)

Customer-initiated payment:

```
$response = Paymob::savedCards()->customerInitiated($data, [$storedToken]);
```

Merchant-initiated payment:

```
$intention = Paymob::savedCards()->merchantInitiatedIntention($motoIntention);
$paymentToken = data_get($intention->toArray(), 'payment_keys.0.key');
$charge = Paymob::savedCards()->payMoto($storedCardToken, $paymentToken);
```

Never expose saved tokens to frontend code.

Refund, void, and capture
-------------------------

[](#refund-void-and-capture)

```
Paymob::payments()->refund($transactionId, 5000);
Paymob::payments()->void($transactionId);
Paymob::payments()->capture($transactionId, 10000);
```

Transaction inquiry
-------------------

[](#transaction-inquiry)

```
Paymob::transactions()->byOrder(orderId: $paymobOrderId);
Paymob::transactions()->byOrder(merchantOrderId: 'ORDER-1001');
Paymob::transactions()->find($transactionId);
```

Quick Links
-----------

[](#quick-links)

```
$link = Paymob::quickLinks()->create([
    'amount_cents' => 10000,
    'payment_methods' => config('paymob.integrations.default'),
    'full_name' => 'Ahmed Abdo',
    'email' => 'ahmed@example.com',
    'phone_number' => '+201000000000',
    'description' => 'Invoice 1001',
    'reference_id' => 'INV-1001',
    'is_live' => false,
    'notification_url' => route('paymob.webhooks.transaction')
]);

Paymob::quickLinks()->cancel($link['id']);
```

Webhooks
--------

[](#webhooks)

The package registers:

```
POST /paymob/webhooks
POST /paymob/webhooks/transaction
POST /paymob/webhooks/token
POST /paymob/webhooks/subscription?secret=...

```

Transaction and token callbacks use HMAC-SHA512 validation. Subscription callbacks use a secret embedded in the registered callback URL because the supplied subscription collection does not define a subscription-specific HMAC calculation.

Processing is:

- HMAC/secret verified before state changes.
- Payload hash deduplicated.
- Webhook row locked before processing.
- Transactions and card tokens upserted.
- Subscription state changed atomically.
- Events dispatched after commit.
- Failed callbacks return an error so Paymob can retry.

Available events:

```
Paymob\Laravel\Events\WebhookHandled
Paymob\Laravel\Events\TransactionUpdated
Paymob\Laravel\Events\SubscriptionUpdated
Paymob\Laravel\Events\PaymentMethodUpdated
```

If the callbacks use Laravel's `web` middleware, exclude these paths from CSRF validation. The default package routes use the `api` middleware group.

Synchronization and cleanup
---------------------------

[](#synchronization-and-cleanup)

```
php artisan paymob:sync-subscriptions --plans
php artisan paymob:sync-subscriptions --with-relations
php artisan paymob:prune-webhooks
php artisan paymob:prune-webhooks --days=30
```

Schedule synchronization as a reconciliation fallback:

```
use Illuminate\Support\Facades\Schedule;

Schedule::command('paymob:sync-subscriptions --plans')->hourly();
Schedule::command('paymob:prune-webhooks')->daily();
```

Raw endpoint coverage
---------------------

[](#raw-endpoint-coverage)

AreaMethodsIntentionscreate, update, retrieve, checkout URLSubscription planscreate, list, update, suspend, resumeSubscriptionscreate, retrieve, update, suspend, resume, cancelSubscription detailslast transaction, transactions, cardsSubscription cardsadd, delete, change primarySaved cardstoken enrollment, CIT, MIT/MOTOPost-paymentrefund, void, captureInquiryorder/merchant reference, transaction IDQuick Linkscreate, cancelImportant production rules
--------------------------

[](#important-production-rules)

- Use integers in minor currency units (`10000` means `100.00`).
- Ensure the intention amount equals the sum of `items[].amount`.
- Test keys only work with test integration IDs; live keys only work with live integration IDs.
- A recurring plan requires a MOTO integration; enrollment normally requires an online 3DS integration.
- Treat callbacks and inquiry responses as authoritative, not browser redirects.
- Keep `APP_KEY`, Paymob keys, HMAC secret, and tokenized cards outside source control.
- Use HTTPS for every callback and redirect URL.
- Only GET requests are retried automatically; non-idempotent payment operations are not replayed by default.
- Add your own authorization around customer billing-management endpoints.

Custom models
-------------

[](#custom-models)

Override model classes in `config/paymob.php`. Custom models should extend the package model so the relationships, casts, and lifecycle methods remain available.

Testing
-------

[](#testing)

```
composer install
composer test
composer lint
```

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance92

Actively maintained with recent releases

Popularity7

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

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

Unknown

Total

1

Last Release

45d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/0e35de11d549981804ef004be489ebcb679d92a949e883b67f451777cef1b4e9?d=identicon)[Ahmed123567](/maintainers/Ahmed123567)

---

Top Contributors

[![ahmed-abd0](https://avatars.githubusercontent.com/u/89392661?v=4)](https://github.com/ahmed-abd0 "ahmed-abd0 (6 commits)")

---

Tags

laravelwebhookspaymentssubscriptionsrefundspayment gatewayrecurring paymentscashierksasaudi-arabiapaymobEgyptuaesaved-cardsoman

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/ahmed-abd0-laravel-paymob/health.svg)

```
[![Health](https://phpackages.com/badges/ahmed-abd0-laravel-paymob/health.svg)](https://phpackages.com/packages/ahmed-abd0-laravel-paymob)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3365.5M359](/packages/psalm-plugin-laravel)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k17.6M165](/packages/laravel-pulse)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

80732.6M270](/packages/laravel-mcp)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

1.0k2.5M152](/packages/roots-acorn)[aedart/athenaeum

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

255.2k](/packages/aedart-athenaeum)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.5k31.8M166](/packages/laravel-cashier)

PHPackages © 2026

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