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,
]);
}
}