Skip to content

Advanced Payment Gateway Development

This guide covers advanced patterns and techniques for creating sophisticated, production-ready payment gateway modules in WhatsmarkSaaS. These topics build upon the foundation established in the previous guides to help you implement complex payment processing features with optimal security, performance, and maintainability.


Advanced Security Patterns

Webhook Signature Verification

Implement robust webhook signature verification to ensure authentic payment notifications:

php
<?php

namespace Modules\YourGateway\Services;

use Illuminate\Support\Facades\Log;
use Modules\YourGateway\Exceptions\WebhookVerificationException;

class WebhookSecurityService
{
    protected string $webhookSecret;
    protected array $trustedIps = [];
    protected int $timestampTolerance = 300; // 5 minutes

    public function __construct(string $webhookSecret, array $trustedIps = [])
    {
        $this->webhookSecret = $webhookSecret;
        $this->trustedIps = $trustedIps;
    }

    /**
     * Verify webhook signature with multiple algorithm support
     */
    public function verifySignature(string $payload, string $signature, string $algorithm = 'sha256'): bool
    {
        $expectedSignature = hash_hmac($algorithm, $payload, $this->webhookSecret);

        // Use hash_equals to prevent timing attacks
        if (!hash_equals($signature, $expectedSignature)) {
            Log::warning('Webhook signature verification failed', [
                'expected' => $expectedSignature,
                'received' => $signature,
                'payload_hash' => hash('sha256', $payload)
            ]);

            throw new WebhookVerificationException('Invalid webhook signature');
        }

        return true;
    }

    /**
     * Verify timestamp to prevent replay attacks
     */
    public function verifyTimestamp(int $timestamp): bool
    {
        $currentTime = time();
        $timeDifference = abs($currentTime - $timestamp);

        if ($timeDifference > $this->timestampTolerance) {
            throw new WebhookVerificationException('Webhook timestamp outside tolerance window');
        }

        return true;
    }

    /**
     * Verify source IP address
     */
    public function verifySourceIp(string $clientIp): bool
    {
        if (empty($this->trustedIps)) {
            return true; // Skip IP verification if no trusted IPs configured
        }

        foreach ($this->trustedIps as $trustedIp) {
            if ($this->ipMatches($clientIp, $trustedIp)) {
                return true;
            }
        }

        throw new WebhookVerificationException('Untrusted source IP: ' . $clientIp);
    }

    /**
     * Complete webhook verification process
     */
    public function verifyWebhook(string $payload, string $signature, int $timestamp, string $clientIp): bool
    {
        $this->verifySignature($payload, $signature);
        $this->verifyTimestamp($timestamp);
        $this->verifySourceIp($clientIp);

        return true;
    }

    private function ipMatches(string $ip, string $cidr): bool
    {
        if (strpos($cidr, '/') === false) {
            return $ip === $cidr;
        }

        [$subnet, $mask] = explode('/', $cidr);
        return (ip2long($ip) & ~((1 << (32 - $mask)) - 1)) === ip2long($subnet);
    }
}

Input Sanitization and Validation

Implement comprehensive input validation for payment data:

php
<?php

namespace Modules\YourGateway\Http\Requests;

use App\Rules\PurifiedInput;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class PaymentProcessRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'amount' => [
                'required',
                'numeric',
                'min:0.01',
                'max:999999.99',
                'regex:/^\d+(\.\d{1,2})?$/', // Ensure proper decimal format
            ],
            'currency' => [
                'required',
                'string',
                'size:3',
                Rule::in(['USD', 'EUR', 'GBP', 'AED', 'SAR']), // Supported currencies
            ],
            'description' => [
                'required',
                'string',
                'max:255',
                new PurifiedInput('Invalid characters in description'),
            ],
            'customer_email' => [
                'required',
                'email:rfc,dns',
                'max:255',
                new PurifiedInput('Invalid email format'),
            ],
            'customer_phone' => [
                'nullable',
                'string',
                'regex:/^\+?[1-9]\d{1,14}$/', // E.164 format
                new PurifiedInput('Invalid phone format'),
            ],
            'invoice_id' => [
                'required',
                'string',
                'max:50',
                'regex:/^[a-zA-Z0-9_-]+$/', // Alphanumeric with underscores and hyphens
            ],
            'return_url' => [
                'required',
                'url',
                'max:255',
                'starts_with:https://', // Enforce HTTPS
            ],
            'cancel_url' => [
                'required',
                'url',
                'max:255',
                'starts_with:https://', // Enforce HTTPS
            ],
        ];
    }

    public function messages(): array
    {
        return [
            'amount.regex' => 'Amount must have at most 2 decimal places',
            'currency.in' => 'Currency must be one of: USD, EUR, GBP, AED, SAR',
            'customer_phone.regex' => 'Phone number must be in valid international format',
            'invoice_id.regex' => 'Invoice ID can only contain letters, numbers, underscores, and hyphens',
            'return_url.starts_with' => 'Return URL must use HTTPS',
            'cancel_url.starts_with' => 'Cancel URL must use HTTPS',
        ];
    }

    /**
     * Get validated and sanitized data
     */
    public function getPaymentData(): array
    {
        $validated = $this->validated();

        return [
            'amount' => round($validated['amount'], 2),
            'currency' => strtoupper($validated['currency']),
            'description' => strip_tags($validated['description']),
            'customer_email' => strtolower(trim($validated['customer_email'])),
            'customer_phone' => $validated['customer_phone'] ? preg_replace('/[^\d+]/', '', $validated['customer_phone']) : null,
            'invoice_id' => $validated['invoice_id'],
            'return_url' => $validated['return_url'],
            'cancel_url' => $validated['cancel_url'],
        ];
    }
}

Advanced Event Handling

Custom Payment Events

Create sophisticated event system for payment lifecycle management:

php
<?php

namespace Modules\YourGateway\Events;

use App\Models\Invoice\Invoice;
use App\Models\Transaction;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class PaymentProcessingStarted
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public Invoice $invoice;
    public Transaction $transaction;
    public array $gatewayData;
    public string $attemptId;

    public function __construct(Invoice $invoice, Transaction $transaction, array $gatewayData, string $attemptId)
    {
        $this->invoice = $invoice;
        $this->transaction = $transaction;
        $this->gatewayData = $gatewayData;
        $this->attemptId = $attemptId;
    }
}

class PaymentAuthorizationCompleted
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public Transaction $transaction;
    public array $authorizationData;
    public string $authorizationId;

    public function __construct(Transaction $transaction, array $authorizationData, string $authorizationId)
    {
        $this->transaction = $transaction;
        $this->authorizationData = $authorizationData;
        $this->authorizationId = $authorizationId;
    }
}

class PaymentCaptureRequested
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public Transaction $transaction;
    public float $captureAmount;
    public string $reason;

    public function __construct(Transaction $transaction, float $captureAmount, string $reason = 'Automatic capture')
    {
        $this->transaction = $transaction;
        $this->captureAmount = $captureAmount;
        $this->reason = $reason;
    }
}

Event Listener with Advanced Logic

php
<?php

namespace Modules\YourGateway\Listeners;

use App\Models\Transaction;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Modules\YourGateway\Events\PaymentProcessingStarted;
use Modules\YourGateway\Services\FraudDetectionService;
use Modules\YourGateway\Services\NotificationService;
use Modules\YourGateway\Services\PaymentAnalyticsService;

class PaymentProcessingStartedListener
{
    protected FraudDetectionService $fraudDetection;
    protected NotificationService $notificationService;
    protected PaymentAnalyticsService $analytics;

    public function __construct(
        FraudDetectionService $fraudDetection,
        NotificationService $notificationService,
        PaymentAnalyticsService $analytics
    ) {
        $this->fraudDetection = $fraudDetection;
        $this->notificationService = $notificationService;
        $this->analytics = $analytics;
    }

    public function handle(PaymentProcessingStarted $event): void
    {
        DB::transaction(function () use ($event) {
            // Log payment attempt
            $this->logPaymentAttempt($event);

            // Run fraud detection
            $fraudScore = $this->fraudDetection->calculateRiskScore($event->invoice, $event->gatewayData);

            if ($fraudScore > 80) {
                $this->handleHighRiskPayment($event, $fraudScore);
                return;
            }

            // Update analytics
            $this->analytics->recordPaymentAttempt($event->transaction, $event->gatewayData);

            // Send notifications for high-value payments
            if ($event->invoice->total >= 1000) {
                $this->notificationService->notifyHighValuePayment($event->invoice, $event->transaction);
            }
        });
    }

    private function logPaymentAttempt(PaymentProcessingStarted $event): void
    {
        Log::info('Payment processing started', [
            'invoice_id' => $event->invoice->id,
            'transaction_id' => $event->transaction->id,
            'attempt_id' => $event->attemptId,
            'amount' => $event->invoice->total,
            'currency' => $event->invoice->currency,
            'gateway' => 'yourgateway',
            'customer_email' => $event->gatewayData['customer_email'] ?? null,
        ]);
    }

    private function handleHighRiskPayment(PaymentProcessingStarted $event, float $fraudScore): void
    {
        // Mark transaction as under review
        $event->transaction->update([
            'status' => Transaction::STATUS_UNDER_REVIEW,
            'notes' => "High fraud risk score: {$fraudScore}",
        ]);

        // Notify administrators
        $this->notificationService->notifyFraudDetection($event->invoice, $fraudScore);

        Log::warning('High-risk payment detected', [
            'invoice_id' => $event->invoice->id,
            'fraud_score' => $fraudScore,
            'attempt_id' => $event->attemptId,
        ]);
    }
}

© 2024 - Corbital Technologies. All rights reserved.