- Nạp tiền: mã thanh toán sống 30 phút (ManualDepositDraft), khôi phục/huỷ mã, lệnh dọn mã hết hạn; total_pending chỉ tính giao dịch đã nộp chứng từ, admin duyệt/từ chối/xoá giao dịch - Gia hạn dịch vụ: phát hành invoice gia hạn trên order gốc và trừ số dư ngay - Đồng bộ domain từ Plesk theo server, ServiceOrderRecorder tạo order/invoice cho dịch vụ admin dựng - Danh sách dịch vụ trả field theo loại (service_plan, dns_records_count), đếm dịch vụ theo loại Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
132 lines
4.9 KiB
PHP
132 lines
4.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Feature\Api\Transaction;
|
|
|
|
use App\Enums\TransactionStatus;
|
|
use App\Models\Customer;
|
|
use App\Models\Transaction;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Http\UploadedFile;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Tests\TestCase;
|
|
|
|
class ManualDepositDraftExpiryTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private function actingAsCustomer(Customer $customer): static
|
|
{
|
|
return $this->actingAs($customer, 'client')
|
|
->withHeaders(['x-access-token' => $this->buildClientAccessToken()]);
|
|
}
|
|
|
|
private function prepare(Customer $customer, int $amount = 500000): array
|
|
{
|
|
return $this->actingAsCustomer($customer)
|
|
->postJson('/v1/transaction/manual-deposit/prepare', ['amount' => $amount])
|
|
->assertOk()
|
|
->json('data');
|
|
}
|
|
|
|
public function test_prepare_returns_expiry_and_show_restores_draft(): void
|
|
{
|
|
$customer = Customer::factory()->create(['total_pending' => 0]);
|
|
|
|
$draft = $this->prepare($customer);
|
|
|
|
$this->assertSame(500000, $draft['amount']);
|
|
$this->assertEqualsWithDelta(now()->addMinutes(30)->timestamp, strtotime($draft['expires_at']), 5);
|
|
|
|
$this->actingAsCustomer($customer)
|
|
->getJson("/v1/transaction/manual-deposit/{$draft['uuid']}")
|
|
->assertOk()
|
|
->assertJsonPath('data.public_code', $draft['public_code'])
|
|
->assertJsonPath('data.amount', 500000);
|
|
}
|
|
|
|
public function test_show_hides_draft_of_another_customer(): void
|
|
{
|
|
$owner = Customer::factory()->create();
|
|
$other = Customer::factory()->create();
|
|
|
|
$draft = $this->prepare($owner);
|
|
|
|
$this->actingAsCustomer($other)
|
|
->getJson("/v1/transaction/manual-deposit/{$draft['uuid']}")
|
|
->assertNotFound();
|
|
}
|
|
|
|
public function test_expired_draft_is_discarded_on_show_and_rejected_on_confirm(): void
|
|
{
|
|
Storage::fake('local');
|
|
$customer = Customer::factory()->create(['total_pending' => 0]);
|
|
|
|
$first = $this->prepare($customer);
|
|
$this->travel(31)->minutes();
|
|
|
|
$this->actingAsCustomer($customer)
|
|
->getJson("/v1/transaction/manual-deposit/{$first['uuid']}")
|
|
->assertStatus(410);
|
|
$this->assertNull(Transaction::withTrashed()->where('uuid', $first['uuid'])->first());
|
|
$this->assertSame(0, (int) $customer->fresh()->total_pending);
|
|
|
|
$second = $this->prepare($customer);
|
|
$this->travel(31)->minutes();
|
|
$path = UploadedFile::fake()->image('proof.jpg')->store('deposit-proofs/' . $customer->id, 'local');
|
|
|
|
$this->actingAsCustomer($customer)
|
|
->postJson('/v1/transaction/manual-deposit/confirm', ['uuid' => $second['uuid'], 'proof_image' => $path])
|
|
->assertStatus(422);
|
|
$this->assertNull(Transaction::where('uuid', $second['uuid'])->value('proof_image'));
|
|
}
|
|
|
|
public function test_purge_command_removes_only_expired_drafts(): void
|
|
{
|
|
$customer = Customer::factory()->create(['total_pending' => 0]);
|
|
|
|
$expired = $this->prepare($customer, 300000);
|
|
$this->travel(31)->minutes();
|
|
$other = Customer::factory()->create(['total_pending' => 0]);
|
|
$fresh = $this->prepare($other, 200000);
|
|
|
|
$submitted = Transaction::create([
|
|
'amount' => 100000,
|
|
'customer_id' => $customer->id,
|
|
'type' => 'manual',
|
|
'status' => TransactionStatus::PENDING->value,
|
|
'proof_image' => 'deposit-proofs/x.jpg',
|
|
'trans' => 'MANTEST',
|
|
]);
|
|
|
|
$this->artisan('deposits:purge-expired-drafts')->assertSuccessful();
|
|
|
|
$this->assertNull(Transaction::withTrashed()->where('uuid', $expired['uuid'])->first());
|
|
$this->assertNotNull(Transaction::where('uuid', $fresh['uuid'])->first());
|
|
$this->assertNotNull($submitted->fresh());
|
|
$this->assertSame(0, (int) $customer->fresh()->total_pending);
|
|
$this->assertSame(0, (int) $other->fresh()->total_pending);
|
|
}
|
|
|
|
public function test_total_pending_only_counts_amount_after_proof_is_submitted(): void
|
|
{
|
|
Storage::fake('local');
|
|
$customer = Customer::factory()->create(['total_pending' => 0]);
|
|
|
|
$draft = $this->prepare($customer, 700000);
|
|
$this->assertSame(0, (int) $customer->fresh()->total_pending);
|
|
|
|
$path = $this->actingAsCustomer($customer)
|
|
->post('/v1/transaction/manual-deposit/upload-proof', ['file' => UploadedFile::fake()->image('proof.jpg')])
|
|
->assertOk()
|
|
->json('path');
|
|
$this->assertSame(0, (int) $customer->fresh()->total_pending);
|
|
|
|
$this->actingAsCustomer($customer)
|
|
->postJson('/v1/transaction/manual-deposit/confirm', ['uuid' => $draft['uuid'], 'proof_image' => $path])
|
|
->assertOk();
|
|
$this->assertSame(700000, (int) $customer->fresh()->total_pending);
|
|
}
|
|
}
|