credentials->forOrganization($organization)->hasValidBird(); } public function usesCustomerSms(Organization $organization): bool { return $this->credentials->forOrganization($organization)->hasValidSms(); } public function emailCostMinor(Organization $organization): int { if ($this->usesCustomerEmail($organization)) { return 0; } $allowance = $this->plans->freeEmailsPerMonth($organization); if ($allowance === null || $this->usage->emailCountThisMonth($organization) < $allowance) { return 0; } return $this->pricing->emailCostMinor(); } public function canAffordEmail(Organization $organization): bool { if ($this->usesCustomerEmail($organization)) { return true; } $cost = $this->emailCostMinor($organization); if ($cost <= 0) { return true; } return $this->canAfford($organization->owner_ref, $cost); } public function canAffordSms(Organization $organization, string $message): bool { if ($this->usesCustomerSms($organization)) { return true; } $cost = $this->pricing->smsCostMinor($message); if ($cost <= 0) { return true; } return $this->canAfford($organization->owner_ref, $cost); } /** * Debit after a successful send. Returns false if the wallet could not be charged. * Customer relay keys bill the Ladill SMS/Bird wallet directly — never double-debit Frontdesk. */ public function chargeEmail(Organization $organization, string $reference, string $description): bool { if ($this->usesCustomerEmail($organization)) { $this->usage->recordEmail($organization, 0); return true; } $cost = $this->emailCostMinor($organization); if ($cost <= 0) { $this->usage->recordEmail($organization, 0); return true; } if (! $this->debit($organization->owner_ref, $cost, 'email', $reference, $description)) { return false; } $this->usage->recordEmail($organization, $cost); return true; } public function chargeSms(Organization $organization, string $message, string $reference, string $description): bool { if ($this->usesCustomerSms($organization)) { $this->usage->recordSms($organization, 0); return true; } $cost = $this->pricing->smsCostMinor($message); if ($cost <= 0) { $this->usage->recordSms($organization, 0); return true; } if (! $this->debit($organization->owner_ref, $cost, 'sms', $reference, $description)) { return false; } $this->usage->recordSms($organization, $cost); return true; } private function canAfford(string $ownerRef, int $costMinor): bool { try { return $this->billing->canAfford($ownerRef, $costMinor); } catch (\Throwable) { return true; } } private function debit(string $ownerRef, int $costMinor, string $channel, string $reference, string $description): bool { try { return $this->billing->debit( $ownerRef, $costMinor, 'notification_'.$channel, 'frontdesk-notify-'.$reference.'-'.Str::uuid(), $description, ); } catch (\Throwable) { return false; } } }