UnknownSec Bypass
403
:
/
mnt
/
lmsestudio-instance-vol002
/
lms_e9b384227ffb
/
app
/
Console
/
Commands
/ [
drwxr-xr-x
]
Menu
Upload
Mass depes
Mass delete
Terminal
Info server
About
name :
RecurringCreditCard.php
<?php namespace EstudioLMS\Console\Commands; use Carbon\Carbon; use EstudioLMS\Cart\Cart; use EstudioLMS\Events\RecurringNotification; use EstudioLMS\Repositories\Financial\RecurringInterface; use EstudioLMS\Repositories\Subscription\PeriodicityInterface; use EstudioLMS\Services\Admin\ConfigurationServices; use EstudioLMS\Services\PagarMeService; use EstudioLMS\Helpers\Helpers; use Illuminate\Console\Command; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Event; /** * Class RecurringCreditCard * @package EstudioLMS\Console\Commands */ class RecurringCreditCard extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'recurring:credit-card'; /** * The console command description. * * @var string */ protected $description = 'Processa as recorrências das assinaturas com forma de pagamento cartão de crédito (API v5)'; /** * @var RecurringInterface */ private $recurring; /** * @var PeriodicityInterface */ private $periodicity; /** * @var PagarMeService */ private $pagarMeService; /** * @var ConfigurationServices */ private $configurationServices; /** * Create a new command instance. * * @param RecurringInterface $recurring * @param PeriodicityInterface $periodicity * @param PagarMeService $pagarMeService * @param ConfigurationServices $configurationServices */ public function __construct( RecurringInterface $recurring, PeriodicityInterface $periodicity, PagarMeService $pagarMeService, ConfigurationServices $configurationServices ) { parent::__construct(); $this->recurring = $recurring; $this->periodicity = $periodicity; $this->pagarMeService = $pagarMeService; $this->configurationServices = $configurationServices; } /** * Execute the console command. * * Os seguintes status serão considerados, para o processamento das recorrências. * 0 - Registro sem processamento, deve ser feita a cobrança; * 1 - Registro processado; * 2 - Houve falha na cobrança - Cartão de Crédito - Será cobrado novamente em X dias; * 3 - Boleto emitido e ainda não pago; * 4 - Cancelamento solicitado. Ao invés de cobrar, deve marcar a assinatura como cancelada; * 5 - Registro de cancelamento processado; * 6 - Cancelado por falta de pagamento; * * @return void */ public function handle() { $paymentType = 'credit_card'; $today = Carbon::now()->format('Y-m-d'); $tomorrow = Carbon::now()->addDay()->format('Y-m-d'); Log::info('Iniciando processamento de recorrências de cartão de crédito - API v5', [ 'date' => $today, 'payment_type' => $paymentType ]); $recurrings = $this->recurring->getDueRecurrences($paymentType); //dd($recurrings); Log::info('Recorrências encontradas para processamento', [ 'count' => (is_array($recurrings) || $recurrings instanceof \Countable) ? count($recurrings) : 0 ]); foreach ($recurrings as $key => $recurring) { try { // Verificar se é o dia correto para processar if ($recurring->next_attempt_date != $today || $recurring->payment_type != $paymentType) { Log::info('Recorrência fora do agendamento', [ 'recurring_id' => $recurring->id, 'next_attempt_date' => $recurring->next_attempt_date, 'today' => $today, 'payment_type' => $recurring->payment_type ]); continue; } Log::info('Processando recorrência de cartão', [ 'recurring_id' => $recurring->id, 'user_id' => $recurring->user_id, 'subscription_hash' => $recurring->subscription_hash, 'gross_amount' => $recurring->gross_amount, 'card_id' => $recurring->card_id, 'attempts' => $recurring->attempts ]); // Validações básicas if (!$recurring->hire_subscription) { Log::error('HireSubscription não encontrada para recurring', ['recurring_id' => $recurring->id]); continue; } if (!$recurring->hire_subscription->subscription) { Log::error('Subscription não encontrada para recurring', ['recurring_id' => $recurring->id]); continue; } // Validar se o valor é positivo if ($recurring->gross_amount <= 0) { Log::error('Valor inválido para recorrência', [ 'recurring_id' => $recurring->id, 'gross_amount' => $recurring->gross_amount ]); continue; } // Validar se existe card_id if (!$recurring->card_id) { Log::error('Card ID não encontrado para recorrência', [ 'recurring_id' => $recurring->id ]); continue; } $attempt = $recurring->attempts + 1; $periodicity = $this->periodicity->find($recurring->hire_subscription->periodicity_id); if (!$periodicity) { Log::error('Periodicidade não encontrada', [ 'recurring_id' => $recurring->id, 'periodicity_id' => $recurring->hire_subscription->periodicity_id ]); continue; } // Montar Cart com validações $cart = new Cart(); $cart->addItem( $recurring->hire_subscription->subscription_id, // course_id (mapeamento correto) $recurring->hire_subscription->subscription->title, $recurring->hire_subscription->subscription_id, // plan_id $periodicity, $recurring->gross_amount, // price null, // image 0.00, // discount 0.00, // extra_amount 0, // shipping_code 0.00, // shipping_price true // subscription flag ); // Log do estado do cart antes do processamento Log::info('Estado do cart para recorrência de cartão', [ 'recurring_id' => $recurring->id, 'cart_data' => $cart->getCart(), 'gross_amount' => $cart->getGrossAmount(), 'discount_amount' => $cart->getDiscountAmount(), 'shipping_amount' => $cart->getShippingAmount(), 'calculated_price' => ($cart->getGrossAmount() - $cart->getDiscountAmount()) + $cart->getShippingAmount() ]); // Obter configurações para soft descriptor $config = $this->configurationServices->configuration(); $softDescriptor = Helpers::sanitizeSoftDescriptor($config->site_name); // CORREÇÃO: Usar cardSubscriptionTransaction (API v5) em vez de cardTransaction (API v4) $card = $this->pagarMeService->cardSubscriptionTransaction( $recurring->card_id, $cart, $recurring->subscription_hash, $recurring->user_id, $softDescriptor ); if (!$card) { Log::error('Falha na criação da transação de cartão', ['recurring_id' => $recurring->id]); continue; } // CRÍTICO: Obter ORDER_ID (or_*) obrigatoriamente - NÃO usar charge_id $orderId = null; $chargeId = null; if (is_array($card)) { $orderId = $card['order']['id'] ?? $card['order_id'] ?? null; $chargeId = $card['id'] ?? $card['charge_id'] ?? null; } else { // Para objetos mock retornados pelo cardSubscriptionTransaction $chargeId = method_exists($card, 'getId') ? $card->getId() : null; $orderId = method_exists($card, 'getOrderId') ? $card->getOrderId() : null; } // NOVA REGRA: ORDER_ID é OBRIGATÓRIO para recorrências if (!$orderId) { Log::error('Order ID não encontrado - recorrência não pode ser processada', [ 'recurring_id' => $recurring->id, 'charge_id' => $chargeId, 'card_response_type' => is_array($card) ? 'array' : 'object' ]); continue; } $paymentCode = $orderId; // SEMPRE usar order_id if (!$paymentCode) { Log::error('Nem order_id nem charge_id encontrados na resposta do cartão', [ 'recurring_id' => $recurring->id, 'card_response' => is_array($card) ? $card : 'object' ]); continue; } Log::info('IDs extraídos do cartão', [ 'recurring_id' => $recurring->id, 'order_id' => $orderId, 'charge_id' => $chargeId, 'payment_code_usado' => $paymentCode ]); // Verificar status da transação $cardStatus = is_array($card) ? ($card['status'] ?? 'unknown') : (method_exists($card, 'getStatus') ? $card->getStatus() : 'unknown'); Log::info('Status da transação de cartão', [ 'recurring_id' => $recurring->id, 'payment_code' => $paymentCode, 'order_id' => $orderId, 'charge_id' => $chargeId, 'status' => $cardStatus, 'attempt' => $attempt ]); // Processar baseado no status if ($cardStatus !== 'paid') { // Transação falhou $this->handleFailedTransaction($recurring, $card, $paymentCode, $attempt, $tomorrow); } else { // Transação aprovada $this->handleSuccessfulTransaction($recurring, $card, $paymentCode, $attempt, $today); } // Output para console $this->info("Recorrência processada ID: {$recurring->id} - Status: {$cardStatus} - Tentativa: {$attempt}"); } catch (\Exception $e) { Log::error('Erro ao processar recorrência de cartão', [ 'recurring_id' => $recurring->id ?? 'N/A', 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); $this->error("Erro ao processar recorrência ID: " . ($recurring->id ?? 'N/A') . " - " . $e->getMessage()); // Continuar com a próxima recorrência em caso de erro continue; } } // Log final de execução $recurringCount = (is_array($recurrings) || $recurrings instanceof \Countable) ? count($recurrings) : 0; Log::useDailyFiles(storage_path() . '/logs/command.log'); Log::alert('recurring:credit-card - Executado com API v5 - ' . date('Y-m-d H:i:s') . ' - Processadas: ' . $recurringCount); $this->info("Comando concluído. Processadas " . $recurringCount . " recorrências."); } /** * Processa transação que falhou */ private function handleFailedTransaction($recurring, $card, $paymentCode, $attempt, $tomorrow) { Log::info('Processando transação que falhou', [ 'recurring_id' => $recurring->id, 'attempt' => $attempt, 'payment_code' => $paymentCode ]); if ($attempt < 10) { // Tentativas 1-9: Reagendar para próximo dia $recurring->status = 2; $recurring->attempts = $attempt; $recurring->payment_code = $paymentCode; $recurring->next_attempt_date = $tomorrow; $recurring->save(); Event::fire(new RecurringNotification('credit-card-failed', $card)); Log::info('Recorrência reagendada', [ 'recurring_id' => $recurring->id, 'next_attempt' => $tomorrow ]); } elseif ($attempt >= 10 && $attempt < 15) { // Tentativas 10-14: Suspender assinatura $recurring->status = 2; $recurring->attempts = $attempt; $recurring->payment_code = $paymentCode; $recurring->next_attempt_date = $tomorrow; $recurring->save(); $recurring->hire_subscription->status = 3; // Suspensa $recurring->hire_subscription->save(); Event::fire(new RecurringNotification('credit-card-suspended', $card)); Log::warning('Assinatura suspensa por falhas recorrentes', [ 'recurring_id' => $recurring->id, 'subscription_hash' => $recurring->subscription_hash ]); } else { // Tentativas 15+: Cancelar assinatura $recurring->status = 6; // Cancelado por falta de pagamento $recurring->attempts = $attempt; $recurring->payment_code = $paymentCode; $recurring->save(); $recurring->hire_subscription->status = 4; // Cancelada $recurring->hire_subscription->save(); Event::fire(new RecurringNotification('credit-card-canceled', $card)); Log::error('Assinatura cancelada por excesso de falhas', [ 'recurring_id' => $recurring->id, 'subscription_hash' => $recurring->subscription_hash, 'total_attempts' => $attempt ]); } } /** * Processa transação bem-sucedida */ private function handleSuccessfulTransaction($recurring, $card, $paymentCode, $attempt, $today) { Log::info('Processando transação bem-sucedida', [ 'recurring_id' => $recurring->id, 'attempt' => $attempt, 'payment_code' => $paymentCode ]); // 1. Atualizar recorrência atual como paga $recurring->status = 1; // Processado $recurring->attempts = $attempt; $recurring->payment_code = $paymentCode; $recurring->payment_date = $today; $recurring->save(); Log::info('Recorrência atual marcada como paga', [ 'recurring_id' => $recurring->id, 'payment_code' => $paymentCode, 'payment_date' => $today ]); // 2. CRÍTICO: Atualizar payment_code na tabela hire_subscriptions // para manter histórico da última transação bem-sucedida try { $hireSubscription = $recurring->hire_subscription; // Fallback: buscar diretamente se relacionamento não estiver carregado if (!$hireSubscription) { $hireSubscription = app('EstudioLMS\Repositories\Financial\HireSubscriptionInterface') ->findByField('subscription_hash', $recurring->subscription_hash)->first(); } if ($hireSubscription) { $hireSubscription->payment_code = $paymentCode; $hireSubscription->save(); Log::info('✅ Payment_code atualizado na assinatura', [ 'subscription_id' => $hireSubscription->id, 'subscription_hash' => $recurring->subscription_hash, 'old_payment_code' => $hireSubscription->getOriginal('payment_code'), 'new_payment_code' => $paymentCode ]); } else { Log::error('❌ HireSubscription não encontrada para atualizar payment_code', [ 'recurring_id' => $recurring->id, 'subscription_hash' => $recurring->subscription_hash ]); } } catch (\Exception $e) { Log::error('Erro ao atualizar payment_code na assinatura', [ 'recurring_id' => $recurring->id, 'subscription_hash' => $recurring->subscription_hash, 'error' => $e->getMessage() ]); } // 2. CRÍTICO: Criar próxima recorrência em tempo real try { $this->createNextRecurrence($recurring, $card); Log::info('✅ Próxima recorrência criada com sucesso', [ 'current_recurring_id' => $recurring->id, 'subscription_hash' => $recurring->subscription_hash ]); } catch (\Exception $e) { Log::error('❌ Erro ao criar próxima recorrência', [ 'recurring_id' => $recurring->id, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); // Não falhar o comando se a próxima recorrência falhar } Event::fire(new RecurringNotification('credit-card', $card)); Log::info('Transação de recorrência concluída com sucesso', [ 'recurring_id' => $recurring->id, 'payment_code' => $paymentCode ]); } /** * Cria a próxima recorrência baseada na atual */ private function createNextRecurrence($currentRecurrence, $card) { $periodicity = $this->periodicity->find($currentRecurrence->hire_subscription->periodicity_id); if (!$periodicity) { throw new \Exception('Periodicidade não encontrada para criar próxima recorrência'); } // Calcular próxima data de vencimento $currentDueDate = Carbon::parse($currentRecurrence->due_date); switch ($periodicity->periodicity) { case 30: $nextDueDate = $currentDueDate->addMonth(); break; case 90: $nextDueDate = $currentDueDate->addMonth(3); break; case 180: $nextDueDate = $currentDueDate->addMonth(6); break; case 365: $nextDueDate = $currentDueDate->addYear(); break; default: throw new \Exception('Periodicidade inválida: ' . $periodicity->periodicity); } // Verificar se já existe recorrência para esta data $existingNext = $this->recurring->findWhere([ ['subscription_hash', '=', $currentRecurrence->subscription_hash], ['due_date', '=', $nextDueDate->format('Y-m-d')] ])->first(); if ($existingNext) { Log::info('Próxima recorrência já existe', [ 'existing_id' => $existingNext->id, 'due_date' => $nextDueDate->format('Y-m-d') ]); return $existingNext; } // Criar próxima recorrência $nextRecurrenceData = [ 'user_id' => $currentRecurrence->user_id, 'subscription_hash' => $currentRecurrence->subscription_hash, 'gateway_id' => $currentRecurrence->gateway_id, 'due_date' => $nextDueDate->format('Y-m-d'), 'next_attempt_date' => $nextDueDate->format('Y-m-d'), 'payment_type' => $currentRecurrence->payment_type, 'card_id' => $currentRecurrence->card_id, 'payment_date' => null, 'attempts' => 0, 'gross_amount' => $currentRecurrence->gross_amount, 'discount_amount' => $currentRecurrence->discount_amount, 'fee_amount' => 0.00, // Será calculado quando processado 'extra_amount' => $currentRecurrence->extra_amount, 'net_amount' => 0.00, // Será calculado quando processado 'charge_number' => $currentRecurrence->charge_number + 1, 'status' => 0, // Pendente 'payment_code' => null // Será definido quando processado ]; $nextRecurrence = $this->recurring->create($nextRecurrenceData); Log::info('Nova recorrência criada', [ 'next_recurring_id' => $nextRecurrence->id, 'due_date' => $nextDueDate->format('Y-m-d'), 'charge_number' => $nextRecurrenceData['charge_number'] ]); return $nextRecurrence; } }
Copyright © 2026 - UnknownSec