Major additions: - Knowledge base with categories, articles, revisions, and voting - Enhanced ticket system: departments, SLA policies, canned responses, tags, custom fields, satisfaction ratings, internal notes - Multi-currency support with exchange rate sync - Shopping cart and quote system with PDF generation - Affiliate program with referrals, commissions, and payouts - Account credits, credit notes, and debit notes - Staff management with granular role-based permissions - Fraud detection and order risk assessment - ServerHunter SEO integration - Service lifecycle events (suspend/unsuspend/terminate) - Service management panels for VPS, Dedicated, Hosting, and Game servers - Plan lifecycle fields and per-customer overrides - 30+ migrations, 17 factories, 8 feature test suites Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
84 lines
2.5 KiB
PHP
84 lines
2.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\Currency;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class SyncExchangeRatesCommand extends Command
|
|
{
|
|
protected $signature = 'currencies:sync-rates';
|
|
|
|
protected $description = 'Sync exchange rates from exchangerate.host API';
|
|
|
|
public function handle(): int
|
|
{
|
|
$baseCurrency = Currency::query()->base()->first();
|
|
|
|
if (! $baseCurrency) {
|
|
$this->error('No base currency configured.');
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$this->info("Syncing exchange rates relative to {$baseCurrency->code}...");
|
|
|
|
try {
|
|
$response = Http::timeout(15)->get('https://api.exchangerate.host/latest', [
|
|
'base' => $baseCurrency->code,
|
|
]);
|
|
|
|
if (! $response->successful()) {
|
|
$this->error('Failed to fetch exchange rates: HTTP '.$response->status());
|
|
Log::error('Exchange rate sync failed', [
|
|
'status' => $response->status(),
|
|
'body' => $response->body(),
|
|
]);
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$data = $response->json();
|
|
$rates = $data['rates'] ?? [];
|
|
|
|
if (empty($rates)) {
|
|
$this->warn('No rates returned from API.');
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$currencies = Currency::query()->where('is_base', false)->get();
|
|
$updated = 0;
|
|
|
|
foreach ($currencies as $currency) {
|
|
if (isset($rates[$currency->code])) {
|
|
$currency->update([
|
|
'exchange_rate' => $rates[$currency->code],
|
|
'last_synced_at' => now(),
|
|
]);
|
|
$updated++;
|
|
$this->line(" {$currency->code}: {$rates[$currency->code]}");
|
|
}
|
|
}
|
|
|
|
// Clear currency cache
|
|
Cache::forget('currencies:enabled');
|
|
Cache::forget('currencies:base');
|
|
|
|
$this->info("Updated {$updated} exchange rates.");
|
|
|
|
return self::SUCCESS;
|
|
} catch (\Throwable $e) {
|
|
$this->error('Exchange rate sync error: '.$e->getMessage());
|
|
Log::error('Exchange rate sync exception', ['error' => $e->getMessage()]);
|
|
|
|
return self::FAILURE;
|
|
}
|
|
}
|
|
}
|