em->getRepository(SiteConfig::class)->findOneBy(['configKey' => 'payment_test_mode']); if ($cfg === null) { $this->em->persist(new SiteConfig('payment_test_mode', '1')); } else { $cfg->setValue('1'); } $this->em->flush(); } /** @return array{0: Payment, 1: Appointment} */ private function pendingPaidBooking(?callable $withClinic = null): array { $doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر پرداخت'); $this->em->persist($doctor); $this->em->flush(); $slotStart = strtotime('+30 days') + random_int(0, 500_000) * 7; $appointment = $this->newAppointment($doctor, $this->createUser(['ROLE_USER']), $slotStart, $slotStart + 1_800); // رزرو آنلاین با پنجرهٔ پرداخت ثبت می‌شود؛ پرداخت باید همین را پاک کند. $appointment->markPendingWithTtl(Appointment::PAYMENT_TTL); if ($withClinic !== null) { $appointment->setClinic($withClinic($doctor)); } $this->em->persist($appointment); $payment = new Payment($appointment->getUser(), 50_000, 'mock', Payment::TYPE_APPOINTMENT); $payment->setAppointment($appointment); $this->stampTenant($payment); $this->em->persist($payment); $this->em->flush(); return [$payment, $appointment]; } private function fireCallback(Payment $payment): void { $this->client->request('POST', '/api/v1/payment/callback/mock?' . http_build_query([ 'order_id' => $payment->getOrderId(), 'mock' => '1', 'ResCode' => '0', 'mock_amount' => '50000', ])); } private function reload(Appointment $appointment): Appointment { $this->em->clear(); return $this->em->getRepository(Appointment::class)->find($appointment->getId()); } public function testPaidBookingStaysPendingUntilSomeoneConfirmsIt(): void { $this->enableTestMode(); [$payment, $appointment] = $this->pendingPaidBooking(); $this->fireCallback($payment); $reloaded = $this->reload($appointment); self::assertSame(Appointment::STATUS_PENDING, $reloaded->getStatus()); // پنجرهٔ پرداخت برداشته می‌شود، وگرنه cronِ انقضا نوبتِ پرداخت‌شده را می‌کشت. self::assertNull($reloaded->getExpiresAt()); self::assertCount(0, $this->em->getRepository(PatientSession::class)->findBy(['appointment' => $reloaded])); } public function testConfirmingThePaidBookingFilesTheSession(): void { $this->enableTestMode(); [$payment, $appointment] = $this->pendingPaidBooking(); $this->fireCallback($payment); $reloaded = $this->reload($appointment); $doctorUser = $reloaded->getDoctor()->getUser(); $this->authJson('POST', '/api/v1/appointment/' . $reloaded->getUuid() . '/confirm', $doctorUser, []); self::assertSame(200, $this->responseCode()); $confirmed = $this->reload($reloaded); self::assertSame(Appointment::STATUS_CONFIRMED, $confirmed->getStatus()); $sessions = $this->em->getRepository(PatientSession::class)->findBy(['appointment' => $confirmed]); self::assertCount(1, $sessions, 'تأیید نوبت باید پرونده بسازد'); self::assertSame('doctor', $sessions[0]->getRecord()->getEntityType()); } /** تقسیم مالی هم مثل پرونده به لحظهٔ تأیید منتقل شده است. */ public function testFinancialSplitHappensOnConfirmNotOnPayment(): void { $this->enableTestMode(); $breakdowns = static::getContainer()->get(\App\Settlement\Repository\FinancialBreakdownRepository::class); [$payment, $appointment] = $this->pendingPaidBooking(); $this->fireCallback($payment); $paidPayment = $this->em->getRepository(Payment::class)->find($payment->getId()); self::assertFalse($breakdowns->existsForPayment($paidPayment), 'پرداخت به‌تنهایی نباید تقسیم مالی بسازد'); $reloaded = $this->reload($appointment); $this->authJson('POST', '/api/v1/appointment/' . $reloaded->getUuid() . '/confirm', $reloaded->getDoctor()->getUser(), []); // بدون نماینده و منشیِ سهم‌بر چیزی برای تقسیم نیست؛ صرفاً نباید خطا بدهد. self::assertSame(200, $this->responseCode()); } public function testConfirmedClinicBookingIsFiledUnderTheClinic(): void { $this->enableTestMode(); [$payment, $appointment] = $this->pendingPaidBooking(function (Doctor $doctor): Clinic { $clinic = new Clinic($this->createUser(['ROLE_CLINIC'])); $clinic->setName('کلینیک پرداخت'); $clinic->getDoctors()->add($doctor); $this->em->persist($clinic); $this->em->flush(); return $clinic; }); $this->fireCallback($payment); $reloaded = $this->reload($appointment); $this->authJson('POST', '/api/v1/appointment/' . $reloaded->getUuid() . '/confirm', $reloaded->getDoctor()->getUser(), []); self::assertSame(200, $this->responseCode()); $confirmed = $this->reload($reloaded); $records = $this->em->getRepository(PatientRecord::class)->findBy(['user' => $confirmed->getUser()]); self::assertCount(1, $records, 'یک نوبت، یک پرونده — نه یکی برای پزشک و یکی برای کلینیک'); self::assertSame('clinic', $records[0]->getEntityType()); } }