Skip to content

Payment Gateway Development ​

Minimum Version Requirement

This feature requires WhatsMarkSaaS v2.0.0 or higher. Ensure your installation meets this requirement before proceeding.

Extended License Required

This feature is available with an Extended License 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 ​

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 ​

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 ​

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
    {
        // 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); 
        $event->addExtension('tap_gateway_api_key', ''); 
        $event->addExtension('tap_gateway_secret_key', ''); 
        $event->addExtension('tap_gateway_sandbox_mode', true); 
        $event->addExtension('tap_gateway_webhook_secret', ''); 
        $event->addExtension('tap_gateway_currency', 'USD'); 
        $event->addExtension('tap_gateway_description', 'Payment via Tap Gateway'); 
    }

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

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') }}
                </h3>
                <p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
                    {{ t('tap_gateway_description') }}
                </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>
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()) { 
            return; 
        } 

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

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

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

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

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

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 ​

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() 
    {
        // Security check - make sure module is active
        if (!Module::find('TapGateway')?->isEnabled()) { 
            return redirect()->route('admin.settings') 
                ->with('error', t('module_not_available')); 
        } 

        // 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) 
    {
        // Validate the input with security in mind
        $request->validate([ 
            'gateway_enabled' => 'boolean', 
            'api_key' => [new PurifiedInput()], // Prevents XSS
            'secret_key' => [new PurifiedInput()], 
            'sandbox_mode' => 'boolean', 
            'webhook_secret' => [new PurifiedInput()], 
            'currency' => 'string|max:3', 
            'description' => [new PurifiedInput(), 'max:255'], 
        ]); 

        $settings = app(PaymentSettings::class);

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

        // 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!
    }

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

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

    // ... 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 () { 
    Route::prefix('/settings/payment')->name('settings.payment.')->group(function () { 
        Route::get('/tap-gateway', [TapGatewaySettingsController::class, 'index'])->name('tap-gateway'); 
        Route::patch('/tap-gateway', [TapGatewaySettingsController::class, 'update'])->name('tap-gateway.update'); 
    }); 
});

Step 8: Add Language Support ​

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", 
  "tap_gateway_description": "Accept credit card payments through Tap Gateway", 
  "tap_gateway_settings": "Tap Gateway Settings", 
  "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", 
  "secret_key": "Secret Key", 
  "webhook_secret": "Webhook Secret", 
  "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", 
  "payment_description_help": "This text appears to customers during checkout", 
  "changes_apply_immediately": "Changes will apply to all new transactions immediately", 
  "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 ​

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 ​

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 ​


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

Β© 2024 - Corbital Technologies. All rights reserved.