post(route('dashboard.payments.store'), ['invoice_id' => 1])->assertRedirect(route('login')); } public function test_store_creates_gateway_payment_for_own_pending_invoice(): void { Http::fake(['gateway.zibal.ir/v1/request' => Http::response(['result' => 100, 'trackId' => 777])]); $invoice = Invoice::factory()->create(['amount' => 500000]); $response = $this->actingAs($invoice->user)->postJson(route('dashboard.payments.store'), ['invoice_id' => $invoice->id]); $response->assertOk() ->assertJsonPath('payment.track_id', '777') ->assertJsonPath('url', 'https://gateway.zibal.ir/start/777'); $this->assertDatabaseHas('payments', [ 'track_id' => '777', 'amount' => 500000, 'invoice_id' => $invoice->id, 'payer_id' => $invoice->user_id, 'purpose' => PaymentPurposeEnum::Invoice->value, ]); } public function test_store_rejects_invoice_of_another_user(): void { Http::fake(); $invoice = Invoice::factory()->create(); $this->actingAs(User::factory()->create())->postJson(route('dashboard.payments.store'), ['invoice_id' => $invoice->id])->assertNotFound(); Http::assertNothingSent(); } public function test_store_rejects_non_pending_invoice(): void { Http::fake(); $invoice = Invoice::factory()->paid()->create(); $this->actingAs($invoice->user)->postJson(route('dashboard.payments.store'), ['invoice_id' => $invoice->id])->assertNotFound(); } public function test_store_validates_invoice_id(): void { $this->actingAs(User::factory()->create())->postJson(route('dashboard.payments.store'), [])->assertUnprocessable()->assertJsonValidationErrors('invoice_id'); } public function test_callback_with_unknown_track_id_redirects_to_dashboard(): void { $this->get(route('dashboard.payments.callback', ['trackId' => 'nope', 'success' => 1]))->assertRedirect(route('dashboard.index')); } public function test_callback_settles_successful_invoice_payment(): void { Http::fake(['gateway.zibal.ir/v1/verify' => Http::response(['result' => 100, 'refNumber' => 'REF'])]); $invoice = Invoice::factory()->create(['amount' => 500000]); $payment = Payment::factory()->create(['invoice_id' => $invoice->id, 'payer_id' => $invoice->user_id, 'amount' => 500000, 'track_id' => '555']); $response = $this->get(route('dashboard.payments.callback', ['trackId' => '555', 'success' => 1])); $response->assertRedirect(route('dashboard.index')); $response->assertSessionHas('payment_result', ['status' => 'paid', 'track_id' => '555', 'purpose' => 'Invoice']); $this->assertSame(PaymentStatusEnum::Paid, $payment->fresh()->status); $invoice->refresh(); $this->assertSame(InvoiceStatusEnum::Paid, $invoice->status); $this->assertSame(InvoicePaymentMethodEnum::Gateway, $invoice->payment_method); $this->assertDatabaseHas('wallet_transactions', ['invoice_id' => $invoice->id, 'payment_id' => $payment->id, 'reason' => WalletTransactionReasonEnum::InvoicePayment->value]); } public function test_callback_settles_successful_wallet_top_up(): void { Http::fake(['gateway.zibal.ir/v1/verify' => Http::response(['result' => 100])]); $wallet = Wallet::factory()->create(['balance' => 1000]); Payment::factory()->walletTopUp()->create(['payer_id' => $wallet->user_id, 'amount' => 250000, 'track_id' => '556']); $this->get(route('dashboard.payments.callback', ['trackId' => '556', 'success' => 1]))->assertRedirect(route('dashboard.index')); $this->assertSame(251000, $wallet->fresh()->balance); $this->assertDatabaseHas('wallet_transactions', ['wallet_id' => $wallet->id, 'reason' => WalletTransactionReasonEnum::TopUp->value, 'amount' => 250000]); } public function test_callback_marks_payment_failed_when_gateway_reports_failure(): void { Http::fake(); $invoice = Invoice::factory()->create(); $payment = Payment::factory()->create(['invoice_id' => $invoice->id, 'payer_id' => $invoice->user_id, 'track_id' => '557']); $response = $this->get(route('dashboard.payments.callback', ['trackId' => '557', 'success' => 0])); $response->assertSessionHas('payment_result.status', 'failed'); $this->assertSame(PaymentStatusEnum::Failed, $payment->fresh()->status); $this->assertSame(InvoiceStatusEnum::Pending, $invoice->fresh()->status); Http::assertNothingSent(); } public function test_callback_marks_payment_failed_when_verification_fails(): void { Http::fake(['gateway.zibal.ir/v1/verify' => Http::response(['result' => 202])]); $payment = Payment::factory()->create(['track_id' => '558']); $this->get(route('dashboard.payments.callback', ['trackId' => '558', 'success' => 1])); $this->assertSame(PaymentStatusEnum::Failed, $payment->fresh()->status); $this->assertDatabaseCount('wallet_transactions', 0); } public function test_callback_does_not_settle_twice_for_already_paid_payment(): void { Http::fake(); $wallet = Wallet::factory()->create(['balance' => 1000]); Payment::factory()->paid()->walletTopUp()->create(['payer_id' => $wallet->user_id, 'amount' => 250000, 'track_id' => '559']); $this->get(route('dashboard.payments.callback', ['trackId' => '559', 'success' => 1]))->assertSessionHas('payment_result.status', 'paid'); $this->assertSame(1000, $wallet->fresh()->balance); $this->assertDatabaseCount('wallet_transactions', 0); } }