Search by

richnessagency / rich-payments

richnessagency

Plug and play Laravel payment gateway package with encrypted database settings, ready views, and Paymob support.

Package info

github.com/richnessagency/rich-payments

pkg:composer/richnessagency/rich-payments

Statistics

Installs: 7

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.2.3 2026-09-06 23:11 UTC

This package is auto-updated.

Last update: 2026-09-06 23:11:34 UTC


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

  1. Visit the admin gateway page:
/admin/rich-payments/gateways
  1. Open the Paymob gateway.
  2. Enter encrypted credentials:
    • secret_key
    • public_key
    • hmac_secret
    • api_key
  3. Add or confirm integration identifiers for enabled methods:
    • cards
    • wallets
    • kiosk
    • bnpl
  4. Enable the gateway.
  5. Enable the payment methods you want customers to see.
  6. 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.success
  • rich-payments::results.failed
  • rich-payments::results.pending
  • rich-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_key
  • public_key
  • hmac_secret
  • api_key

Method integration identifiers are stored per payment method and encrypted:

  • cards
  • wallets
  • kiosk
  • bnpl

Webhooks

Webhook endpoint:

POST /payments/{gateway}/webhook

The selected gateway driver receives the Laravel request and returns a WebhookResult.

The package then:

  1. Stores a sanitized webhook event.
  2. Verifies the gateway signature/HMAC.
  3. Rejects invalid payloads with 400.
  4. Processes valid events inside a database transaction.
  5. Locks duplicate events using a canonical payload hash.
  6. Updates the matching payment attempt.
  7. Dispatches payment events.

Webhook payload snapshots are sanitized before storage.

Payment Status

Payment attempts use PaymentStatus:

  • initiated
  • redirected
  • pending
  • paid
  • failed
  • refunded
  • cancelled

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:

  • PaymentPaid
  • PaymentFailed
  • PaymentPending
  • PaymentRefunded
  • WebhookRejected

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 payload for 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.