Deploy Ladill Hosting / deploy (push) Successful in 20s
The account overview queried a local mailboxes table that the extracted hosting app does not have; guard those lookups and skip email-addon upsells when mailboxes are managed on Ladill Email. Co-authored-by: Cursor <cursoragent@cursor.com>
191 lines
7.2 KiB
PHP
191 lines
7.2 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Hosting;
|
|
|
|
use App\Jobs\DeactivateMailboxJob;
|
|
use App\Jobs\ProvisionMailboxJob;
|
|
use App\Services\Mail\MailboxUserProvisioner;
|
|
use App\Models\Domain;
|
|
use App\Models\HostingAccount;
|
|
use App\Models\HostingBillingInvoice;
|
|
use App\Models\Mailbox;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
class HostingMailboxService
|
|
{
|
|
public const EXTRA_MAILBOX_PRICE_CEDIS = 5;
|
|
|
|
public function __construct(
|
|
protected MailboxUserProvisioner $mailboxUsers,
|
|
) {}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function usageSummary(HostingAccount $account): array
|
|
{
|
|
$account->loadMissing(['product']);
|
|
|
|
$mailboxes = $account->loadedMailboxes();
|
|
$freeAllowance = $account->freeEmailAllowance();
|
|
$mailboxCount = $mailboxes->count();
|
|
$paidCount = $mailboxes->where('is_paid', true)->count();
|
|
$freeUsed = $freeAllowance === null ? $mailboxCount : min($mailboxCount, $freeAllowance);
|
|
$extraCount = max($mailboxCount - ($freeAllowance ?? $mailboxCount), 0);
|
|
|
|
return [
|
|
'mailbox_count' => $mailboxCount,
|
|
'free_allowance' => $freeAllowance,
|
|
'free_used' => $freeUsed,
|
|
'paid_count' => $paidCount,
|
|
'extra_count' => $extraCount,
|
|
'extra_price' => self::EXTRA_MAILBOX_PRICE_CEDIS,
|
|
'extra_total' => round($extraCount * self::EXTRA_MAILBOX_PRICE_CEDIS, 2),
|
|
'breakdown' => sprintf(
|
|
'Extra Emails: %d x GHS %d = GHS %.2f',
|
|
$extraCount,
|
|
self::EXTRA_MAILBOX_PRICE_CEDIS,
|
|
round($extraCount * self::EXTRA_MAILBOX_PRICE_CEDIS, 2)
|
|
),
|
|
];
|
|
}
|
|
|
|
public function createMailbox(HostingAccount $account, array $input): array
|
|
{
|
|
$account->loadMissing(['product', 'sites']);
|
|
|
|
if (! HostingAccount::tracksLocalMailboxes()) {
|
|
throw ValidationException::withMessages([
|
|
'local_part' => 'Mailbox creation is managed in Ladill Email.',
|
|
]);
|
|
}
|
|
|
|
$account->loadMissing(['mailboxes']);
|
|
|
|
$domain = Domain::query()
|
|
->whereKey((int) ($input['domain_id'] ?? 0))
|
|
->where('hosting_account_id', $account->id)
|
|
->first();
|
|
|
|
if (! $domain) {
|
|
throw ValidationException::withMessages([
|
|
'domain_id' => 'Select a domain linked to this hosting account.',
|
|
]);
|
|
}
|
|
|
|
$localPart = strtolower(trim((string) ($input['local_part'] ?? '')));
|
|
$address = "{$localPart}@{$domain->host}";
|
|
|
|
if (Mailbox::query()->where('address', $address)->exists()) {
|
|
throw ValidationException::withMessages([
|
|
'local_part' => 'This mailbox address already exists.',
|
|
]);
|
|
}
|
|
|
|
$summary = $this->usageSummary($account);
|
|
$freeAllowance = $summary['free_allowance'];
|
|
$isPaid = $freeAllowance !== null && $summary['mailbox_count'] >= $freeAllowance;
|
|
|
|
$mailbox = Mailbox::query()->create([
|
|
'user_id' => $account->user_id,
|
|
'website_id' => null,
|
|
'hosting_account_id' => $account->id,
|
|
'domain_id' => $domain->id,
|
|
'display_name' => (string) ($input['display_name'] ?? $localPart),
|
|
'local_part' => $localPart,
|
|
'address' => $address,
|
|
'password_hash' => Hash::make((string) $input['password']),
|
|
'quota_mb' => (int) ($input['quota_mb'] ?? Mailbox::defaultQuotaMb()),
|
|
'status' => 'provisioning',
|
|
'is_paid' => $isPaid,
|
|
'paid_at' => $isPaid ? now() : null,
|
|
'billing_type' => $isPaid ? 'subscription' : 'free',
|
|
'monthly_price' => $isPaid ? self::EXTRA_MAILBOX_PRICE_CEDIS : null,
|
|
'subscription_started_at' => $isPaid ? now() : null,
|
|
'subscription_expires_at' => $isPaid ? now()->addMonth() : null,
|
|
'subscription_status' => 'active',
|
|
'maildir_path' => sprintf('hosting/%d/%s/%s', $account->id, $domain->host, $localPart),
|
|
'credentials_issued_at' => now(),
|
|
'incoming_token' => Str::random(64),
|
|
'inbound_enabled' => true,
|
|
'outbound_enabled' => true,
|
|
'outbound_provider' => 'smtp',
|
|
'metadata' => [
|
|
'provisioned_by' => 'hosting_account',
|
|
'hosting_account_id' => $account->id,
|
|
],
|
|
]);
|
|
|
|
$this->mailboxUsers->provisionMailboxSilently($mailbox, (string) $input['password']);
|
|
|
|
ProvisionMailboxJob::dispatch($mailbox->id, encrypt((string) $input['password']));
|
|
|
|
$invoice = $this->syncAddonInvoice($account->fresh());
|
|
|
|
return [
|
|
'mailbox' => $mailbox->fresh(['domain']),
|
|
'usage' => $this->usageSummary($account->fresh(['product'])),
|
|
'invoice' => $invoice,
|
|
];
|
|
}
|
|
|
|
public function deleteMailbox(HostingAccount $account, Mailbox $mailbox): ?HostingBillingInvoice
|
|
{
|
|
if ((int) $mailbox->hosting_account_id !== (int) $account->id) {
|
|
throw ValidationException::withMessages([
|
|
'mailbox_id' => 'Mailbox does not belong to this hosting account.',
|
|
]);
|
|
}
|
|
|
|
$mailbox->update(['status' => 'deleting']);
|
|
DeactivateMailboxJob::dispatch($mailbox->id);
|
|
|
|
return $this->syncAddonInvoice($account->fresh());
|
|
}
|
|
|
|
public function syncAddonInvoice(HostingAccount $account): ?HostingBillingInvoice
|
|
{
|
|
$account->loadMissing(['product']);
|
|
$summary = $this->usageSummary($account);
|
|
if ($summary['extra_count'] === 0) {
|
|
HostingBillingInvoice::query()
|
|
->where('hosting_account_id', $account->id)
|
|
->where('invoice_type', HostingBillingInvoice::TYPE_EMAIL_ADDON)
|
|
->where('status', HostingBillingInvoice::STATUS_PENDING)
|
|
->delete();
|
|
|
|
return null;
|
|
}
|
|
|
|
return HostingBillingInvoice::query()->updateOrCreate(
|
|
[
|
|
'hosting_account_id' => $account->id,
|
|
'invoice_type' => HostingBillingInvoice::TYPE_EMAIL_ADDON,
|
|
'status' => HostingBillingInvoice::STATUS_PENDING,
|
|
],
|
|
[
|
|
'user_id' => $account->user_id,
|
|
'currency' => 'GHS',
|
|
'subtotal_amount' => $summary['extra_total'],
|
|
'credit_amount' => 0,
|
|
'total_amount' => $summary['extra_total'],
|
|
'description' => sprintf('Extra mailbox charges for %s', $account->primary_domain ?: $account->username),
|
|
'line_items' => [[
|
|
'code' => 'extra_emails',
|
|
'label' => 'Extra Emails',
|
|
'quantity' => $summary['extra_count'],
|
|
'unit_amount' => self::EXTRA_MAILBOX_PRICE_CEDIS,
|
|
'amount' => $summary['extra_total'],
|
|
'description' => $summary['breakdown'],
|
|
]],
|
|
'metadata' => [
|
|
'billing_period' => now()->format('Y-m'),
|
|
'extra_mailboxes' => $summary['extra_count'],
|
|
],
|
|
]
|
|
);
|
|
}
|
|
}
|