147 lines
6.3 KiB
PHP
147 lines
6.3 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Http\Client;
|
|
|
|
use App\Enums\InvoicePaymentMethodEnum;
|
|
use App\Enums\InvoiceStatusEnum;
|
|
use App\Enums\PaymentPurposeEnum;
|
|
use App\Enums\PaymentStatusEnum;
|
|
use App\Enums\WalletTransactionReasonEnum;
|
|
use App\Models\Invoice;
|
|
use App\Models\Payment;
|
|
use App\Models\User;
|
|
use App\Models\Wallet;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Tests\TestCase;
|
|
|
|
class PaymentControllerTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
Queue::fake();
|
|
}
|
|
|
|
public function test_store_requires_authentication(): void
|
|
{
|
|
$this->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);
|
|
}
|
|
}
|