PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.0
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.0
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
fluent-cart / app / Services / Renderer / ModalCheckoutRenderer.php

ModalCheckoutRenderer.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.0, at app/Services/Renderer/ModalCheckoutRenderer.php

761 lines 34.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Services\Renderer;
4
5 use FluentCart\Api\PaymentMethods;
6 use FluentCart\Api\Resource\CustomerResource;
7 use FluentCart\Api\Resource\FrontendResource\CustomerAddressResource;
8 use FluentCart\Api\StoreSettings;
9 use FluentCart\App\App;
10 use FluentCart\App\Helpers\AddressHelper;
11 use FluentCart\App\Helpers\CartHelper;
12 use FluentCart\App\Helpers\Helper;
13 use FluentCart\App\Models\Cart;
14 use FluentCart\App\Models\ProductVariation;
15 use FluentCart\App\Services\Localization\LocalizationManager;
16 use FluentCart\App\Services\URL;
17 use FluentCart\App\Vite;
18 use FluentCart\Framework\Support\Arr;
19 use FluentCart\App\Models\ProductMeta;
20
21 class ModalCheckoutRenderer
22 {
23 private $cart;
24
25 private $requireShipping;
26
27 private $hasSubscription = false;
28
29 private $config = [];
30
31 private $storeSettings;
32
33 private $productMeta;
34
35 private $matchedVariation;
36
37 private $checkoutRenderer;
38
39 private $cartSummaryRenderer;
40
41 private $customer;
42
43 public function __construct(Cart $cart, $config = [])
44 {
45 $this->cart = $cart;
46 $this->config = $config;
47 $this->requireShipping = $cart->requireShipping();
48 $this->storeSettings = new StoreSettings();
49 $this->checkoutRenderer = new CheckoutRenderer($cart);
50 $this->cartSummaryRenderer = new CartSummaryRender($cart);
51 $this->hasSubscription = $cart->hasSubscription();
52 $this->customer = CustomerResource::getCurrentCustomer();
53
54 $this->productMeta = ProductMeta::query()->where('object_id', Arr::get($this->cart->cart_data, '0.post_id'))->where('meta_key', 'license_settings')->first();
55
56 $variations = $this->productMeta->meta_value['variations'] ?? [];
57
58 $cartItem = $this->cart->cart_data[0] ?? [];
59 $objectId = $cartItem['object_id'] ?? null;
60
61 $this->matchedVariation = $objectId && isset($variations[$objectId])
62 ? $variations[$objectId]
63 : null;
64 }
65
66 public function getFragment($fragmentName)
67 {
68 $maps = [
69 'payment_methods' => 'renderPaymentMethods',
70 'summary_group' => 'renderSummaryGroup',
71 'checkout_summary' => 'renderCheckoutSummary'
72 ];
73
74 if(isset($maps[$fragmentName])) {
75 ob_start();
76 $this->{$maps[$fragmentName]}();
77 return ob_get_clean();
78 }
79
80 return '';
81
82 }
83
84 public function render()
85 {
86 ?>
87 <div class="fct-checkout-modal-container" data-fct-checkout-modal-container style="opacity: 0; visibility: hidden;">
88 <div class="fct-checkout-modal" data-fct-checkout-modal>
89 <div class="fct-checkout-modal-loader" data-fct-checkout-modal-loader>
90 <div class="fct-checkout-modal-loader-spinner"></div>
91 </div>
92 <button class="fct-checkout-modal-close" data-fct-checkout-modal-close aria-label="<?php echo esc_attr__('Close checkout', 'fluent-cart');?>">
93 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
94 <line x1="18" y1="6" x2="6" y2="18"></line>
95 <line x1="6" y1="6" x2="18" y2="18"></line>
96 </svg>
97 </button>
98 <div class="fct-checkout-modal-content">
99 <?php $this->renderIframe();?>
100 </div>
101 </div>
102 </div>
103 <?php
104 }
105
106 public function renderIframe()
107 {
108 ?>
109 <iframe class="fct-checkout-modal-iframe" data-fct-checkout-modal-iframe title="<?php echo esc_attr__('Checkout', 'fluent-cart');?>" frameborder="0" allowtransparency="true" spellcheck="false"></iframe>
110 <?php
111 }
112
113 public function renderForm()
114 {
115 // Without a billing country in checkout_data, the tax recalc below
116 // produces no per-item tax. Pre-fill from the current customer's
117 // primary billing address (if any) so the per-item tax badge can
118 // render on the very first modal open instead of only after the
119 // customer interacts with the address form.
120 $this->maybePopulateFormDataFromCustomer();
121
122 // Trigger the recalc only when the cart hasn't been taxed yet. Once
123 // line_meta.tax_config is on the item, the badge already renders from
124 // existing data — another recalc here would just duplicate work
125 // (Tax + Shipping each call $cart->save() on this action).
126 // Payload shape matches Cart::addItem() so every handler receives the
127 // data it expects.
128 if (empty(Arr::get($this->cart->cart_data, '0.line_meta.tax_config'))) {
129 do_action('fluent_cart/cart/cart_data_items_updated', [
130 'cart' => $this->cart,
131 'scope' => 'modal_open',
132 'scope_data' => null,
133 ]);
134 }
135 ?>
136 <div class="fct-modal-checkout-form-wrapper" data-fluent-cart-checkout-page>
137 <form
138 class="fct-modal-checkout-form"
139 action="<?php echo esc_url(home_url('/')); ?>"
140 method="POST"
141 data-fluent-cart-checkout-page-checkout-form
142 enctype="multipart/form-data"
143 aria-label="<?php esc_attr_e('Checkout Form', 'fluent-cart'); ?>"
144 >
145 <div class="fct-modal-checkout-form-inner" data-fct-modal-checkout-form-inner>
146 <?php $this->renderCheckoutDetails(); ?>
147 <?php $this->renderCheckoutBilling(); ?>
148 </div>
149 </form>
150 </div>
151 <?php
152 }
153
154
155 private function maybePopulateFormDataFromCustomer()
156 {
157 if (!$this->customer) {
158 return;
159 }
160
161 $checkoutData = is_array($this->cart->checkout_data) ? $this->cart->checkout_data : [];
162 $formData = Arr::get($checkoutData, 'form_data', []);
163
164 // Never clobber an answer the customer has actively chosen; only
165 // pre-fill when the cart has no billing country yet.
166 if (!empty(Arr::get($formData, 'billing_country'))) {
167 return;
168 }
169
170 $primaryBilling = $this->customer->primary_billing_address;
171 if (!$primaryBilling) {
172 return;
173 }
174
175 foreach ($primaryBilling->getFormattedDataForCheckout('billing_') as $key => $value) {
176 if (!isset($formData[$key]) || $formData[$key] === '' || $formData[$key] === null) {
177 $formData[$key] = $value;
178 }
179 }
180
181 $checkoutData['form_data'] = $formData;
182 $this->cart->checkout_data = $checkoutData;
183 // No explicit save — the recalc action that follows persists this
184 // together with the newly computed tax data, avoiding a double write.
185 }
186
187 public function renderCheckoutDetails()
188 {
189 ?>
190 <div class="fct-modal-checkout-details">
191 <!-- Stable swap target: tax recalculation re-renders the item card
192 here via the checkout_summary fragment. -->
193 <div data-fct-modal-checkout-summary>
194 <?php $this->renderCheckoutSummary(); ?>
195 </div>
196
197 <?php $this->checkoutRenderer->renderAddressFields(); ?>
198
199 <div class="fct_checkout_shipping_methods <?php echo $this->requireShipping ? '' : 'is-hidden' ?>">
200 <?php $this->checkoutRenderer->renderShippingOptions(); ?>
201 </div>
202
203 <?php do_action('fluent_cart/before_payment_methods', ['cart' => $this->cart]); ?>
204
205 <?php $this->checkoutRenderer->agreeTerms(); ?>
206
207 <?php $this->renderSummaryGroup(); ?>
208
209 <?php //$this->renderCheckoutReviews();?>
210 </div>
211 <?php
212 }
213
214 public function renderCheckoutSummary()
215 {
216 $title = Arr::get($this->cart->cart_data, '0.title', '');
217 $postTitle = Arr::get($this->cart->cart_data, '0.post_title', '');
218 $subTotal = Helper::toDecimal(Arr::get($this->cart->cart_data, '0.subtotal', 0));
219 $couponDiscount = (int) Arr::get($this->cart->cart_data, '0.coupon_discount', 0);
220 $subtotalRaw = (int) Arr::get($this->cart->cart_data, '0.subtotal', 0);
221 $lineTotalRaw = (int) Arr::get($this->cart->cart_data, '0.line_total', 0);
222 $hasCouponDiscount = $couponDiscount > 0 && $lineTotalRaw < $subtotalRaw;
223 $media = Arr::get($this->cart->cart_data, '0.featured_media', '');
224
225 // Mirror CartItemRenderer's event payload so the shared line-item hooks
226 // (e.g. the per-item tax breakdown) also fire in modal checkout.
227 $lineItemEventInfo = [
228 'item' => Arr::get($this->cart->cart_data, '0', []),
229 'cart' => $this->cart,
230 'product' => null,
231 'variant' => null,
232 ];
233
234 ?>
235 <div class="fct-modal-checkout-summary">
236 <div class="fct-modal-cs-img">
237 <img src="<?php echo esc_url($media);?>" alt="<?php echo esc_attr($postTitle);?>">
238 </div>
239
240 <div class="fct-modal-cs-content">
241 <div class="fct-modal-cs-line-item">
242 <div class="fct-modal-cs-item-content">
243 <h2 class="fct-modal-cs-item-name">
244 <?php echo esc_html($postTitle);?>
245 </h2>
246 <h3 class="fct-modal-cs-item-variation">
247 - <?php echo esc_html($title);?>
248 </h3>
249 </div>
250
251 <?php if ($hasCouponDiscount) : ?>
252 <div class="fct-modal-cs-price-wrapper">
253 <span class="fct-modal-cs-line-price fct-modal-cs-line-price--original" aria-label="<?php esc_attr_e('Original price', 'fluent-cart'); ?>">
254 <?php echo esc_html(Helper::toDecimal($subtotalRaw)); ?>
255 </span>
256 <span class="fct-modal-cs-line-price fct-modal-cs-line-price--discounted" aria-label="<?php esc_attr_e('Discounted price', 'fluent-cart'); ?>">
257 <?php echo esc_html(Helper::toDecimal($lineTotalRaw)); ?>
258 </span>
259 </div>
260 <?php else : ?>
261 <span class="fct-modal-cs-line-price">
262 <?php echo esc_html($subTotal); ?>
263 </span>
264 <?php endif; ?>
265 <?php do_action('fluent_cart/cart/line_item/after_total', $lineItemEventInfo); ?>
266 </div>
267
268 <?php do_action('fluent_cart/cart/line_item/footer_start', $lineItemEventInfo); ?>
269
270 <?php $this->renderPaymentTypeInfo(); ?>
271
272 <?php if($this->matchedVariation) :?>
273 <div class="fct-modal-cs-license">
274 <?php
275 $limit = Arr::get($this->matchedVariation, 'activation_limit');
276
277 if (empty($limit) || (int) $limit === 0) {
278 echo esc_html__('Unlimited Activation', 'fluent-cart');
279 } else {
280 echo esc_html(sprintf(__('Activation limit %d', 'fluent-cart'), $limit));
281 }
282 ?>
283 </div>
284 <?php endif;?>
285
286
287 </div>
288 </div>
289
290 <?php
291
292 }
293
294 public function renderPaymentTypeInfo() {
295 $otherInfo = Arr::get($this->cart->cart_data, '0.other_info', []);
296 $paymentType = Arr::get($otherInfo, 'payment_type', '');
297 $itemPrice = Arr::get($this->cart->cart_data, '0.unit_price', 0);
298
299 if ($paymentType === 'subscription') {
300 $subscriptionInfo = Helper::generateSubscriptionInfo($otherInfo, $itemPrice);
301 $setupFeeInfo = Helper::generateSetupFeeInfo($otherInfo);
302 $trialInfo = Helper::generateTrialInfo($otherInfo);
303 ?>
304 <div class="fct-modal-cs-payment-info">
305 <span class="sr-only">
306 <?php esc_html_e('Payment information', 'fluent-cart'); ?>
307 </span>
308
309 <span>
310 <?php echo wp_kses($subscriptionInfo, ['del' => true]); ?>&#44;
311 </span>
312
313 <?php if ($trialInfo): ?>
314 <span class="trial-days">
315 <?php echo esc_html($trialInfo); ?>&#44;
316 </span>
317 <?php endif; ?>
318
319 <?php if (!empty($setupFeeInfo)): ?>
320 <span class="setup-fee">
321 <?php echo esc_html($setupFeeInfo); ?>
322 </span>
323 <?php endif; ?>
324 </div>
325 <?php
326 return;
327 }
328
329 $quantity = (int) Arr::get($this->cart->cart_data, '0.quantity', 1);
330 if ($quantity < 2) {
331 return;
332 }
333
334 $lineItemEventInfo = [
335 'item' => Arr::get($this->cart->cart_data, '0', []),
336 'cart' => $this->cart,
337 'product' => null,
338 'variant' => null,
339 ];
340 ?>
341 <div class="fct-modal-cs-payment-info">
342 <?php
343 /* translators: %1$s: formatted unit price */
344 printf(esc_html__('%1$s each', 'fluent-cart'), esc_html(Helper::toDecimal($itemPrice)));
345 ?>
346 <?php do_action('fluent_cart/cart/line_item/unit_price_hint', $lineItemEventInfo); ?>
347 </div>
348 <?php
349 }
350
351 public function renderPromoCode() {
352 ?>
353 <div class="fct-modal-checkout-promo-code">
354 <div class="fct-modal-input-control-wrap" data-fct-modal-input-control-wrap>
355 <button type="button" class="fct-modal-input-toggle-btn" data-fct-modal-input-toggle-btn>
356 <?php echo esc_html__('Have a promotional code?', 'fluent-cart');?>
357 </button>
358
359 <div class="fct-modal-input-controls fct-hidden" data-fct-modal-input-controls>
360 <span class="fct-input-icon">
361 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
362 <path fill="currentColor" d="M336 352c97.2 0 176-78.8 176-176S433.2 0 336 0 160 78.8 160 176c0 18.7 2.9 36.8 8.3 53.7L7 391c-4.5 4.5-7 10.6-7 17l0 80c0 13.3 10.7 24 24 24l80 0c13.3 0 24-10.7 24-24l0-40 40 0c13.3 0 24-10.7 24-24l0-40 40 0c6.4 0 12.5-2.5 17-7l33.3-33.3c16.9 5.4 35 8.3 53.7 8.3zM376 96a40 40 0 1 1 0 80 40 40 0 1 1 0-80z"/></svg>
363 </span>
364
365 <input
366 class="fct-input-field"
367 id="coupon"
368 type="text"
369 name="coupon"
370 placeholder="<?php echo esc_attr__('Enter promotion code', 'fluent-cart');?>"
371 >
372
373
374 <button type="button" class="fct-input-submit-btn" data-fluent-cart-checkout-page-coupon-validate>
375 <?php echo esc_html__('Apply', 'fluent-cart');?>
376 </button>
377 </div>
378 </div>
379 </div>
380 <?php
381 }
382 public function renderSummaryGroup() {
383 ?>
384
385 <div class="fct-modal-checkout-summary-group" data-fct-modal-checkout-summary-group>
386 <?php
387 $this->cartSummaryRenderer->renderItemsFooter();
388 ?>
389 </div>
390
391 <?php
392
393 }
394
395
396 public function showCouponApplied()
397 {
398 $discounts = $this->cart->getDiscountLines();
399
400 if (!$discounts) {
401 return;
402 }
403
404 ?>
405
406 <div class="fct-modal-coupon-applied" data-fluent-cart-checkout-page-discount-container>
407 <?php foreach ($discounts as $couponCode => $discount_data): ?>
408 <div class="fct-coupon-applied-item">
409 <div class="fct-coupon-info">
410 <span class="fct-coupon">
411 <span class="fct-coupon-icon">
412 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M32.5 96l0 149.5c0 17 6.7 33.3 18.7 45.3l192 192c25 25 65.5 25 90.5 0L483.2 333.3c25-25 25-65.5 0-90.5l-192-192C279.2 38.7 263 32 246 32L96.5 32c-35.3 0-64 28.7-64 64zm112 16a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
413 </span>
414
415 <?php echo esc_html($discount_data['formatted_title']) ?>
416
417 <a
418 href="#"
419 class="fct-remove-coupon"
420 data-remove-coupon
421 data-coupon="<?php echo esc_attr($couponCode); ?>"
422 >
423 <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 384 512"><path d="M55.1 73.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L147.2 256 9.9 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192.5 301.3 329.9 438.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.8 256 375.1 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192.5 210.7 55.1 73.4z"/></svg>
424 </a>
425 </span>
426 <span class="fct-coupon-text">
427 <?php echo esc_html__( 'Coupon discount', 'fluent-cart'); ?>
428 </span>
429 </div>
430 <div class="fct-coupon-price">
431 &#8211;<?php echo esc_html($discount_data['actual_formatted_discount']) ?>
432 </div>
433 </div>
434 <?php endforeach; ?>
435 </div>
436
437 <?php
438 }
439
440 public function renderCheckoutReviews() {
441 ?>
442 <div class="fct-modal-checkout-reviews-wrap">
443 <div class="fct-modal-checkout-reviews">
444 <div class="fct-reviews-picture">
445 <img width="40" height="40" src="https://images.pexels.com/photos/23885849/pexels-photo-23885849.jpeg" alt="">
446 </div>
447 <div class="fct-reviews-content">
448 <div class="fct-reviews-text">
449 I got the annual unlimited websites premium version and I'm upgrading to the Lifetime Unlimited Websites plan real soon. Building WordPress website
450 </div>
451 <div class="fct-reviews-meta">
452 <div class="fct-review-stars">
453 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
454 <path fill="currentColor" d="M309.5-18.9c-4.1-8-12.4-13.1-21.4-13.1s-17.3 5.1-21.4 13.1L193.1 125.3 33.2 150.7c-8.9 1.4-16.3 7.7-19.1 16.3s-.5 18 5.8 24.4l114.4 114.5-25.2 159.9c-1.4 8.9 2.3 17.9 9.6 23.2s16.9 6.1 25 2L288.1 417.6 432.4 491c8 4.1 17.7 3.3 25-2s11-14.2 9.6-23.2L441.7 305.9 556.1 191.4c6.4-6.4 8.6-15.8 5.8-24.4s-10.1-14.9-19.1-16.3L383 125.3 309.5-18.9z"/>
455 </svg>
456 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
457 <path fill="currentColor" d="M309.5-18.9c-4.1-8-12.4-13.1-21.4-13.1s-17.3 5.1-21.4 13.1L193.1 125.3 33.2 150.7c-8.9 1.4-16.3 7.7-19.1 16.3s-.5 18 5.8 24.4l114.4 114.5-25.2 159.9c-1.4 8.9 2.3 17.9 9.6 23.2s16.9 6.1 25 2L288.1 417.6 432.4 491c8 4.1 17.7 3.3 25-2s11-14.2 9.6-23.2L441.7 305.9 556.1 191.4c6.4-6.4 8.6-15.8 5.8-24.4s-10.1-14.9-19.1-16.3L383 125.3 309.5-18.9z"/>
458 </svg>
459 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
460 <path fill="currentColor" d="M309.5-18.9c-4.1-8-12.4-13.1-21.4-13.1s-17.3 5.1-21.4 13.1L193.1 125.3 33.2 150.7c-8.9 1.4-16.3 7.7-19.1 16.3s-.5 18 5.8 24.4l114.4 114.5-25.2 159.9c-1.4 8.9 2.3 17.9 9.6 23.2s16.9 6.1 25 2L288.1 417.6 432.4 491c8 4.1 17.7 3.3 25-2s11-14.2 9.6-23.2L441.7 305.9 556.1 191.4c6.4-6.4 8.6-15.8 5.8-24.4s-10.1-14.9-19.1-16.3L383 125.3 309.5-18.9z"/>
461 </svg>
462 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
463 <path fill="currentColor" d="M309.5-18.9c-4.1-8-12.4-13.1-21.4-13.1s-17.3 5.1-21.4 13.1L193.1 125.3 33.2 150.7c-8.9 1.4-16.3 7.7-19.1 16.3s-.5 18 5.8 24.4l114.4 114.5-25.2 159.9c-1.4 8.9 2.3 17.9 9.6 23.2s16.9 6.1 25 2L288.1 417.6 432.4 491c8 4.1 17.7 3.3 25-2s11-14.2 9.6-23.2L441.7 305.9 556.1 191.4c6.4-6.4 8.6-15.8 5.8-24.4s-10.1-14.9-19.1-16.3L383 125.3 309.5-18.9z"/>
464 </svg>
465 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
466 <path fill="currentColor" d="M309.5-18.9c-4.1-8-12.4-13.1-21.4-13.1s-17.3 5.1-21.4 13.1L193.1 125.3 33.2 150.7c-8.9 1.4-16.3 7.7-19.1 16.3s-.5 18 5.8 24.4l114.4 114.5-25.2 159.9c-1.4 8.9 2.3 17.9 9.6 23.2s16.9 6.1 25 2L288.1 417.6 432.4 491c8 4.1 17.7 3.3 25-2s11-14.2 9.6-23.2L441.7 305.9 556.1 191.4c6.4-6.4 8.6-15.8 5.8-24.4s-10.1-14.9-19.1-16.3L383 125.3 309.5-18.9z"/>
467 </svg>
468 </div>
469 <div class="fct-review-author-info">
470 <span class="fct-author-name">Bryson Suleiman,</span>
471 <span class="fct-author-role">Founder and WordPress Expert at Webbryson</span>
472 </div>
473 </div>
474 </div>
475 </div>
476 </div>
477
478 <?php
479
480 }
481
482
483 public function renderCheckoutBilling() {
484 $user = wp_get_current_user();
485 $formRender = new FormFieldRenderer();
486 $fullName = trim(
487 ($this->customer->first_name ?? '') . ' ' . ($this->customer->last_name ?? '')
488 );
489
490 if (!$fullName && $user) {
491 $fullName = $user->display_name;
492 }
493
494 $fieldsSchema = CheckoutFieldsSchema::getFieldsSettings();
495
496 $fullNameField = Arr::get($fieldsSchema, 'basic_info.full_name', []);
497 $emailField = Arr::get($fieldsSchema, 'basic_info.email', []);
498 $isRequiredFullName = Arr::get($fullNameField, 'required', 'no') === 'yes' ? 'yes' : '';
499 $isRequiredEmail = Arr::get($emailField, 'required', 'no') === 'yes' ? 'yes' : '';
500
501 ?>
502 <div class="fct-modal-checkout-billing-wrap">
503 <!-- Account Details -->
504 <div class="fct-modal-account-details" data-fct-checkout-form-section>
505 <div class="fct-modal-form-info">
506 <div class="fct-modal-form-field col-6">
507 <?php
508 $formRender->renderField([
509 'label' => esc_attr__('Full name', 'fluent-cart') . ($isRequiredFullName ? ' *' : ''),
510 'id' => 'billing_full_name',
511 'type' => 'text',
512 'placeholder' => __('Jon Doe', 'fluent-cart'),
513 'name' => 'billing_full_name',
514 'autocomplete' => 'given-name',
515 'aria-label' => esc_attr__('Full name', 'fluent-cart'),
516 'required' => $isRequiredFullName,
517 'value' => $fullName
518 ]);
519 ?>
520 </div>
521 <div class="fct-modal-form-field col-6">
522 <?php
523 $formRender->renderField([
524 'label' => esc_attr__('Email address', 'fluent-cart') . ($isRequiredEmail ? ' *' : ''),
525 'id' => 'billing_email',
526 'type' => 'text',
527 'placeholder' => 'jon.doe@example.com',
528 'name' => 'billing_email',
529 'autocomplete' => 'email',
530 'required' => $isRequiredEmail,
531 'aria-label' => esc_attr__('Email address', 'fluent-cart'),
532 'value' => $this->customer->email ?? $user->user_email,
533 'disabled' => (bool)($this->customer->email ?? $user->user_email)
534 ]);
535 ?>
536 </div>
537
538 <!-- <div class="fct-modal-form-field col-3">-->
539 <!-- --><?php
540 // $formRender->renderField([
541 // 'label' => __('Last name', 'fluent-cart'),
542 // 'id' => 'billing_last_name',
543 // 'type' => 'text',
544 // 'placeholder' => __('Doe', 'fluent-cart'),
545 // 'name' => 'billing_last_name',
546 // 'autocomplete' => 'given-name',
547 // 'required' => true,
548 // 'value' => $this->customer->last_name ?? ''
549 // ]);
550 // ?>
551 <!-- </div>-->
552 </div>
553
554 <?php $this->checkoutRenderer->renderCreateAccountField(); ?>
555
556 <!-- Error Message -->
557 <div class="fct_form_error fct_error_billing_personal_information_section"></div>
558 </div>
559
560 <!-- Billing Details -->
561 <div class="fct-modal-billing-details">
562 <header class="fct-modal-billing-header">
563 <h4 class="fct-modal-billing-title">
564 <?php echo esc_html__('Billing information', 'fluent-cart');?>
565 </h4>
566 </header>
567
568 <div class="fct_checkout_payment_methods" data-fluent-cart-checkout-payment-methods>
569 <?php $this->renderPaymentMethods(); ?>
570 </div>
571
572 <div class="fct-modal-checkout-btn-wrap">
573 <?php (new CheckoutRenderer($this->cart))->renderCheckoutButton(); ?>
574 </div>
575 </div>
576 </div>
577
578 <?php
579
580 }
581
582 public function renderPaymentMethods()
583 {
584 if ($this->cart->getEstimatedTotal() <= 0) {
585 if (!$this->cart->hasSubscription() || $this->cart->getEstimatedRecurringTotal() <= 0) {
586 return '';
587 }
588 }
589
590 $selectedPaymentMethod = Arr::get($this->cart->checkout_data, 'form_data._fct_pay_method', '');
591 $activePaymentMethods = PaymentMethods::getActiveMethodInstance($this->cart);
592 $hadActiveMethods = !empty($activePaymentMethods);
593
594 $activePaymentMethods = apply_filters('fluent_cart/checkout_active_payment_methods', $activePaymentMethods, [
595 'cart' => $this->cart
596 ]);
597
598 // filter the active payment methods to only include the selected payment method
599 $selectedModalPaymentMethod = apply_filters('fluent_cart/modal_checkout/filter_active_payment_methods', []);
600 if (!empty($selectedModalPaymentMethod)) {
601 $activePaymentMethods = array_filter($activePaymentMethods, function ($method) use ($selectedModalPaymentMethod) {
602 return in_array($method->getMeta('route'), $selectedModalPaymentMethod);
603 });
604 }
605
606 if (!$selectedPaymentMethod && !empty($activePaymentMethods)) {
607 // reset() not [0] — array_filter above preserves keys, so key 0 may be gone.
608 $firstMethod = reset($activePaymentMethods);
609 $selectedPaymentMethod = $firstMethod ? $firstMethod->getMeta('route') : '';
610 }
611
612 $checkoutMethodStyle = $this->storeSettings->get('checkout_method_style', 'logo');
613
614 ?>
615 <div id="fluent_payment_methods" class="fct_modal_payment_methods fluent_payment_methods">
616 <!-- Payment Methods -->
617 <div class="fct_payment_methods_list fct_payment_method_mode_<?php echo esc_attr($checkoutMethodStyle); ?>">
618 <?php if (!empty($activePaymentMethods)): ?>
619 <?php foreach ($activePaymentMethods as $method) {
620 $isSelected = ($selectedPaymentMethod === $method->getMeta('route'));
621
622 $this->renderPaymentMethod($method, [
623 'selected_id' => $selectedPaymentMethod,
624 'style' => $checkoutMethodStyle,
625 'aria_checked' => $isSelected ? 'true' : 'false',
626 'role' => 'radio'
627 ]);
628 } ?>
629 <?php else: ?>
630 <?php
631 if ($hadActiveMethods) {
632 $emptyText = esc_html__('None of the available payment methods can process this order. Please contact the store.', 'fluent-cart');
633 } else {
634 $emptyText = esc_html__('No Payment method is activated for this site yet.', 'fluent-cart');
635 }
636 if (current_user_can('manage_options')) {
637 $emptyText .= '<a href="' . esc_url(URL::getDashboardUrl('settings/payments')) . '" target="_blank">' . esc_html__('Activate from settings.', 'fluent-cart') . '</a>';
638 }
639 echo '<div class="fct-empty-state">' . wp_kses_post($emptyText) . '</div>';
640 ?>
641 <?php endif; ?>
642 </div>
643
644 <!-- Payment Method Content -->
645 <div class="fct_payment_method_contents">
646 <?php foreach ($activePaymentMethods as $method) {
647 $this->renderPaymentMethodContent($method, [
648 'style' => $checkoutMethodStyle
649 ]);
650 } ?>
651 </div>
652
653
654
655 </div>
656 <?php
657 }
658
659
660 public function renderPaymentMethod($method, $config = [])
661 {
662 $route = $method->getMeta('route');
663 $methodTitle = $method->getMeta('title');
664 $methodStyle = Arr::get($config, 'style', 'logo');
665
666 $inputAttributes = array_filter([
667 'class' => 'form-radio-input',
668 'type' => 'radio',
669 'name' => '_fct_pay_method',
670 'id' => 'fluent_cart_payment_method_' . $route,
671 'value' => $route,
672 'required' => true,
673 'checked' => $route === Arr::get($config, 'selected_id', '') ? 'true' : '',
674 'role' => Arr::get($config, 'role', 'radio'),
675 'aria-checked' => Arr::get($config, 'aria_checked', 'false'),
676 ]);
677
678 $wrapperClass = $methodStyle === 'logo' ? 'fct_payment_method_logo' : 'fct_payment_method';
679
680 $wrapperAttributes = [
681 'class' => $wrapperClass . ' ' . 'fct_payment_method_wrapper fct_payment_method_' . $route,
682 'tabindex' => '0',
683 'role' => 'presentation'
684 ];
685
686 ?>
687 <div <?php RenderHelper::renderAtts($wrapperAttributes); ?>>
688 <span class="fct-payment-method-loader">
689 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
690 <circle cx="12" cy="12" r="10" opacity="0.2" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="2.5"></circle>
691
692 <path d="m12,2c5.52,0,10,4.48,10,10" fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2.5">
693 <animateTransform attributeName="transform" attributeType="XML" type="rotate" dur="0.5s" from="0 12 12" to="360 12 12" repeatCount="indefinite"></animateTransform>
694 </path>
695 </svg>
696 </span>
697
698 <input <?php RenderHelper::renderAtts($inputAttributes); ?>/>
699
700 <label for="<?php echo esc_attr('fluent_cart_payment_method_' . $route); ?>">
701 <?php
702 if ($methodStyle === 'logo') {
703 $method->prepare('logo', $this->hasSubscription);
704 } else {
705 $method->prepare('radio', $this->hasSubscription);
706 }
707 ?>
708
709 <?php echo esc_html($methodTitle); ?>
710 </label>
711 </div>
712 <?php
713 }
714
715 public function renderPaymentMethodContent($method, $config = []) {
716 $route = $method->getMeta('route');
717 $methodTitle = $method->getMeta('title');
718 $methodStyle = Arr::get($config, 'style', 'logo');
719 $pmContext = [
720 'route' => $route,
721 'method_title' => $methodTitle,
722 'method_style' => $methodStyle,
723 ];
724 $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.');
725 $paymentMethodClass = apply_filters('fluent_cart/payment_method_list_class', $paymentMethodClass, $pmContext);
726
727 ?>
728 <div class="fluent-cart-checkout_embed_payment_wrapper">
729 <?php if ($method->getMeta('instructions')): ?>
730 <div class="fct-modal-payment-method-instructions">
731 <?php echo wp_kses_post($method->getMeta('instructions')); ?>
732 </div>
733 <?php endif; ?>
734
735 <div class="<?php echo "fluent-cart-checkout_embed_payment_container fluent-cart-checkout_embed_payment_container_" . esc_attr($route . ' ' . $paymentMethodClass); ?>"
736 aria-hidden="true">
737 <?php do_action(
738 'fluent_cart/checkout_embed_payment_method_content',
739 [
740 'method' => $method,
741 'cart' => $this->cart,
742 'route' => $route
743 ]
744 ); ?>
745 </div>
746 </div>
747
748 <?php
749 }
750
751
752
753
754
755
756
757
758
759
760 }
761