---
url: >-
  https://docs.corbitaltech.dev/products/whatsmark-saas/customization/payment-gateway/payment-gateway-guide.md
description: >-
  Professional guide for integrating third-party payment gateways into
  WhatsMarkSaaS billing system with multi-tenant support
---

# Payment Gateway Development

::: warning Minimum Version Requirement
This feature requires **WhatsMarkSaaS v2.0.0 or higher**. Ensure your installation meets this requirement before proceeding.
:::

::: warning Extended License Required
This feature is available with an [Extended License](https://codecanyon.net/item/whatsmarksaas-whatsapp-marketing-automation-saas-platform-with-bots-chats-bulk-sender-ai/58714968/support) only.
:::

## Overview

This guide demonstrates how to integrate third-party payment gateways into WhatsMarkSaaS. You will implement the PaymentGatewayInterface and register your gateway with the multi-tenant billing system.

Payment gateway modules are self-contained extensions that add new payment processing capabilities to the platform while maintaining multi-tenant isolation and clean architecture.

## Payment Gateway Development Workflow {#workflow-payment-gateway}

### Implementation Steps

1. Generate payment gateway module using command
2. Configure module metadata for payment gateway
3. Create payment gateway service implementing PaymentGatewayInterface
4. Implement gateway API service for payment provider communication
5. Add payment event listeners to register gateway
6. Create admin settings for gateway configuration
7. Build payment flow with checkout and callback handling
8. Test payment processing and webhook handling
9. Enable module and verify integration

## Step 1: Build the Database Foundation

::: danger Important Security Note
Payment gateway settings contain sensitive API keys and secrets. Always use proper validation and encryption for these values.
:::

Payment gateways require database settings for configuration storage.

**Create settings migration:**

```bash
php artisan module:make-migration create_tap_gateway_settings TapGateway
```

**Migration file (Database/Migrations/):**

```php
<?php

use Spatie\LaravelSettings\Migrations\SettingsMigration;

return new class extends SettingsMigration
{
    protected array $settings = [
        // Gateway controls
        'payment.tap_gateway_enabled' => false,

        // API configuration
        'payment.tap_gateway_api_key' => '',
        'payment.tap_gateway_secret_key' => '',

        // Environment settings
        'payment.tap_gateway_sandbox_mode' => true,

        // Webhook configuration
        'payment.tap_gateway_webhook_secret' => '',

        // Payment preferences
        'payment.tap_gateway_currency' => 'USD',
        'payment.tap_gateway_description' => 'Payment via Tap Gateway',
    ];

    public function up(): void
    {
        foreach ($this->settings as $key => $value) {
            if (! $this->migrator->exists($key)) {
                $this->migrator->add($key, $value);
            }
        }
    }

    public function down(): void
    {
        foreach (array_keys($this->settings) as $key) {
            if ($this->migrator->exists($key)) {
                $this->migrator->delete($key);
            }
        }
    }
};
```

**Run migration:**

```bash
php artisan migrate --path=Modules/TapGateway/Database/Migrations
php artisan migrate --path=Modules/TapGateway/Database/Settings
```

## Step 2: Extend the Settings System with Dynamic Properties

::: warning Architecture Insight
The application uses dynamic settings, which allows extending the main PaymentSettings class without modifying core files. This is a powerful architectural pattern!
:::

**Listener file: `Listeners/ExtendPaymentSettings.php`**

```php
<?php

namespace Modules\TapGateway\Listeners;

use App\Events\PaymentSettingsExtending;
use Nwidart\Modules\Facades\Module;

class ExtendPaymentSettings
{
    public function handle(PaymentSettingsExtending $event): void // [!code focus]
    {
        // Only add extensions if our module is active
        if (! $this->isModuleActive()) {
            return;
        }

        // Add your payment gateway settings extensions
        // These become properties on the PaymentSettings class
        $event->addExtension('tap_gateway_enabled', false); // [!code focus]
        $event->addExtension('tap_gateway_api_key', ''); // [!code focus]
        $event->addExtension('tap_gateway_secret_key', ''); // [!code focus]
        $event->addExtension('tap_gateway_sandbox_mode', true); // [!code focus]
        $event->addExtension('tap_gateway_webhook_secret', ''); // [!code focus]
        $event->addExtension('tap_gateway_currency', 'USD'); // [!code focus]
        $event->addExtension('tap_gateway_description', 'Payment via Tap Gateway'); // [!code focus]
    }

    private function isModuleActive(): bool
    {
        return Module::find('TapGateway')?->isEnabled() ?? false;
    }
}
```

::: tip How Dynamic Properties Work
When this listener runs, it adds properties to the PaymentSettings class at runtime. This means you can access these properties elsewhere in your code:

```php
$settings = app(PaymentSettings::class);
echo $settings->tap_gateway_enabled; // This property now exists!
```

:::

## Step 3: Create the Admin Settings Card

Now let's build the visual component that appears in the main admin payment settings page. This card gives admins a quick overview and easy access to your gateway configuration.

**Create the Card view:**

```blade
<!-- Tap Gateway Payment Card -->
<a href="{{ route('admin.settings.payment.tap-gateway') }}" class="group relative">
    <div class="block p-6 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl hover:border-info-500 dark:hover:border-info-500 transition-all duration-200 shadow-sm hover:shadow-md">
        <div class="flex items-center">
            <div class="shrink-0">
                <div class="relative">
                    <!-- Gateway Icon Container -->
                    <div class="w-12 h-12 bg-blue-100 dark:bg-blue-900/50 rounded-lg flex items-center justify-center">
                        <svg class="w-6 h-6 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
                        </svg>
                    </div>
                    <!-- Status Indicator Dot -->
                    <span class="absolute -top-1 -right-1 h-3 w-3 rounded-full border-2 border-white dark:border-gray-800 {{ $statusClass }}"></span>
                </div>
            </div>
            <div class="ml-4 flex-1">
                <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
                    {{ t('tap_gateway') }} // [!code focus]
                </h3>
                <p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
                    {{ t('tap_gateway_description') }} // [!code focus]
                </p>
            </div>
        </div>

        <!-- Status Section -->
        <div class="mt-2 border-t border-gray-200 dark:border-gray-700 pt-2">
            @if ($gatewayEnabled)
                <span class="inline-flex items-center text-xs font-medium text-success-600 dark:text-success-400">
                    <span class="w-2 h-2 rounded-full bg-success-400 mr-2"></span>
                    {{ t('active') }}
                </span>
            @else
                <span class="inline-flex items-center text-xs font-medium text-gray-500 dark:text-gray-400">
                    <span class="w-2 h-2 rounded-full bg-gray-300 dark:bg-gray-600 mr-2"></span>
                    {{ t('not_configured') }}
                </span>
            @endif
        </div>
    </div>
</a>
```

::: details Card Components Breakdown

* **Icon Container**: The blue rounded box with the credit card icon - make this your brand colors
* **Status Indicator**: The small colored dot that shows active/inactive status
* **Gateway Info**: Title and description of your payment gateway
* **Status Text**: Clear text showing whether it's configured or not
* **Hover Effects**: Smooth animations when admins hover over the card
  :::

## Step 4: Create the Card Display Listener

Create the Card Display Listener to render the card in the admin payment settings page.

```bash
php artisan module:make-listener AddTapGatewayPaymentSettings TapGateway
```

**Listener file: `Listeners/AddTapGatewayPaymentSettings.php`**

```php
<?php

namespace Modules\TapGateway\Listeners;

use App\Events\PaymentSettingsViewRendering;
use App\Settings\PaymentSettings;
use Nwidart\Modules\Facades\Module;

class AddTapGatewayPaymentSettings
{
    public function handle(PaymentSettingsViewRendering $event): void
    {
        // Check if module is active
        if (! $this->isModuleActive()) { // [!code focus]
            return; // [!code focus]
        } // [!code focus]

        // Get payment settings with our dynamic properties
        $paymentSettings = app(PaymentSettings::class);
        $gatewayEnabled = $paymentSettings->tap_gateway_enabled ?? false; // [!code focus]

        // Generate the card HTML and add it to the admin interface
        $cardHtml = $this->getTapGatewayCard($gatewayEnabled); // [!code focus]
        $event->addPaymentGateway($cardHtml); // [!code focus]
    }

    private function getTapGatewayCard(bool $gatewayEnabled): string // [!code focus]
    {
        $statusClass = $gatewayEnabled ? 'bg-success-400' : 'bg-gray-200'; // [!code focus]
        $statusText = $gatewayEnabled ? 'active' : 'not_configured'; // [!code focus]
        $statusTextClass = $gatewayEnabled // [!code focus]
            ? 'text-success-600 dark:text-success-400' // [!code focus]
            : 'text-gray-600 dark:text-gray-400'; // [!code focus]

        return view('TapGateway::partials.payment-settings-card', [ // [!code focus]
            'gatewayEnabled' => $gatewayEnabled, // [!code focus]
            'statusClass' => $statusClass, // [!code focus]
            'statusText' => $statusText, // [!code focus]
            'statusTextClass' => $statusTextClass, // [!code focus]
        ])->render(); // [!code focus]
    }

    private function isModuleActive(): bool
    {
        return Module::find('TapGateway')?->isEnabled() ?? false;
    }
}
```

::: tip How the Event System Works
This is really elegant - the admin payment settings page fires a `PaymentSettingsViewRendering` event, and any module can listen for this event to add their own cards. Your card will automatically appear alongside Stripe, PayPal, and any other payment gateways.
:::

### Step 5: Build the Complete Settings Configuration Page

::: warning Security First
This page handles sensitive payment credentials. Always use HTTPS in production and implement proper input validation.
:::

Now for the main event - the actual settings page where admins configure your gateway. This page needs to be intuitive, secure, and handle all the edge cases.

Create the Admin Controller

```bash
php artisan module:make-controller Admin/TapGatewaySettingsController TapGateway
```

**File: `Http/Controllers/Admin/TapGatewaySettingsController.php`**

```php
<?php

namespace Modules\TapGateway\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Http\Requests\PurifiedInput;
use App\Settings\PaymentSettings;
use Illuminate\Http\Request;
use Nwidart\Modules\Facades\Module;

class TapGatewaySettingsController extends Controller
{
    public function index() // [!code focus]
    {
        // Security check - make sure module is active
        if (!Module::find('TapGateway')?->isEnabled()) { // [!code focus]
            return redirect()->route('admin.settings') // [!code focus]
                ->with('error', t('module_not_available')); // [!code focus]
        } // [!code focus]

        // Get current settings to populate the form
        $settings = app(PaymentSettings::class);

        return view('tapgateway::admin.settings.tap-gateway', compact('settings'));
    }

    public function update(Request $request) // [!code focus]
    {
        // Validate the input with security in mind
        $request->validate([ // [!code focus]
            'gateway_enabled' => 'boolean', // [!code focus]
            'api_key' => [new PurifiedInput()], // Prevents XSS // [!code focus]
            'secret_key' => [new PurifiedInput()], // [!code focus]
            'sandbox_mode' => 'boolean', // [!code focus]
            'webhook_secret' => [new PurifiedInput()], // [!code focus]
            'currency' => 'string|max:3', // [!code focus]
            'description' => [new PurifiedInput(), 'max:255'], // [!code focus]
        ]); // [!code focus]

        $settings = app(PaymentSettings::class);

        // Update each setting from the form
        $settings->tap_gateway_enabled = $request->boolean('gateway_enabled'); // [!code focus]
        $settings->tap_gateway_api_key = $request->input('api_key', ''); // [!code focus]
        $settings->tap_gateway_secret_key = $request->input('secret_key', ''); // [!code focus]
        $settings->tap_gateway_sandbox_mode = $request->boolean('sandbox_mode'); // [!code focus]
        $settings->tap_gateway_webhook_secret = $request->input('webhook_secret', ''); // [!code focus]
        $settings->tap_gateway_currency = $request->input('currency', 'USD'); // [!code focus]
        $settings->tap_gateway_description = $request->input('description', 'Payment via Tap Gateway'); // [!code focus]

        // Save all changes at once
        $settings->save();

        // Clear tenant cache so changes take effect immediately
        clear_cache();

        // Log this change for security/audit purposes
        payment_log('Tap Gateway settings updated', 'info', [
            'gateway' => 'tap_gateway',
            'enabled' => $request->boolean('gateway_enabled'),
            'sandbox_mode' => $request->boolean('sandbox_mode'),
            'admin_id' => auth()->id(),
        ]);

        return redirect()->back()->with('success', t('settings_updated_successfully'));
    }
}
```

### Step 6: Wire Everything Together

Don't forget to register all your event listeners in the service provider - this is crucial for everything to work together!

Now we need to register all our listeners and routes. Update your service provider to handle all the events:

**File: `Providers/TapGatewayServiceProvider.php`**

```php
<?php

namespace Modules\TapGateway\Providers;

use App\Events\PaymentSettingsExtending;
use App\Events\PaymentSettingsViewRendering;
use Illuminate\Support\ServiceProvider;
use Modules\TapGateway\Listeners\AddTapGatewayPaymentSettings;
use Modules\TapGateway\Listeners\ExtendPaymentSettings;

class TapGatewayServiceProvider extends ServiceProvider
{
    protected $moduleName = 'TapGateway';

    public function boot()
    {
        $this->registerTranslations();
        $this->registerConfig();
        $this->registerViews();
        $this->loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations'));
        $this->registerRoutes();
        $this->registerEventListeners(); // This is crucial! // [!code focus]
    }

    protected function registerEventListeners() // [!code focus]
    {
        // Register listener for admin card display
        $this->app['events']->listen( // [!code focus]
            PaymentSettingsViewRendering::class, // [!code focus]
            AddTapGatewayPaymentSettings::class // [!code focus]
        ); // [!code focus]

        // Register listener for dynamic settings extension
        $this->app['events']->listen( // [!code focus]
            PaymentSettingsExtending::class, // [!code focus]
            ExtendPaymentSettings::class // [!code focus]
        ); // [!code focus]
    }

    // ... other standard methods
}
```

## Step 7: Implement Admin Routes

Admin Routes

**File: `Routes/web.php`**

```php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Middleware\AdminMiddleware;
use App\Http\Middleware\SanitizeInputs;
use Modules\TapGateway\Http\Controllers\Admin\TapGatewaySettingsController;

// Admin routes for payment gateway configuration
Route::middleware(['web', 'auth', AdminMiddleware::class, SanitizeInputs::class])->prefix('admin')->name('admin.')->group(function () { // [!code focus]
    Route::prefix('/settings/payment')->name('settings.payment.')->group(function () { // [!code focus]
        Route::get('/tap-gateway', [TapGatewaySettingsController::class, 'index'])->name('tap-gateway'); // [!code focus]
        Route::patch('/tap-gateway', [TapGatewaySettingsController::class, 'update'])->name('tap-gateway.update'); // [!code focus]
    }); // [!code focus]
});
```

## Step 8: Add Language Support

::: info Internationalization
Always prepare your modules for multiple languages from the start. It's much easier than retrofitting later!
:::

Create your admin language file to support internationalization:

**File: `Resources/lang/en.json`**

```json
{
  "tap_gateway": "Tap Gateway", // [!code focus]
  "tap_gateway_description": "Accept credit card payments through Tap Gateway", // [!code focus]
  "tap_gateway_settings": "Tap Gateway Settings", // [!code focus]
  "configure_tap_payments_description": "Configure Tap Gateway to accept online payments from your tenants",
  "enable_tap_gateway": "Enable Tap Gateway",
  "enable_tap_payments_description": "Allow tenants to pay their invoices using Tap Gateway",
  "api_key": "API Key", // [!code focus]
  "secret_key": "Secret Key", // [!code focus]
  "webhook_secret": "Webhook Secret", // [!code focus]
  "default_currency": "Default Currency",
  "sandbox_mode": "Sandbox Mode",
  "payment_description": "Payment Description",
  "api_key_description": "Your public API key from Tap Gateway dashboard",
  "secret_key_description": "Your private secret key - keep this secure!",
  "webhook_secret_description": "Secret used to verify webhook signatures from Tap Gateway", // [!code focus]
  "payment_description_help": "This text appears to customers during checkout", // [!code focus]
  "changes_apply_immediately": "Changes will apply to all new transactions immediately", // [!code focus]
  "settings_updated_successfully": "Tap Gateway settings have been updated successfully",
  "module_not_available": "This module is not currently available",
  "back": "Back",
  "save_changes": "Save Changes",
  "active": "Active",
  "not_configured": "Not Configured"
}
```

## Complete Command Reference

::: details All Artisan Commands Used

Here's a quick reference of all the Artisan commands we used:

```bash
# 1. Create the module
php artisan module:make TapGateway

# 2. Create listeners
php artisan module:make-listener ExtendPaymentSettings TapGateway
php artisan module:make-listener AddTapGatewayPaymentSettings TapGateway

# 3. Create admin controller
php artisan module:make-controller Admin/TapGatewaySettingsController TapGateway

# 4. Create services (for next steps)
php artisan module:make-class Services/TapPaymentGateway TapGateway

# 5. Optional components
php artisan module:make-request TapGatewaySettingsRequest TapGateway
php artisan module:make-job ProcessTapGatewayPayment TapGateway
php artisan module:make-event TapGatewayPaymentProcessed TapGateway
```

:::

## Testing Your Admin Configuration

::: tip Testing Checklist
Before moving on to tenant billing, test your admin interface:
:::

1. **Access the Admin Payment Settings**:

   * Go to your admin panel
   * Navigate to Settings → Payment Settings
   * You should see your Tap Gateway card

2. **Configure Your Gateway**:

   * Click on the Tap Gateway card
   * Fill in test API credentials
   * Enable sandbox mode
   * Save the settings

3. **Verify Everything Works**:
   * Check that the card shows "Active" status
   * Verify settings are saved correctly
   * Test form validation by leaving required fields empty

## Next Steps

* **[Tenant Billing Integration](./tenant-billing-integration.md)** - Billing system integration
* **[Language Synchronization](./language-synchronization.md)** - Multi-language support
* **[Advanced Topics](./advanced-topics.md)** - Webhooks, error handling, logging

***

*This guide is for WhatsMarkSaaS v2.0.0+ payment gateway development.*
