hasSubscription()) { return $methods; } $checkoutData = (array) $cart->checkout_data; if (Arr::get($checkoutData, 'renewal_order')) { return $this->gatewaysForExistingSubscription( $methods, SubscriptionManagementMode::resolveRenewalSubscription($cart), $cart ); } if (Arr::get($checkoutData, 'renew_data.subscription_hash')) { return $this->gatewaysForExistingSubscription( $methods, SubscriptionManagementMode::resolveReactivationSubscription($cart), $cart ); } return $this->gatewaysForNewSubscription($methods, $cart); } private function gatewaysForNewSubscription($methods, $cart) { $zeroPayable = self::getPayableNow($cart) <= 0; if (SubscriptionManagementMode::isStoreManaged()) { return self::storeBilledOnly($methods, $zeroPayable, SubscriptionManagementMode::isSystemChargeEnabled()); } if (self::manualFallbackOnGatewayManage()) { return $zeroPayable ? self::zeroFirstPaymentCapable($methods) : $methods; } return self::subscriptionCapableOnly($methods); } /** * $subscription is null when the cart names one that can't be resolved — the * charge itself will fail later with `no_subscription`, so nothing is gated. */ private function gatewaysForExistingSubscription($methods, $subscription, $cart) { if (!$subscription) { return $methods; } $storeManaged = SubscriptionManagementMode::isStoreManaged(); $collectionMethod = $subscription->collection_method; if (!$storeManaged && $collectionMethod === 'automatic') { // Keeps its vendor schedule; a one-time gateway would strand it. $eligible = self::subscriptionCapableOnly($methods); } elseif (!$storeManaged && $collectionMethod === 'manual' && !SubscriptionManagementMode::isSubscriptionStoreManaged($subscription)) { // Born gateway-managed and still unstamped: paying via a // subscription-capable gateway converts it to automatic, anything else // leaves it billing manually forever — opt-in only. Except before the // due date, where converting would cost the customer days (see // isAdvanceRenewal()). if (self::isAdvanceRenewal($cart)) { $eligible = self::oneTimePaymentOnly($methods); } else { $eligible = self::manualFallbackOnGatewayManage() ? $methods : self::subscriptionCapableOnly($methods); } } else { // A `system` sub is auto-charged whatever the store setting says today. $autoChargeable = $collectionMethod === 'system' || SubscriptionManagementMode::isSystemChargeEnabled(); $eligible = self::storeBilledOnly($methods, self::getPayableNow($cart) <= 0, $autoChargeable); } return self::currentMethodFirst($eligible, $subscription); } private static function subscriptionCapableOnly($methods) { return array_filter($methods, function ($gateway) { return $gateway->has('subscriptions'); }); } private static function oneTimePaymentOnly($methods) { return array_filter($methods, function ($gateway) { return !$gateway->has('subscriptions'); }); } /** * Renewal invoices are created days ahead of their due date. Paying one early * through a subscription-capable gateway converts the subscription to automatic * (Stripe.php / PayPal.php) and the vendor schedule starts from TODAY — the days * between now and the due date are forfeited. Store-billed payment keeps the * cadence instead: handleRenewalPaid() anchors the next date to due_date. * * Only gateway-managed carts can convert, so only they need this guard. */ private static function isAdvanceRenewal($cart): bool { if (!$cart || !$cart->order_id) { return false; } $order = Order::query()->find($cart->order_id); if (!$order) { return false; } $dueDate = $order->getMeta('due_date'); return $dueDate && strtotime($dueDate) > time(); } /** Nothing due today: only a gateway that can start the schedule at zero. */ private static function zeroFirstPaymentCapable($methods) { return array_filter($methods, function ($gateway) { return $gateway->has('subscriptions') || $gateway->has('offline'); }); } /** * One-time gateways, plus subscription-capable ones that opt into store * billing (`manual_subscription`) — shouldChargeSubscriptionAsOneTime() routes * both to a single payment, so no vendor schedule is created. `manual_subscription` * only claims a one-time-charge path exists; it says nothing about being able to * vault a card without charging it, so it plays no part in the zero-payable case. * With nothing due today only a gateway that can vault without charging AND is * wired for later auto-charge (`system_subscription`) qualifies. */ private static function storeBilledOnly($methods, $zeroPayable, $autoChargeable) { return array_filter($methods, function ($gateway) use ($zeroPayable, $autoChargeable) { if ($zeroPayable) { return $gateway->has('offline') || ($autoChargeable && $gateway->has('system_subscription') && $gateway->supportsSetupWithoutCharge()); } return $gateway->has('offline') || !$gateway->has('subscriptions') || $gateway->has('manual_subscription'); }); } private static function currentMethodFirst($methods, $subscription) { $current = $subscription->current_payment_method; if (!$current || !isset($methods[$current])) { return $methods; } return array_merge([$current => $methods[$current]], $methods); } private static function manualFallbackOnGatewayManage(): bool { return SubscriptionManagementMode::isManualFallbackEnabled(); } /** * Admin-only notice on new-subscription carts explaining which gateways were * hidden and why. Renewal/reactivation carts don't get it — they follow the * subscription, not the store setting. */ public function renderSubscriptionGatewayNotice($data) { $cart = Arr::get((array) $data, 'cart'); if (!$this->isNewSubscriptionCart($cart)) { return; } $storeManaged = SubscriptionManagementMode::isStoreManaged(); $zeroPayable = self::getPayableNow($cart) <= 0; $manualFallback = !$storeManaged && self::manualFallbackOnGatewayManage(); if ($manualFallback && !$zeroPayable) { return; } if ($zeroPayable) { echo '