Deploy Ladill Hosting / deploy (push) Failing after 17s
Shared web hosting extracted from the platform monolith, with CI deploy to /var/www/ladill-hosting matching Bird/Domains/Email. Co-authored-by: Cursor <cursoragent@cursor.com>
286 lines
8.8 KiB
PHP
286 lines
8.8 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Domain;
|
|
|
|
use App\Models\DomainPricing;
|
|
use App\Services\Currency\CurrencyConverterService;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class DomainPricingService
|
|
{
|
|
private const TLD_PRICING_CACHE_KEY = 'dynadot:tld_pricing';
|
|
private const LIVE_TLD_PRICING_CACHE_KEY = 'dynadot:tld_pricing:live';
|
|
private const TLD_PRICING_CACHE_TTL_HOURS = 6;
|
|
|
|
public function __construct(
|
|
private DynadotService $dynadot,
|
|
private CurrencyConverterService $currency,
|
|
) {}
|
|
|
|
/**
|
|
* Get the markup percentage from config.
|
|
*/
|
|
public function getMarkupPercent(): float
|
|
{
|
|
return (float) config('mailinfra.dynadot_price_markup_percent', 35);
|
|
}
|
|
|
|
/**
|
|
* Get pricing for a specific TLD in GHS with markup.
|
|
*
|
|
* @return array{register: int, renew: int, transfer: int, currency: string, usd_rate: float}|null
|
|
*/
|
|
public function getPricingForTld(string $tld): ?array
|
|
{
|
|
$tld = ltrim(strtolower(trim($tld)), '.');
|
|
$allPricing = $this->getAllTldPricing();
|
|
|
|
if (! isset($allPricing[$tld])) {
|
|
return null;
|
|
}
|
|
|
|
return $allPricing[$tld];
|
|
}
|
|
|
|
/**
|
|
* Get pricing for multiple TLDs.
|
|
*
|
|
* @return array<string, array{register: int, renew: int, transfer: int, currency: string}>
|
|
*/
|
|
public function getPricingForTlds(array $tlds): array
|
|
{
|
|
$allPricing = $this->getAllTldPricing();
|
|
$result = [];
|
|
|
|
foreach ($tlds as $tld) {
|
|
$tld = ltrim(strtolower(trim($tld)), '.');
|
|
if (isset($allPricing[$tld])) {
|
|
$result[$tld] = $allPricing[$tld];
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Get only TLDs that currently have live Dynadot pricing.
|
|
*
|
|
* @return list<string>
|
|
*/
|
|
public function getLivePricedTlds(): array
|
|
{
|
|
return array_keys($this->getLiveDynadotPricing());
|
|
}
|
|
|
|
/**
|
|
* Get all TLD pricing with conversion and markup applied.
|
|
* Price overrides from DomainPricing model take precedence.
|
|
*
|
|
* @return array<string, array{register: int, renew: int, transfer: int, currency: string, usd_rate: float, is_override: bool}>
|
|
*/
|
|
public function getAllTldPricing(): array
|
|
{
|
|
$cacheKey = self::TLD_PRICING_CACHE_KEY . ':ghs';
|
|
|
|
return Cache::remember($cacheKey, now()->addHours(self::TLD_PRICING_CACHE_TTL_HOURS), function () {
|
|
$usdPricing = $this->fetchDynadotPricing();
|
|
$overrides = DomainPricing::getOverrides();
|
|
|
|
if (empty($usdPricing) && empty($overrides)) {
|
|
return [];
|
|
}
|
|
|
|
$markup = $this->getMarkupPercent();
|
|
$usdRate = $this->currency->getUsdToGhsRate();
|
|
$converted = [];
|
|
|
|
// First, add all Dynadot prices with conversion
|
|
foreach ($usdPricing as $tld => $prices) {
|
|
$converted[$tld] = [
|
|
'register' => $this->convertPrice($prices['register'] ?? 0, $markup),
|
|
'renew' => $this->convertPrice($prices['renew'] ?? 0, $markup),
|
|
'transfer' => $this->convertPrice($prices['transfer'] ?? 0, $markup),
|
|
'currency' => 'GHS',
|
|
'usd_rate' => $usdRate,
|
|
'usd_register' => $prices['register'] ?? 0,
|
|
'usd_renew' => $prices['renew'] ?? 0,
|
|
'usd_transfer' => $prices['transfer'] ?? 0,
|
|
'is_override' => false,
|
|
];
|
|
}
|
|
|
|
// Apply overrides (manual GHS prices take precedence)
|
|
foreach ($overrides as $tld => $override) {
|
|
$converted[$tld] = [
|
|
'register' => $override['register'],
|
|
'renew' => $override['renew'],
|
|
'transfer' => $override['transfer'],
|
|
'currency' => 'GHS',
|
|
'usd_rate' => $usdRate,
|
|
'usd_register' => null,
|
|
'usd_renew' => null,
|
|
'usd_transfer' => null,
|
|
'is_override' => true,
|
|
];
|
|
}
|
|
|
|
return $converted;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Convert USD cents to GHS pesewas with markup.
|
|
*/
|
|
private function convertPrice(int $usdCents, float $markupPercent): int
|
|
{
|
|
return $this->currency->convertCentsTopesewaswithMarkup($usdCents, $markupPercent);
|
|
}
|
|
|
|
/**
|
|
* Fetch TLD pricing from Dynadot API.
|
|
*
|
|
* @return array<string, array{register: int, renew: int, transfer: int}>
|
|
*/
|
|
private function fetchDynadotPricing(): array
|
|
{
|
|
$livePricing = $this->getLiveDynadotPricing();
|
|
|
|
if ($livePricing !== []) {
|
|
return $livePricing;
|
|
}
|
|
|
|
return $this->getFallbackPricing();
|
|
}
|
|
|
|
/**
|
|
* Fallback pricing in USD cents for common TLDs.
|
|
*/
|
|
private function getFallbackPricing(): array
|
|
{
|
|
return [
|
|
'com' => ['register' => 1099, 'renew' => 1099, 'transfer' => 1099],
|
|
'net' => ['register' => 1199, 'renew' => 1199, 'transfer' => 1199],
|
|
'org' => ['register' => 1099, 'renew' => 1099, 'transfer' => 1099],
|
|
'io' => ['register' => 3999, 'renew' => 3999, 'transfer' => 3999],
|
|
'co' => ['register' => 2999, 'renew' => 2999, 'transfer' => 2999],
|
|
'info' => ['register' => 1899, 'renew' => 1899, 'transfer' => 1899],
|
|
'biz' => ['register' => 1699, 'renew' => 1699, 'transfer' => 1699],
|
|
'xyz' => ['register' => 1299, 'renew' => 1299, 'transfer' => 1299],
|
|
'online' => ['register' => 2999, 'renew' => 2999, 'transfer' => 2999],
|
|
'store' => ['register' => 4999, 'renew' => 4999, 'transfer' => 4999],
|
|
'tech' => ['register' => 4999, 'renew' => 4999, 'transfer' => 4999],
|
|
'site' => ['register' => 2999, 'renew' => 2999, 'transfer' => 2999],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Fetch raw live TLD pricing from Dynadot without fallback expansion.
|
|
*
|
|
* @return array<string, array{register: int, renew: int, transfer: int}>
|
|
*/
|
|
private function getLiveDynadotPricing(): array
|
|
{
|
|
return Cache::remember(self::LIVE_TLD_PRICING_CACHE_KEY, now()->addHours(self::TLD_PRICING_CACHE_TTL_HOURS), function () {
|
|
if (! $this->dynadot->isConfigured()) {
|
|
Log::warning('DomainPricingService: Dynadot not configured');
|
|
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
$result = $this->dynadot->getTldPricing();
|
|
|
|
if (! ($result['success'] ?? false) || empty($result['pricing'])) {
|
|
Log::warning('DomainPricingService: Failed to fetch Dynadot pricing', $result);
|
|
|
|
return [];
|
|
}
|
|
|
|
return $result['pricing'];
|
|
} catch (\Throwable $e) {
|
|
Log::error('DomainPricingService: Exception fetching pricing', ['error' => $e->getMessage()]);
|
|
|
|
return [];
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Format price for display.
|
|
*/
|
|
public function formatPrice(int $pesewas): string
|
|
{
|
|
return 'GH₵ ' . number_format($pesewas / 100, 2);
|
|
}
|
|
|
|
public function convertRegistrarAmountToGhsMinor(int $amountMinor, string $currency): ?int
|
|
{
|
|
$currency = strtoupper(trim($currency));
|
|
|
|
if ($currency === 'GHS') {
|
|
return $amountMinor;
|
|
}
|
|
|
|
if ($currency === 'USD') {
|
|
return $this->convertPrice($amountMinor, $this->getMarkupPercent());
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get price info for a domain (for checkout/cart).
|
|
*/
|
|
public function getDomainPrice(string $domain, string $action = 'register'): ?array
|
|
{
|
|
$parts = explode('.', strtolower($domain), 2);
|
|
|
|
if (count($parts) < 2) {
|
|
return null;
|
|
}
|
|
|
|
$tld = $parts[1];
|
|
$pricing = $this->getPricingForTld($tld);
|
|
|
|
if (! $pricing) {
|
|
return null;
|
|
}
|
|
|
|
$priceKey = match ($action) {
|
|
'renew' => 'renew',
|
|
'transfer' => 'transfer',
|
|
default => 'register',
|
|
};
|
|
|
|
return [
|
|
'domain' => $domain,
|
|
'tld' => $tld,
|
|
'action' => $action,
|
|
'amount_minor' => $pricing[$priceKey],
|
|
'currency' => 'GHS',
|
|
'price_label' => $this->formatPrice($pricing[$priceKey]),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Clear cached pricing.
|
|
*/
|
|
public function clearCache(): void
|
|
{
|
|
Cache::forget(self::TLD_PRICING_CACHE_KEY . ':ghs');
|
|
Cache::forget(self::LIVE_TLD_PRICING_CACHE_KEY);
|
|
}
|
|
|
|
/**
|
|
* Get current exchange rate info.
|
|
*/
|
|
public function getExchangeRateInfo(): array
|
|
{
|
|
return [
|
|
...$this->currency->getRateInfo(),
|
|
'markup_percent' => $this->getMarkupPercent(),
|
|
];
|
|
}
|
|
}
|