- Add comprehensive OxaPay payment provider with invoice creation, webhook processing, and subscription status sync - Implement conditional payload fields (to_currency, callback_url) based on configuration - Create universal sync command for all non-recurring payment providers - Add subscription model fields for payment tracking - Implement proper status mapping between OxaPay and Laravel subscription states - Add webhook signature validation using HMAC SHA512
143 lines
4.8 KiB
PHP
143 lines
4.8 KiB
PHP
<?php
|
||
|
||
namespace App\Console\Commands\Payment;
|
||
|
||
use App\Models\PaymentProvider;
|
||
use App\Models\Subscription;
|
||
use Illuminate\Console\Command;
|
||
use Illuminate\Support\Facades\Log;
|
||
|
||
class SyncNonRecurringSubscriptions extends Command
|
||
{
|
||
/**
|
||
* The name and signature of the console command.
|
||
*
|
||
* @var string
|
||
*/
|
||
protected $signature = 'payment:sync-non-recurring-subscriptions {--dry-run}';
|
||
|
||
/**
|
||
* The console command description.
|
||
*
|
||
* @var string
|
||
*/
|
||
protected $description = 'Sync status of subscriptions for non-recurring payment providers (activation_key, oxapay, etc.) based on ends_at timestamp';
|
||
|
||
/**
|
||
* Execute the console command.
|
||
*/
|
||
public function handle()
|
||
{
|
||
$dryRun = $this->option('dry-run');
|
||
|
||
$this->info('🔄 Syncing non-recurring payment provider subscriptions...');
|
||
|
||
if ($dryRun) {
|
||
$this->warn('📋 DRY RUN MODE - No actual updates will be made');
|
||
}
|
||
|
||
// Get all active non-recurring payment providers
|
||
$nonRecurringProviders = PaymentProvider::where('is_active', true)
|
||
->where('supports_recurring', false)
|
||
->where('supports_one_time', true)
|
||
->pluck('name')
|
||
->toArray();
|
||
|
||
$this->info('📋 Non-recurring providers found: '.implode(', ', $nonRecurringProviders));
|
||
|
||
$totalProcessed = 0;
|
||
$totalExpired = 0;
|
||
|
||
foreach ($nonRecurringProviders as $providerName) {
|
||
$this->info("🔍 Processing provider: {$providerName}");
|
||
|
||
$result = $this->syncProviderSubscriptions($providerName, $dryRun);
|
||
$totalProcessed += $result['processed'];
|
||
$totalExpired += $result['expired'];
|
||
}
|
||
|
||
$this->info('✅ Sync completed!');
|
||
$this->info("📊 Total subscriptions processed: {$totalProcessed}");
|
||
$this->info("⏰ Total subscriptions expired: {$totalExpired}");
|
||
|
||
if (! $dryRun && $totalExpired > 0) {
|
||
$this->info('💡 Tip: Set up a cron job to run this command every few hours:');
|
||
$this->info(' */4 * * * * php artisan app:sync-non-recurring-subscriptions');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Sync subscriptions for a specific provider
|
||
*/
|
||
protected function syncProviderSubscriptions(string $providerName, bool $dryRun): array
|
||
{
|
||
$query = Subscription::where('provider', $providerName)
|
||
->where('status', '!=', 'expired')
|
||
->where('status', '!=', 'cancelled')
|
||
->whereNotNull('ends_at');
|
||
|
||
if ($dryRun) {
|
||
$subscriptions = $query->get();
|
||
$expiredCount = $subscriptions->filter(function ($sub) {
|
||
return $sub->ends_at->isPast();
|
||
})->count();
|
||
|
||
$this->line(" 📊 Found {$subscriptions->count()} subscriptions");
|
||
$this->line(" ⏰ Would expire {$expiredCount} subscriptions");
|
||
|
||
return [
|
||
'processed' => $subscriptions->count(),
|
||
'expired' => $expiredCount,
|
||
];
|
||
}
|
||
|
||
// Get subscriptions that should be expired
|
||
$expiredSubscriptions = $query->where('ends_at', '<', now())->get();
|
||
|
||
$expiredCount = 0;
|
||
foreach ($expiredSubscriptions as $subscription) {
|
||
try {
|
||
$subscription->update([
|
||
'status' => 'expired',
|
||
'unified_status' => 'expired',
|
||
'updated_at' => now(),
|
||
]);
|
||
|
||
$expiredCount++;
|
||
|
||
$this->line(" ✅ Expired subscription #{$subscription->id} (User: {$subscription->user_id})");
|
||
|
||
Log::info('Non-recurring subscription expired via sync command', [
|
||
'subscription_id' => $subscription->id,
|
||
'provider' => $providerName,
|
||
'user_id' => $subscription->user_id,
|
||
'ends_at' => $subscription->ends_at,
|
||
'command' => 'app:sync-non-recurring-subscriptions',
|
||
]);
|
||
|
||
} catch (\Exception $e) {
|
||
$this->error(" ❌ Failed to expire subscription #{$subscription->id}: {$e->getMessage()}");
|
||
|
||
Log::error('Failed to expire non-recurring subscription', [
|
||
'subscription_id' => $subscription->id,
|
||
'provider' => $providerName,
|
||
'error' => $e->getMessage(),
|
||
]);
|
||
}
|
||
}
|
||
|
||
$totalProcessed = $query->count();
|
||
|
||
if ($expiredCount > 0) {
|
||
$this->info(" ✅ Expired {$expiredCount} subscriptions for {$providerName}");
|
||
} else {
|
||
$this->info(" ℹ️ No expired subscriptions found for {$providerName}");
|
||
}
|
||
|
||
return [
|
||
'processed' => $totalProcessed,
|
||
'expired' => $expiredCount,
|
||
];
|
||
}
|
||
}
|