PHPackages                             iabduul7/laravel-moyasar - 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. iabduul7/laravel-moyasar

ActiveLibrary[Payment Processing](/categories/payments)

iabduul7/laravel-moyasar
========================

A modern Laravel package for Moyasar payment gateway integration. Built for Saudi Arabian e-commerce.

v0.1.0(2mo ago)00MITPHPPHP ^8.3CI passing

Since May 24Pushed 2mo agoCompare

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

READMEChangelog (1)Dependencies (9)Versions (2)Used By (0)

Laravel Moyasar
===============

[](#laravel-moyasar)

[![Tests](https://github.com/iabduul7/laravel-moyasar/actions/workflows/tests.yml/badge.svg)](https://github.com/iabduul7/laravel-moyasar/actions/workflows/tests.yml)[![PHPStan](https://github.com/iabduul7/laravel-moyasar/actions/workflows/phpstan.yml/badge.svg)](https://github.com/iabduul7/laravel-moyasar/actions/workflows/phpstan.yml)[![Latest Version](https://camo.githubusercontent.com/b88e8f6f9f47b6744e0fc44eaa2d5f1ddd615642395488931d2047c6412b5efd/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6961626475756c372f6c61726176656c2d6d6f79617361722e737667)](https://packagist.org/packages/iabduul7/laravel-moyasar)[![License](https://camo.githubusercontent.com/888a4159fdf2b154357f5f29933888867c1d451d8aeed44c29b70783d9bd93b6/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6961626475756c372f6c61726176656c2d6d6f79617361722e737667)](LICENSE)

A modern, opinionated Laravel package for the **[Moyasar](https://moyasar.com)** payment gateway. Built for Saudi Arabian e-commerce: mada, Visa, Mastercard, Apple Pay, Google Pay, Samsung Pay, and STC Pay.

> **Status:** v0.2.0-dev — Adds Tokens, Payouts, the `Money` value object, a specialised exception hierarchy, the Sadad source type, and `MoyasarTestCards`. v0.1.0 covered Payments, Invoices, Webhooks, Events, and an Eloquent trait.

Why this package?
-----------------

[](#why-this-package)

Other Moyasar Laravel packages exist. This one is the only one that:

- **Handles webhooks end-to-end.** Constant-time `secret_token` verification, cache-backed replay protection, and a typed Laravel event per Moyasar event type. The other packages leave you to parse the JSON yourself.
- **Ships eight typed events** (`PaymentPaid`, `PaymentFailed`, `PaymentAuthorized`, `PaymentCaptured`, `PaymentRefunded`, `PaymentVoided`, `PaymentAbandoned`, `PaymentVerified`) — not one generic "transaction created" event.
- **Has a real Eloquent layer.** `HasMoyasarPayments` trait + polymorphic `moyasar_payments` table + automatic webhook→DB sync. Attach payments to `Order`, `Subscription`, anything.
- **Is built for modern Laravel.** PHP 8.2+, Laravel 11/12/13, strict types everywhere, readonly DTOs, backed enums. Tested under PHPStan level 8.
- **Uses Laravel's own HTTP client.** `Http::fake()` works out of the box for downstream tests. No Guzzle wiring, no Mockery.
- **Is idempotency-aware** via Moyasar's `given_id` and retries 5xx automatically (never 4xx).
- **Is MIT-licensed.** Safe for commercial use, no GPL contamination.

This package`roduankd/laravel-moyasar``ahmedebead/moyasar-laravel`Webhooks + signature verification✓——Typed event per Moyasar event type8 events1 generic—Eloquent trait + auto-sync✓partial—Readonly DTOs + enums✓——Idempotency support✓——Retry on 5xx (not 4xx)✓——Card number / CVC log masking✓——Pest 4 + `Http::fake()` test suite109 testsPest 1PHPUnit + MockeryLaravel 11/12/13 support✓Laravel 8Laravel 9Active maintenance✓abandoned Oct 2023last push Sept 2024LicenseMITMITGPL-3.0Requirements
------------

[](#requirements)

- PHP **8.2+** (the test suite itself requires 8.3+ due to Pest 4 — runtime works fine on 8.2)
- Laravel **11.x, 12.x, or 13.x**

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

[](#installation)

```
composer require iabduul7/laravel-moyasar
```

Publish the config and migration:

```
php artisan vendor:publish --tag=moyasar-config
php artisan vendor:publish --tag=moyasar-migrations
php artisan migrate
```

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

[](#configuration)

Add your keys to `.env`:

```
MOYASAR_SECRET_KEY=sk_test_...
MOYASAR_PUBLISHABLE_KEY=pk_test_...
MOYASAR_WEBHOOK_SECRET=whsec_...
```

Get test keys from your [Moyasar Dashboard → Settings → API keys](https://dashboard.moyasar.com).

All values, including the webhook route, retry behaviour, logging, and replay-cache TTL, live in `config/moyasar.php`.

Quick start
-----------

[](#quick-start)

```
use Iabduul7\Moyasar\Facades\Moyasar;

$payment = Moyasar::payments()->create([
    'amount' => 10000,             // halalas = 100.00 SAR
    'currency' => 'SAR',
    'description' => 'Order #123',
    'callback_url' => route('checkout.callback'),
    'source' => [
        'type' => 'creditcard',
        'name' => 'John Doe',
        'number' => '4111111111111111',
        'cvc' => '123',
        'month' => 12,
        'year' => 2030,
    ],
]);

// $payment is an Iabduul7\Moyasar\DataObjects\PaymentData (readonly DTO)
$payment->id;
$payment->status;            // Iabduul7\Moyasar\Enums\PaymentStatus
$payment->source->company;   // 'visa'
```

Payments
--------

[](#payments)

```
use Iabduul7\Moyasar\Enums\PaymentStatus;
use Iabduul7\Moyasar\Facades\Moyasar;

Moyasar::payments()->find('pay_123');

Moyasar::payments()->list(
    page: 1,
    perPage: 50,
    filters: ['status' => PaymentStatus::Paid, 'metadata[order_id]' => '123'],
);

Moyasar::payments()->refund('pay_123');                   // full refund
Moyasar::payments()->refund('pay_123', amount: 5000);     // 50.00 SAR partial
Moyasar::payments()->capture('pay_123', amount: 10000);   // capture an authorized payment
Moyasar::payments()->void('pay_123');                     // void an authorized payment
```

### Idempotency

[](#idempotency)

Moyasar supports an idempotency key called `given_id` on payment creation. Pass a UUID v4 to safely retry the same request:

```
use Illuminate\Support\Str;

Moyasar::payments()->create([
    'given_id' => (string) Str::uuid(),
    'amount' => 10000,
    'currency' => 'SAR',
    // ...
]);
```

### Errors

[](#errors)

All non-2xx responses raise an `Iabduul7\Moyasar\Exceptions\ApiException` that carries `statusCode`, `type`, `errors`, and the full decoded payload:

```
use Iabduul7\Moyasar\Exceptions\ApiException;

try {
    Moyasar::payments()->create([...]);
} catch (ApiException $e) {
    $e->statusCode;   // 400
    $e->type;         // 'invalid_request_error'
    $e->errors;       // ['amount' => ['must be at least 1']]
}
```

5xx responses and connection errors are automatically retried (count + delay are configurable). 4xx responses are never retried.

#### Typed exception hierarchy

[](#typed-exception-hierarchy)

`ApiException::fromResponse()` returns the most specific subclass available so callers can catch by intent:

```
use Iabduul7\Moyasar\Exceptions\{
    AuthenticationException, NotFoundException, RateLimitException, ServerErrorException,
    ValidationException, CardDeclinedException, InsufficientFundsException,
    ExpiredCardException, InvalidCardException,
};

try {
    Moyasar::payments()->create([...]);
} catch (InsufficientFundsException $e) {
    // ISO 8583 response code 51 — prompt the customer to try a different card
} catch (CardDeclinedException $e) {
    // Any other declined card (response codes 04, 05, 41, 43, 62, 65)
} catch (AuthenticationException $e) {
    // 401 — API key invalid or revoked
} catch (RateLimitException $e) {
    // 429 — back off and retry
} catch (ApiException $e) {
    // Catch-all
}
```

Card-level exceptions win over generic status mapping. Each carries `sourceMessage` and `sourceResponseCode` as readonly properties for forensic logging.

Money
-----

[](#money)

Use the `Money` value object to convert between major-unit decimals and Moyasar's smallest-unit integers — the #1 source of integration bugs:

```
use Iabduul7\Moyasar\Money;

Money::sar(100.00)->amount;          // 10000 (halalas)
Money::halalas(10000)->format();     // "100.00 SAR"
Money::fromMajor(10.500, 'KWD');     // 10500 minor (3-decimal currency)

$total = Money::sar(99.99)->plus(Money::sar(0.01));   // 100.00 SAR
$total->amount;                      // 10000
```

Invoices
--------

[](#invoices)

```
use Iabduul7\Moyasar\Facades\Moyasar;

$invoice = Moyasar::invoices()->create([
    'amount' => 10000,
    'currency' => 'SAR',
    'description' => 'Invoice INV-001',
    'callback_url' => route('checkout.callback'),
]);

$invoice->url;      // hosted checkout page

Moyasar::invoices()->find($invoice->id);
Moyasar::invoices()->list(page: 1, perPage: 50);
Moyasar::invoices()->update($invoice->id, ['description' => 'Updated']);
Moyasar::invoices()->cancel($invoice->id);
```

Tokens
------

[](#tokens)

Server-side tokenisation for trusted-environment flows (admin tools, card-on-file migrations). For browser tokenisation, use Moyasar's JS SDK with your publishable key — the PAN never touches your server that way.

```
$token = Moyasar::tokens()->create([
    'name' => 'John Doe',
    'number' => '4111111111111111',
    'cvc' => '123',
    'month' => '12',
    'year' => '30',
    'callback_url' => route('checkout.token-callback'),
]);

$token->id;                  // 'token_...'
$token->status;              // TokenStatus enum
$token->lastFour;            // '1111'
$token->verificationUrl;     // 3DS URL when status === Initiated

// Use the token in a subsequent payment:
Moyasar::payments()->create([
    'amount' => 10000,
    'currency' => 'SAR',
    'source' => ['type' => 'token', 'token' => $token->id],
]);

Moyasar::tokens()->find('token_...');
```

Payouts
-------

[](#payouts)

```
use Iabduul7\Moyasar\Enums\PayoutStatus;

$payout = Moyasar::payouts()->create([
    'amount' => 100000,           // 1000.00 SAR
    'currency' => 'SAR',
    'description' => 'Vendor settlement',
    'beneficiary' => [
        'name' => 'Vendor Co.',
        'iban' => 'SA0380000000608010167519',
        'type' => 'iban',
    ],
]);

Moyasar::payouts()->find($payout->id);
Moyasar::payouts()->list(page: 1, perPage: 50, filters: ['status' => PayoutStatus::Paid]);
```

Bulk payouts and payout-account endpoints aren't wrapped yet — open an issue if you need them.

Testing helpers
---------------

[](#testing-helpers)

```
use Iabduul7\Moyasar\Testing\MoyasarTestCards;

// Named constants instead of magic 16-digit literals:
MoyasarTestCards::VISA_SUCCESS;
MoyasarTestCards::VISA_DECLINE;
MoyasarTestCards::MADA_SUCCESS;
MoyasarTestCards::MASTERCARD_SUCCESS;
MoyasarTestCards::AMEX_SUCCESS;

// Ready-to-spread source arrays:
$source = MoyasarTestCards::source(
    number: MoyasarTestCards::MADA_SUCCESS,
    threeDs: true,
    manual: true,
);

$stcPay = MoyasarTestCards::stcPaySource('0512345678');
```

Webhooks
--------

[](#webhooks)

The package registers a `POST /moyasar/webhook` route automatically (configurable). Add the URL to your Moyasar dashboard with the **same** secret you set in `MOYASAR_WEBHOOK_SECRET`. Moyasar embeds that secret as `secret_token` inside the webhook body — the package verifies it in constant time and rejects any mismatch with a `401`.

Replay protection caches each event `id` for `MOYASAR_WEBHOOK_REPLAY_TTL` seconds (default 24h). A redelivered event returns `200` but is **not** dispatched a second time.

### Events

[](#events)

Listen for the events you care about:

```
use Iabduul7\Moyasar\Events\PaymentPaid;
use Illuminate\Support\Facades\Event;

Event::listen(function (PaymentPaid $event) {
    $event->payment;     // PaymentData DTO
    $event->rawPayload;  // raw webhook body, array
});
```

All eight Moyasar event types are mapped:

Moyasar eventLaravel event class`payment_paid``Iabduul7\Moyasar\Events\PaymentPaid``payment_failed``Iabduul7\Moyasar\Events\PaymentFailed``payment_authorized``Iabduul7\Moyasar\Events\PaymentAuthorized``payment_captured``Iabduul7\Moyasar\Events\PaymentCaptured``payment_refunded``Iabduul7\Moyasar\Events\PaymentRefunded``payment_voided``Iabduul7\Moyasar\Events\PaymentVoided``payment_abandoned``Iabduul7\Moyasar\Events\PaymentAbandoned``payment_verified``Iabduul7\Moyasar\Events\PaymentVerified`Eloquent integration
--------------------

[](#eloquent-integration)

Add the `HasMoyasarPayments` trait to any model that needs to track payments:

```
use Iabduul7\Moyasar\Concerns\HasMoyasarPayments;

class Order extends Model
{
    use HasMoyasarPayments;
}
```

```
$order = Order::create([...]);

$record = $order->createMoyasarPayment([
    'amount' => 10000,
    'currency' => 'SAR',
    'description' => "Order #{$order->id}",
    'source' => [...],
]);

$record->moyasar_id;
$record->status;             // PaymentStatus enum

$order->moyasarPayments();        // MorphMany
$order->latestMoyasarPayment();
$order->hasSuccessfulPayment();   // true if any payment is paid, captured, or authorized
```

When webhooks arrive, the package automatically updates any existing `moyasar_payments` row keyed by `moyasar_id` — your local copy stays in sync without any wiring. Disable via `MOYASAR_WEBHOOK_SYNC_ELOQUENT=false`.

### `MoyasarPaymentCast`

[](#moyasarpaymentcast)

Cast a payment-ID column to a live `PaymentData` DTO (lazy fetch + per-instance cache):

```
use Iabduul7\Moyasar\Casts\MoyasarPaymentCast;

protected function casts(): array
{
    return ['moyasar_payment_id' => MoyasarPaymentCast::class];
}
```

> Reading the attribute calls the Moyasar API. Use sparingly to avoid N+1.

Testing your integration
------------------------

[](#testing-your-integration)

Downstream apps should fake the Moyasar HTTP API the same way the package's own suite does:

```
use Iabduul7\Moyasar\Facades\Moyasar;
use Illuminate\Support\Facades\Http;

Http::fake([
    'api.moyasar.com/v1/payments' => Http::response([
        'id' => 'pay_test',
        'status' => 'paid',
        'amount' => 10000,
        'currency' => 'SAR',
        // ...
    ], 201),
]);

$payment = Moyasar::payments()->create([...]);

expect($payment->isPaid())->toBeTrue();
```

For webhook tests, post a JSON body that includes a matching `secret_token` and assert against the dispatched event with `Event::fake([PaymentPaid::class, ...])`.

Saudi-specific notes
--------------------

[](#saudi-specific-notes)

- **Amounts are in halalas.** 100 halalas = 1.00 SAR. The DTOs expose both `amount` (int, halalas) and `amount_format` (string, e.g. `100.00 SAR`).
- **mada** is the Saudi domestic card scheme. It is selected automatically by Moyasar based on the BIN — clients just send the card number.
- **STC Pay** uses the `stcpay` source with a Saudi mobile number (`05xxxxxxxx`, `+9665xxxxxxxx`, or `009665xxxxxxxx`).
- **Apple Pay / Google Pay / Samsung Pay** must be tokenised on the client. Pass the encrypted token under `source.token` (Apple/Samsung Pay) or as a generic `token`-type source (Google Pay).

Configuration reference
-----------------------

[](#configuration-reference)

```
return [
    'secret_key' => env('MOYASAR_SECRET_KEY'),
    'publishable_key' => env('MOYASAR_PUBLISHABLE_KEY'),
    'webhook_secret' => env('MOYASAR_WEBHOOK_SECRET'),
    'base_url' => env('MOYASAR_BASE_URL', 'https://api.moyasar.com/v1'),

    'http' => [
        'timeout' => env('MOYASAR_HTTP_TIMEOUT', 30),
        'retry_times' => env('MOYASAR_HTTP_RETRY_TIMES', 3),
        'retry_delay' => env('MOYASAR_HTTP_RETRY_DELAY', 200),
    ],

    'webhooks' => [
        'enabled' => env('MOYASAR_WEBHOOKS_ENABLED', true),
        'route' => env('MOYASAR_WEBHOOK_ROUTE', 'moyasar/webhook'),
        'middleware' => ['api'],
        'replay_ttl' => env('MOYASAR_WEBHOOK_REPLAY_TTL', 86400),
        'sync_eloquent' => env('MOYASAR_WEBHOOK_SYNC_ELOQUENT', true),
    ],

    'default_currency' => env('MOYASAR_DEFAULT_CURRENCY', 'SAR'),

    'logging' => [
        'enabled' => env('MOYASAR_LOGGING_ENABLED', false),
        'channel' => env('MOYASAR_LOG_CHANNEL', 'stack'),
    ],
];
```

Contributing
------------

[](#contributing)

Pull requests welcome. Before submitting:

```
composer test       # Pest 4 suite
composer phpstan    # Larastan level 8
composer pint       # Laravel preset
```

Run the full check locally:

```
composer test && composer phpstan && composer pint-test
```

Security
--------

[](#security)

Found a security issue? Email **** rather than opening a public issue.

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

34

—

LowBetter than 75% of packages

Maintenance88

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity38

Early-stage or recently created project

 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

62d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/dc056cb14243e9c095935dd51acd334fb0a7bdbec31472295930ea0489dc7527?d=identicon)[iabduul.7](/maintainers/iabduul.7)

---

Top Contributors

[![iabduul7](https://avatars.githubusercontent.com/u/24309704?v=4)](https://github.com/iabduul7 "iabduul7 (18 commits)")

---

Tags

laravelmadamoyasarpayment-gatewaysaudi-arabialaravelpaymentpayment gatewayksasaudi-arabiaMoyasarmadastc-pay

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/iabduul7-laravel-moyasar/health.svg)

```
[![Health](https://phpackages.com/badges/iabduul7-laravel-moyasar/health.svg)](https://phpackages.com/packages/iabduul7-laravel-moyasar)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M347](/packages/psalm-plugin-laravel)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

77922.3M186](/packages/laravel-mcp)[api-platform/laravel

API Platform support for Laravel

58174.6k17](/packages/api-platform-laravel)[defstudio/telegraph

A laravel facade to interact with Telegram Bots

813336.8k3](/packages/defstudio-telegraph)[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.

5022.6k](/packages/simplestats-io-laravel-client)[linkxtr/laravel-qrcode

A clean, modern, and easy-to-use QR code generator for Laravel

3720.4k](/packages/linkxtr-laravel-qrcode)

PHPackages © 2026

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