Files
zemailnator/app/Console/Commands/Payment/SyncNonRecurringSubscriptions.php
idevakk 9a32511e97 feat(payment): implement OxaPay provider and non-recurring subscription sync
- 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
2025-12-07 09:01:53 -08:00

143 lines
4.8 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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,
];
}
}