block(5, function () use ($key, $mode, $checkId) { $result = DB::transaction(function () use ($key, $mode, $checkId) { if ($e = SerpBudgetEntry::where('reservation_key', $key)->lockForUpdate()->first()) { return $e; } $this->ramp->assertAllowsNewPaid(); $estimate = Money::micros((string) config('serp.estimated_costs.'.$mode)); $day = SerpBudgetEntry::where('reserved_at', '>=', now('UTC')->startOfDay())->sum(DB::raw('CASE WHEN actual_micros IS NULL THEN reserved_micros ELSE actual_micros END')); $month = SerpBudgetEntry::where('reserved_at', '>=', now('UTC')->startOfMonth())->sum(DB::raw('CASE WHEN actual_micros IS NULL THEN reserved_micros ELSE actual_micros END')); if ($day + $estimate > Money::micros((string) config('serp.daily_budget_usd')) || $month + $estimate > Money::micros((string) config('serp.monthly_budget_usd'))) { return false; } return SerpBudgetEntry::create(['reservation_key' => $key, 'serp_check_id' => $checkId, 'mode' => $mode, 'reserved_micros' => $estimate, 'currency' => 'USD', 'reserved_at' => now('UTC')]); }); if ($result === false) { $this->kill->activate('budget_exceeded'); $this->audit->record('budget_blocked', ['cost_micros' => Money::micros((string) config('serp.estimated_costs.'.$mode))]); throw new OperationBlockedException('budget_blocked'); } $this->audit->record('budget_reserved', ['mode' => $mode, 'cost_micros' => $result->reserved_micros]); return $result; }); } public function reconcile(string $key, string|int|float|null $actual, ?string $taskId = null): void { DB::transaction(function () use ($key, $actual, $taskId) { $e = SerpBudgetEntry::where('reservation_key', $key)->lockForUpdate()->first(); if (! $e || $e->reconciled_at) { return; }$e->forceFill(['provider_task_id' => $taskId, 'actual_micros' => Money::micros($actual), 'reconciled_at' => now('UTC')])->save(); $this->audit->record('budget_reconciled', ['cost_micros' => $e->actual_micros]); }); } public function usage(): array { $daily = (int) SerpBudgetEntry::where('reserved_at', '>=', now('UTC')->startOfDay())->sum(DB::raw('CASE WHEN actual_micros IS NULL THEN reserved_micros ELSE actual_micros END')); $monthly = (int) SerpBudgetEntry::where('reserved_at', '>=', now('UTC')->startOfMonth())->sum(DB::raw('CASE WHEN actual_micros IS NULL THEN reserved_micros ELSE actual_micros END')); return ['daily_micros' => $daily, 'monthly_micros' => $monthly]; } }