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
@@ -0,0 +1,21 @@
<?php
namespace App\Payment\Repository;
use App\Payment\Entity\PaymentLog;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class PaymentLogRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, PaymentLog::class);
}
public function save(PaymentLog $entity, bool $flush = true): void
{
$this->getEntityManager()->persist($entity);
if ($flush) $this->getEntityManager()->flush();
}
}
@@ -59,6 +59,19 @@ class PaymentRepository extends ServiceEntityRepository
return $this->findOneBy(['referenceId' => $referenceId]);
}
/**
* قفل بدبینانه روی ردیف پرداخت (باید داخل یک transaction فعال صدا زده شود).
* برای جلوگیری از verify هم‌زمانِ دو callback (race / double-verify).
*/
public function findByOrderIdForUpdate(string $orderId): ?Payment
{
return $this->createQueryBuilder('p')
->where('p.orderId = :o')->setParameter('o', $orderId)
->getQuery()
->setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)
->getOneOrNullResult();
}
public function findPendingByAppointment(Appointment $appointment): ?Payment
{
return $this->findOneBy([