createUser(['ROLE_DOCTOR']); $doctor = new Doctor($owner, 'دکتر'); $this->em->persist($doctor); $this->em->flush(); $patient = $this->createUser(['ROLE_USER']); $record = new PatientRecord('doctor', $doctor->getId(), $patient, 'doctor', $doctor->getId()); $this->em->persist($record); $this->em->flush(); return [$owner, $record, $patient]; } public function testListsPatientPayments(): void { [$owner, $record, $patient] = $this->recordFor(); $payment = $this->stampTenant(new Payment($patient, 250000, 'mellat', 'appointment')); $payment->setStatus(Payment::STATUS_SUCCESS); $this->em->persist($payment); $this->em->flush(); $res = $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/payments', $owner); self::assertSame(200, $this->responseCode()); self::assertCount(1, $res['data']); self::assertSame(250000, $res['data'][0]['amount_rials']); self::assertSame(1, $res['meta']['totalRecords']); } public function testWalletBalanceReflectsCreditMinusDebit(): void { [$owner, $record, $patient] = $this->recordFor(); $this->em->persist(new WalletTransaction($patient, 500000, 'credit', 500000)); $this->em->persist(new WalletTransaction($patient, 200000, 'debit', 300000)); $this->em->flush(); $res = $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/wallet', $owner); self::assertSame(200, $this->responseCode()); self::assertSame(300000, $res['data']['balance_rials']); self::assertCount(2, $res['data']['recent_transactions']); } public function testListsWalletTransactionsPaginated(): void { [$owner, $record, $patient] = $this->recordFor(); $this->em->persist(new WalletTransaction($patient, 100000, 'credit', 100000)); $this->em->flush(); $res = $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/wallet/transactions', $owner); self::assertSame(200, $this->responseCode()); self::assertCount(1, $res['data']); self::assertSame('credit', $res['data'][0]['type']); self::assertSame(1, $res['meta']['totalRecords']); } public function testChargeWalletCreatesCreditAndReturnsBalance(): void { [$owner, $record, $patient] = $this->recordFor(); $this->em->persist(new WalletTransaction($patient, 200000, 'credit', 200000)); $this->em->flush(); $res = $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/wallet/charge', $owner, [ 'amount_rials' => 300000, 'description' => 'بیعانه نوبت', ]); self::assertSame(201, $this->responseCode()); self::assertSame(500000, $res['data']['balance_rials']); self::assertSame('credit', $res['data']['transaction']['type']); self::assertSame('بیعانه نوبت', $res['data']['transaction']['description']); $wallet = $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/wallet', $owner); self::assertSame(500000, $wallet['data']['balance_rials']); } public function testChargeWalletRejectsNonPositiveAmount(): void { [$owner, $record] = $this->recordFor(); $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/wallet/charge', $owner, ['amount_rials' => 0]); self::assertSame(422, $this->responseCode()); } public function testWithdrawWalletCreatesDebitAndReducesBalance(): void { [$owner, $record, $patient] = $this->recordFor(); $this->em->persist(new WalletTransaction($patient, 500000, 'credit', 500000)); $this->em->flush(); $res = $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/wallet/withdraw', $owner, [ 'amount_rials' => 200000, 'description' => 'عودت وجه', ]); self::assertSame(201, $this->responseCode()); self::assertSame(300000, $res['data']['balance_rials']); self::assertSame('debit', $res['data']['transaction']['type']); self::assertSame('عودت وجه', $res['data']['transaction']['description']); $wallet = $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/wallet', $owner); self::assertSame(300000, $wallet['data']['balance_rials']); } public function testWithdrawWalletDefaultsDescription(): void { [$owner, $record, $patient] = $this->recordFor(); $this->em->persist(new WalletTransaction($patient, 400000, 'credit', 400000)); $this->em->flush(); $res = $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/wallet/withdraw', $owner, [ 'amount_rials' => 400000, ]); self::assertSame(201, $this->responseCode()); self::assertSame(0, $res['data']['balance_rials']); self::assertSame('برداشت از کیف پول', $res['data']['transaction']['description']); } public function testWithdrawWalletRejectsAmountAboveBalance(): void { [$owner, $record, $patient] = $this->recordFor(); $this->em->persist(new WalletTransaction($patient, 100000, 'credit', 100000)); $this->em->flush(); $res = $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/wallet/withdraw', $owner, [ 'amount_rials' => 150000, ]); self::assertSame(422, $this->responseCode()); self::assertSame('ERR_WALLET_INSUFFICIENT', $res['errors'][0]['code']); } public function testWithdrawWalletRejectsNonPositiveAmount(): void { [$owner, $record] = $this->recordFor(); $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/wallet/withdraw', $owner, ['amount_rials' => 0]); self::assertSame(422, $this->responseCode()); } public function testChargeRecordsActingUserPaymentMethodAndStatus(): void { [$owner, $record] = $this->recordFor(); $res = $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/wallet/charge', $owner, [ 'amount_rials' => 300000, 'payment_method' => 'card', ]); self::assertSame(201, $this->responseCode()); $txn = $res['data']['transaction']; self::assertSame('card', $txn['payment_method']); self::assertSame('confirmed', $txn['status']); // بدون پروفایل → نامِ ثبت‌کننده = شماره موبایلِ کاربرِ عامل self::assertSame($owner->getMobileNumber(), $txn['created_by_name']); } public function testChargeIgnoresUnknownPaymentMethod(): void { [$owner, $record] = $this->recordFor(); $res = $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/wallet/charge', $owner, [ 'amount_rials' => 100000, 'payment_method' => 'bitcoin', ]); self::assertSame(201, $this->responseCode()); self::assertNull($res['data']['transaction']['payment_method']); } public function testFinancialsAreOwnershipScoped(): void { [, $record] = $this->recordFor(); [$other] = $this->recordFor(); // A different owner cannot read this record's finances (or charge them). $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/payments', $other); self::assertSame(404, $this->responseCode()); $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/wallet/charge', $other, ['amount_rials' => 1000]); self::assertSame(404, $this->responseCode()); $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/wallet/withdraw', $other, ['amount_rials' => 1000]); self::assertSame(404, $this->responseCode()); $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/wallet', $other); self::assertSame(404, $this->responseCode()); $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/wallet/transactions', $other); self::assertSame(404, $this->responseCode()); } }