service = app(WalletService::class); } public function test_for_user_creates_wallet_when_user_has_none(): void { $user = User::factory()->create(); $wallet = $this->service->forUser($user); $this->assertSame($user->id, $wallet->user_id); $this->assertSame(0, $wallet->balance); $this->assertDatabaseCount('wallets', 1); } public function test_for_user_returns_existing_wallet(): void { $wallet = Wallet::factory()->create(['balance' => 5000]); $found = $this->service->forUser($wallet->user); $this->assertTrue($found->is($wallet)); $this->assertSame(5000, $found->balance); $this->assertDatabaseCount('wallets', 1); } public function test_system_returns_single_wallet_without_user(): void { $first = $this->service->system(); $second = $this->service->system(); $this->assertNull($first->user_id); $this->assertTrue($first->is($second)); $this->assertDatabaseCount('wallets', 1); } public function test_deposit_increases_balance_and_records_transaction(): void { $wallet = Wallet::factory()->create(['balance' => 1000]); $payment = Payment::factory()->paid()->walletTopUp()->create(['payer_id' => $wallet->user_id, 'amount' => 2500]); $transaction = $this->service->deposit($wallet->user, 2500, WalletTransactionReasonEnum::TopUp, $payment); $this->assertSame(3500, $wallet->fresh()->balance); $this->assertDatabaseHas('wallet_transactions', [ 'id' => $transaction->id, 'wallet_id' => $wallet->id, 'type' => WalletTransactionTypeEnum::Deposit->value, 'reason' => WalletTransactionReasonEnum::TopUp->value, 'amount' => 2500, 'balance_after' => 3500, 'payment_id' => $payment->id, 'invoice_id' => null, 'withdraw_request_id' => null, ]); } public function test_deposit_creates_wallet_when_user_has_none(): void { $user = User::factory()->create(); $this->service->deposit($user, 700, WalletTransactionReasonEnum::Commission); $this->assertDatabaseHas('wallets', ['user_id' => $user->id, 'balance' => 700]); } public function test_deposit_to_wallet_works_on_system_wallet(): void { $wallet = Wallet::factory()->system()->create(['balance' => 100]); $transaction = $this->service->depositToWallet($wallet, 400, WalletTransactionReasonEnum::Commission); $this->assertSame(500, $wallet->fresh()->balance); $this->assertSame($wallet->id, $transaction->wallet_id); $this->assertSame(500, $transaction->balance_after); } public function test_withdraw_decreases_balance_and_records_transaction(): void { $wallet = Wallet::factory()->create(['balance' => 5000]); $invoice = Invoice::factory()->create(['user_id' => $wallet->user_id, 'amount' => 3000]); $transaction = $this->service->withdraw($wallet->user, 3000, WalletTransactionReasonEnum::InvoicePayment, $invoice); $this->assertSame(2000, $wallet->fresh()->balance); $this->assertDatabaseHas('wallet_transactions', [ 'id' => $transaction->id, 'wallet_id' => $wallet->id, 'type' => WalletTransactionTypeEnum::Withdrawal->value, 'reason' => WalletTransactionReasonEnum::InvoicePayment->value, 'amount' => 3000, 'balance_after' => 2000, 'invoice_id' => $invoice->id, 'payment_id' => null, ]); } public function test_withdraw_allows_spending_entire_balance(): void { $wallet = Wallet::factory()->create(['balance' => 1000]); $this->service->withdraw($wallet->user, 1000, WalletTransactionReasonEnum::Withdraw); $this->assertSame(0, $wallet->fresh()->balance); } public function test_withdraw_throws_when_balance_is_insufficient(): void { $wallet = Wallet::factory()->create(['balance' => 1000]); try { $this->service->withdraw($wallet->user, 1001, WalletTransactionReasonEnum::Withdraw); $this->fail('ValidationException was not thrown'); } catch (ValidationException $e) { $this->assertArrayHasKey('amount', $e->errors()); } $this->assertSame(1000, $wallet->fresh()->balance); $this->assertDatabaseCount('wallet_transactions', 0); } public function test_pay_invoice_withdraws_amount_and_marks_invoice_paid(): void { $wallet = Wallet::factory()->create(['balance' => 2000000]); $invoice = Invoice::factory()->create(['user_id' => $wallet->user_id, 'amount' => 1200000]); $paid = $this->service->payInvoice($wallet->user, $invoice); $this->assertSame(InvoiceStatusEnum::Paid, $paid->status); $this->assertSame(InvoicePaymentMethodEnum::Wallet, $paid->payment_method); $this->assertNotNull($paid->paid_at); $this->assertSame(800000, $wallet->fresh()->balance); $this->assertDatabaseHas('wallet_transactions', [ 'wallet_id' => $wallet->id, 'type' => WalletTransactionTypeEnum::Withdrawal->value, 'reason' => WalletTransactionReasonEnum::InvoicePayment->value, 'amount' => 1200000, 'invoice_id' => $invoice->id, ]); } public function test_pay_invoice_dispatches_payment_confirmed_sms(): void { config(['services.iranpayamak.patterns.payment_confirmed' => 'pattern-code']); $wallet = Wallet::factory()->create(['balance' => 500000]); $invoice = Invoice::factory()->create(['user_id' => $wallet->user_id, 'amount' => 400000]); $this->service->payInvoice($wallet->user, $invoice); Queue::assertPushed(SendPatternSms::class); } public function test_pay_invoice_throws_when_invoice_is_not_pending(): void { $wallet = Wallet::factory()->create(['balance' => 2000000]); $invoice = Invoice::factory()->paid()->create(['user_id' => $wallet->user_id, 'amount' => 1000]); try { $this->service->payInvoice($wallet->user, $invoice); $this->fail('ValidationException was not thrown'); } catch (ValidationException $e) { $this->assertArrayHasKey('invoice', $e->errors()); } $this->assertSame(2000000, $wallet->fresh()->balance); $this->assertDatabaseCount('wallet_transactions', 0); } public function test_pay_invoice_throws_and_keeps_invoice_pending_when_balance_is_insufficient(): void { $wallet = Wallet::factory()->create(['balance' => 1000]); $invoice = Invoice::factory()->create(['user_id' => $wallet->user_id, 'amount' => 1200000]); $this->expectException(ValidationException::class); try { $this->service->payInvoice($wallet->user, $invoice); } finally { $this->assertSame(InvoiceStatusEnum::Pending, $invoice->fresh()->status); $this->assertSame(1000, $wallet->fresh()->balance); Queue::assertNothingPushed(); } } public function test_record_gateway_invoice_payment_creates_transaction_without_changing_balance(): void { $wallet = Wallet::factory()->create(['balance' => 9000]); $invoice = Invoice::factory()->create(['user_id' => $wallet->user_id, 'amount' => 700000]); $payment = Payment::factory()->paid()->create(['payer_id' => $wallet->user_id, 'invoice_id' => $invoice->id, 'amount' => 700000]); $transaction = $this->service->recordGatewayInvoicePayment($invoice, $payment); $this->assertSame(9000, $wallet->fresh()->balance); $this->assertDatabaseHas('wallet_transactions', [ 'id' => $transaction->id, 'wallet_id' => $wallet->id, 'type' => WalletTransactionTypeEnum::Withdrawal->value, 'reason' => WalletTransactionReasonEnum::InvoicePayment->value, 'amount' => 700000, 'balance_after' => 9000, 'invoice_id' => $invoice->id, 'payment_id' => $payment->id, ]); } public function test_record_gateway_invoice_payment_creates_wallet_when_user_has_none(): void { $invoice = Invoice::factory()->create(['amount' => 500000]); $payment = Payment::factory()->paid()->create(['payer_id' => $invoice->user_id, 'invoice_id' => $invoice->id]); $transaction = $this->service->recordGatewayInvoicePayment($invoice, $payment); $this->assertDatabaseHas('wallets', ['user_id' => $invoice->user_id, 'balance' => 0]); $this->assertSame(0, $transaction->balance_after); $this->assertInstanceOf(WalletTransaction::class, $transaction); } }