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