feat: implement wallet and invoice factories, enhance wallet service methods, and add comprehensive tests

This commit is contained in:
2026-09-21 05:01:19 +03:30
parent 45da0103c0
commit b4b1fdc288
8 changed files with 568 additions and 37 deletions
-19
View File
@@ -1,19 +0,0 @@
<?php
namespace Tests\Feature;
// use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*/
public function test_the_application_returns_a_successful_response(): void
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
@@ -0,0 +1,247 @@
<?php
namespace Tests\Feature\Services\Wallet;
use App\Enums\InvoicePaymentMethodEnum;
use App\Enums\InvoiceStatusEnum;
use App\Enums\WalletTransactionReasonEnum;
use App\Enums\WalletTransactionTypeEnum;
use App\Jobs\SendPatternSms;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\User;
use App\Models\Wallet;
use App\Models\WalletTransaction;
use App\Services\Wallet\WalletService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use Illuminate\Validation\ValidationException;
use Tests\TestCase;
class WalletServiceTest extends TestCase
{
use RefreshDatabase;
protected WalletService $service;
protected function setUp(): void
{
parent::setUp();
Queue::fake();
$this->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);
}
}
-16
View File
@@ -1,16 +0,0 @@
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*/
public function test_that_true_is_true(): void
{
$this->assertTrue(true);
}
}