findOneBy(['uuid' => $uuid]); } /** @return Payment[] */ public function findByUser(User $user, ?string $status = null, int $page = 1, int $limit = 20): array { $qb = $this->createQueryBuilder('p') ->where('p.user = :user')->setParameter('user', $user) ->orderBy('p.createdAt', 'DESC') ->setFirstResult(($page - 1) * $limit) ->setMaxResults($limit); if ($status !== null) { $qb->andWhere('p.status = :status')->setParameter('status', $status); } return $qb->getQuery()->getResult(); } public function countByUser(User $user, ?string $status = null): int { $qb = $this->createQueryBuilder('p') ->select('COUNT(p.id)') ->where('p.user = :user')->setParameter('user', $user); if ($status !== null) { $qb->andWhere('p.status = :status')->setParameter('status', $status); } return (int) $qb->getQuery()->getSingleScalarResult(); } public function findByOrderId(string $orderId): ?Payment { return $this->findOneBy(['orderId' => $orderId]); } public function findByReferenceId(string $referenceId): ?Payment { 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([ 'appointment' => $appointment, 'status' => Payment::STATUS_PENDING, ]); } /** * Batch variant of findPendingByAppointment: all pending payments for the * given appointments in ONE query, keyed by appointment id. Avoids the N+1 * in AppointmentExpiryService when many bookings expire at once. * * @param Appointment[] $appointments * @return array appointment id => pending Payment */ public function findPendingByAppointments(array $appointments): array { if ($appointments === []) { return []; } $payments = $this->createQueryBuilder('p') ->andWhere('p.appointment IN (:appointments)') ->andWhere('p.status = :status') ->setParameter('appointments', $appointments) ->setParameter('status', Payment::STATUS_PENDING) ->getQuery() ->getResult(); $byAppointment = []; foreach ($payments as $payment) { $byAppointment[$payment->getAppointment()->getId()] = $payment; } return $byAppointment; } public function save(Payment $entity, bool $flush = true): void { $this->getEntityManager()->persist($entity); if ($flush) $this->getEntityManager()->flush(); } }