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
This commit is contained in:
142
app/Console/Commands/Payment/SyncNonRecurringSubscriptions.php
Normal file
142
app/Console/Commands/Payment/SyncNonRecurringSubscriptions.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?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,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user