laravel-payments maintained by mhaggag

Laravel Payments
A comprehensive and flexible Laravel package for handling payments, subscriptions, and webhooks across multiple gateways including Stripe, PayPal, and Paymob.
Requirements
- PHP: >= 8.2
- Laravel: >= 11
Table of Contents
- 1. Installation
- 2. Publishing Package Files
- 3. Configuration
- 4. Environment Variables
- 5. Supported Gateways
- 6. Using Gateway Enums
- 7. Using the Payment Facade
- 8. Using the Payable Trait
- 9. Using the Subscribable Trait
- 10. Gateway Payload & Parameters
- 11. Web & Webhook Routes
- 12. Events & Listeners
- 13. Models & Database
- 14. Views & Translations
- 15. Debug Mode & Logging
- 16. Custom Gateways
- 17. Screenshots from default routes
- 18. Testing
- 19. License
- 20. Contact
1. Installation
Install the package via Composer:
composer require mhaggag/laravel-payments
2. Publishing Package Files
Publish all package assets:
php artisan vendor:publish --provider="MHaggag\Payments\PaymentsServiceProvider"
Or publish selectively:
# Migrations (REQUIRED)
php artisan vendor:publish --tag=payments-migrations
# Config (OPTIONAL)
php artisan vendor:publish --tag=payments-config
# Views (OPTIONAL)
php artisan vendor:publish --tag=payments-views
# Translations (OPTIONAL)
php artisan vendor:publish --tag=payments-translations
Run migrations:
php artisan migrate
3. Configuration (config/payments.php)
All package behavior is controlled from one config file:
use MHaggag\Payments\Console\PaypalWebhookCommand;
use MHaggag\Payments\Console\StripeWebhookCommand;
use MHaggag\Payments\Enums\GatewayName;
return [
/*
|--------------------------------------------------------------------------
| Default Payment Gateway
|--------------------------------------------------------------------------
*/
'default' => env('PAYMENT_DEFAULT_GATEWAY', GatewayName::STRIPE),
/*
|--------------------------------------------------------------------------
| Default Currency
|--------------------------------------------------------------------------
*/
'default_currency' => env('PAYMENT_DEFAULT_CURRENCY', 'USD'),
/*
|--------------------------------------------------------------------------
| Route Configuration
|--------------------------------------------------------------------------
| * On enable it, will set 3 routes
| POST payments/checkout ............ payments.checkout › MHaggag\Payments\Http\Controllers\PaymentController@checkout
| GET payments/success/{gateway} ... payments.success › MHaggag\Payments\Http\Controllers\PaymentController@success
| GET payments/cancel/{gateway} .... payments.cancel › MHaggag\Payments\Http\Controllers\PaymentController@cancel
*/
'route' => [
'enabled' => env('PAYMENT_ROUTE_ENABLED', true),
'prefix' => env('PAYMENT_ROUTE_PREFIX', 'payments'),
'middleware' => ['web'],
'name_prefix' => env('PAYMENT_ROUTE_NAME_PREFIX', 'payments.'),
],
/*
|--------------------------------------------------------------------------
| Model Configuration
|--------------------------------------------------------------------------
*/
'models' => [
'payment' => \MHaggag\Payments\Models\Payment::class,
'subscription' => \MHaggag\Payments\Models\Subscription::class,
],
/*
|--------------------------------------------------------------------------
| Payment Gateways Configuration
|--------------------------------------------------------------------------
*/
'gateways' => [
GatewayName::STRIPE => [
'class' => \MHaggag\Payments\Gateways\StripeGateway::class,
'api_key' => env('STRIPE_API_KEY'),
'secret_key' => env('STRIPE_SECRET_KEY'),
'webhook_secret' => env('STRIPE_WEBHOOK_SECRET'),
'mode' => env('STRIPE_MODE', 'test'),
'currency' => env('STRIPE_CURRENCY', 'usd'),
],
GatewayName::PAYPAL => [
'class' => \MHaggag\Payments\Gateways\PaypalGateway::class,
'client_id' => env('PAYPAL_CLIENT_ID'),
'client_secret' => env('PAYPAL_CLIENT_SECRET'),
'webhook_id' => env('PAYPAL_WEBHOOK_ID'),
'mode' => env('PAYPAL_MODE', 'sandbox'),
'currency' => env('PAYPAL_CURRENCY', 'USD'),
],
GatewayName::PAYMOB => [
'class' => \MHaggag\Payments\Gateways\PaymobGateway::class,
'api_key' => env('PAYMOB_API_KEY'),
'secret_key' => env('PAYMOB_SECRET_KEY'),
'public_key' => env('PAYMOB_PUBLIC_KEY'),
'hmac_secret' => env('PAYMOB_HMAC_SECRET'),
'integration_id' => env('PAYMOB_INTEGRATION_ID'),
'moto_integration_id' => env('PAYMOB_MOTO_INTEGRATION_ID'),
'mode' => env('PAYMOB_MODE', 'test'),
'currency' => env('PAYMOB_CURRENCY', 'EGP'),
'region' => env('PAYMOB_REGION', 'EGY'),
],
],
/*
|--------------------------------------------------------------------------
| Webhook Configuration
|--------------------------------------------------------------------------
|
| * with built in payments.verify_signature midlleware that you can remove it to prevent the signature verification.
| * On enable it, will set 3 routes
| POST payments/webhook/{gateway} ........ MHaggag\Payments\Http\Controllers\WebhookController@handle
*/
'webhook' => [
'enabled' => env('PAYMENT_ROUTE_WEBHOOK_ENABLED', true),
'prefix' => '/payments/webhook',
'middleware' => ['api', 'payments.verify_signature'],
'events' => [
'stripe' => StripeWebhookCommand::DEFAULT_EVENTS,
'paypal' => PaypalWebhookCommand::DEFAULT_EVENTS,
],
],
/*
|--------------------------------------------------------------------------
| Events Configuration
|--------------------------------------------------------------------------
*/
'events' => [
'payment' => [
'received' => \MHaggag\Payments\Events\PaymentRedirectReceived::class,
'processed' => \MHaggag\Payments\Events\PaymentRedirectProcessed::class,
],
'webhook' => [
'received' => \MHaggag\Payments\Events\PaymentWebhookReceived::class,
'processed' => \MHaggag\Payments\Events\PaymentWebhookProcessed::class,
],
],
/*
|--------------------------------------------------------------------------
| Logging Configuration
|--------------------------------------------------------------------------
*/
'logging' => [
'enabled' => env('PAYMENT_LOGGING_ENABLED', false),
'channel' => env('PAYMENT_LOGGING_CHANNEL', 'stack'),
'level' => env('PAYMENT_LOGGING_LEVEL', 'info'),
],
];
4. Environment Variables
Add these variables to your .env file to configure the package:
# General
PAYMENT_DEFAULT_GATEWAY=stripe
PAYMENT_DEFAULT_CURRENCY=USD
PAYMENT_ROUTE_ENABLED=true
PAYMENT_ROUTE_PREFIX=payments
PAYMENT_ROUTE_NAME_PREFIX=payments.
PAYMENT_ROUTE_WEBHOOK_ENABLED=true
PAYMENT_LOGGING_ENABLED=true
PAYMENT_LOGGING_CHANNEL=stack
PAYMENT_LOGGING_LEVEL=info
# Stripe
STRIPE_API_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_MODE=test
STRIPE_CURRENCY=usd
# PayPal
PAYPAL_CLIENT_ID=...
PAYPAL_CLIENT_SECRET=...
PAYPAL_WEBHOOK_ID=...
PAYPAL_MODE=sandbox
PAYPAL_CURRENCY=USD
# Paymob
PAYMOB_API_KEY=...
PAYMOB_SECRET_KEY=...
PAYMOB_PUBLIC_KEY=...
PAYMOB_HMAC_SECRET=...
PAYMOB_INTEGRATION_ID=...
PAYMOB_MOTO_INTEGRATION_ID=...
PAYMOB_MODE=test
PAYMOB_CURRENCY=EGP
PAYMOB_REGION=EGY
5. Supported Gateways
- Stripe
- PayPal
- Paymob
- Easily extendable with custom gateways
Each gateway has:
- Its own config section
- Its own payload definition
6. Using Gateway Enums
Select the gateway for a payment process using the GatewayName enum:
use MHaggag\Payments\Enums\GatewayName;
GatewayName::STRIPE;
GatewayName::PAYPAL;
GatewayName::PAYMOB;
Example:
use MHaggag\Payments\Facades\Payment;
Payment::gateway(GatewayName::Stripe);
7. Using the Payment Facade
The Payment facade provides a fluent interface to construct and execute payment requests.
Available Methods
gateway(string $name): Set the payment gateway driver, or use the default inconfig/payments.phpwithdefaultkey.withPayload(array $payload): Pass gateway-specific parameters (including the next methods parameters and any custom parameters).
Or you can set your payload as separate methods:
amount(float $amount, ?string $currency = null): Set the amount to charge.currency(string $currency): Set the currency (e.g., 'USD').for(Model $payable): Associate the payment with a model.isSubscription(bool $isSubscription = true): Flag as subscription.items(array $items): Set items. See gateway documentation for structure.metadata(array $metadata): Set metadata.description(string $description): Set description.redirectUrls(string $successUrl, string $cancelUrl): Set success and cancel URLs. Default URLs are configured inconfig/payments.phpunder theroutekey.
You can review the Gateways Payloads & Parameters section for more details and any custom parameters you may need to pass to the gateway individually.
Execution Methods
create(): Create a payment intent/transaction.checkout(): Initiate a checkout session that will returncheckout_urlfor redirect to payment page.refund(Model $payment, ?float $amount = null): Refund a payment.handleRedirect(string $gateway, array $payload): Handle gateway redirect.verifySignature(string $gateway, Request $request): Handle gateway verification signature.handleWebhook(string $gateway, array $payload): Process incoming webhooks.
Examples
Basic Payment:
use MHaggag\Payments\Facades\Payment;
use MHaggag\Payments\Enums\GatewayName;
Payment::gateway(GatewayName::PAYPAL)
->amount(50)
->currency('EUR')
->description('Order #123')
->redirectUrls(route('payment.success'), route('payment.cancel'))
->isSubscription()
->items([
{
'name' => 'Product A',
'price' => 10,
'quantity' => 2,
},
{
'name' => 'Product B',
'price' => 20,
'quantity' => 1,
},
])
->metadata([
'order_id' => 123,
])
->checkout();
// Or can you set all with
Payment::gateway(GatewayName::PAYPAL)
->withPayload([
'amount' => 50,
'currency' => 'EUR',
'description' => 'Order #123',
'success_url' => route('payment.success'),
'cancel_url' => route('payment.cancel'),
'allow_promotion_codes' => true,
'is_subscription' => false,
'items' => [
{
'name' => 'Product A',
'amount' => 10,
'quantity' => 2,
},
{
'name' => 'Product B',
'amount' => 20,
'quantity' => 1,
},
],
'metadata' => [
'order_id' => 123,
],
])
->checkout();
8. Using the Payable Trait
Attach payments to any Eloquent model:
use MHaggag\Payments\Traits\Payable;
class User extends Model
{
use Payable;
}
Usage:
$user->payment(GatewayName::STRIPE)
->amount(100)
->currency('USD')
->checkout();
Available Methods
payment(string $gateway): Initiate a payment process, and using all previous methods in the payment facade.
payments(): Get all payments relationship.successfulPayments(): Get successful payments relationship.pendingPayments(): Get pending payments relationship.failedPayments(): Get failed payments relationship.refundedPayments(): Get refunded payments relationship.lastPayment(): Get the latest payment model.totalPaid(): Get total amount paid.totalRefunded(): Get total amount refunded.hasPayments(): Check if model has any payments.hasSuccessfulPayment(): Check if model has successful payment.hasPendingPayment(): Check if model has pending payment.
Scopes
hasPaymentWithStatus(string $status): Filter models by payment status.hasPaymentWithGateway(string $gateway): Filter models by gateway.
9. Using the Subscribable Trait
Using it with Payable Trait when you want to support subscriptions mode.
use MHaggag\Payments\Traits\Subscribable;
class User extends Model
{
use Subscribable;
}
Usage:
$user->subscribe(GatewayName::PAYPAL)
->amount(20)
->currency('USD')
->checkout();
Available Methods
subscribe(string $gateway): Initiate a subscription process, and using all previous methods in the payment facade.
subscriptions(): Get all subscriptions relationship.activeSubscriptions(): Get active subscriptions relationship.canceledSubscriptions(): Get canceled subscriptions relationship.trialingSubscriptions(): Get trialing subscriptions relationship.latestSubscription(): Get the latest subscription model.isSubscribed(): Check if model has an active subscription.onTrial(): Check if model is currently on trial.
Scopes
hasSubscriptionWithStatus(string $status): Filter models by subscription status.hasSubscriptionWithGateway(string $gateway): Filter models by gateway.
10. Gateway Payload & Parameters
Each gateway defines specific parameters.
Stripe
Payment::gateway(GatewayName::STRIPE)
// Special parameters for stripe
->payload([
'payment_method_types' => ['card'],
'customer' => $user->name,
'customer_email' => $user->email,
'client_reference_id' => $user->id, // Optional, the default will be payment->uuid
'subscription_data' => [], // if you want to create a subscription
'allow_promotion_codes' => true,
'source' => '', // Token, card reference, etc when using create method instead of checkout
'items' => [
// items can be
{
'name' => 'Product A',
'amount' => 10,
'quantity' => 2,
},
// Or
{
'price_id' => 'stripe_price_id',
'quantity' => 1,
},
// if you do not pass any items, the default will be the payment amount, and name will be description.
],
]);
/**
* Note: By using isSubscription() method to enable subscription mode
* You must provide at least one recurring price in `subscription` mode when using prices.
*/
Payment::gateway(GatewayName::STRIPE)
->withPayload([])
->isSubscription()
->items([
['price_id' => 'price_1St7q................', 'quantity' => 1]
])
Test Card Details Card Number: 4242424242424242 Expiration Date: 12/34 CVV: 123
PayPal
Payment::gateway(GatewayName::PAYPAL)
// Special parameters for paypal
->payload([
'brand_name' => 'Your Brand', // default will be app name
'locale' => 'en_US', // default will be 'en_US'
'source' => '', // Token, card reference, etc when using create method instead of checkout
'items' => [
// items can be
{
'name' => 'Product A',
'amount' => 10,
'quantity' => 2,
'description' => 'Product A description'
},
// if you do not pass any items, the default will be the payment amount, and name will be description.
],
]);
/**
* Note: By using isSubscription() method to enable subscription mode
* You must send plan_id that you created in paypal.
*/
Payment::gateway(GatewayName::PAYPAL)
->withPayload([
'plan_id' => 'paypal_plan_id',
])
->isSubscription()
Paymob
Payment::gateway(GatewayName::PAYMOB)
->withPayload([
'notification_url' => 'notification_url', // default will be configured in config/payments.php under the webhook key
'billing_data' => [
"email" => "mhaggag@gmail.com", // required
"first_name" => "mahmoud", // required
"last_name" => "haggag", // required
"phone_number" => "+201121xxxxxx", // required
"apartment" => "dumy", // optional
"street" => "dumy", // optional
"building" => "dumy", // optional
"city" => "dumy", // optional
"country" => "dumy", // optional
"floor" => "dumy", // optional
"state" => "dumy" // optional
],
'items' => [ // optional
[
'name' => 'Item name',
'amount' => 100,
'quantity' => 1,
'description' => 'Item description'
],
],
'special_reference' => $user->uuid, // Optional, the default will be payment->uuid
]);
/**
* Note: By using isSubscription() method to enable subscription mode
* You must send plan_id that you created in next step.
*/
$plan = Payment::driver(GatewayName::PAYMOB)->createSubscriptionPlan([
'name' => 'Test Plan',
'amount' => 100,
'frequency' => 7, // Values can be (7, 15, 30, 60, 90, 180, 360)
'use_transaction_amount' => true, // default is true
'reminder_days' => 2, // optional
'retrial_days' => 2, // optional
'number_of_deductions' => 2, // default is null
'webhook_url' => 'webhook_url', // default will be configured in config/payments.php under the webhook key
]);
Payment::gateway(GatewayName::PAYMOB)
->withPayload([
'plan_id' => $plan['id'],
])
->isSubscription()
/**
* Subscription creation is being done by completing one 3DS transaction to save the customer's card and connect it with the subscription.
*/
Card Test Credentials Card Number: 2223000000000007 Expiration Date: 01/39 CVV: 100 Card Holder Name: Test Account
Wallet Test Credentials Wallet Number: 01010101010 MPin Code: 123456 OTP: 123456
11. Web & Webhook Routes
Enable or disable routes from config, and by default the webhook routes use the payments.verify_signature middleware to verify the signature or use the verifySignature method on the facade. You can control all these by route and webhook keys in config/payments.php:
'route' => [
'enabled' => env('PAYMENT_ROUTE_ENABLED', true),
'prefix' => env('PAYMENT_ROUTE_PREFIX', 'payments'),
'middleware' => ['web'],
'name_prefix' => env('PAYMENT_ROUTE_NAME_PREFIX', 'payments.'),
]
'webhook' => [
'enabled' => env('PAYMENT_ROUTE_WEBHOOK_ENABLED', true),
'prefix' => '/payments/webhook',
'middleware' => ['api', 'payments.verify_signature'],
]
Default web URLs (you can see the screenshots below):
- POST
/payments/checkout - GET
/payments/success/{gateway} - GET
/payments/cancel/{gateway}
Default webhook URLs:
- POST
/payments/webhook/{gateway}
Also, you can create webhook events by this commands:
php artisan stripe:webhook
# Available options (optional)
--url=https://domain.com/payments/webhook/stripe
--api-version=2022-08-01
--disabled=false
php artisan paypal:webhook
# Available options (optional)
--url=https://domain.com/payments/webhook/paypal
- This command will create a webhook by default values in
config/payments.phpunder thewebhookkey. - By this key, you can custom the webhook url and events that you want to handle.
- Default webhook url is:
https://domain.com/payments/webhook/{gateway}. - And default events that will create. Will be the package need to handle the all payment/subscription process logic to all functions.
12. Events & Listeners
You can customize your events instead of using in config/payments.php by adding them to the events key:
'events' => [
'payment' => [
'received' => \MHaggag\Payments\Events\PaymentRedirectReceived::class,
'processed' => \MHaggag\Payments\Events\PaymentRedirectProcessed::class,
],
'webhook' => [
'received' => \MHaggag\Payments\Events\PaymentWebhookReceived::class,
'processed' => \MHaggag\Payments\Events\PaymentWebhookProcessed::class,
],
]
Available Events on using handleRedirect method on facade
events.payment.receiveddefault isPaymentRedirectReceivedevents.payment.processeddefault isPaymentRedirectProcessed
Available Events on using handleWebhook method on facade
events.webhook.receiveddefault isPaymentWebhookReceivedevents.webhook.processeddefault isPaymentWebhookProcessed
Listening to Events
Event::listen(PaymentRedirectProcessed::class, function ($event) {
logger('Payment success:', $event->payment->toArray());
});
Event::listen(PaymentWebhookProcessed::class, function ($event) {
logger('Webhook processed:', $event->payment->toArray());
});
13. Models & Database
Published migrations create tables for:
- payments
- subscriptions (if enabled)
Override models in config:
'models' => [
'payment' => \MHaggag\Payments\Models\Payment::class,
'subscription' => \MHaggag\Payments\Models\Subscription::class,
],
14. Views & Translations
Views
Publish views:
php artisan vendor:publish --tag=payments-views
Customize checkout, success, and error pages.
Translations
Publish language files:
php artisan vendor:publish --tag=payments-translations
Add your own language:
lang/fr/success.php
15. Debug Mode & Logging
Enable debug mode:
PAYMENT_LOGGING_ENABLED=true
This logs:
- Gateway requests
- Payloads
- Responses
- Webhook data
16. Custom Gateways
You can add your own gateway by extending the MHaggag\Payments\Gateways\BaseGateway class.
- Create your gateway class:
namespace App\Payments\Gateways;
use MHaggag\Payments\Gateways\BaseGateway;
use Illuminate\Database\Eloquent\Model;
class MyCustomGateway extends BaseGateway
{
public function createCheckout(array $payload): array
{
// Implement logic to create checkout session/transaction
// Return ['checkout_url' => '...', 'session_id' => '...']
}
public function handleRedirect(array $payload): Model
{
// Verify payment status and update the payment model
}
// Implement other required methods...
}
- Register in
config/payments.php:
'gateways' => [
'custom_gateway' => [
'class' => \App\Payments\Gateways\MyCustomGateway::class,
'api_key' => env('CUSTOM_API_KEY'),
],
],
17. Screenshots from default routes
Success
| English | Arabic |
|---|---|
![]() |
![]() |
Cancel
| English | Arabic |
|---|---|
![]() |
![]() |
Error
| English | Arabic |
|---|---|
![]() |
![]() |
18. Testing
php artisan test
```bash
./vendor/bin/phpunit
Mock gateways are included for local testing.
19. License
MIT License.
Final Notes
This package is designed to be:
- Framework-native
- Fully configurable
- Easy to extend
- Safe for production use
If you need custom gateways, advanced subscriptions, or multi-tenant support, extend the base gateway class or contact me for assistance.
Happy coding 🚀
20. Contact
If you have any questions, suggestions, or need support, feel free to contact me:
- Email: mahmoudhaggag641@gmail.com
- Phone: +201121300234
- LinkedIn: Mahmoud Haggag





