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

754 lines 33.9 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
593 $activePaymentMethods = apply_filters('fluent_cart/checkout_active_payment_methods', $activePaymentMethods, [
594 'cart' => $this->cart
595 ]);
596
597 // filter the active payment methods to only include the selected payment method
598 $selectedModalPaymentMethod = apply_filters('fluent_cart/modal_checkout/filter_active_payment_methods', []);
599 if (!empty($selectedModalPaymentMethod)) {
600 $activePaymentMethods = array_filter($activePaymentMethods, function ($method) use ($selectedModalPaymentMethod) {
601 return in_array($method->getMeta('route'), $selectedModalPaymentMethod);
602 });
603 }
604
605 if (!$selectedPaymentMethod && !empty($activePaymentMethods)) {
606 $selectedPaymentMethod = $activePaymentMethods[0] ? $activePaymentMethods[0]->getMeta('route') : '';
607 }
608
609 $checkoutMethodStyle = $this->storeSettings->get('checkout_method_style', 'logo');
610
611 ?>
612 <div id="fluent_payment_methods" class="fct_modal_payment_methods fluent_payment_methods">
613 <!-- Payment Methods -->
614 <div class="fct_payment_methods_list fct_payment_method_mode_<?php echo esc_attr($checkoutMethodStyle); ?>">
615 <?php if (!empty($activePaymentMethods)): ?>
616 <?php foreach ($activePaymentMethods as $method) {
617 $isSelected = ($selectedPaymentMethod === $method->getMeta('route'));
618
619 $this->renderPaymentMethod($method, [
620 'selected_id' => $selectedPaymentMethod,
621 'style' => $checkoutMethodStyle,
622 'aria_checked' => $isSelected ? 'true' : 'false',
623 'role' => 'radio'
624 ]);
625 } ?>
626 <?php else: ?>
627 <?php
628 $emptyText = esc_html__('No Payment method is activated for this site yet.', 'fluent-cart');
629 if (current_user_can('manage_options')) {
630 $emptyText .= '<a href="' . esc_url(URL::getDashboardUrl('settings/payments')) . '" target="_blank">' . esc_html__('Activate from settings.', 'fluent-cart') . '</a>';
631 }
632 echo '<div class="fct-empty-state">' . wp_kses_post($emptyText) . '</div>';
633 ?>
634 <?php endif; ?>
635 </div>
636
637 <!-- Payment Method Content -->
638 <div class="fct_payment_method_contents">
639 <?php foreach ($activePaymentMethods as $method) {
640 $this->renderPaymentMethodContent($method, [
641 'style' => $checkoutMethodStyle
642 ]);
643 } ?>
644 </div>
645
646
647
648 </div>
649 <?php
650 }
651
652
653 public function renderPaymentMethod($method, $config = [])
654 {
655 $route = $method->getMeta('route');
656 $methodTitle = $method->getMeta('title');
657 $methodStyle = Arr::get($config, 'style', 'logo');
658
659 $inputAttributes = array_filter([
660 'class' => 'form-radio-input',
661 'type' => 'radio',
662 'name' => '_fct_pay_method',
663 'id' => 'fluent_cart_payment_method_' . $route,
664 'value' => $route,
665 'required' => true,
666 'checked' => $route === Arr::get($config, 'selected_id', '') ? 'true' : '',
667 'role' => Arr::get($config, 'role', 'radio'),
668 'aria-checked' => Arr::get($config, 'aria_checked', 'false'),
669 ]);
670
671 $wrapperClass = $methodStyle === 'logo' ? 'fct_payment_method_logo' : 'fct_payment_method';
672
673 $wrapperAttributes = [
674 'class' => $wrapperClass . ' ' . 'fct_payment_method_wrapper fct_payment_method_' . $route,
675 'tabindex' => '0',
676 'role' => 'presentation'
677 ];
678
679 ?>
680 <div <?php RenderHelper::renderAtts($wrapperAttributes); ?>>
681 <span class="fct-payment-method-loader">
682 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
683 <circle cx="12" cy="12" r="10" opacity="0.2" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="2.5"></circle>
684
685 <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">
686 <animateTransform attributeName="transform" attributeType="XML" type="rotate" dur="0.5s" from="0 12 12" to="360 12 12" repeatCount="indefinite"></animateTransform>
687 </path>
688 </svg>
689 </span>
690
691 <input <?php RenderHelper::renderAtts($inputAttributes); ?>/>
692
693 <label for="<?php echo esc_attr('fluent_cart_payment_method_' . $route); ?>">
694 <?php
695 if ($methodStyle === 'logo') {
696 $method->prepare('logo', $this->hasSubscription);
697 } else {
698 $method->prepare('radio', $this->hasSubscription);
699 }
700 ?>
701
702 <?php echo esc_html($methodTitle); ?>
703 </label>
704 </div>
705 <?php
706 }
707
708 public function renderPaymentMethodContent($method, $config = []) {
709 $route = $method->getMeta('route');
710 $methodTitle = $method->getMeta('title');
711 $methodStyle = Arr::get($config, 'style', 'logo');
712 $pmContext = [
713 'route' => $route,
714 'method_title' => $methodTitle,
715 'method_style' => $methodStyle,
716 ];
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.');
718 $paymentMethodClass = apply_filters('fluent_cart/payment_method_list_class', $paymentMethodClass, $pmContext);
719
720 ?>
721 <div class="fluent-cart-checkout_embed_payment_wrapper">
722 <?php if ($method->getMeta('instructions')): ?>
723 <div class="fct-modal-payment-method-instructions">
724 <?php echo wp_kses_post($method->getMeta('instructions')); ?>
725 </div>
726 <?php endif; ?>
727
728 <div class="<?php echo "fluent-cart-checkout_embed_payment_container fluent-cart-checkout_embed_payment_container_" . esc_attr($route . ' ' . $paymentMethodClass); ?>"
729 aria-hidden="true">
730 <?php do_action(
731 'fluent_cart/checkout_embed_payment_method_content',
732 [
733 'method' => $method,
734 'cart' => $this->cart,
735 'route' => $route
736 ]
737 ); ?>
738 </div>
739 </div>
740
741 <?php
742 }
743
744
745
746
747
748
749
750
751
752
753 }
754