HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/ai.naniguide.com/app/Http/Controllers/Refactor/SubscriptionController.php
<?php

namespace App\Http\Controllers\Refactor;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Model\Plan;
use App\Library\Facades\Billing;
use App\Library\Subscription\InvoiceStage;
use App\Model\Invoice;
use App\Model\PaymentMethod;
use App\Library\Facades\Hook;
use App\Model\PlanRemoteMapping;
use App\Model\PaymentGateway;

class SubscriptionController extends Controller
{
    protected function findOwnedInvoice(Request $request, string $invoiceUid): ?Invoice
    {
        $customer = $request->user()->customer;

        return $customer->invoices()->where('invoices.uid', $invoiceUid)->first();
    }

    protected function findOwnedPaymentMethod(Request $request, string $paymentMethodUid): ?PaymentMethod
    {
        $customer = $request->user()->customer;

        return $customer->paymentMethods()->where('payment_methods.uid', $paymentMethodUid)->first();
    }

    public function selectPlan(Request $request)
    {
        if (!$request->user()->customer->can('updateProfile', $request->user()->customer)) {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;
        $subscription = $customer->getNewOrActiveSubscription();

        return view('refactor.pages.subscription.select_plan', [
            'plans' => Plan::getAvailablePlans(),
            'subscription' => $subscription,
            'lastEndedSub' => $customer->getLastCancelledOrEndedSubscription(),
        ]);
    }

    public function assignPlan(Request $request)
    {
        if (!$request->user()->customer->can('updateProfile', $request->user()->customer)) {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;
        $plan = Plan::findByUid($request->plan_uid);

        if (!$plan) {
            return redirect()->route('refactor.subscription.select_plan')
                ->with('alert-error', 'Plan not found.');
        }

        // Already has active subscription — cannot assign new plan
        if ($customer->getCurrentActiveSubscription()) {
            return redirect()->route('refactor.account.subscription')
                ->with('alert-error', 'You already have an active subscription.');
        }

        $subscription = app(\App\Services\Subscription\SubscriptionManagementService::class)
            ->replacePendingPlan($customer, $plan);
        $invoice = $subscription->getItsOnlyUnpaidInitOrder()->getItsOnlyInvoice();

        return redirect()->away(InvoiceStage::urlFor($invoice));
    }

    public function billingInformation(Request $request)
    {
        $customer = $request->user()->customer;
        $invoice = $customer->invoices()->where('invoices.uid', '=', $request->invoice_uid)->first();
        $billingAddress = $customer->getDefaultBillingAddress();

        if (!$invoice) {
            return redirect()->route('refactor.account.subscription')
                ->with('alert-warning', 'Invoice not found.');
        }

        // Customer may revisit billing form even after billing is filled (re-edit).
        // Redirect away only if invoice is at a stage where billing edits don't apply.
        if ($r = InvoiceStage::ensureIn(
            [InvoiceStage::NEEDS_BILLING, InvoiceStage::PAYMENT],
            $invoice
        )) return $r;

        if ($request->isMethod('post')) {
            $validator = $invoice->updateBillingInformation($request->all());

            if ($validator->fails()) {
                return response()->view('refactor.pages.subscription.billing_information', [
                    'invoice' => $invoice,
                    'billingAddress' => $billingAddress,
                    'errors' => $validator->errors(),
                ], 400);
            }

            $customer->updateBillingInformationFromInvoice($invoice);

            return redirect()->route('refactor.subscription.payment', [
                'invoice_uid' => $invoice->uid,
            ])->with('alert-success', trans('messages.billing_address.updated'));
        }

        return view('refactor.pages.subscription.billing_information', [
            'invoice' => $invoice,
            'billingAddress' => $billingAddress,
        ]);
    }

    public function payment(Request $request)
    {
        if (!$request->user()->customer->can('updateProfile', $request->user()->customer)) {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;
        $invoice = $customer->invoices()->where('invoices.uid', '=', $request->invoice_uid)->first();

        if (!$invoice) {
            return redirect()->route('refactor.account.subscription')
                ->with('alert-warning', 'Invoice not found.');
        }

        if ($r = InvoiceStage::ensureIn(
            [InvoiceStage::PAYMENT, InvoiceStage::PENDING_APPROVAL],
            $invoice
        )) return $r;

        // PENDING_APPROVAL shares URL with PAYMENT — render different view here.
        if (InvoiceStage::of($invoice) === InvoiceStage::PENDING_APPROVAL) {
            return view('refactor.pages.subscription.pending', [
                'invoice' => $invoice,
                'paymentIntent' => $invoice->getPendingPaymentIntent(),
            ]);
        }

        $orderItem = $invoice->order->orderItems->first();
        $plan = $orderItem && $orderItem->subscription ? $orderItem->subscription->plan : null;

        // Detect stale prices (plan price changed since order created) so user sees an
        // explicit "price changed" notice with a refresh button — never silently mutate.
        $stalePrices = [];
        try {
            app(\App\Library\OrderFulfillment\FulfillmentService::class)->checkPrice($invoice->order);
        } catch (\App\Library\OrderFulfillment\StalePriceException $e) {
            $stalePrices = $e->stale;
        }

        // Surface the last failed payment attempt so the customer doesn't see a
        // silent gateway picker after a vendor-side decline (e.g. T-Bank REJECTED
        // returned via the return URL, intent.failed_reason captured the bank
        // message). Only surface if the most recent intent is terminal=FAILED —
        // a still-pending intent means the user is mid-flow and shouldn't see
        // the banner from a prior attempt.
        $lastIntent = $invoice->paymentIntents()->orderByDesc('created_at')->orderByDesc('id')->first();
        $lastFailedIntent = ($lastIntent && $lastIntent->isFailed()) ? $lastIntent : null;

        return view('refactor.pages.subscription.payment', [
            'invoice' => $invoice,
            'plan' => $plan,
            'stalePrices' => $stalePrices,
            'lastFailedIntent' => $lastFailedIntent,
        ]);
    }

    public function refreshPrice(Request $request, $invoice_uid)
    {
        if (!$request->user()->customer->can('updateProfile', $request->user()->customer)) {
            return $this->notAuthorized();
        }

        $invoice = $request->user()->customer->invoices()->where('invoices.uid', $invoice_uid)->first();
        if (!$invoice || $invoice->isPaid()) {
            return redirect()->route('refactor.account.subscription');
        }

        app(\App\Library\OrderFulfillment\FulfillmentService::class)->refreshPrice($invoice->order);

        return redirect()->route('refactor.subscription.payment', ['invoice_uid' => $invoice->uid])
            ->with('alert-success', trans('refactor/account.subscription.checkout.price_refreshed'));
    }

    public function checkout(Request $request)
    {
        if (!$request->user()->customer->can('updateProfile', $request->user()->customer)) {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;
        $invoice = $customer->invoices()->where('invoices.uid', '=', $request->invoice_uid)->first();

        if (!$invoice) {
            return redirect()->route('refactor.account.subscription');
        }

        if ($r = InvoiceStage::ensureIn(InvoiceStage::PAYMENT, $invoice)) return $r;

        $paymentGateway = PaymentGateway::global()->where('uid', $request->payment_gateway_id)->first();
        if (!$paymentGateway) {
            return redirect()->route('refactor.subscription.payment', [
                'invoice_uid' => $invoice->uid,
            ])->with('alert-error', 'Payment gateway not found.');
        }

        $order = $invoice->order;
        if (!$order) {
            return redirect()->route('refactor.subscription.payment', [
                'invoice_uid' => $invoice->uid,
            ])->with('alert-error', 'Invoice order is invalid.');
        }

        $orderItemModel = $order->orderItems->first();
        if (!$orderItemModel) {
            return redirect()->route('refactor.subscription.payment', [
                'invoice_uid' => $invoice->uid,
            ])->with('alert-error', 'Invoice order has no items.');
        }

        $subscription = $orderItemModel->subscription ?? null;
        $plan = $subscription ? $subscription->plan : null;

        // Server-side guard: re-check prices at pay-attempt; if user races a price change,
        // bounce back to payment page where the banner + refresh button are shown.
        try {
            app(\App\Library\OrderFulfillment\FulfillmentService::class)->checkPrice($invoice->order);
        } catch (\App\Library\OrderFulfillment\StalePriceException $e) {
            return redirect()->route('refactor.subscription.payment', [
                'invoice_uid' => $invoice->uid,
            ])->with('alert-error', trans('refactor/account.subscription.checkout.stale_price_alert'));
        }

        // Set return URL to refactored subscription page
        $returnUrl = route('refactor.account.subscription');

        /*
        if ($invoice->total() == 0) {
            $invoice->paySuccess();
            return redirect()->route('refactor.account.subscription');
        }
        */

        // Guard: remote subscription requires plan mapping
        if (Billing::supportsRemoteSubscription($paymentGateway)) {
            if ($plan) {
                $mapping = PlanRemoteMapping::where('plan_id', $plan->id)
                    ->where('payment_gateway_id', $paymentGateway->id)
                    ->first();

                if (!$mapping) {
                    return redirect()->route('refactor.subscription.payment', [
                        'invoice_uid' => $invoice->uid,
                    ])->with('alert-error', trans('messages.subscription.plan_not_mapped_to_gateway', [
                        'plan' => $plan->name,
                        'gateway' => $paymentGateway->name,
                    ]));
                }

                $mismatches = $mapping->getMismatches();
                if (!empty($mismatches)) {
                    $details = array_map(fn($m) => $m['message'], $mismatches);
                    return redirect()->route('refactor.subscription.payment', [
                        'invoice_uid' => $invoice->uid,
                    ])->with('alert-error', trans('messages.subscription.plan_mapping_mismatch', [
                        'plan' => $plan->name,
                        'gateway' => $paymentGateway->name,
                        'details' => implode('. ', $details),
                    ]));
                }
            }
        }

        $extraParams = isset($mapping) ? ['remote_plan_id' => $mapping->remote_plan_id] : [];
        return redirect()->away(Billing::buildCheckoutUrl($paymentGateway, $invoice, $returnUrl, $extraParams));
    }

    public function autoCharge(Request $request)
    {
        $invoice = $this->findOwnedInvoice($request, $request->invoice_uid);
        $paymentMethod = $this->findOwnedPaymentMethod($request, $request->payment_method_id);

        if (!$invoice || !$paymentMethod) {
            return redirect()->route('refactor.account.subscription')
                ->with('alert-error', 'Invoice or payment method not found.');
        }

        app(\App\Services\Subscription\SubscriptionManagementService::class)
            ->autoChargeInvoice($invoice, $paymentMethod);

        return redirect()->route('refactor.account.subscription');
    }

    public function changePlan(Request $request)
    {
        if (!$request->user()->customer->can('updateProfile', $request->user()->customer)) {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;
        $subscription = $customer->getCurrentActiveSubscription();

        if (!$subscription) {
            return redirect()->route('refactor.account.subscription')
                ->with('alert-error', trans('messages.subscription.not_found'));
        }

        $plans = Plan::getAvailablePlans();

        if (!$request->user()->customer->can('changePlan', $subscription)) {
            return $this->notAuthorized();
        }

        if ($request->isMethod('post')) {
            $newPlan = Plan::findByUid($request->plan_uid);

            try {
                // Replace any lingering unpaid change-plan order from an earlier,
                // abandoned attempt so the customer can freely pick a different plan
                // instead of being blocked by the stale pending order.
                $invoice = app(\App\Services\Subscription\SubscriptionManagementService::class)
                    ->changePlan($subscription, $newPlan, true);

                if (!$invoice) {
                    return redirect()->route('refactor.account.subscription')
                        ->with('alert-success', trans('messages.subscription.changed_plan'));
                }

                return redirect()->route('refactor.subscription.payment', [
                    'invoice_uid' => $invoice->uid,
                ]);
            } catch (\Exception $e) {
                return redirect()->route('refactor.account.subscription')
                    ->with('alert-error', $e->getMessage());
            }
        }

        return view('refactor.pages.subscription.change_plan', [
            'subscription' => $subscription,
            'plans' => $plans,
        ]);
    }

    public function noPaymentConfirmation(Request $request)
    {
        $invoice = $request->user()->customer->invoices()
            ->where('invoices.uid', '=', $request->invoice_uid)->first();

        if (!$invoice || $invoice->isPaid()) {
            return redirect()->route('refactor.account.subscription');
        }

        if (!$invoice->no_payment_required_when_free || !$invoice->isFree()) {
            return redirect()->route('refactor.account.subscription');
        }

        if ($request->isMethod('post')) {
            app(\App\Library\OrderFulfillment\FulfillmentService::class)->bypassFreeInvoice($invoice);
            return redirect()->route('refactor.account.subscription');
        }

        return view('refactor.pages.subscription.no_payment_confirmation', [
            'invoice' => $invoice,
        ]);
    }

    public function disableRecurring(Request $request)
    {
        if (!$request->user()->customer->can('updateProfile', $request->user()->customer)) {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;
        $subscription = $customer->getNewOrActiveSubscription();

        if ($customer->can('disableRecurring', $subscription)) {
            app(\App\Services\Subscription\SubscriptionManagementService::class)
                ->disableRecurring($subscription);
        }

        if ($request->ajax()) {
            return response()->json(['message' => trans('messages.subscription.disabled_recurring'), 'reload' => true]);
        }

        return redirect()->route('refactor.account.subscription')
            ->with('alert-success', trans('messages.subscription.disabled_recurring'));
    }

    public function enableRecurring(Request $request)
    {
        if (!$request->user()->customer->can('updateProfile', $request->user()->customer)) {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;
        $subscription = $customer->getNewOrActiveSubscription();

        if ($customer->can('enableRecurring', $subscription)) {
            app(\App\Services\Subscription\SubscriptionManagementService::class)
                ->enableRecurring($subscription);
        }

        if ($request->ajax()) {
            return response()->json(['message' => trans('messages.subscription.enabled_recurring'), 'reload' => true]);
        }

        return redirect()->route('refactor.account.subscription')
            ->with('alert-success', trans('messages.subscription.enabled_recurring'));
    }

    public function cancelNow(Request $request)
    {
        if (!$request->user()->customer->can('updateProfile', $request->user()->customer)) {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;
        $subscription = $customer->getNewOrActiveSubscription();

        if ($customer->can('cancelNow', $subscription)) {
            app(\App\Services\Subscription\SubscriptionManagementService::class)
                ->cancelNow($subscription, true);
        }

        if ($request->ajax()) {
            return response()->json(['message' => trans('messages.subscription.cancelled_now'), 'reload' => true]);
        }

        return redirect()->route('refactor.account.subscription')
            ->with('alert-success', trans('messages.subscription.cancelled_now'));
    }

    public function cancelInvoice(Request $request, $uid)
    {
        if (!$request->user()->customer->can('updateProfile', $request->user()->customer)) {
            return $this->notAuthorized();
        }

        $invoice = $this->findOwnedInvoice($request, $uid);

        if (!$invoice) {
            return redirect()->route('refactor.account.subscription')
                ->with('alert-error', 'Invoice not found.');
        }

        if (!$request->user()->customer->can('delete', $invoice)) {
            return $this->notAuthorized();
        }

        app(\App\Library\OrderFulfillment\FulfillmentService::class)->cancelInvoice($invoice);

        // If still in new subscription state, go to select plan
        if ($request->user()->customer->getNewSubscription()) {
            if ($request->ajax()) {
                return response()->json(['message' => trans('messages.invoice.cancelled'), 'redirect' => route('refactor.subscription.select_plan')]);
            }
            return redirect()->route('refactor.subscription.select_plan');
        }

        if ($request->ajax()) {
            return response()->json(['message' => trans('messages.invoice.cancelled'), 'reload' => true]);
        }

        return redirect()->route('refactor.account.subscription')
            ->with('alert-success', trans('messages.invoice.cancelled'));
    }

    public function orderSummary(Request $request)
    {
        $customer = $request->user()->customer;
        $invoice = $customer->invoices()->unpaid()->where('invoices.uid', '=', $request->invoice_uid)->first();

        if (!$invoice) {
            return response('', 404);
        }

        return view('refactor.pages.subscription._order_summary', [
            'invoice' => $invoice,
            'paymentGatewayUid' => $request->payment_gateway_id,
            'paymentMethodUid' => $request->payment_method_id,
        ]);
    }

}