'role-' . uniqid(), 'guard_name' => 'admin']); $role->givePermissionTo(Permission::firstOrCreate(['name' => 'customer.edit', 'guard_name' => 'admin'])); $admin = User::factory()->create(); $admin->assignRole($role); return $admin; } private function service(int $balance): Service { $customer = Customer::factory()->create(['total' => $balance]); $product = Product::factory()->create(['product_type' => 'hosting', 'is_active' => true]); $order = Order::create([ 'order_num' => 'ORD-' . uniqid(), 'customer_id' => $customer->id, 'sub_total' => 90_000, 'total' => 90_000, 'status' => OrderStatus::ACTIVE->value, ]); $item = OrderItem::create([ 'order_id' => $order->id, 'product_id' => $product->id, 'billing_cycle' => 'monthly', 'months' => 1, 'quantity' => 1, 'price' => 90_000, ]); $product->pricings()->create(['billing_cycle' => 'monthly', 'price' => 90_000, 'is_active' => 1]); return Service::create([ 'customer_id' => $customer->id, 'product_id' => $product->id, 'order_item_id' => $item->id, 'status' => 'suspended', 'server_name' => 'test-server', 'expires_at' => now()->subDay(), ]); } private function renew(Service $service, array $payload) { return $this->actingAs($this->admin(), 'admin') ->postJson("/api/v1/administrator/customers/{$service->customer->uuid}/services/{$service->id}/renew", $payload); } public function test_manual_payment_renews_without_touching_balance(): void { $service = $this->service(0); $this->renew($service, ['months' => 1, 'payment' => 'manual', 'note' => 'CK VCB'])->assertOk(); $service->refresh(); $this->assertSame('active', $service->status); $this->assertTrue($service->expires_at->isFuture()); $this->assertSame(InvoiceStatus::PAID->value, Invoice::find($service->invoice_id)->status); $this->assertSame(0, (int) $service->customer->fresh()->total); } public function test_renewal_is_logged_with_actor_and_payment_method_and_listed(): void { $service = $this->service(0); $admin = $this->admin(); $admin->givePermissionTo(\Spatie\Permission\Models\Permission::firstOrCreate(['name' => 'customer.view', 'guard_name' => 'admin'])); $base = "/api/v1/administrator/customers/{$service->customer->uuid}/services/{$service->id}"; $this->actingAs($admin, 'admin')->postJson("{$base}/renew", ['months' => 1, 'payment' => 'manual'])->assertOk(); $this->actingAs($admin, 'admin')->getJson("{$base}/renewals") ->assertOk() ->assertJsonCount(1, 'data') ->assertJsonPath('data.0.causer_type', 'admin') ->assertJsonPath('data.0.new_values.payment_method', 'admin') ->assertJsonPath('data.0.new_values.months', 1); } public function test_balance_payment_deducts_customer_balance(): void { $service = $this->service(200_000); $this->renew($service, ['months' => 1, 'payment' => 'balance'])->assertOk(); $this->assertSame(110_000, (int) $service->customer->fresh()->total); $this->assertSame('active', $service->fresh()->status); } public function test_balance_payment_with_insufficient_funds_leaves_invoice_unpaid(): void { $service = $this->service(1_000); $this->renew($service, ['months' => 1, 'payment' => 'balance'])->assertStatus(422); $this->assertSame(InvoiceStatus::UNPAID->value, Invoice::find($service->fresh()->invoice_id)->status); } public function test_rejects_months_outside_the_billing_cycle(): void { $service = $this->service(200_000); $this->renew($service, ['months' => 25, 'payment' => 'manual'])->assertStatus(422); } public function test_renewal_queues_email_to_customer_and_admin(): void { Mail::fake(); (new TemplateSeeder())->run(); Setting::create(['key' => 'setting', 'value' => json_encode(['admin_email' => 'ops@example.com'])]); $service = $this->service(0); $this->renew($service, ['months' => 1, 'payment' => 'manual'])->assertOk(); Mail::assertQueued(ServiceRenewedMail::class, fn ($mail) => $mail->hasTo($service->customer->email)); Mail::assertQueued(ServiceRenewedMail::class, fn ($mail) => $mail->hasTo('ops@example.com')); } public function test_rejects_period_already_renewed_before(): void { $service = $this->service(0); $this->renew($service, ['months' => 1, 'payment' => 'manual'])->assertOk(); $service->refresh()->update(['expires_at' => now()->addDays(5)]); $this->renew($service, ['months' => 1, 'payment' => 'manual']) ->assertStatus(422) ->assertJsonPath('error', true); } }