CluPilotCloud/tests/Feature/Billing/StoragePackPricingTest.php

102 lines
4.4 KiB
PHP

<?php
use App\Actions\BookAddon;
use App\Actions\GrantAddon;
use App\Actions\OpenSubscription;
use App\Livewire\Billing;
use App\Models\Customer;
use App\Models\Instance;
use App\Models\Operator;
use App\Models\Order;
use App\Models\User;
use App\Services\Billing\AddonCatalogue;
use App\Services\Billing\PlanCatalogue;
/**
* Was ein Block kostet, und wer ihn nicht kaufen kann.
*
* Die Preisregel: ein Block muss teurer je GB sein als der Aufstieg in die
* nächste Stufe. 20 GB für 15 € sind 0,75 €/GB gegen 0,73 (Start → Team) und
* 0,67 (Team → Business). Bei 12 € wäre er mit 0,60 billiger als jeder
* Aufstieg gewesen, und Stapeln hätte sich gelohnt.
*/
it('verkauft 20 GB für 15 Euro netto', function () {
$catalogue = app(AddonCatalogue::class);
expect($catalogue->packGb())->toBe(20)
->and($catalogue->packDiskGb())->toBe(22)
->and($catalogue->priceCents(AddonCatalogue::STORAGE))->toBe(1500)
->and($catalogue->maxQuantity(AddonCatalogue::STORAGE))->toBe(3);
});
it('bleibt teurer je Gigabyte als der Aufstieg', function () {
// Gerechnet wird mit dem, was Katalog und Konfiguration WIRKLICH sagen —
// mit festen Zahlen prüfte dieser Test nur, ob PHP dividieren kann. So
// schlägt er an, wenn ein Preis später einmal so gesetzt wird, dass
// Stapeln sich wieder lohnt.
$catalogue = app(AddonCatalogue::class);
$plans = app(PlanCatalogue::class)->sellable();
$blockPerGb = $catalogue->priceCents(AddonCatalogue::STORAGE) / $catalogue->packGb();
foreach ([['start', 'team'], ['team', 'business']] as [$von, $nach]) {
$mehrGb = $plans[$nach]['quota_gb'] - $plans[$von]['quota_gb'];
$mehrCent = $plans[$nach]['price_cents'] - $plans[$von]['price_cents'];
expect($blockPerGb)->toBeGreaterThan($mehrCent / $mehrGb);
}
});
it('bietet Enterprise keinen Zusatzspeicher an', function () {
// Enterprise wird seit der neuen Leiter angefragt statt gekauft; der
// Vertrag hier ist einer aus der Zeit davor, und die laufen weiter.
$order = Order::factory()->create(['plan' => 'enterprise', 'datacenter' => 'fsn', 'status' => 'paid']);
$subscription = asGrandfathered('enterprise', fn () => app(OpenSubscription::class)($order));
expect(fn () => app(BookAddon::class)($subscription, AddonCatalogue::STORAGE, 1))
->toThrow(RuntimeException::class);
});
/**
* Nachtrag: derselbe Ausschluss, wenn der Betreiber ein Modul verschenkt.
*
* GrantAddon bucht durch BookAddon (siehe dessen Kopfkommentar), also reicht
* die Prüfung an einer Stelle — dieser Test belegt nur, dass der Weg
* tatsächlich derselbe ist und nicht heimlich daran vorbeiführt.
*/
it('lässt auch den Betreiber keinen Zusatzspeicher an Enterprise verschenken', function () {
$order = Order::factory()->create(['plan' => 'enterprise', 'datacenter' => 'fsn', 'status' => 'paid']);
$subscription = asGrandfathered('enterprise', fn () => app(OpenSubscription::class)($order));
$operator = Operator::factory()->create();
expect(fn () => app(GrantAddon::class)($subscription, $operator, AddonCatalogue::STORAGE, 0, 1))
->toThrow(RuntimeException::class);
});
/**
* Nachtrag: dieselbe Sperre, bevor Geld fließt, nicht erst danach.
*
* `storageLimitNote` ist das, was den Knopf im Portal verschwinden lässt (statt
* ihn anzubieten und dann an der Zahlung zu scheitern) — derselbe Grund, aus
* dem die kaufmännische Grenze dort schon geprüft wurde. Ohne diese Prüfung
* würde ein Enterprise-Kunde einen zahlbaren Auftrag in den Warenkorb legen
* können, den BookAddon nach der Zahlung nur noch ablehnen könnte, während der
* Auftrag selbst „paid" stehen bliebe.
*/
it('bietet Enterprise auch im Warenkorb keinen Zusatzspeicher an', function () {
$customer = Customer::factory()->create();
$user = User::factory()->create(['email' => $customer->email]);
$order = asGrandfathered('enterprise', fn () => Order::factory()->withSubscription()->for($customer)->create(['plan' => 'enterprise']));
Instance::factory()->for($customer)->create([
'order_id' => $order->id, 'plan' => 'enterprise', 'status' => 'active',
]);
withStripeSecret();
Livewire\Livewire::actingAs($user)
->test(Billing::class)
->call('purchase', 'storage');
expect(Order::query()->where('customer_id', $customer->id)->where('type', 'storage')->count())->toBe(0);
});