diff --git a/app/app/Services/Billing/BillingTopupService.php b/app/app/Services/Billing/BillingTopupService.php index 5683961e..382c618f 100644 --- a/app/app/Services/Billing/BillingTopupService.php +++ b/app/app/Services/Billing/BillingTopupService.php @@ -41,6 +41,10 @@ final class BillingTopupService // bcadd — DECIMAL-точность, НЕ PHP float (паттерн LedgerService). $newBalanceRub = bcadd((string) $tenant->balance_rub, $amountRub, 2); + // Eloquent decimal:2 cast сохраняет bcmath-строку без потери точности + // при save() (в отличие от decrement(), который требует float|int — + // именно поэтому LedgerService использует raw DB::table()->update(); + // здесь же присваивание уже посчитанной строки через модель безопасно). $tenant->balance_rub = $newBalanceRub; $tenant->save(); diff --git a/app/phpstan-baseline.neon b/app/phpstan-baseline.neon index e71edca3..b18d49f0 100644 --- a/app/phpstan-baseline.neon +++ b/app/phpstan-baseline.neon @@ -591,7 +591,7 @@ parameters: - message: '#^Call to an undefined method Pest\\PendingCalls\\TestCall\:\:postJson\(\)\.$#' identifier: method.notFound - count: 7 + count: 8 path: tests/Feature/Billing/TopupControllerTest.php - diff --git a/app/tests/Feature/Billing/TopupControllerTest.php b/app/tests/Feature/Billing/TopupControllerTest.php index 832734aa..4cde5b22 100644 --- a/app/tests/Feature/Billing/TopupControllerTest.php +++ b/app/tests/Feature/Billing/TopupControllerTest.php @@ -33,6 +33,7 @@ test('POST /api/billing/topup пишет строку balance_transactions с us 'user_id' => $this->user->id, 'type' => 'topup', 'amount_rub' => '100.00', + 'balance_rub_after' => '600.00', ]); }); @@ -44,6 +45,16 @@ test('POST /api/billing/topup использует bcmath-точность', fun expect((string) $this->tenant->fresh()->balance_rub)->toBe('100.30'); }); +test('POST /api/billing/topup не затрагивает баланс чужого тенанта', function () { + $otherTenant = Tenant::factory()->create(['balance_rub' => '777.00']); + + $this->postJson('/api/billing/topup', ['amount_rub' => 100])->assertStatus(201); + + // Топап эндпоинт не принимает tenant_id — резолвит из auth-пользователя; + // баланс чужого тенанта неприкосновенен (defense-in-depth, паттерн проекта). + expect((string) $otherTenant->fresh()->balance_rub)->toBe('777.00'); +}); + test('POST /api/billing/topup отклоняет сумму ниже минимума 100 ₽', function () { $this->postJson('/api/billing/topup', ['amount_rub' => 50]) ->assertStatus(422)