PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.21
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.21
1.6.6 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 All 49 releases
fluent-cart / app / Services / Renderer / CheckoutRenderer.php

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

917 lines 36.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\Models\Cart;
13 use FluentCart\App\Services\Localization\LocalizationManager;
14 use FluentCart\App\Services\URL;
15 use FluentCart\Framework\Support\Arr;
16
17 class CheckoutRenderer
18 {
19
20 private $cart;
21
22 private $requireShipping;
23
24 private $hasSubscription = false;
25
26 private $config = [];
27
28 private $billingAddress = [];
29
30 private $shippingAddress = [];
31
32 private $storeSettings;
33
34 public function __construct(Cart $cart, $config = [])
35 {
36 $this->cart = $cart;
37 $this->requireShipping = $cart->requireShipping();
38 $this->hasSubscription = $cart->hasSubscription();
39
40 $formData = Arr::get($cart->checkout_data, 'form_data', []);
41 $this->storeSettings = new StoreSettings();
42
43 $billingValidations = array_filter(CheckoutFieldsSchema::getCheckoutFieldsRequirements('billing', 'physical'));
44 $storeCountry = (new StoreSettings())->get('store_country');
45 if (!Arr::has($billingValidations, 'country') && Arr::get($formData, 'billing_country') !== $storeCountry) {
46 $formData['billing_country'] = $storeCountry;
47 // save $this->cart
48 $checkoutData = $this->cart->checkout_data;
49 $checkoutData['form_data'] = $formData;
50 $this->cart->checkout_data = $checkoutData;
51 $this->cart->save();
52 }
53
54 $fallbackCountry = '';
55 $HTTP_CF_IP_COUNTRY = Arr::get(App::request()->server(), 'HTTP_CF_IPCOUNTRY');
56
57 if ($HTTP_CF_IP_COUNTRY) {
58 $fallbackCountry = $HTTP_CF_IP_COUNTRY;
59 }
60
61 $this->billingAddress = [
62 'full_name' => trim($this->cart->first_name . ' ' . $this->cart->last_name),
63 'country' => Arr::get($formData, 'billing_country', $fallbackCountry),
64 'address_1' => Arr::get($formData, 'billing_address_1', ''),
65 'address_2' => Arr::get($formData, 'billing_address_2', ''),
66 'city' => Arr::get($formData, 'billing_city', ''),
67 'state' => Arr::get($formData, 'billing_state', ''),
68 'postcode' => Arr::get($formData, 'billing_postcode', ''),
69 'company_name' => Arr::get($formData, 'billing_company_name', ''),
70 'phone' => Arr::get($formData, 'billing_phone', ''),
71 ];
72
73 if ($this->requireShipping) {
74 if (Arr::get($formData, 'ship_to_different', '') === 'yes') {
75 $shippingCountry = Arr::get($formData, 'shipping_country', $fallbackCountry);
76 $shippingValidations = array_filter(CheckoutFieldsSchema::getCheckoutFieldsRequirements('shipping', 'physical'));
77 if (!Arr::has($shippingValidations, 'country')) {
78 $shippingCountry = Arr::get($formData, 'billing_country');
79 }
80
81 $this->shippingAddress = [
82 'full_name' => Arr::get($formData, 'shipping_full_name', ''),
83 'country' => $shippingCountry,
84 'address_1' => Arr::get($formData, 'shipping_address_1', ''),
85 'address_2' => Arr::get($formData, 'shipping_address_2', ''),
86 'city' => Arr::get($formData, 'shipping_city', ''),
87 'state' => Arr::get($formData, 'shipping_state', ''),
88 'postcode' => Arr::get($formData, 'shipping_postcode', ''),
89 'company_name' => Arr::get($formData, 'shipping_company_name', ''),
90 'phone' => Arr::get($formData, 'shipping_phone', ''),
91 ];
92 } else {
93 $this->shippingAddress = $this->billingAddress;
94 }
95 }
96
97 $this->config = $config;
98 }
99
100 public function render($config = [])
101 {
102 if ($config) {
103 $this->config = wp_parse_args($config, $this->config);
104 }
105
106 $this->wrapperStart();
107 $this->renderNotices();
108
109 $this->renderCheckoutForm();
110
111 $this->wrapperEnd();
112 }
113
114 public function getFragment($fragmentName)
115 {
116 $maps = [
117 'shipping_methods' => 'renderShippingOptions',
118 'payment_methods' => 'renderPaymentMethods',
119 'cart_summary_fragment' => 'renderSummaryFragment'
120 ];
121
122 if (isset($maps[$fragmentName])) {
123 ob_start();
124 $this->{$maps[$fragmentName]}();
125 return ob_get_clean();
126 }
127 return '';
128
129 }
130
131 public function wrapperStart()
132 {
133 $classNames = [
134 'fluent-cart-checkout-page',
135 'fct-checkout',
136 'fct-checkout-type-' . $this->cart->cart_group
137 ];
138 $configClass = Arr::get($this->config, 'wrapper_class', '');
139
140 if ($configClass) {
141 $classNames[] = $configClass;
142 }
143
144 $classNames = apply_filters('fluent_cart/checkout_page_css_classes', $classNames, [
145 'cart' => $this->cart
146 ]);
147
148 $classNames = array_filter(array_unique($classNames));
149
150 $atts = [
151 'class' => implode(' ', $classNames),
152 'data-fluent-cart-checkout-page' => '',
153 ];
154
155 do_action('fluent_cart/before_checkout_page_start', [
156 'cart' => $this->cart
157 ]);
158 ?>
159 <div <?php RenderHelper::renderAtts($atts); ?> role="main"
160 aria-label="<?php esc_attr_e('Checkout Page', 'fluent-cart'); ?>">
161 <?php
162 $hookData = [
163 'cart' => $this->cart
164 ];
165 do_action_deprecated('fluent_cart/afrer_checkout_page_start', [$hookData], '1.3.16', 'fluent_cart/after_checkout_page_start', 'Use fluent_cart/after_checkout_page_start instead of fluent_cart/afrer_checkout_page_start.');
166 do_action('fluent_cart/after_checkout_page_start', $hookData);
167 }
168
169 public function renderNotices()
170 {
171 $notices = Arr::get($this->cart->checkout_data, '__cart_notices', []);
172 $hookedNotices = apply_filters('fluent_cart/checkout_page_notices', [], [
173 'cart' => $this->cart
174 ]);
175 if (!$notices && !$hookedNotices) {
176 return;
177 }
178 ?>
179 <div class="fct-cart-notices" role="status" aria-live="polite">
180 <?php foreach ($notices as $notice):
181 if (empty($notice['content'])) {
182 continue;
183 } ?>
184 <div class="fct-alert">
185 <?php echo wp_kses_post($notice['content']); ?>
186 </div>
187 <?php endforeach; ?>
188 <?php foreach ($hookedNotices as $notice):
189 if (empty($notice['content'])) {
190 continue;
191 } ?>
192 <div class="fct-alert">
193 <?php echo wp_kses_post($notice['content']); ?>
194 </div>
195 <?php endforeach; ?>
196 </div>
197 <?php
198 }
199
200 public function renderCheckoutForm()
201 {
202 global $wp;
203 $current_url = home_url(add_query_arg([], $wp->request));
204 $current_url = add_query_arg(App::request()->all(), $current_url);
205
206 $formAttributes = [
207 'method' => 'POST',
208 'data-fluent-cart-checkout-page-checkout-form' => '',
209 'class' => 'fct_checkout fluent-cart-checkout-page-checkout-form',
210 'action' => $current_url,
211 'enctype' => 'multipart/form-data',
212 ];
213 do_action('fluent_cart/before_checkout_form', ['cart' => $this->cart]);
214 ?>
215 <form <?php RenderHelper::renderAtts($formAttributes); ?>
216 aria-label="<?php esc_attr_e('Checkout Form', 'fluent-cart'); ?>">
217 <?php do_action('fluent_cart/checkout_form_opening', ['cart' => $this->cart]); ?>
218 <div class="fct_checkout_inner">
219 <div class="fct_checkout_form">
220 <div class="fct_checkout_form_items">
221 <?php $this->renderNameFields(); ?>
222
223
224 <?php $this->renderCreateAccountField(); ?>
225
226 <?php do_action('fluent_cart/before_billing_fields', ['cart' => $this->cart]); ?>
227
228 <?php $this->renderAddressFields(); ?>
229 <div class="fct_checkout_shipping_methods <?php echo $this->requireShipping ? '' : 'is-hidden' ?>">
230 <?php $this->renderShippingOptions(); ?>
231 </div>
232
233 <?php $this->agreeTerms(); ?>
234
235 <?php do_action('fluent_cart/before_payment_methods', ['cart' => $this->cart]); ?>
236
237 <div class="fct_checkout_payment_methods " data-fluent-cart-checkout-payment-methods>
238 <?php $this->renderPaymentMethods(); ?>
239 </div>
240
241 <?php do_action('fluent_cart/after_payment_methods', ['cart' => $this->cart]); ?>
242
243 <?php $this->renderCheckoutButton(); ?>
244
245 <?php do_action('fluent_cart/after_checkout_button', ['cart' => $this->cart]); ?>
246
247 </div>
248 </div>
249 <div class="fct_checkout_summary">
250 <div class="fct_summary active" data-fluent-cart-checkout-page-checkout-form-order-summary
251 aria-labelledby="order-summary-heading">
252 <span id="order-summary-heading" class="sr-only">
253 <?php esc_html_e('Order Summary', 'fluent-cart'); ?>
254 </span>
255
256 <?php (new CartSummaryRender($this->cart))->render(); ?>
257 <?php $this->renderOrderNoteField(); ?>
258 <?php do_action('fluent_cart/after_order_notes', ['cart' => $this->cart]); ?>
259 </div>
260 </div>
261 </div>
262 </form>
263 <?php
264 do_action('fluent_cart/after_checkout_form', ['cart' => $this->cart]);
265 }
266
267 public function wrapperEnd()
268 {
269 do_action('fluent_cart/before_checkout_page_close', [
270 'cart' => $this->cart
271 ]);
272 ?>
273 </div>
274 <?php
275 do_action('fluent_cart/after_checkout_page', [
276 'cart' => $this->cart
277 ]);
278 }
279
280 public function renderCreateAccountField($atts = [])
281 {
282 //check store settings also
283 $attr_title = Arr::get($atts, 'title');
284 $extraClass = Arr::get($atts, 'wrapper_atts') ? '' : 'fct-has-default-font-size';
285
286 ?>
287 <?php if (!is_user_logged_in() && $this->storeSettings->get('user_account_creation_mode') === 'user_choice'): ?>
288 <div class="fct_allow_create_account_wrapper <?php echo esc_attr($extraClass); ?>">
289 <?php
290 $formRender = new FormFieldRenderer();
291 $label = __('Create an account?', 'fluent-cart');
292 if (!empty($attr_title)) {
293 $label = $attr_title;
294 }
295 $formRender->renderField([
296 'type' => 'checkbox',
297 'id' => 'allow_create_account',
298 'name' => 'allow_create_account',
299 'checkbox_value' => 'yes',
300 'label' => $label,
301 'value' => Arr::get($this->cart->checkout_data, 'form_data.allow_create_account', ''),
302 'wrapper_class' => 'fct_create_account_wrapper',
303 ]);
304 ?>
305 </div>
306 <?php endif;?>
307
308 <?php
309 }
310
311 public function renderNameFields()
312 {
313 $schema = CheckoutFieldsSchema::getNameEmailFieldsSchema($this->cart);
314 if (!$schema) {
315 return;
316 }
317 (new FormFieldRenderer())->renderSection($schema);
318 }
319
320 public function validateAddressField($config, $fields)
321 {
322
323 $type = Arr::get($config, 'type', 'billing'); // billing or shipping
324
325 $customer = CustomerResource::getCurrentCustomer();
326 if ($customer) {
327 $requirementsFields = CheckoutFieldsSchema::getCheckoutFieldsRequirements(
328 $type,
329 Arr::get($config, 'product_type'),
330 Arr::get($config, 'with_shipping')
331 );
332 $allowedAddresses = AddressHelper::getCustomerValidatedAddresses($config, $customer);
333
334 if (!empty($allowedAddresses)) {
335 $primaryAddress = AddressHelper::getPrimaryAddress(
336 $allowedAddresses,
337 $config,
338 $customer,
339 $type
340 );
341
342
343 $addressLabel = $type === 'billing' ?
344 __('Billing Address', 'fluent-cart') :
345 __('Shipping Address', 'fluent-cart');
346
347 $countries = LocalizationManager::getInstance()->countries();
348
349 return [
350 'address_select' => [
351 'type' => 'address_select',
352 'address_type' => $type,
353 'options' => $allowedAddresses,
354 'title' => $addressLabel,
355 'label' => '',
356 'countries' => $countries,
357 'primary_address' => $primaryAddress,
358 'value' => Arr::get($primaryAddress, 'id'),
359 'requirements_fields' => $requirementsFields
360 ]
361 ];
362 }
363
364 return $fields;
365 }
366
367 return $fields;
368 }
369
370 public function renderAddressFields()
371 {
372 $requireShipping = $this->requireShipping;
373 // $formData = Arr::get($this->cart->checkout_data, 'form_data', []);
374
375
376 // $billingAddress = $this->billingAddress;
377
378
379 // $billingAddress['type'] = 'billing';
380 // $billingAddress['product_type'] = $requireShipping ? 'physical' : 'digital';
381 // $billingAddress['with_shipping'] = $requireShipping && Arr::get($formData, 'ship_to_different', '') !== 'yes';
382 // $billingAddress['billing_address_id'] = Arr::get($formData, 'billing_address_id', '');
383
384 // $billingFields = CheckoutFieldsSchema::getAddressBaseFields($billingAddress);
385
386 // $billingFields = $this->validateAddressField($billingAddress, $billingFields);
387
388
389 // foreach ($billingFields as &$field) {
390 // if(empty($field['wrapper_atts'])) {
391 // $field['wrapper_atts'] = [];
392 // }
393 // $field['wrapper_atts']['data-fluent-cart-checkout-page-form-input-wrapper'] = '';
394 // }
395
396 // $billingFields = $this->maybeRearrangeAddressFields($billingFields);
397
398
399 // $billingFields = apply_filters('fluent_cart/checkout_renderer/billing_fields', $billingFields, [
400 // 'checkout_renderer' => $this,
401 // 'cart' => $this->cart
402 // ]);
403
404
405 // $formRender = new FormFieldRenderer();
406
407 echo '<div class="fct_checkout_billing_and_shipping">';
408
409 $this->renderBillingAddressFields();
410
411 if (!$requireShipping) {
412 echo '</div>';
413 return;
414 }
415
416 $this->renderShipToDifferentField();
417 do_action('fluent_cart/after_billing_fields_section', ['cart' => $this->cart]);
418
419 $this->renderShippingAddressFields();
420 echo '</div>';
421 }
422
423 public function renderBillingAddressFields($section_title = '')
424 {
425
426 $formData = Arr::get($this->cart->checkout_data, 'form_data', []);
427 $requireShipping = $this->requireShipping;
428 $billingAddress = $this->billingAddress;
429
430 $billingAddress['type'] = 'billing';
431 $billingAddress['product_type'] = $requireShipping ? 'physical' : 'digital';
432 $billingAddress['with_shipping'] = $requireShipping && Arr::get($formData, 'ship_to_different', '') !== 'yes';
433 $billingAddress['billing_address_id'] = Arr::get($formData, 'billing_address_id', '');
434 $billingAddress['order_id'] = $this->cart->order_id ?? null;
435
436 $billingFields = CheckoutFieldsSchema::getAddressBaseFields($billingAddress);
437
438 $billingFields = $this->validateAddressField($billingAddress, $billingFields);
439
440 foreach ($billingFields as &$field) {
441 if (empty($field['wrapper_atts'])) {
442 $field['wrapper_atts'] = [];
443 }
444 $field['wrapper_atts']['data-fluent-cart-checkout-page-form-input-wrapper'] = '';
445 }
446
447 $billingFields = $this->maybeRearrangeAddressFields($billingFields);
448
449
450 $billingFields = apply_filters('fluent_cart/checkout_renderer/billing_fields', $billingFields, [
451 'checkout_renderer' => $this,
452 'cart' => $this->cart
453 ]);
454
455 do_action('fluent_cart/before_billing_fields_section', ['cart' => $this->cart]);
456
457 $formRender = new FormFieldRenderer();
458 $title = __('Billing Address', 'fluent-cart');
459 if (!empty($section_title)) {
460 $title = $section_title;
461 }
462
463 $formRender->renderSection([
464 'id' => 'billing_address_section_section',
465 'type' => 'section',
466 'heading' => $title,
467 'fields' => $billingFields,
468 'wrapper_atts' => [
469 'data-fluent-cart-checkout-page-form-section' => '',
470 'role' => 'region',
471 'aria-label' => __('Billing Address', 'fluent-cart')
472 ]
473 ]);
474 }
475
476 public function renderShipToDifferentField($atts = [])
477 {
478 $formRender = new FormFieldRenderer();
479 $attr_title = Arr::get($atts, 'title');
480 $extraClass = Arr::get($atts, 'wrapper_atts') ? '' : 'fct-has-default-font-size';
481 $title = __('Ship to a different address?', 'fluent-cart');
482 if (!empty($attr_title)) {
483 $title = $attr_title;
484 }
485
486 ?>
487
488 <div class="fct_ship_to_different_wrapper <?php echo esc_attr($extraClass); ?>">
489 <?php
490 $formRender->renderField([
491 'type' => 'checkbox',
492 'id' => 'ship_to_different',
493 'name' => 'ship_to_different',
494 'checkbox_value' => 'yes',
495 'label' => $title,
496 'value' => Arr::get($this->cart->checkout_data, 'form_data.ship_to_different', ''),
497 'extra_atts' => [
498 'data-fluent-cart-ship-to-different-address' => 'yes',
499 'aria-controls' => 'shipping_address_section_section',
500 ],
501 ]);
502 ?>
503 </div>
504
505 <?php
506 }
507
508 public function renderShippingAddressFields($section_title = '')
509 {
510 $formData = Arr::get($this->cart->checkout_data, 'form_data', []);
511
512 $formRender = new FormFieldRenderer();
513
514 $shippingAddress = $this->shippingAddress;
515 $shippingAddress['type'] = 'shipping';
516 $shippingAddress['product_type'] = 'physical';
517 $shippingAddress['shipping_address_id'] = Arr::get($formData, 'shipping_address_id', '');
518
519 $shippingFields = CheckoutFieldsSchema::getAddressBaseFields($shippingAddress);
520
521 $shippingFields = $this->validateAddressField($shippingAddress, $shippingFields);
522
523 $shippingFields = apply_filters('fluent_cart/checkout_renderer/shipping_fields', $shippingFields, [
524 'checkout_renderer' => $this,
525 'cart' => $this->cart
526 ]);
527
528 foreach ($shippingFields as &$field) {
529 if (empty($field['wrapper_atts'])) {
530 $field['wrapper_atts'] = [];
531 }
532 $field['wrapper_atts']['data-fluent-cart-checkout-page-form-input-wrapper'] = '';
533 }
534
535 $shippingFields = $this->maybeRearrangeAddressFields($shippingFields);
536
537 $title = __('Shipping Address', 'fluent-cart');
538 if (!empty($section_title)) {
539 $title = $section_title;
540 }
541
542 do_action('fluent_cart/before_shipping_fields_section', ['cart' => $this->cart]);
543 $formRender->renderSection([
544 'id' => 'shipping_address_section_section',
545 'type' => 'section',
546 'heading' => $title,
547 'fields' => $shippingFields,
548 'wrapper_atts' => [
549 'data-fluent-cart-checkout-page-shipping-fields' => '',
550 'style' => Arr::get($this->cart->checkout_data, 'form_data.ship_to_different', '') === 'yes' ? '' : 'display:none',
551 'role' => 'region',
552 'aria-label' => __('Shipping Address', 'fluent-cart')
553 ]
554 ]);
555 do_action('fluent_cart/after_shipping_fields_section', ['cart' => $this->cart]);
556 }
557
558 public function renderOrderNoteField($attr_title = '')
559 {
560 if (
561 !$this->requireShipping &&
562 apply_filters('fluent_cart/disable_order_notes_for_digital_products', true, [
563 'cart' => $this->cart
564 ])
565 ) {
566 return;
567 }
568
569 $noteTitle = __('Leave a Note', 'fluent-cart');
570 if (!empty($attr_title)) {
571 $noteTitle = $attr_title;
572 }
573 $fieldId = 'order_notes';
574
575 (new FormFieldRenderer())->renderField([
576 'type' => 'textarea',
577 'id' => $fieldId,
578 'name' => 'order_notes',
579 'aria-label' => __('Order Notes', 'fluent-cart'),
580 'placeholder' => __('Notes about your order, e.g. Leave it at my doorstep.', 'fluent-cart'),
581 'extra_atts' => [
582 'rows' => 4
583 ],
584 'wrapper_atts' => [
585 'data-fct-item-toggle' => '',
586 'class' => 'fct-toggle-field fct_order_note'
587 ],
588 'before_callback' => function ($field) use ($fieldId, $noteTitle) {
589 $toggleId = 'order_notes_toggle';
590 $wrapperId = 'order_notes_wrapper';
591 ?>
592
593 <button type="button" data-fct-item-toggle-control id="<?php echo esc_attr($toggleId); ?>"
594 class="fct-toggle-control fct_order_note_toggle" aria-expanded="false"
595 aria-controls="<?php echo esc_attr($wrapperId); ?>">
596 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none">
597 <path d="M15.6 12.0001L10.2 17.4001V6.6001L15.6 12.0001Z" fill="currentColor" />
598 </svg>
599 <?php echo esc_html($noteTitle); ?>
600 </button>
601
602 <div id="<?php echo esc_attr($wrapperId); ?>" class="fct_toggle-wrapper fct_order_note_wrapper" aria-hidden="true">
603 <?php
604 },
605 'after_callback' => function ($field) {
606 echo '</div>';
607 }
608 ]);
609
610 do_action('fluent_cart/after_order_notes_field', ['cart' => $this->cart]);
611 }
612
613 public function renderShippingOptions()
614 {
615 if (!$this->requireShipping) {
616 return;
617 }
618
619 $countryCode = $this->shippingAddress['country'] ?: $this->billingAddress['country'];
620 $stateCode = $this->shippingAddress['state'] ?: $this->billingAddress['state'];
621 $billingValidations = array_filter(CheckoutFieldsSchema::getCheckoutFieldsRequirements('billing', 'physical'));
622
623 if (!isset($billingValidations['country'])) {
624 $countryCode = (new StoreSettings())->get('store_country');
625 }
626
627 $availableShippingMethods = AddressHelper::getShippingMethods($countryCode, $stateCode);
628
629
630 $selectedId = Arr::get($this->cart->checkout_data, 'shipping_data.shipping_method_id', '');
631
632 if (!$availableShippingMethods || is_wp_error($availableShippingMethods)) {
633 (new ShippingMethodsRender($availableShippingMethods, $selectedId))->render();
634 } else {
635 foreach ($availableShippingMethods as $method) {
636 $method->charge_amount = CartHelper::calculateShippingMethodCharge($method, $this->cart->cart_data);
637 }
638
639 (new ShippingMethodsRender($availableShippingMethods, $selectedId))->render();
640 }
641 }
642
643 /**
644 * Get available shipping methods considering cart's shipping classes.
645 * This is used by the frontend to display profile-aware methods.
646 */
647 public function getProfileAwareShippingMethods($countryCode, $stateCode)
648 {
649 // Get general methods (backward compatible)
650 $methods = AddressHelper::getShippingMethods($countryCode, $stateCode);
651
652 if (is_wp_error($methods) || empty($methods)) {
653 return $methods;
654 }
655
656 return $methods;
657 }
658
659 public function renderPaymentMethods($atts = [])
660 {
661 if ($this->cart->getEstimatedTotal() <= 0) {
662 if (!$this->cart->hasSubscription() || $this->cart->getEstimatedRecurringTotal() <= 0) {
663 return '';
664 }
665 }
666
667 $selectedPaymentMethod = Arr::get($this->cart->checkout_data, 'form_data._fct_pay_method', '');
668 $activePaymentMethods = PaymentMethods::getActiveMethodInstance($this->cart);
669
670 $activePaymentMethods = apply_filters('fluent_cart/checkout_active_payment_methods', $activePaymentMethods, [
671 'cart' => $this->cart
672 ]);
673
674 if (!$selectedPaymentMethod && !empty($activePaymentMethods)) {
675 $selectedPaymentMethod = $activePaymentMethods[0] ? $activePaymentMethods[0]->getMeta('route') : '';
676 }
677
678 $checkoutMethodStyle = $this->storeSettings->get('checkout_method_style', 'logo');
679
680 ?>
681 <div id="fluent_payment_methods" class="fluent_payment_methods">
682 <div class="fct_checkout_form_section" aria-labelledby="payment_methods_label" role="radiogroup">
683 <div class="fct_form_section_header">
684 <h4 id="payment_methods_label" class="fct_form_section_header_label">
685 <?php esc_html_e('Payment', 'fluent-cart'); ?>
686 </h4>
687 </div>
688 <div class="fct_form_section_body">
689 <div
690 class="fct_payment_methods_list fct_payment_method_mode_<?php echo esc_attr($checkoutMethodStyle); ?>">
691 <?php if (!empty($activePaymentMethods)): ?>
692 <?php foreach ($activePaymentMethods as $method): ?>
693 <?php
694 $isSelected = ($selectedPaymentMethod === $method->getMeta('route'));
695
696 $this->renderPaymentMethod($method, [
697 'selected_id' => $selectedPaymentMethod,
698 'style' => $checkoutMethodStyle,
699 'aria_checked' => $isSelected ? 'true' : 'false',
700 'role' => 'radio'
701 ]);
702 ?>
703 <?php endforeach; ?>
704 <?php else: ?>
705 <?php
706 $emptyText = esc_html__('No Payment method is activated for this site yet.', 'fluent-cart');
707 if (current_user_can('manage_options')) {
708 $emptyText .= '<a href="' . esc_url(URL::getDashboardUrl('settings/payments')) . '" target="_blank">' . esc_html__('Activate from settings.', 'fluent-cart') . '</a>';
709 }
710 echo '<div class="fct-empty-state">' . wp_kses_post($emptyText) . '</div>';
711 ?>
712 <?php endif; ?>
713 </div>
714 </div>
715 </div>
716 </div>
717 <?php
718 }
719
720 public function renderCheckoutButton($atts = '')
721 {
722
723 $placeOrderButtonText = apply_filters('fluent_cart/checkout_page_order_button_text', __('Place order', 'fluent-cart'));
724 $attributes = [
725 'type' => 'submit',
726 'class' => 'fct_place_order_btn large',
727 'id' => 'fluent_cart_order_btn',
728 'data-fluent-cart-checkout-page-checkout-button' => '',
729 'data-value' => $placeOrderButtonText,
730 'disabled' => ''
731 ];
732
733 // Parse $atts using WordPress shortcode_atts or wp_parse_args
734 if (!empty($atts)) {
735 // Extract attributes from string
736 $parsed = shortcode_parse_atts($atts);
737
738 if (isset($parsed['class'])) {
739 $attributes['class'] .= ' ' . $parsed['class'];
740 unset($parsed['class']);
741 }
742
743 $attributes = array_merge($attributes, $parsed);
744 }
745
746 ?>
747 <div class="fct_place_order_btn_wrap">
748 <button <?php RenderHelper::renderAtts($attributes); ?>>
749 <?php echo esc_html($placeOrderButtonText); ?>
750 </button>
751 </div>
752 <?php
753 }
754
755 protected function renderPaymentMethod($method, $config = [])
756 {
757 $route = $method->getMeta('route');
758 $methodTitle = $method->getMeta('title');
759 $methodStyle = Arr::get($config, 'style', 'logo');
760
761 $inputAttributes = array_filter([
762 'class' => 'form-radio-input',
763 'type' => 'radio',
764 'name' => '_fct_pay_method',
765 'id' => 'fluent_cart_payment_method_' . $route,
766 'value' => $route,
767 'required' => true,
768 'checked' => $route === Arr::get($config, 'selected_id', '') ? 'true' : '',
769 'role' => Arr::get($config, 'role', 'radio'),
770 'aria-checked' => Arr::get($config, 'aria_checked', 'false'),
771 ]);
772
773 $wrapperClass = $methodStyle === 'logo' ? 'fct_payment_method_logo' : 'fct_payment_method';
774
775 $wrapperAttributes = [
776 'class' => $wrapperClass . ' ' . 'fct_payment_method_wrapper fct_payment_method_' . $route,
777 'tabindex' => '0',
778 'role' => 'presentation'
779 ];
780
781 ?>
782 <div <?php RenderHelper::renderAtts($wrapperAttributes); ?>>
783 <span class="fct-payment-method-loader">
784 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
785 <circle cx="12" cy="12" r="10" opacity="0.2" fill="none" stroke="currentColor" stroke-miterlimit="10"
786 stroke-width="2.5"></circle>
787
788 <path d="m12,2c5.52,0,10,4.48,10,10" fill="none" stroke="currentColor" stroke-linecap="round"
789 stroke-miterlimit="10" stroke-width="2.5">
790 <animateTransform attributeName="transform" attributeType="XML" type="rotate" dur="0.5s"
791 from="0 12 12" to="360 12 12" repeatCount="indefinite"></animateTransform>
792 </path>
793 </svg>
794 </span>
795
796 <input <?php RenderHelper::renderAtts($inputAttributes); ?> />
797 <label for="<?php echo esc_attr('fluent_cart_payment_method_' . $route); ?>">
798 <?php
799 if ($methodStyle === 'logo') {
800 $method->prepare('logo', $this->hasSubscription);
801 } else {
802 $method->prepare('radio', $this->hasSubscription);
803 }
804 ?>
805
806 <?php echo esc_html($methodTitle); ?>
807 </label>
808 <?php if ($method->getMeta('instructions')): ?>
809 <div class="fct_payment_method_instructions" style="display: none;">
810 <?php echo wp_kses_post($method->getMeta('instructions')); ?>
811 </div>
812 <?php endif; ?>
813 <div class="fluent-cart-checkout_embed_payment_wrapper">
814 <?php
815 $pmContext = [
816 'route' => $route,
817 'method_title' => $methodTitle,
818 'method_style' => $methodStyle,
819 ];
820 $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.');
821 $paymentMethodClass = apply_filters('fluent_cart/payment_method_list_class', $paymentMethodClass, $pmContext);
822
823 ?>
824 <div class="<?php echo "fluent-cart-checkout_embed_payment_container fluent-cart-checkout_embed_payment_container_" . esc_attr($route . ' ' . $paymentMethodClass); ?>"
825 aria-hidden="true">
826 <?php do_action(
827 'fluent_cart/checkout_embed_payment_method_content',
828 [
829 'method' => $method,
830 'cart' => $this->cart,
831 'route' => $route
832 ]
833 ); ?>
834 </div>
835 </div>
836 </div>
837 <?php
838 }
839
840 private function maybeRearrangeAddressFields($fields)
841 {
842 if (isset($fields['city']) && isset($fields['postcode'])) {
843 $cityField = $fields['city'];
844 $postcodeField = $fields['postcode'];
845 unset($fields['city'], $fields['postcode']);
846 $fields['city_postal'] = [
847 'type' => 'sub_section',
848 'wrapper_class' => 'fct_2_columns fct_checkout_city_postcode',
849 'fields' => [
850 'city' => $cityField,
851 'postcode' => $postcodeField
852 ],
853 ];
854 }
855
856 if (isset($fields['phone'])) {
857 $phoneField = $fields['phone'];
858 unset($fields['phone']);
859 $fields['phone'] = $phoneField;
860 }
861
862 if (isset($fields['company_name'])) {
863 $companyField = $fields['company_name'];
864 unset($fields['company_name']);
865 $fields['company_name'] = $companyField;
866 }
867
868 return $fields;
869 }
870
871
872 public function agreeTerms($atts = [])
873 {
874 if (!CheckoutFieldsSchema::isTermsVisible()) {
875 return;
876 }
877
878 $termsText = CheckoutFieldsSchema::getTermsText();
879 $sectionId = 'agree_terms_section';
880 $extraClass = Arr::get($atts, 'wrapper_atts') ? '' : 'fct-has-default-font-size';
881 $title = Arr::get($atts, 'title');
882
883 ?>
884 <div class="fct_checkout_form_section <?php echo esc_attr($extraClass); ?>" role="group" aria-labelledby="agree_terms_label" data-fct-checkout-form-section>
885 <div class="fct_form_section_body">
886 <div class="fct_checkout_agree_terms">
887 <div>
888 <label for="agree_terms" class="fct_input_label fct_input_label_checkbox">
889 <input data-fluent-cart-agree-terms="yes" type="checkbox" class="fct-input fct-input-checkbox"
890 id="agree_terms" name="agree_terms" value="yes" required aria-required="true"
891 aria-label="<?php echo esc_attr($termsText); ?>">
892 <?php
893 if (!empty($title)) {
894 echo esc_html($title);
895 } else {
896 echo wp_kses_post($termsText);
897 } ?>
898 </label>
899 <span
900 id="<?php echo esc_attr($sectionId); ?>"
901 data-fluent-cart-checkout-page-form-error=""
902 class="fct_form_error fct_error_<?php echo esc_attr($sectionId); ?>"
903 role="alert"
904 aria-live="polite"
905 ></span>
906 </div>
907 </div>
908 </div>
909 </div>
910 <?php }
911
912 private function renderSummaryFragment()
913 {
914 (new CartSummaryRender($this->cart))->render(false);
915 }
916 }
917