feat(payment): implement PaymentManager for handling payment logic and callbacks
- Refactor PaymentController to delegate payment processing to PaymentManager. - Add findByOrderIdForUpdate method in PaymentRepository for pessimistic locking. - Create PaymentLog entity and repository for auditing payment actions. - Implement startGatewayHandoff and processCallback methods in PaymentManager. - Introduce transaction handling and logging for payment verification. - Update payment flow to ensure idempotency and prevent race conditions. - Enhance security by logging sensitive actions without exposing credentials. - Update database schema with migration for payment_logs table. - Document changes in payment flow architecture.
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Payment\Gateway;
|
||||
|
||||
use App\Config\Repository\SiteConfigRepository;
|
||||
|
||||
/**
|
||||
* انتخاب/ساخت درگاه پرداخت (Factory + Strategy).
|
||||
*
|
||||
* تمام منطقِ «کدام درگاه، فعال است یا نه، حالت تست» اینجا متمرکز است تا کنترلر
|
||||
* فقط Orchestration کند. افزودن درگاه جدید = ثبت آن بهعنوان سرویس و اضافهکردن
|
||||
* یک case در map + یک برچسب، بدون تغییر کنترلر.
|
||||
*/
|
||||
class GatewayFactory
|
||||
{
|
||||
/** @var array<string, PaymentGatewayInterface> */
|
||||
private array $gateways;
|
||||
|
||||
/** @var array<string, string> برچسب فارسی هر درگاه */
|
||||
private const LABELS = [
|
||||
'mellat' => 'بانک ملت',
|
||||
'sep' => 'سپ (سامان کیش)',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
MellatGateway $mellat,
|
||||
SepGateway $sep,
|
||||
private readonly MockGateway $mock,
|
||||
private readonly SiteConfigRepository $configRepo,
|
||||
) {
|
||||
$this->gateways = [
|
||||
$mellat->getName() => $mellat,
|
||||
$sep->getName() => $sep,
|
||||
];
|
||||
}
|
||||
|
||||
public function isTestMode(): bool
|
||||
{
|
||||
return $this->configRepo->get('payment_test_mode') === '1';
|
||||
}
|
||||
|
||||
/**
|
||||
* درگاهِ قابلاستفاده برای این نام؛ در حالت تست همیشه Mock، در غیر اینصورت
|
||||
* درگاه واقعی در صورت فعال بودن. null یعنی نامعتبر/غیرفعال.
|
||||
*/
|
||||
public function resolve(string $name): ?PaymentGatewayInterface
|
||||
{
|
||||
if ($this->isTestMode()) {
|
||||
return $this->mock;
|
||||
}
|
||||
if (!$this->isEnabled($name)) {
|
||||
return null;
|
||||
}
|
||||
return $this->gateways[$name] ?? null;
|
||||
}
|
||||
|
||||
/** آیا درگاه در تنظیمات فعال است؟ کلید تنظیمنشده = فعال (سازگاری با نصبهای قبلی). */
|
||||
public function isEnabled(string $name): bool
|
||||
{
|
||||
$v = $this->configRepo->get($name . '_enabled');
|
||||
return $v === null || $v === '1';
|
||||
}
|
||||
|
||||
/**
|
||||
* درگاههای قابلانتخاب برای نمایش به کاربر: در حالت تست فقط درگاه آزمایشی،
|
||||
* در غیر اینصورت هر درگاهی که اعتبارنامهاش ست شده و فعال است.
|
||||
*
|
||||
* @return array<int, array{name: string, label: string}>
|
||||
*/
|
||||
public function activeGateways(): array
|
||||
{
|
||||
if ($this->isTestMode()) {
|
||||
return [['name' => 'mellat', 'label' => 'بانک ملت (آزمایشی)']];
|
||||
}
|
||||
|
||||
$out = [];
|
||||
foreach ($this->gateways as $name => $gateway) {
|
||||
if ($gateway->isConfigured() && $this->isEnabled($name)) {
|
||||
$out[] = ['name' => $name, 'label' => self::LABELS[$name] ?? $name];
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user