158 lines
5.7 KiB
PHP
158 lines
5.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Feature\Api\Admin\Customer;
|
|
|
|
use App\Enums\Invoice\InvoiceStatus;
|
|
use App\Mail\ServiceRenewedMail;
|
|
use App\Enums\Order\OrderStatus;
|
|
use App\Models\Customer;
|
|
use App\Models\Invoice;
|
|
use App\Models\Order;
|
|
use App\Models\OrderItem;
|
|
use App\Models\Product;
|
|
use App\Models\Service;
|
|
use App\Models\Setting;
|
|
use App\Models\User;
|
|
use Database\Seeders\TemplateSeeder;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Spatie\Permission\Models\Permission;
|
|
use Spatie\Permission\Models\Role;
|
|
use Tests\TestCase;
|
|
|
|
class AdminRenewServiceTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private function admin(): User
|
|
{
|
|
$role = Role::create(['name' => '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);
|
|
}
|
|
}
|