richnessagency / rich-payments
Plug and play Laravel payment gateway package with encrypted database settings, ready views, and Paymob support.
Requires
- php: ^8.3
- illuminate/contracts: ^13.0
- illuminate/database: ^13.0
- illuminate/encryption: ^13.0
- illuminate/http: ^13.0
- illuminate/routing: ^13.0
- illuminate/support: ^13.0
- illuminate/view: ^13.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.27
- orchestra/testbench: ^11.0
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
richnessagency/rich-payments is a reusable Laravel payment package for
building reliable online payment flows across Richness projects and community
Laravel applications.
The package is designed around gateway drivers. Paymob is included as the first driver, and new gateways can be added without changing the checkout, webhook, credential storage, audit log, or admin management layers.
Goals
- Make online payments easy to add to any Laravel application.
- Keep gateway integrations isolated behind stable contracts.
- Store sensitive credentials encrypted in the database.
- Support hosted checkout redirects, callbacks, webhooks, inquiries, refunds, voids, and captures.
- Let each application customize routes, middleware, branding, result pages, and payment methods.
- Preserve backward compatibility for existing projects using earlier versions.
Installation
Install with Composer:
composer require richnessagency/rich-payments
Run package migrations:
php artisan migrate
Seed the default Paymob gateway and payment methods:
php artisan db:seed --class="Richness\\RichPayments\\Database\\Seeders\\RichPaymentsPaymobSeeder"
Publish optional files:
php artisan vendor:publish --tag=rich-payments-config php artisan vendor:publish --tag=rich-payments-views php artisan vendor:publish --tag=rich-payments-migrations
Quick Start
- Visit the admin gateway page:
/admin/rich-payments/gateways
- Open the Paymob gateway.
- Enter encrypted credentials:
secret_keypublic_keyhmac_secretapi_key
- Add or confirm integration identifiers for enabled methods:
cardswalletskioskbnpl
- Enable the gateway.
- Enable the payment methods you want customers to see.
- Start a checkout session from your application or use the package start route.
Configuration
Publish the config file and edit config/rich-payments.php.
return [ 'route_prefix' => 'payments', 'admin_route_prefix' => 'admin/rich-payments', 'public_payment_start_enabled' => true, 'middleware' => [ 'checkout' => ['web'], 'admin' => ['web', 'admin.placeholder', 'permission:payments.view'], 'admin_manage' => ['permission:payments.manage'], 'admin_transactions' => ['permission:payments.transactions'], 'webhook' => ['api'], ], 'default_currency' => 'EGP', 'default_gateway' => 'paymob', 'response_redirect_route' => null, 'response_redirect_parameter' => 'order', 'response_verified_reference_session_key' => null, ];
Important Config Keys
| Key | Purpose |
|---|---|
route_prefix |
Public payment route prefix. Default: payments. |
admin_route_prefix |
Admin management route prefix. |
public_payment_start_enabled |
Enables package-provided /methods and /start routes. |
middleware.checkout |
Middleware for checkout, callback, success, failed, and pending pages. |
middleware.admin |
Middleware for gateway settings and transaction actions. |
middleware.admin_manage |
Extra middleware for gateway updates and connection tests. |
middleware.admin_transactions |
Extra middleware for inquiry, refund, void, and capture actions. |
middleware.webhook |
Middleware for gateway webhook endpoints. |
default_currency |
Default ISO currency code. |
default_gateway |
Gateway used by fallback response handling. |
response_redirect_route |
Optional host route after verified payment callback. |
response_redirect_parameter |
Route parameter name used for redirect. |
response_verified_reference_session_key |
Optional session key used to prevent unverified return redirects. |
views.* |
Branding values for built-in pages. |
gateways.* |
Driver class and gateway-specific endpoints. |
Environment Variables
The default config reads these values:
RICHPAYMENTS_VIEWS_SITE_NAME="My Store" RICHPAYMENTS_VIEWS_LOGO_URL=https://example.com/logo.png RICHPAYMENTS_VIEWS_PRIMARY_COLOR=#111827 RICHPAYMENTS_VIEWS_ACCENT_COLOR=#f97316 RICHPAYMENTS_PAYMOB_BASE_URL=https://accept.paymob.com
Payment secrets are intentionally managed from the encrypted database admin UI, not from frontend code.
Routes
Default public routes:
| Method | URI | Route Name | Purpose |
|---|---|---|---|
GET |
/payments/methods |
rich-payments.methods |
Shows enabled payment methods. |
POST |
/payments/start |
rich-payments.start |
Starts a hosted checkout payment. |
GET |
/payments/pending |
rich-payments.pending |
Pending state page. |
GET |
/payments/status/{reference} |
rich-payments.status |
JSON payment status/inquiry endpoint. |
GET |
/payments/success |
rich-payments.success |
Default success page. |
GET |
/payments/failed |
rich-payments.failed |
Default failure page. |
POST |
/payments/{gateway}/webhook |
rich-payments.webhook |
Gateway webhook endpoint. |
GET |
/payments/{gateway}/callback |
rich-payments.response |
Gateway browser callback endpoint. |
Default admin routes:
| Method | URI | Purpose |
|---|---|---|
GET |
/admin/rich-payments/gateways |
Gateway list. |
GET |
/admin/rich-payments/gateways/{gateway} |
Gateway settings. |
PUT |
/admin/rich-payments/gateways/{gateway} |
Save credentials and methods. |
POST |
/admin/rich-payments/gateways/{gateway}/test-connection |
Test gateway credentials. |
GET |
/admin/rich-payments/attempts |
Payment attempts. |
POST |
/admin/rich-payments/attempts/{attempt}/inquire |
Transaction inquiry. |
POST |
/admin/rich-payments/attempts/{attempt}/refund |
Refund. |
POST |
/admin/rich-payments/attempts/{attempt}/void |
Void. |
POST |
/admin/rich-payments/attempts/{attempt}/capture |
Capture. |
GET |
/admin/rich-payments/audit-logs |
Audit logs. |
Route prefixes and middleware are configurable.
Starting A Payment From Code
Use the RichPayments service when your app owns the checkout/order flow.
use App\Models\Order; use Richness\RichPayments\Data\PaymentRequest; use Richness\RichPayments\Models\PaymentGateway; use Richness\RichPayments\RichPayments; $gateway = PaymentGateway::query() ->where('code', 'paymob') ->where('active', true) ->firstOrFail(); $order = Order::query()->findOrFail($orderId); $session = app(RichPayments::class)->start( gateway: $gateway, request: new PaymentRequest( amountMinor: 150000, currency: 'EGP', merchantReference: (string) $order->getKey(), methodCode: 'cards', items: [ ['name' => 'Order #' . $order->getKey(), 'amount_minor' => 150000, 'quantity' => 1], ], customer: [ 'name' => $order->customer_name, 'email' => $order->customer_email, 'phone' => $order->customer_phone, ], metadata: [ 'order_id' => $order->getKey(), ], notificationUrl: route('rich-payments.webhook', ['gateway' => 'paymob']), redirectionUrl: route('rich-payments.response', ['gateway' => 'paymob']), ), payable: $order, ); return redirect()->away($session->checkoutUrl);
Amounts use minor units. For EGP, 150000 means 1500.00 EGP.
Using The Built-in Start Route
The package can expose a generic start endpoint when:
'public_payment_start_enabled' => true,
Example form:
<form method="POST" action="{{ route('rich-payments.start') }}"> @csrf <input type="hidden" name="gateway" value="paymob"> <input type="hidden" name="method" value="cards"> <input type="hidden" name="amount_minor" value="150000"> <input type="hidden" name="currency" value="EGP"> <input type="hidden" name="merchant_reference" value="{{ $order->id }}"> <input type="hidden" name="customer[name]" value="{{ $order->customer_name }}"> <input type="hidden" name="customer[email]" value="{{ $order->customer_email }}"> <input type="hidden" name="customer[phone]" value="{{ $order->customer_phone }}"> <button type="submit">Pay Now</button> </form>
For production stores, prefer starting payments from your own checkout controller so you can calculate totals server-side and prevent amount tampering.
Success, Failure, Pending, And Callback Pages
Built-in result views:
rich-payments::results.successrich-payments::results.failedrich-payments::results.pendingrich-payments::checkout.methods
Publish views:
php artisan vendor:publish --tag=rich-payments-views
Published files are placed under:
resources/views/vendor/rich-payments/
Customize these pages like normal Blade files:
resources/views/vendor/rich-payments/results/success.blade.php
resources/views/vendor/rich-payments/results/failed.blade.php
resources/views/vendor/rich-payments/results/pending.blade.php
resources/views/vendor/rich-payments/checkout/methods.blade.php
Redirecting Back To Your App
Set a verified redirect route:
'response_redirect_route' => 'orders.show', 'response_redirect_parameter' => 'order', 'response_verified_reference_session_key' => 'verified_payment_references',
When a gateway callback is verified, the package redirects to:
route('orders.show', ['order' => $merchantReference])
If verification fails, the package sends the user to the pending page. Redirect pages are never treated as proof of payment. Verified webhook/callback data and transaction inquiry are the source of truth.
Branding Built-in Pages
Configure:
RICHPAYMENTS_VIEWS_SITE_NAME="My Store" RICHPAYMENTS_VIEWS_LOGO_URL=https://example.com/logo.png RICHPAYMENTS_VIEWS_PRIMARY_COLOR=#111827 RICHPAYMENTS_VIEWS_ACCENT_COLOR=#f97316
Or edit the published Blade views for full control.
Credential Management
Gateway credentials are stored in rich_payment_credentials.
Security behavior:
- Secret values are encrypted before storage.
- Masked previews are shown after save.
- Raw secret values are never stored in audit logs.
- Credential rotations are audited.
- Frontend code must never receive secret keys.
Paymob default credential keys:
secret_keypublic_keyhmac_secretapi_key
Method integration identifiers are stored per payment method and encrypted:
cardswalletskioskbnpl
Webhooks
Webhook endpoint:
POST /payments/{gateway}/webhook
The selected gateway driver receives the Laravel request and returns a
WebhookResult.
The package then:
- Stores a sanitized webhook event.
- Verifies the gateway signature/HMAC.
- Rejects invalid payloads with
400. - Processes valid events inside a database transaction.
- Locks duplicate events using a canonical payload hash.
- Updates the matching payment attempt.
- Dispatches payment events.
Webhook payload snapshots are sanitized before storage.
Payment Status
Payment attempts use PaymentStatus:
initiatedredirectedpendingpaidfailedrefundedcancelled
The status endpoint:
GET /payments/status/{reference}
If the attempt is not final and has an external transaction id, the package may ask the gateway driver for an inquiry result and update the attempt.
Events
The package dispatches events your application can listen to:
PaymentPaidPaymentFailedPaymentPendingPaymentRefundedWebhookRejected
Use these events to update orders, send notifications, or queue fulfillment. Keep listeners idempotent because webhooks can be retried.
Refund, Void, And Capture
Drivers that support money actions implement ManagesTransactions.
use Richness\RichPayments\Contracts\ManagesTransactions; $driver = app(\Richness\RichPayments\Gateways\GatewayManager::class)->driver($gateway); if ($driver instanceof ManagesTransactions) { $result = $driver->refund($gateway, $transactionId, 50000); }
Admin screens already expose inquiry, refund, void, and capture actions when the gateway supports them. Every money action should create transaction records and audit logs.
Adding A New Gateway
Add a driver class implementing PaymentGatewayDriver.
<?php declare(strict_types=1); namespace App\Payments\Gateways\Stripe; use Illuminate\Http\Request; use Richness\RichPayments\Contracts\PaymentGatewayDriver; use Richness\RichPayments\Contracts\SupportsConnectionTest; use Richness\RichPayments\Data\ConnectionResult; use Richness\RichPayments\Data\InquiryResult; use Richness\RichPayments\Data\PaymentRequest; use Richness\RichPayments\Data\PaymentSession; use Richness\RichPayments\Data\WebhookResult; use Richness\RichPayments\Enums\PaymentStatus; use Richness\RichPayments\Models\PaymentGateway; final class StripeGateway implements PaymentGatewayDriver, SupportsConnectionTest { public function createSession(PaymentGateway $gateway, PaymentRequest $request): PaymentSession { // Read encrypted credentials, call the gateway API, and return checkout data. return new PaymentSession( gatewayCode: $gateway->code, status: PaymentStatus::Redirected->value, externalReference: 'gateway-session-id', checkoutUrl: 'https://checkout.example/session', payload: [], ); } public function handleWebhook(PaymentGateway $gateway, Request $request): WebhookResult { // Verify signature, normalize status, and return a gateway-independent result. return new WebhookResult( verified: true, success: true, status: PaymentStatus::Paid->value, merchantReference: 'order-123', externalTransactionId: 'txn-123', paidAmountMinor: 150000, currency: 'EGP', payload: $request->all(), ); } public function inquire(PaymentGateway $gateway, string $externalTransactionId): InquiryResult { // Return normalized inquiry status from the gateway. } public function checkoutUrl(PaymentGateway $gateway, string $clientSecret): string { return 'https://checkout.example/' . $clientSecret; } public function testConnection(PaymentGateway $gateway): ConnectionResult { // Call a lightweight authenticated gateway endpoint. } }
Register the driver in config/rich-payments.php:
'gateways' => [ 'paymob' => [ 'driver' => \Richness\RichPayments\Gateways\Paymob\PaymobGateway::class, 'name' => 'Paymob', ], 'stripe' => [ 'driver' => \App\Payments\Gateways\Stripe\StripeGateway::class, 'name' => 'Stripe', 'base_url' => env('STRIPE_BASE_URL', 'https://api.stripe.com'), ], ],
Seed or create a rich_payment_gateways row with code stripe, then add methods
such as cards, apple_pay, or google_pay.
Driver Contract Rules
A gateway driver should:
- Convert app payment requests into gateway sessions.
- Verify webhooks cryptographically before returning
verified: true. - Normalize gateway statuses into
PaymentStatus. - Use minor currency units.
- Never trust browser redirects alone.
- Never log raw credentials, card data, or full webhook secrets.
- Throw clear exceptions for setup/configuration errors.
- Keep external API payloads in
payloadfor debugging after sanitization.
Backward Compatibility
Existing applications may already depend on:
- Config keys in
config/rich-payments.php. - Route names beginning with
rich-payments.*. - View namespace
rich-payments::. - Database table names beginning with
rich_payment_. - Driver contracts in
src/Contracts. - Paymob method codes:
cards,wallets,kiosk,bnpl.
Do not rename these without a major version release and migration guide.
Safe additions:
- New gateway config entries.
- New optional contract methods through extra interfaces.
- New nullable database columns.
- New views that do not replace existing names.
- New events.
Risky changes:
- Changing route names.
- Changing enum values.
- Changing amount units.
- Changing webhook verification behavior.
- Changing credential key names.
- Removing or renaming model columns.
Security Checklist
Before enabling a gateway in production:
- Use HTTPS for checkout, callback, and webhook URLs.
- Configure gateway webhooks to the exact production endpoint.
- Verify HMAC/signature for every webhook/callback type you accept.
- Keep API keys encrypted and out of source control.
- Use strong
APP_KEY; rotating it requires a credential rotation plan. - Restrict admin routes with authentication and payment permissions.
- Validate amounts server-side from orders/carts, not from public forms.
- Make webhook/order listeners idempotent.
- Store only sanitized payload snapshots.
- Use gateway inquiry for ambiguous or pending states.
- Test success, failure, pending, duplicate webhook, refund, void, and capture flows.
Testing
Run the package test suite:
composer test
Or:
vendor/bin/phpunit vendor/bin/phpstan analyse vendor/bin/pint --test
Recommended integration tests in consuming apps:
- Start checkout with each enabled method.
- Receive valid webhook and mark order paid.
- Reject invalid webhook signature.
- Handle duplicate webhook idempotently.
- Redirect success only after verified callback/webhook.
- Show pending page for unverified browser callback.
- Refund partial and full amounts.
- Rotate credentials and confirm old values are not shown.
Production Release Workflow
This package is distributed through GitHub and Composer/Packagist:
composer require richnessagency/rich-payments
For stable public usage, tag semantic versions:
git tag v1.0.0 git push origin v1.0.0
Consuming apps should prefer stable constraints such as:
{
"require": {
"richnessagency/rich-payments": "^1.0"
}
}
Use dev-main only for active internal development.
License
MIT License. Created by Richness Agency.