PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.5
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.5
1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk All 48 releases
← All changes | app/Services/Renderer/ModalCheckoutRenderer.php +107 -4 1.5.3 → 1.6.5 View file →
@@ -39,8 +39,16 @@
39 39 private $cartSummaryRenderer;
40 40
41 41 private $customer;
42 42
43 + /**
44 + * Memoised result of the before_payment_methods placement filter. Null until
45 + * resolved; see beforePaymentMethodsPlacement() for why it is asked only once.
46 + *
47 + * @var string|null
48 + */
49 + private $paymentMethodsHookPlacement = null;
50 +
43 51 public function __construct(Cart $cart, $config = [])
44 52 {
45 53 $this->cart = $cart;
46 54 $this->config = $config;
@@ -183,8 +191,40 @@
183 191 // No explicit save — the recalc action that follows persists this
184 192 // together with the newly computed tax data, avoiding a double write.
185 193 }
186 194
195 + /**
196 + * Where `fluent_cart/before_payment_methods` fires in the modal.
197 + *
198 + * 'payment' (default) wraps the payment-method list, matching CheckoutRenderer.
199 + * 'details' restores the pre-fix position in the address/shipping pane, for a
200 + * store whose add-on positioned itself around that spot.
201 + *
202 + * Resolved ONCE per renderer and memoised. The two call sites are mutually
203 + * exclusive, so the hook must fire exactly once — but that only holds if both
204 + * sites agree, and a filter is free to be stateful or context-sensitive. Asking
205 + * it twice would let it answer differently and either fire the hook twice or
206 + * swallow it entirely. Ask once, remember the answer.
207 + *
208 + * @return string 'payment'|'details'
209 + */
210 + private function beforePaymentMethodsPlacement(): string
211 + {
212 + if ($this->paymentMethodsHookPlacement !== null) {
213 + return $this->paymentMethodsHookPlacement;
214 + }
215 +
216 + $placement = apply_filters('fluent_cart/modal_checkout/before_payment_methods_placement', 'payment', [
217 + 'cart' => $this->cart,
218 + ]);
219 +
220 + // Anything unrecognised falls back to the correct placement rather than
221 + // silently dropping the hook.
222 + $this->paymentMethodsHookPlacement = $placement === 'details' ? 'details' : 'payment';
223 +
224 + return $this->paymentMethodsHookPlacement;
225 + }
226 +
187 227 public function renderCheckoutDetails()
188 228 {
189 229 ?>
190 230 <div class="fct-modal-checkout-details">
@@ -199,10 +239,28 @@
199 239 <div class="fct_checkout_shipping_methods <?php echo $this->requireShipping ? '' : 'is-hidden' ?>">
200 240 <?php $this->checkoutRenderer->renderShippingOptions(); ?>
201 241 </div>
202 242
203 - <?php do_action('fluent_cart/before_payment_methods', ['cart' => $this->cart]); ?>
243 + <?php
244 + /**
245 + * The position `fluent_cart/before_payment_methods` used to
246 + * occupy in the modal. Given its own name so an add-on that
247 + * genuinely wants this pane — under shipping, above the terms
248 + * — has a stable place to render, instead of relying on a hook
249 + * whose name promises it sits before the payment methods.
250 + *
251 + * @param array $data ['cart' => Cart]
252 + */
253 + do_action('fluent_cart/modal_checkout/after_shipping_methods', ['cart' => $this->cart]);
204 254
255 + // Opt-in restoration of the old placement, for a store whose
256 + // add-on positioned itself around the previous spot. Mutually
257 + // exclusive with the call site in renderCheckoutBilling().
258 + if ($this->beforePaymentMethodsPlacement() === 'details') {
259 + do_action('fluent_cart/before_payment_methods', ['cart' => $this->cart]);
260 + }
261 + ?>
262 +
205 263 <?php $this->checkoutRenderer->agreeTerms(); ?>
206 264
207 265 <?php $this->renderSummaryGroup(); ?>
208 266
@@ -564,12 +622,35 @@
564 622 <?php echo esc_html__('Billing information', 'fluent-cart');?>
565 623 </h4>
566 624 </header>
567 625
626 + <?php
627 + /**
628 + * Fired here, wrapping the payment methods, exactly as
629 + * CheckoutRenderer does. It used to fire from
630 + * renderCheckoutDetails() — the address/shipping pane — which
631 + * is a different part of the modal entirely, so anything
632 + * hooked to it (the saved-payment-method picker, Turnstile)
633 + * rendered detached from the methods it belongs to. A hook
634 + * named "before payment methods" has to fire before the
635 + * payment methods.
636 + */
637 + if ($this->beforePaymentMethodsPlacement() === 'payment') {
638 + do_action('fluent_cart/before_payment_methods', ['cart' => $this->cart]);
639 + }
640 + ?>
641 +
568 642 <div class="fct_checkout_payment_methods" data-fluent-cart-checkout-payment-methods>
569 643 <?php $this->renderPaymentMethods(); ?>
570 644 </div>
571 645
646 + <?php
647 + // The modal never fired this at all, so add-ons rendering
648 + // below the methods (the save-my-card consent box) were
649 + // simply absent from quick checkout.
650 + do_action('fluent_cart/after_payment_methods', ['cart' => $this->cart]);
651 + ?>
652 +
572 653 <div class="fct-modal-checkout-btn-wrap">
573 654 <?php (new CheckoutRenderer($this->cart))->renderCheckoutButton(); ?>
574 655 </div>
575 656 </div>
@@ -588,8 +669,9 @@
588 669 }
589 670
590 671 $selectedPaymentMethod = Arr::get($this->cart->checkout_data, 'form_data._fct_pay_method', '');
591 672 $activePaymentMethods = PaymentMethods::getActiveMethodInstance($this->cart);
673 + $hadActiveMethods = !empty($activePaymentMethods);
592 674
593 675 $activePaymentMethods = apply_filters('fluent_cart/checkout_active_payment_methods', $activePaymentMethods, [
594 676 'cart' => $this->cart
595 677 ]);
@@ -602,11 +684,28 @@
602 684 });
603 685 }
604 686
605 687 if (!$selectedPaymentMethod && !empty($activePaymentMethods)) {
606 - $selectedPaymentMethod = $activePaymentMethods[0] ? $activePaymentMethods[0]->getMeta('route') : '';
688 + // reset() not [0] — array_filter above preserves keys, so key 0 may be gone.
689 + $firstMethod = reset($activePaymentMethods);
690 + $selectedPaymentMethod = $firstMethod ? $firstMethod->getMeta('route') : '';
607 691 }
608 692
693 + /**
694 + * The payment method rendered as checked. An add-on that renders its own
695 + * selector outside this list (e.g. saved payment methods) returns a route
696 + * that is not in the list, so no gateway here is checked — the page then
697 + * arrives with the add-on's choice already selected instead of a gateway
698 + * being checked first and switched over by JS after load.
699 + *
700 + * @param string $selectedPaymentMethod
701 + * @param array $context ['cart' => Cart, 'payment_methods' => array]
702 + */
703 + $selectedPaymentMethod = (string) apply_filters('fluent_cart/checkout/selected_payment_method', $selectedPaymentMethod, [
704 + 'cart' => $this->cart,
705 + 'payment_methods' => $activePaymentMethods
706 + ]);
707 +
609 708 $checkoutMethodStyle = $this->storeSettings->get('checkout_method_style', 'logo');
610 709
611 710 ?>
612 711 <div id="fluent_payment_methods" class="fct_modal_payment_methods fluent_payment_methods">
@@ -624,9 +723,13 @@
624 723 ]);
625 724 } ?>
626 725 <?php else: ?>
627 726 <?php
628 - $emptyText = esc_html__('No Payment method is activated for this site yet.', 'fluent-cart');
727 + if ($hadActiveMethods) {
728 + $emptyText = esc_html__('None of the available payment methods can process this order. Please contact the store.', 'fluent-cart');
729 + } else {
730 + $emptyText = esc_html__('No Payment method is activated for this site yet.', 'fluent-cart');
731 + }
629 732 if (current_user_can('manage_options')) {
630 733 $emptyText .= '<a href="' . esc_url(URL::getDashboardUrl('settings/payments')) . '" target="_blank">' . esc_html__('Activate from settings.', 'fluent-cart') . '</a>';
631 734 }
632 735 echo '<div class="fct-empty-state">' . wp_kses_post($emptyText) . '</div>';
@@ -713,9 +816,9 @@
713 816 'route' => $route,
714 817 'method_title' => $methodTitle,
715 818 'method_style' => $methodStyle,
716 819 ];
717 - $paymentMethodClass = apply_filters_deprecated('fluent_cart_payment_method_list_class', ['', $pmContext], '1.3.16', 'fluent_cart/payment_method_list_class', 'Use fluent_cart/payment_method_list_class instead of fluent_cart_payment_method_list_class.');
820 + $paymentMethodClass = '';
718 821 $paymentMethodClass = apply_filters('fluent_cart/payment_method_list_class', $paymentMethodClass, $pmContext);
719 822
720 823 ?>
721 824 <div class="fluent-cart-checkout_embed_payment_wrapper">