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:
hamed
2026-07-02 15:36:08 +03:30
parent ca71c49451
commit c247ac2c80
12 changed files with 761 additions and 378 deletions
+73
View File
@@ -0,0 +1,73 @@
<?php
namespace App\Payment\Entity;
use App\Payment\Repository\PaymentLogRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* ردپای حسابرسی (audit trail) هر گام از چرخهٔ پرداخت.
* اعتبارنامهٔ درگاه هرگز اینجا ذخیره نمی‌شود.
*/
#[ORM\Entity(repositoryClass: PaymentLogRepository::class)]
#[ORM\Table(name: 'payment_logs')]
#[ORM\Index(columns: ['payment_id'], name: 'idx_payment_logs_payment')]
class PaymentLog
{
public const ACTION_INITIATE = 'initiate';
public const ACTION_VERIFY = 'verify';
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private ?int $id = null;
#[ORM\Column(name: 'payment_id', type: 'integer')]
private int $paymentId;
#[ORM\Column(type: 'string', length: 20)]
private string $action;
#[ORM\Column(type: 'string', length: 20)]
private string $gateway;
/** نتیجهٔ گام: success | failed | canceled | pending */
#[ORM\Column(type: 'string', length: 20)]
private string $result;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private ?string $authority = null;
#[ORM\Column(name: 'client_ip', type: 'string', length: 45, nullable: true)]
private ?string $clientIp = null;
#[ORM\Column(type: 'json', nullable: true)]
private ?array $payload = null;
#[ORM\Column(name: 'created_at', type: 'integer')]
private int $createdAt;
public function __construct(
int $paymentId,
string $action,
string $gateway,
string $result,
?string $authority = null,
?string $clientIp = null,
?array $payload = null,
) {
$this->paymentId = $paymentId;
$this->action = $action;
$this->gateway = $gateway;
$this->result = $result;
$this->authority = $authority;
$this->clientIp = $clientIp;
$this->payload = $payload;
$this->createdAt = time();
}
public function getId(): ?int { return $this->id; }
public function getPaymentId(): int { return $this->paymentId; }
public function getAction(): string { return $this->action; }
public function getResult(): string { return $this->result; }
}