PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.12.8
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.12.8
3.13.3 3.13.2 3.13.1 3.13.0 3.12.13 3.12.12 3.12.11 3.12.10 3.12.9 3.12.8 3.12.7 3.12.6 3.12.5 3.12.4 3.12.3 3.12.1 3.12.2 3.12.0 3.11.1 3.11.0 3.10.9 3.10.8 3.10.7 3.10.6 2.8.16 All 261 releases
wpfunnels / includes / core / offer / class-wpfnl-offer.php

class-wpfnl-offer.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 3.12.8, at includes/core/offer/class-wpfnl-offer.php

1,870 lines 77.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPFunnels\Offer;
4
5 use WPFunnels\Frontend\Module\Wpfnl_Frontend_Module;
6 use WPFunnels\Wpfnl_functions;
7
8 use WPFunnels\lms\helper\Wpfnl_lms_learndash_functions;
9 use WPFunnels\Gateway\Payment_Gateways_Factory;
10 use WPFunnelsPro\Wpfnl_Pro;
11 use WPFunnels\Discount\WpfnlDiscount;
12 use WPFunnels\Wpfnl;
13
14 class Wpfnl_Offer extends Wpfnl_Frontend_Module
15 {
16 public $stripe;
17 public $offerStatus;
18 public function __construct()
19 {
20
21 add_action( 'wp_enqueue_scripts', array( $this, 'load_offer_scripts' ) );
22 add_action( 'wp_footer', array( $this, 'load_offer_loader' ) );
23
24 /**
25 * init upsell/downsell gateways
26 * if user is in funnel
27 */
28 add_action( 'woocommerce_pre_payment_complete', array( $this, 'trigger_upsell_downsell_process' ), 99, 1 );
29
30 /**
31 * trigger the upsell/downsell process if this is not
32 * triggered on previous hooks
33 */
34 add_action( 'woocommerce_checkout_order_processed', array( $this, 'maybe_trigger_upsell_downsell_process' ), 100, 3 );
35
36 add_shortcode('funnel_sell_accept', [$this, 'funnel_upsell_shortcode_button']);
37 add_shortcode('funnel_sell_reject', [$this, 'funnel_upsell_reject_shortcode_button']);
38
39 add_action('wp_ajax_wpfnl_upsell_accepted', [$this, 'process_upsell_accepted_action']);
40 add_action('wp_ajax_nopriv_wpfnl_upsell_accepted', [$this, 'process_upsell_accepted_action']);
41
42 add_action('wp_ajax_wpfnl_upsell_rejected', [$this, 'process_upsell_rejected_action']);
43 add_action('wp_ajax_nopriv_wpfnl_upsell_rejected', [$this, 'process_upsell_rejected_action']);
44
45
46 add_action('wp_ajax_wpfnl_downsell_accepted', [$this, 'process_downsell_accepted_action']);
47 add_action('wp_ajax_nopriv_wpfnl_downsell_accepted', [$this, 'process_downsell_accepted_action']);
48
49 add_action('wp_ajax_wpfnl_downsell_rejected', [$this, 'process_downsell_rejected_action']);
50 add_action('wp_ajax_nopriv_wpfnl_downsell_rejected', [$this, 'process_downsell_rejected_action']);
51
52 add_action('wp_ajax_wpfnl_after_process_with_auth_ajax', [$this, 'wpfnl_after_process_with_auth_ajax']);
53 add_action('wp_ajax_nopriv_wpfnl_after_process_with_auth_ajax', [$this, 'wpfnl_after_process_with_auth_ajax']);
54
55
56 //== Discount price support ==//
57 add_filter('wpfnl_offer_custom_price', [$this, 'wpfnl_modify_product_price'], 10, 1);
58 add_filter('wpfnl_offer_product_price', [$this, 'wpfnl_modify_product_price'], 10, 1);
59
60
61 add_action('wpfunnels/after_offer_button', [$this, 'after_offer_button'], 10);
62 add_filter('woocommerce_is_checkout', [$this, 'make_offer_page_as_checkout'],10);
63 add_filter('wpfunnels/current_step_id', [$this, 'current_step_id'],10, 2);
64 add_filter('wpfunnels/order_meta', [$this, 'update_order_key'],10, 2);
65
66
67 add_action('woocommerce_checkout_order_processed', [$this, 'before_offer_checkout_process'], 10, 2);
68 add_action('woocommerce_checkout_update_order_meta', [$this, 'save_checkout_fields'], 10, 2);
69
70 add_action('wp_ajax_wpfnl_load_payment', [$this, 'add_offer_product_to_cart']);
71 add_action('wp_ajax_nopriv_wpfnl_load_payment', [$this, 'add_offer_product_to_cart']);
72 add_action('woocommerce_after_checkout_validation', [$this, 'remove_checkout_validation'], 10, 2);
73
74 wp_ajax_helper()->handle('wpfnl-get-variation-id')
75 ->with_callback([ $this, 'wpfnl_get_variation_id' ]);
76
77 // Register admin AJAX handler for saving offer settings
78 wp_ajax_helper()->handle('update-offer-settings')
79 ->with_callback([ $this, 'update_settings' ])
80 ->with_validation([
81 'logged_in' => true,
82 'user_can' => 'wpf_manage_funnels',
83 ]);
84
85 }
86
87 /**
88 * Save offer (upsell/downsell) settings from admin
89 *
90 * @param array $payload The settings data from Vue component
91 * @return array Response with success status and message
92 * @since 3.1.0
93 */
94 public function update_settings($payload) {
95 $step_id = sanitize_text_field($payload['step_id']);
96 $products = isset($payload['products']) ? $payload['products'] : '';
97 $type = sanitize_text_field($payload['type']);
98 $discount = isset($payload['discount']) ? $payload['discount'] : '';
99
100 // Save products if provided
101 if($products){
102 update_post_meta( $step_id, '_wpfnl_'.$type.'_products', $products );
103 }
104
105 // Save discount settings
106 update_post_meta( $step_id, '_wpfnl_'.$type.'_discount', $discount );
107
108 $step_info = array(
109 'step_id' => $step_id,
110 'step_type' => $type,
111 );
112
113 // Handle replacement settings (pro feature if available)
114 if( isset($payload['replaceSettings']) && $payload['replaceSettings'] == 'true' ){
115 update_post_meta( $step_info['step_id'], '_wpfnl_'.$step_info['step_type'].'_replacement_settings', 'true' );
116
117 // Only handle OrderReplacement if pro plugin is available
118 if( class_exists('WPFunnelsPro\Admin\Modules\Steps\Module\OrderReplacement') ){
119 $offerReplacement = isset($payload['offerReplacement']) ? $payload['offerReplacement'] : array();
120 $replacement_type = isset($offerReplacement['replacement_type']) && $offerReplacement['replacement_type'] ? $offerReplacement['replacement_type'] : '';
121 $value = isset($offerReplacement['value']) && $offerReplacement['value'] ? $offerReplacement['value'] : '';
122 \WPFunnelsPro\Admin\Modules\Steps\Module\OrderReplacement::save_replace_order_condition( $step_info, $replacement_type, $value );
123 }
124 }else{
125 update_post_meta( $step_info['step_id'], '_wpfnl_'.$step_info['step_type'].'_replacement_settings', 'false' );
126 delete_post_meta( $step_info['step_id'], '_wpfnl_'.$step_info['step_type'].'_replacement' );
127 }
128
129 // Save time-bound discount settings
130 if( isset($payload['timeBoundDiscount'])){
131 update_post_meta($step_id, '_wpfnl_time_bound_discount_settings', $payload['timeBoundDiscount']);
132 }
133
134 return array(
135 'success' => true,
136 'step_id' => $step_id,
137 'message' => __('Product Saved Successfully', 'wpfnl')
138 );
139 }
140
141 /**
142 * Removes the checkout validation for the upsell module.
143 * This is required to prevent the checkout validation from running on the offer page.
144 *
145 * @param array $data The data submitted during checkout.
146 * @param array $errors The validation errors.
147 * @return array The modified data and errors.
148 * @since 2.0.5
149 */
150 public function remove_checkout_validation($data, $errors) {
151 if ( isset( $_POST['wpfnl_offer_page'] ) ) {
152 $errors->errors = [];
153 $errors->error_messages = [];
154 $errors->error_data = [];
155 }
156 }
157
158
159 /**
160 * Adds the offer product to the cart.
161 * This method is used to add the offer product to the cart when the user clicks on the offer button.
162 *
163 * @return void
164 * @since 2.0.5
165 */
166 public function add_offer_product_to_cart(){
167 $product_id = isset( $_POST['product_id'] ) ? intval( $_POST['product_id'] ) : 0;
168 $quantity = isset( $_POST['quantity'] ) ? intval( $_POST['quantity'] ) : 0;
169 $variation_id = isset( $_POST['variation_id'] ) ? intval( $_POST['variation_id'] ) : 0;
170 $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0;
171 if( isset( $_POST['is_variable'], $_POST['attr'] ) && $_POST['is_variable'] == 'true' ){
172 $payload = [
173 'product_id' => $product_id,
174 'data' => $_POST['attr'],
175 ];
176 $variation_id = $this->wpfnl_get_variation_id($payload);
177 }
178
179
180 $total = $this->get_discounted_price( $step_id, $product_id, $quantity );
181 $total = floatval($total)/intval($quantity);
182 $custom_data = [
183 'custom_price' => $total,
184 ];
185
186 // Make sure WC cart is loaded before using it
187 if (!function_exists('WC') || !WC()->cart) {
188 wc_load_cart();
189 }
190
191 // Check again to ensure cart is available
192 if (function_exists('WC') && WC()->cart) {
193 WC()->cart->empty_cart();
194 WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, [], $custom_data);
195 }
196
197 $result = [
198 'success' => true,
199 ];
200 wp_send_json($result);
201 }
202
203
204 /**
205 * Calculates the discounted price for a given step, product, and quantity.
206 *
207 * @param int $step_id The ID of the step.
208 * @param int $product_id The ID of the product.
209 * @param int $quantity The quantity of the product.
210 * @return float The discounted price.
211 * @since 2.0.5
212 */
213 public function get_discounted_price( $step_id, $product_id, $quantity ){
214 $discount_instance = new WpfnlDiscount();
215
216
217 $type = get_post_meta($step_id, '_step_type', true);
218 $discount_data = get_post_meta($step_id, '_wpfnl_'.$type.'_discount', true);
219 $product = wc_get_product( $product_id );
220 $total = isset($discount_data['discountApplyTo']) && 'regular' === $discount_data['discountApplyTo'] ? $product->get_regular_price() : $product->get_sale_price();
221 $total = !$total ? $product->get_price() : $total;
222 $total = floatval($total) * intval($quantity);
223 if( $discount_instance->maybe_time_bound_discount( $step_id ) && !$discount_instance->maybe_validate_discount_time( $step_id ) ) {
224 return $total;
225 }
226 if( isset($discount_data['discountType']) && $discount_data['discountType'] && 'original' !== $discount_data['discountType'] ){
227
228 $discount_amount = $discount_instance->get_discount_amount( $discount_data['discountType'], $discount_data['discountValue'], $total );
229 $discount_amount = filter_var($discount_amount, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
230 $discount_amount = floatval($discount_amount);
231 $total = floatval($total) - floatval($discount_amount);
232 }
233
234 return $total;
235 }
236
237
238 /**
239 * Saves the checkout fields for an order.
240 * This method is used to save the offer details on the order.
241 *
242 * @param int $order_id The ID of the order.
243 * @param array $posted The posted data from the checkout form.
244 * @return void
245 * @since 2.0.5
246 */
247 public function save_checkout_fields( $order_id, $posted ){
248
249 if ( isset( $_POST['wpfnl_offer_page'] ) ) {
250 $order = wc_get_order($order_id);
251 $step_id = isset($_POST['wpfnl_step_id']) ? $_POST['wpfnl_step_id'] : 0;
252 $funnel_id = isset($_POST['wpfnl_funnel_id']) ? $_POST['wpfnl_funnel_id'] : 0;
253 $type = isset($_POST['wpfnl_offer_type']) ? $_POST['wpfnl_offer_type'] : '';
254 $parent_order_id = isset($_POST['wpfnl_parent_order_id']) ? $_POST['wpfnl_parent_order_id'] : 0;
255 $parent_order_key = isset($_POST['wpfnl_parent_order_key']) ? $_POST['wpfnl_parent_order_key'] : '';
256
257 $order->update_meta_data('_wpfunnels_offer', 'yes');
258 $order->update_meta_data('_wpfunnels_order', 'yes');
259 $order->update_meta_data('_wpfunnels_offer_type', sanitize_text_field($type));
260 $order->update_meta_data('_wpfunnels_parent_funnel_id', intval($funnel_id));
261 $order->update_meta_data('_wpfunnels_funnel_id', intval($funnel_id));
262 $order->update_meta_data('_wpfunnels_offer_step_id', intval($step_id));
263 $order->update_meta_data('_wpfunnels_offer_parent_id', intval($parent_order_id));
264 $parent_order = wc_get_order($parent_order_id);
265 $order->update_meta_data('_wpfunnels_offer_parent_key', $parent_order_key);
266
267 $offer_orders_meta = $parent_order->get_meta('_wpfunnels_offer_child_orders');
268 if ( !is_array($offer_orders_meta) ) {
269 $offer_orders_meta = [];
270 }
271
272 $offer_orders_meta[$order->get_id()] = ['type' => $type];
273 $parent_order->update_meta_data('_wpfunnels_offer_child_orders', $offer_orders_meta);
274 $parent_order->save();
275 $order->calculate_totals();
276 $order->save();
277
278 $actual_product_id = '';
279 $quantity = '';
280 foreach ($order->get_items() as $item_id => $item) {
281 $product_id = $item->get_product_id();
282 $variation_id = $item->get_variation_id();
283 $actual_product_id = $variation_id ? $variation_id : $product_id;
284 $quantity = $item->get_quantity();
285 break; // Exit the loop after the first iteration
286 }
287
288 $offer_product = self::update_variation_product_details( $step_id, $actual_product_id, $quantity, $order_id );
289 /**
290 * $order /WC_Order object
291 * $step_type String
292 * $offer_product Array
293 * $offer_product['step_id'] String represent the associate step id
294 * $offer_product['id'] String represent the id of the product
295 * $offer_product['name'] String represent the name of the product
296 * $offer_product['desc'] String represent the description of the product
297 * $offer_product['qty'] String represent the quantity of the product
298 * $offer_product['original_price'] String represent the price of the product
299 * $offer_product['unit_price'] String represent the unit_price of the product
300 * $offer_product['unit_price_tax'] String represent the unit_price with tax of the product
301 * $offer_product['args'] Array represent the extra arguments of the product
302 * $offer_product['args']['subtotal'] String represent the subtotal of the product
303 * $offer_product['args']['total'] String represent the total of the product
304 * $offer_product['price'] String represent the price of the product
305 * $offer_product['url'] String represent the url of the product
306 * $offer_product['total_unit_price_amount'] String represent the $unit_price_tax * $product_qty of the product
307 * $offer_product['total'] String represent the $custom_price of the product if any
308 * $offer_product['cancel_main_order'] Bool checker if cancel main order is enabled/disabled from funnel settings
309 */
310 ob_start();
311 do_action( 'wpfunnels/offer_accepted', $order, $offer_product );
312 ob_get_clean();
313 }
314 }
315
316
317 /**
318 * Executes before the offer checkout process.
319 * This method is used to set the billing and shipping address on the new order.
320 *
321 * @param int $order_id The ID of the order.
322 * @param array $posted_data The data posted during checkout.
323 * @return void
324 * @since 2.0.5
325 */
326 public function before_offer_checkout_process($order_id, $posted_data) {
327 if ( isset( $_POST['wpfnl_offer_page']) && isset( WC()->session ) ) {
328 $parent_order_id = $_POST['wpfnl_parent_order_id'];
329 if ($order_id) {
330 $this->set_address_on_new_order($order_id, $parent_order_id);
331 }
332 }
333 }
334
335
336 /**
337 * Sets the address on a new order based on the previous order.
338 *
339 * @param int $order_id The ID of the new order.
340 * @param int $previous_order_id The ID of the previous order.
341 * @return void
342 * @since 2.0.5
343 */
344 private function set_address_on_new_order($order_id, $previous_order_id) {
345 $order = wc_get_order($order_id);
346
347 if ( false === is_a( $order, 'WC_Order' ) ) {
348 return; // Order not found
349 }
350
351 $address = $this->get_address_from_previous_order($previous_order_id);
352
353 if ($address) {
354 // Set billing and shipping addresses
355 $order->set_address($address['billing'], 'billing');
356 $order->set_address($address['shipping'], 'shipping');
357
358 // Save the order
359 $order->save();
360 }
361 }
362
363
364 /**
365 * Retrieves the address from a previous order.
366 *
367 * @param int $previous_order_id The ID of the previous order.
368 * @return array|null The address details if found, null otherwise.
369 * @since 2.0.5
370 */
371 private function get_address_from_previous_order($previous_order_id) {
372 $previous_order = wc_get_order($previous_order_id);
373 if ( false === is_a( $previous_order, 'WC_Order' ) ) {
374 return false; // Previous order not found
375 }
376
377 // Get address from previous order
378 return array(
379 'billing' => $previous_order->get_address('billing'),
380 'shipping' => $previous_order->get_address('shipping')
381 );
382 }
383
384
385 /**
386 * Makes the offer page act as a checkout page.
387 * This is required for some payment gateways to work properly.
388 *
389 * @param bool $isCheckout Whether the offer page should act as a checkout page.
390 * @return void
391 * @since 2.0.5
392 */
393 public function make_offer_page_as_checkout($isCheckout){
394 if( self::is_upsell_downsell_step() && Wpfnl_functions::maybe_unsupported_payment_gateway() ){
395 return true;
396 }
397 return $isCheckout;
398 }
399
400
401 /**
402 * Sets the current step ID for the upsell module.
403 * This is required for some payment gateways to work properly.
404 *
405 * @param int $step_id The ID of the current step.
406 * @return int The ID of the current step.
407 * @since 2.0.5
408 */
409 public function current_step_id( $step_id, $order ){
410 $current_step_id = $order->get_meta('_wpfunnels_offer_step_id');
411 if( $current_step_id ){
412 return $current_step_id;
413 }
414 return $step_id;
415 }
416
417
418 /**
419 * Updates the order key.
420 * This is required for some payment gateways to work properly.
421 *
422 * @param array $query_args The query arguments.
423 * @param object $order The order object.
424 * @return array The updated query arguments.
425 * @since 2.0.5
426 */
427 public function update_order_key( $query_args, $order ){
428 $parent_order_id = $order->get_meta('_wpfunnels_offer_parent_id');
429 $parent_order_key = $order->get_meta('_wpfunnels_offer_parent_key');
430 if( $parent_order_id && $parent_order_key ){
431 $query_args = array(
432 'wpfnl-order' => $parent_order_id,
433 'wpfnl-key' => $parent_order_key,
434 'key' => $parent_order_key,
435 );
436 }
437 return $query_args;
438 }
439
440
441
442
443 /**
444 * This method is called after the offer button is displayed.
445 * It can be used to perform additional actions or modifications.
446 *
447 * @return void
448 * @since 2.0.5
449 */
450 public function after_offer_button(){
451 global $post;
452 $step_id = $post->ID;
453 $funnel_id = get_post_meta( $step_id, '_funnel_id', true );
454 $offer_type = get_post_meta( $step_id, '_step_type', true );
455 $order_id = ( isset( $_GET['wpfnl-order'] ) ) ? intval( $_GET['wpfnl-order'] ) : 0;
456 $order_key = ( isset( $_GET['wpfnl-key'] ) ) ? sanitize_text_field( wp_unslash( $_GET['wpfnl-key'] ) ) : '';
457 $offer_product = self::get_offer_product_data( $step_id, '', '', $order_id );
458 $quantity = 0;
459 $product_id = '';
460
461
462 if ( is_array($offer_product) && !empty($offer_product)) {
463 $product_id = $offer_product['id'];
464 $quantity = $offer_product['qty'];
465 }
466 $is_variable = false;
467 if( !$product_id ){
468 return;
469 }
470
471 // Make sure WC cart is loaded before using it
472 if (!function_exists('WC') || !WC()->cart) {
473 wc_load_cart();
474 }
475
476 // Check if cart is available before using it
477 if (function_exists('WC') && WC()->cart) {
478 WC()->cart->empty_cart();
479 WC()->cart->add_to_cart( $product_id, $quantity, 0, []);
480 }
481
482 ?>
483 <div class="wpfnl-offer-payment-form" style="display: none;">
484 <div class="wpfnl-offer-payment-form-inner">
485 <div class="wpfnl-offer-payment-form-wrapper">
486 <button class="close-modal" type="button">
487 <svg width="12" height="13" fill="none" viewBox="0 0 12 13" xmlns="http://www.w3.org/2000/svg"><path stroke="#7A8B9A" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 1.969l-10 9.99m0-9.99l10 9.99"></path></svg>
488 </button>
489
490 <h3 class="wpfnl-offer-payment-title"><?php
491 echo __( 'Complete Payment', 'wpfnl-pro' );
492 ?></h3>
493 <div class="modal-overflow wpfnl-checkout">
494 <div class="modal-loader">
495 <span class="wpfnl-loader"></span>
496 <h4><?php
497 echo __( 'Loading', 'wpfnl-pro' );
498 ?></h4>
499 <p><?php
500 echo __( 'Please wait...', 'wpfnl-pro' );
501 ?></p>
502 </div>
503
504 <form name="checkout" method="post" class="checkout woocommerce-checkout rex-checkoutify-checkout-form" action="<?php echo esc_url( wc_get_checkout_url() ); ?>" enctype="multipart/form-data" style="display: none">
505 <input type="hidden" name="wpfnl_offer_page" value="yes"/>
506 <input type="hidden" name="wpfnl_step_id" value="<?php echo $step_id;?>"/>
507 <input type="hidden" name="wpfnl_funnel_id" value="<?php echo $funnel_id;?>"/>
508 <input type="hidden" name="wpfnl_offer_type" value="<?php echo $offer_type;?>"/>
509 <input type="hidden" name="wpfnl_parent_order_id" value="<?php echo $order_id;?>"/>
510 <input type="hidden" name="wpfnl_parent_order_key" value="<?php echo $order_key;?>"/>
511 <?php
512 // Get checkout object
513 $checkout = WC()->checkout();
514 // Show billing country field
515 woocommerce_form_field( 'billing_country', $checkout->get_checkout_fields()['billing']['billing_country'], $checkout->get_value( 'billing_country' ) );
516 woocommerce_checkout_payment();
517 ?>
518 <form>
519 </div>
520 </div>
521 </div>
522 </div>
523 <?php
524 }
525
526
527 /**
528 * trigger upsell/downsell process
529 *
530 * @param $order_id
531 * @param $posted_data
532 * @param $order
533 *
534 * @since 1.0.0
535 */
536 public function maybe_trigger_upsell_downsell_process($order_id, $posted_data, $order) {
537 }
538
539
540 /**
541 * setup
542 *
543 * @param string $order_id
544 *
545 * @since 1.0.0
546 */
547 public function trigger_upsell_downsell_process( $order_id = '' ) {
548
549 if ( '' == $order_id ) {
550 return;
551 }
552 $order = wc_get_order( $order_id );
553 $this->start_upsell_downsell_process( $order );
554 }
555
556
557 /**
558 * start the upsell/downsell process
559 *
560 * @param $order
561 *
562 * @since 1.0.0
563 */
564 private function start_upsell_downsell_process($order) {
565 if ( ! is_object( $order ) ) {
566 return;
567 }
568
569 $order_gateway = $order->get_payment_method();
570 $payment_gateway_obj = Payment_Gateways_Factory::getInstance()->build_gateway($order_gateway);
571 }
572
573
574 /**
575 * enqueue upsell/downsell offer
576 * scripts
577 *
578 * @since 1.0.0
579 */
580 public function load_offer_scripts() {
581 if( Wpfnl_functions::is_wc_active() && self::is_upsell_downsell_step() ) {
582 // Enqueue the offer CSS file
583 wp_enqueue_style(
584 'wpfnl-offer',
585 WPFNL_DIR_URL . 'public/assets/css/wpfnl-offer.css',
586 array(),
587 WPFNL_VERSION
588 );
589
590 // Enqueue the offer JavaScript file
591 wp_enqueue_script(
592 'wpfnl-offer',
593 WPFNL_DIR_URL . 'public/assets/js/wpfnl-offer.js',
594 array( 'jquery' ),
595 WPFNL_VERSION,
596 true
597 );
598
599 global $post;
600 $step_id = $post->ID;
601 $funnel_id = get_post_meta( $step_id, '_funnel_id', true );
602 $step_type = get_post_meta( $step_id, '_step_type', true );
603 $order_id = ( isset( $_GET['wpfnl-order'] ) ) ? intval( $_GET['wpfnl-order'] ) : 0;
604 $order_key = ( isset( $_GET['wpfnl-key'] ) ) ? sanitize_text_field( wp_unslash( $_GET['wpfnl-key'] ) ) : '';
605 $order = wc_get_order( $order_id );
606 $payment_method = '';
607 $is_reference_transaction = false;
608 $skip_offer = 'no';
609 $offer_product = self::get_offer_product_data( $step_id, '', '', $order_id );
610 $maybe_unsupported_payment = Wpfnl_functions::maybe_unsupported_payment_gateway();
611 $quantity = 0;
612 $product_id = '';
613
614
615 if ( is_array($offer_product) && !empty($offer_product)) {
616 $product_id = $offer_product['id'];
617 $quantity = $offer_product['qty'];
618 }
619 $is_variable = false;
620 if( $product_id ){
621 $product = wc_get_product( $product_id );
622 if( $product && ($product->get_type() === 'variable' || 'variable-subscription' === $product->get_type()) ){
623 $is_variable = true;
624 }
625 }
626
627 if($order) {
628 $gateways = array( 'paypal','ppec_paypal' );
629 $payment_method = $order->get_payment_method();
630 // PayPal Standard can't do one-click upsells (no reference transactions),
631 // so treat the offer page like an unsupported gateway: show a fallback
632 // checkout form so the customer can still accept the offer.
633 if ( in_array( $payment_method, $gateways, true ) && ! $is_reference_transaction ) {
634 $maybe_unsupported_payment = true;
635 }
636 }
637 $currency_symbol = get_woocommerce_currency_symbol();
638
639 $localize_data = array(
640 'ajaxUrl' => admin_url('admin-ajax.php'),
641 'step_id' => $step_id,
642 'funnel_id' => $funnel_id,
643 'is_lms' => get_post_meta($funnel_id, '_wpfnl_funnel_type', true) === 'lms' ? 'yes' : 'no',
644 'product_id' => $product_id,
645 'is_variable' => $is_variable,
646 'quantity' => $quantity,
647 'order_id' => $order_id,
648 'order_key' => $order_key,
649 'offer_type' => get_post_meta($step_id, '_step_type', true),
650 'currency_symbol' => $currency_symbol,
651 'skip_offer' => $skip_offer,
652 'wpfnl_offer_nonce' => wp_create_nonce( 'wpfnl_offer_nonce' ),
653 'wpfnl_upsell_accepted_nonce' => wp_create_nonce( 'wpfnl_upsell_accepted_nonce' ),
654 'wpfnl_upsell_rejected_nonce' => wp_create_nonce( 'wpfnl_upsell_rejected_nonce' ),
655 'wpfnl_downsell_accepted_nonce' => wp_create_nonce( 'wpfnl_downsell_accepted_nonce' ),
656 'wpfnl_downsell_rejected_nonce' => wp_create_nonce( 'wpfnl_downsell_rejected_nonce' ),
657 'wpfnl_create_paypal_order_nonce'=> wp_create_nonce( 'wpfnl_create_paypal_order_nonce' ),
658 'wpfnl_capture_paypal_order_nonce'=> wp_create_nonce( 'wpfnl_capture_paypal_order_nonce' ),
659 'payment_gateway' => $payment_method,
660 'maybe_unsupported_payment' => $maybe_unsupported_payment ? 'yes' : 'no',
661 );
662
663 if ( 'stripe' === $payment_method ) {
664 $localize_data['wpfnl_stripe_sca_check_nonce'] = wp_create_nonce( 'wpfnl_stripe_sca_check_nonce' );
665 wp_register_script( 'stripe', 'https://js.stripe.com/v3/', '', '3.0', true );
666 wp_enqueue_script( 'stripe' );
667 }
668
669 if ( 'woocommerce_payments' === $payment_method ) {
670 $localize_data['wpfunnels_woop_create_payment_intent'] = wp_create_nonce( 'wpfunnels_woop_create_payment_intent' );
671 wp_register_script( 'stripe', 'https://js.stripe.com/v3/', '', '3.0', true );
672 wp_enqueue_script( 'stripe' );
673 }
674
675 if ( 'mollie_wc_gateway_creditcard' === $payment_method ) {
676 $localize_data['wpfnl_mollie_cc_process_nonce'] = wp_create_nonce( 'wpfnl_mollie_cc_process_nonce' );
677 }
678
679 if ( 'mollie_wc_gateway_creditcard' === $payment_method ) {
680 $localize_data['wpfnl_mollie_ideal_process_nonce'] = wp_create_nonce( 'wpfnl_mollie_ideal_process_nonce' );
681 }
682
683 $localize_script = '<!-- script to print WPFunnels Offer variables -->';
684 $localize_script .= '<script type="text/javascript">';
685 $localize_script .= 'var WPFunnelsOfferVars = ' . wp_json_encode( $localize_data ) . ';';
686 $localize_script .= '</script>';
687 echo $localize_script;
688 }else{
689 // Enqueue the offer CSS file for non-offer steps
690 wp_enqueue_style(
691 'wpfnl-offer',
692 WPFNL_DIR_URL . 'public/assets/css/wpfnl-offer.css',
693 array(),
694 WPFNL_VERSION
695 );
696
697 // Enqueue the offer JavaScript file for non-offer steps
698 wp_enqueue_script(
699 'wpfnl-offer',
700 WPFNL_DIR_URL . 'public/assets/js/wpfnl-offer.js',
701 array( 'jquery' ),
702 WPFNL_VERSION,
703 true
704 );
705
706 global $post;
707 $step_id = '';
708 if( $post ){
709 $step_id = $post->ID;
710 }
711
712 $funnel_id = get_post_meta( $step_id, '_funnel_id', true );
713
714 if( $funnel_id ){
715 $localize_data = array(
716 'ajaxUrl' => admin_url('admin-ajax.php'),
717 'step_id' => $step_id,
718 'funnel_id' => $funnel_id,
719 'is_lms' => get_post_meta($funnel_id, '_wpfnl_funnel_type', true) === 'lms' ? 'yes' : 'no',
720 'wpfnl_offer_nonce' => wp_create_nonce( 'wpfnl_offer_nonce' ),
721 'wpfnl_upsell_accepted_nonce' => wp_create_nonce( 'wpfnl_upsell_accepted_nonce' ),
722 'wpfnl_upsell_rejected_nonce' => wp_create_nonce( 'wpfnl_upsell_rejected_nonce' ),
723 'wpfnl_downsell_accepted_nonce' => wp_create_nonce( 'wpfnl_downsell_accepted_nonce' ),
724 'wpfnl_downsell_rejected_nonce' => wp_create_nonce( 'wpfnl_downsell_rejected_nonce' ),
725 'wpfnl_create_paypal_order_nonce'=> wp_create_nonce( 'wpfnl_create_paypal_order_nonce' ),
726 'wpfnl_capture_paypal_order_nonce'=> wp_create_nonce( 'wpfnl_capture_paypal_order_nonce' ),
727 );
728
729 $localize_script = '<!-- script to print WPFunnels Offer variables -->';
730 $localize_script .= '<script type="text/javascript">';
731 $localize_script .= 'var WPFunnelsOfferVars = ' . wp_json_encode( $localize_data ) . ';';
732 $localize_script .= '</script>';
733 echo $localize_script;
734 }
735
736 }
737 }
738
739
740 /**
741 * Load offer loader screen
742 *
743 * @since 1.0.0
744 */
745 public function load_offer_loader() {
746 if( Wpfnl_functions::is_wc_active() && self::is_upsell_downsell_step() ) {
747 ?>
748 <div class="wpfunnels-offer-loader">
749 <div class="wpfunnels-offer-loader-wrapper">
750 <div class="wpfnl-loader"></div>
751 <h2 class="loader-ittle">
752 Loading
753 <span class="dot-wrapper">
754 <span class="dot-one">.</span>
755 <span class="dot-two">.</span>
756 <span class="dot-three">.</span>
757 </span>
758 </h2>
759 <p class="description"><?php echo __('Please wait while processing your order', 'wpfnl'); ?></p>
760 </div>
761 </div>
762 <?php
763 }
764 }
765
766
767 /**
768 * Process upsell accept ajax action
769 *
770 * @since 1.0.0
771 */
772 public function process_upsell_accepted_action() {
773
774 $nonce = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
775
776 if ( $nonce && ! wp_verify_nonce( $nonce, 'wpfnl_upsell_accepted_nonce' ) ) {
777 return array(
778 'status' => 'success',
779 'message' => __( 'Unauthorized request', 'wpfnl-pro' )
780 );
781 }
782
783
784
785 $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0;
786 $funnel_id = isset( $_POST['funnel_id'] ) ? intval( $_POST['funnel_id'] ) : 0;
787 $order_id = isset( $_POST['order_id'] ) ? intval( $_POST['order_id'] ) : 0;
788 $order_key = isset( $_POST['order_key'] ) ? sanitize_text_field( wp_unslash($_POST['order_key']) ) : '';
789 $offer_type = isset( $_POST['offer_type'] ) ? sanitize_text_field( $_POST['offer_type'] ) : '';
790 $offer_action = isset( $_POST['offer_action'] ) ? sanitize_text_field( $_POST['offer_action'] ) : '';
791 $product_id = isset( $_POST['product_id'] ) ? intval( $_POST['product_id'] ) : 0;
792 $quantity = isset( $_POST['quantity'] ) ? intval( $_POST['quantity'] ) : 0;
793 $variation_id = isset( $_POST['variation_id'] ) ? intval( $_POST['variation_id'] ) : 0;
794 $step_type = get_post_meta($step_id, '_step_type', true);
795 $attr = [];
796 if( isset( $_POST['is_variable'], $_POST['attr'] ) && $_POST['is_variable'] == 'true' ){
797 $payload = [
798 'product_id' => $product_id,
799 'data' => $_POST['attr'],
800 ];
801 $attr = $_POST['attr'];
802
803 $product_id = $this->wpfnl_get_variation_id($payload);
804 }
805 if( $variation_id ){
806 $product_id = $variation_id;
807 }
808
809 $order = wc_get_order($order_id);
810
811 $wpfnl_functions_instance = new Wpfnl_functions();
812 if( method_exists($wpfnl_functions_instance,'is_valid_order_owner')){
813 if (!Wpfnl_functions::is_valid_order_owner($order, $order_key)) {
814 wp_send_json([
815 'status' => 'error',
816 'message' => __( 'Unauthorized request', 'wpfnl-pro' ),
817 ]);
818 }
819 }
820
821 if ( $order_id && $product_id ) {
822 $data = array(
823 'order_id' => $order_id,
824 'product_id' => $product_id,
825 'quantity' => $quantity,
826 'order_key' => $order_key,
827 'step_type' => $step_type,
828 'step_id' => $step_id,
829 'funnel_id' => $funnel_id,
830 );
831
832 $result = $this->offer_accepted( $step_id, $data , $attr );
833 wp_send_json($result);
834 } else {
835 wp_send_json([
836 'status' => 'error',
837 'message' => 'No product data found',
838 ]);
839 }
840 }
841
842
843
844 /**
845 * Process downsell accept ajax action
846 *
847 * @since 1.0.0
848 */
849 public function process_downsell_accepted_action() {
850
851 $nonce = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
852 if ( $nonce && ! wp_verify_nonce( $nonce, 'wpfnl_downsell_accepted_nonce' ) ) {
853 return array(
854 'status' => 'success',
855 'message' => __( 'Unauthorized request', 'wpfnl-pro' )
856 );
857 }
858
859 $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0;
860 $funnel_id = isset( $_POST['funnel_id'] ) ? intval( $_POST['funnel_id'] ) : 0;
861 $order_id = isset( $_POST['order_id'] ) ? intval( $_POST['order_id'] ) : 0;
862 $order_key = isset( $_POST['order_key'] ) ? sanitize_text_field( wp_unslash($_POST['order_key']) ) : '';
863 $offer_type = isset( $_POST['offer_type'] ) ? sanitize_text_field( $_POST['offer_type'] ) : '';
864 $offer_action = isset( $_POST['offer_action'] ) ? sanitize_text_field( $_POST['offer_action'] ) : '';
865 $product_id = isset( $_POST['product_id'] ) ? intval( $_POST['product_id'] ) : 0;
866 $quantity = isset( $_POST['quantity'] ) ? intval( $_POST['quantity'] ) : 0;
867 $variation_id = isset( $_POST['variation_id'] ) ? intval( $_POST['variation_id'] ) : 0;
868 $step_type = get_post_meta($step_id, '_step_type', true);
869 $attr = [];
870 if( isset( $_POST['is_variable'], $_POST['attr'] ) && $_POST['is_variable'] == 'true' ){
871 $payload = [
872 'product_id' => $product_id,
873 'data' => $_POST['attr'],
874 ];
875 $attr = $_POST['attr'];
876
877 $product_id = $this->wpfnl_get_variation_id($payload);
878 }
879
880 if( $variation_id ){
881 $product_id = $variation_id;
882 }
883
884 $order = wc_get_order($order_id);
885
886 $wpfnl_functions_instance = new Wpfnl_functions();
887 if( method_exists($wpfnl_functions_instance,'is_valid_order_owner')){
888 if (!Wpfnl_functions::is_valid_order_owner($order, $order_key)) {
889 wp_send_json([
890 'status' => 'error',
891 'message' => __( 'Unauthorized request', 'wpfnl-pro' ),
892 ]);
893 }
894 }
895
896 $all_products = [];
897 $product_data = array();
898
899 if ( $order_id && $product_id ) {
900
901 $data = array(
902 'order_id' => $order_id,
903 'product_id' => $product_id,
904 'quantity' => $quantity,
905 'order_key' => $order_key,
906 'step_type' => $step_type,
907 'step_id' => $step_id,
908 'funnel_id' => $funnel_id,
909 );
910 $result = $this->offer_accepted( $step_id, $data , $attr );
911
912 wp_send_json($result);
913 } else {
914 wp_send_json([
915 'status' => 'error',
916 'message' => 'No product data found',
917 ]);
918 }
919 }
920
921
922
923 /**
924 * Process upsell reject ajax action
925 *
926 * @since 1.0.0
927 */
928 public function process_upsell_rejected_action() {
929 $nonce = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
930 if ( ! wp_verify_nonce( $nonce, 'wpfnl_upsell_rejected_nonce' ) ) {
931 return array(
932 'status' => 'success',
933 'message' => __( 'Unauthorized request', 'wpfnl-pro' )
934 );
935 }
936
937 $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0;
938 $funnel_id = isset( $_POST['funnel_id'] ) ? intval( $_POST['funnel_id'] ) : 0;
939 $order_id = isset( $_POST['order_id'] ) ? intval( $_POST['order_id'] ) : 0;
940 $order_key = isset( $_POST['order_key'] ) ? sanitize_text_field( wp_unslash($_POST['order_key']) ) : '';
941 $product_id = isset( $_POST['product_id'] ) ? intval( $_POST['product_id'] ) : 0;
942 $quantity = isset( $_POST['quantity'] ) ? intval( $_POST['quantity'] ) : 0;
943 $step_type = get_post_meta($step_id, '_step_type', true);
944
945 $result = array(
946 'status' => 'failed',
947 'redirect' => '#',
948 );
949
950 if ( $step_id ) {
951
952 $type = get_post_meta( $funnel_id, '_wpfnl_funnel_type', true );
953 if( $type === 'lms' ){
954
955 /** Change lms order meta value */
956 Wpfnl_lms_learndash_functions::update_lms_order_status( $funnel_id, $step_id, get_current_user_id() );
957
958 $result['message'] = 'Redirecting...';
959 $result['status'] = 'success';
960 $result['redirect_url'] = $this->offer_rejected( $step_id, [] );
961 }else{
962 $result = array(
963 'status' => 'failed',
964 'redirect_url' => '#',
965 'message' => __( 'Order does not exist', 'wpfnl-pro' ),
966 );
967
968 if ( $order_id ) {
969 $data = array(
970 'order_id' => $order_id,
971 'product_id' => $product_id,
972 'quantity' => $quantity,
973 'order_key' => $order_key,
974 'step_type' => $step_type,
975 'step_id' => $step_id,
976 'funnel_id' => $funnel_id,
977 );
978 $result['message'] = 'Redirecting...';
979 $result['status'] = 'success';
980 $result['redirect_url'] = $this->offer_rejected( $step_id, $data );
981 }
982 }
983
984 }
985
986 // send json.
987 wp_send_json( $result );
988 }
989
990
991 /**
992 * Process downsell reject ajax action
993 *
994 * @since 1.0.0
995 */
996 public function process_downsell_rejected_action() {
997 $nonce = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
998 if ( ! wp_verify_nonce( $nonce, 'wpfnl_downsell_rejected_nonce' ) ) {
999 return array(
1000 'status' => 'success',
1001 'message' => __( 'Unauthorized request', 'wpfnl-pro' )
1002 );
1003 }
1004 $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0;
1005 $funnel_id = isset( $_POST['funnel_id'] ) ? intval( $_POST['funnel_id'] ) : 0;
1006 $order_id = isset( $_POST['order_id'] ) ? intval( $_POST['order_id'] ) : 0;
1007 $order_key = isset( $_POST['order_key'] ) ? sanitize_text_field( wp_unslash($_POST['order_key']) ) : '';
1008 $product_id = isset( $_POST['product_id'] ) ? intval( $_POST['product_id'] ) : 0;
1009 $quantity = isset( $_POST['quantity'] ) ? intval( $_POST['quantity'] ) : 0;
1010 $step_type = get_post_meta($step_id, '_step_type', true);
1011
1012 $result = array(
1013 'status' => 'failed',
1014 'redirect' => '#',
1015 );
1016
1017 if ( $step_id ) {
1018 $type = get_post_meta( $funnel_id, '_wpfnl_funnel_type', true );
1019 if( $type === 'lms' ){
1020
1021 /** Change lms order meta value */
1022 Wpfnl_lms_learndash_functions::update_lms_order_status( $funnel_id, $step_id, get_current_user_id() );
1023
1024 $result['message'] = 'Redirecting...';
1025 $result['status'] = 'success';
1026 $result['redirect_url'] = $this->offer_rejected( $step_id, [] );
1027 }else{
1028
1029 $result = array(
1030 'status' => 'failed',
1031 'redirect_url' => '#',
1032 'message' => __( 'Order does not exist', 'wpfnl-pro' ),
1033 );
1034
1035 if ( $order_id ) {
1036 $data = array(
1037 'order_id' => $order_id,
1038 'product_id' => $product_id,
1039 'quantity' => $quantity,
1040 'order_key' => $order_key,
1041 'step_type' => $step_type,
1042 'step_id' => $step_id,
1043 'funnel_id' => $funnel_id,
1044 );
1045 $result['message'] = 'Redirecting...';
1046 $result['status'] = 'success';
1047 $result['redirect_url'] = $this->offer_rejected( $step_id, $data );
1048 }
1049 }
1050 }
1051
1052 // send json.
1053 wp_send_json( $result );
1054 }
1055
1056
1057 /**
1058 * Process upsell accept ajax action with auth
1059 *
1060 * @since 1.0.0
1061 */
1062 public function wpfnl_after_process_with_auth_ajax()
1063 {
1064 $step_id = $_POST['step_id'];
1065 $order_id = $_POST['order_id'];
1066 $order_key = $_POST['order_key'];
1067 $step_type = get_post_meta($step_id, '_step_type', true);
1068 $intent_id = '';
1069
1070 if (isset($_POST['intent_id'])) {
1071 $intent_id = $_POST['intent_id'];
1072 }
1073
1074 $extra_data = [];
1075 $extra_data['step_id'] = $step_id;
1076 $extra_data['step_type'] = $step_type;
1077 $extra_data['order_id'] = $order_id;
1078 $extra_data['order_key'] = $order_key;
1079 $extra_data['intent_id'] = $intent_id;
1080
1081 $step_meta = get_post_meta($step_id, '_wpfnl_'.$step_type.'_products', true);
1082
1083 if ($step_meta[0]) {
1084 $step_meta = $step_meta[0];
1085 } else {
1086 wp_send_json([
1087 'status' => 'error',
1088 'message' => 'No attach product found',
1089 ]);
1090 }
1091
1092 $product_id = $step_meta['id'];
1093 $quantity = $step_meta['quantity'];
1094 $extra_data['product_id'] = $product_id;
1095 $extra_data['input_qty'] = $quantity;
1096
1097 $response = $this->offer_accepted($step_id, $extra_data);
1098 wp_send_json($response);
1099 }
1100
1101
1102 /**
1103 * offer accepted action
1104 *
1105 * @param $step_id
1106 * @param $data
1107 * @return array
1108 * @throws \Exception
1109 */
1110 public function offer_accepted( $step_id, $data , $attr = null) {
1111 $order_id = $data['order_id'];
1112 $funnel_id = $data['funnel_id'];
1113 $order_key = $data['order_key'];
1114 $input_qty = $data['quantity'];
1115 $order = wc_get_order($order_id);
1116 $skip_payment = filter_input(INPUT_POST, 'stripe_sca_payment', FILTER_VALIDATE_BOOLEAN);
1117 $order_gateway = $order->get_payment_method();
1118 if( 'stripe_cc' === $order_gateway ){
1119 $order_gateway = 'stripe';
1120 }
1121 $payment_gateway_obj = Payment_Gateways_Factory::getInstance()->build_gateway($order_gateway);
1122 $is_charge_success = false;
1123
1124 if ($skip_payment) {
1125 $_stripe_intent_id = get_post_meta($order->get_id(), '_stripe_intent_id_' . $step_id, true);
1126 $intent_id = isset( $_POST['stripe_intent_id'] ) ? sanitize_text_field( wp_unslash( $_POST['stripe_intent_id'] ) ) : '';
1127 $skip_payment = ($intent_id === $_stripe_intent_id) ? true : false;
1128 }
1129
1130 $offer_product = self::update_variation_product_details( $step_id, $data['product_id'], $input_qty, $order_id );
1131
1132 $product = wc_get_product($data['product_id']);
1133
1134 if( $product && !$product->is_in_stock() || $product->managing_stock() && ( $product->get_stock_quantity() < intval($offer_product['qty']) || $product->get_stock_quantity() == 0 || intval($offer_product['qty']) == 0 ) ) {
1135 if( 'variation' === $product->get_type() ) {
1136 $message = __( 'The selected variation is out of stock. Please, try choosing different variation.', 'wpfnl-pro' );
1137 }
1138 else {
1139 $message = __( 'Product is out of stock', 'wpfnl-pro' );
1140 }
1141 return array(
1142 'status' => 'failed',
1143 'message' => $message,
1144 );
1145 }
1146 $no_payment_gateway = false;
1147 $shipping = [];
1148
1149 if($offer_product['qty'] != 0){
1150 if ( (isset($offer_product['price']) && '' === trim($offer_product['price'])) || $skip_payment ) {
1151 $is_charge_success = array(
1152 'is_success' => true,
1153 'message' => '',
1154 );
1155
1156 if ( $skip_payment ) {
1157
1158 $response = \WC_Stripe_API::retrieve('charges?payment_intent=' . $intent_id);
1159 $charge_data = reset($response->data);
1160 if ($charge_data->status === 'succeeded') {
1161 $order->update_meta_data('_wpfunnels_offer_txn_resp_' . $step_id, $charge_data->id);
1162 $order->save();
1163 $is_charge_success = [
1164 'is_success' => true,
1165 'message' => 'Payment succeeded.',
1166 ];
1167 } else {
1168 $is_charge_success = [
1169 'is_success' => false,
1170 'message' => 'Payment failed: ' . ($charge_data->failure_message ?? 'Unknown error'),
1171 ];
1172 }
1173
1174 }
1175 } else {
1176 if ($payment_gateway_obj) {
1177 $tax_enabled = get_option( 'woocommerce_calc_taxes' );
1178 if ( 'yes' === $tax_enabled ) {
1179 if ( !wc_prices_include_tax() ) {
1180 $product = wc_get_product($offer_product['id']);
1181 $offer_product['total'] = $product ? wc_get_price_including_tax( $product, array( 'price' => $offer_product['total'] ) ) : '';
1182 }
1183 }
1184
1185 $offer_product_id = isset( $offer_product[ 'id' ] ) ? $offer_product[ 'id' ] : 0;
1186 $offer_total_price = isset( $offer_product[ 'total' ] ) ? $offer_product[ 'total' ] : 0;
1187 $shipping = $this->calculate_offer_product_shipping_price( $order, $offer_product_id, $offer_total_price );
1188 $shipping_price = isset( $shipping[ 'total' ] ) ? $shipping[ 'total' ] : 0;
1189 $shipping_tax = isset( $shipping[ 'total_tax' ] ) ? $shipping[ 'total_tax' ] : 0;
1190 $shipping_price = $shipping_price + $shipping_tax;
1191
1192 $offer_product[ 'price' ] = $offer_total_price + $shipping_price;
1193 $offer_product[ 'total' ] = $offer_total_price + $shipping_price;
1194
1195 $offer_product[ 'price' ] = $offer_total_price;
1196 $offer_product[ 'total' ] = $offer_total_price;
1197
1198 // Handle chained products if pro plugin is active
1199 $total_chained_product_price = 0;
1200 if( class_exists( '\WPFunnelsPro\Compatibility\ChainedProduct' ) && Wpfnl_functions::is_wc_active() ){
1201 $chained_product_class_instance = new \WPFunnelsPro\Compatibility\ChainedProduct();
1202 $chained_products = $chained_product_class_instance->get_chain_product_details( $offer_product['id'] );
1203
1204 if ( !empty($chained_products) ){
1205 foreach ($chained_products as $chained_product_id => $chianed_product){
1206 $new_product_price = 0;
1207 $product = wc_get_product( $chained_product_id );
1208 $chained_product = wc_get_product( $chained_product_id );
1209 if( 'yes' === $chianed_product['priced_individually'] ){
1210 $chained_product_price = $chained_product->get_price();
1211 if ( ! empty( $chained_product_price ) ) {
1212 $total_chained_product_price += $chained_product_price * $chianed_product['unit'];
1213 }
1214 }
1215 }
1216 }
1217 }
1218 $offer_product[ 'price' ] = $offer_product[ 'price' ] + $total_chained_product_price;
1219 $offer_product[ 'total' ] = $offer_product[ 'total' ] + $total_chained_product_price;
1220
1221 $is_charge_success = $payment_gateway_obj->process_payment( $order, $offer_product );
1222
1223
1224 }else{
1225 $no_payment_gateway = true;
1226 }
1227 }
1228 }
1229
1230 if( isset($is_charge_success['is_success'] ) && $is_charge_success['is_success'] ) {
1231 $response = self::after_offer_charged( $funnel_id, $step_id, $order_id, $order_key, $offer_product, true , $attr, '', '', $shipping );
1232 if( 'success' === $response['status'] ) {
1233 delete_option( 'wpfunnels_dynamic_offer_data' );
1234 $result = array(
1235 'status' => 'success',
1236 'message' => __( 'Offer product is added successfully', 'wpfnl-pro' ),
1237 );
1238 } else {
1239 $result = array(
1240 'status' => 'success',
1241 'message' => __( 'Offer product is not added', 'wpfnl-pro' ),
1242 );
1243 }
1244 } else{
1245 if( $no_payment_gateway ){
1246 $result = array(
1247 'status' => 'failed',
1248 'message' => __( 'No payment gateway found', 'wpfnl-pro' ),
1249 );
1250 }else{
1251 $result = array(
1252 'status' => 'failed',
1253 'message' => $is_charge_success['message'],
1254 );
1255 }
1256
1257 }
1258 $this->offerStatus = 'accept';
1259
1260 $result['redirect_url'] = $this->get_next_step_url( $order, $step_id, $funnel_id );
1261 $next_step = Wpfnl_functions::get_next_step( $funnel_id, $step_id );
1262 $next_step = apply_filters( 'wpfunnels/next_step_data', $next_step );
1263 $custom_url = Wpfnl_functions::custom_url_for_thankyou_page( $next_step[ 'step_id' ] );
1264
1265 if( $custom_url ){
1266 $result['redirect_url'] = $custom_url;
1267 }
1268 if( (isset($result['redirect_url']) && !$result['redirect_url']) ){
1269 $result['redirect_url'] = Wpfnl_functions::redirect_to_deafult_thankyou();
1270 }
1271 return $result;
1272 }
1273
1274
1275 /**
1276 * @param $step_id
1277 * @param $data
1278 * @return string
1279 */
1280 public function offer_rejected( $step_id, $data ) {
1281
1282 if( $data ){
1283 $order = wc_get_order($data['order_id']);
1284 $product_id = $data['product_id'];
1285 $input_qty = $data['quantity'];
1286 $this->offerStatus = 'reject';
1287 $offer_product = self::get_offer_product_data( $step_id, $product_id, $input_qty, $data['order_id'] );
1288 delete_option( 'wpfunnels_dynamic_offer_data' );
1289
1290 ob_start();
1291 do_action( 'wpfunnels/offer_rejected', $order, $offer_product );
1292 ob_get_clean();
1293
1294 $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $step_id );
1295 $next_step = Wpfnl_functions::get_next_step( $funnel_id, $step_id );
1296 $next_step = apply_filters( 'wpfunnels/next_step_data', $next_step );
1297 $custom_url = Wpfnl_functions::custom_url_for_thankyou_page( $next_step[ 'step_id' ] );
1298
1299 if( $order ){
1300 $order->update_meta_data('_wpfunnels_order', 'yes');
1301 $order->update_meta_data('_wpfunnels_funnel_id', $funnel_id);
1302 }
1303
1304 if( $custom_url ){
1305 return $custom_url;
1306 }
1307 if( $this->get_next_step_url( $order, $step_id, $funnel_id ) ){
1308 return $this->get_next_step_url( $order, $step_id, $funnel_id );
1309 }
1310
1311 return Wpfnl_functions::redirect_to_deafult_thankyou();
1312
1313 }else{
1314 $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $step_id );
1315 $step_type = get_post_meta( $step_id, '_step_type', true );
1316 $offer_product = get_post_meta( $step_id, '_wpfnl_' . $step_type . '_products', true );
1317
1318 if( isset($offer_product[0]) ){
1319 $offer_product[0]['step_id'] = $step_id;
1320
1321 ob_start();
1322 do_action( 'wpfunnels/offer_rejected', '', $offer_product[0] );
1323 ob_get_clean();
1324
1325 }
1326
1327 $next_step = Wpfnl_functions::get_next_step( $funnel_id, $step_id );
1328 $next_step = apply_filters( 'wpfunnels/next_step_data', $next_step );
1329 $custom_url = Wpfnl_functions::custom_url_for_thankyou_page( $next_step[ 'step_id' ] );
1330
1331 if( $custom_url ){
1332 return $custom_url;
1333 }
1334 return $this->get_next_step_url_for_lms( (object)[], $step_id, $funnel_id );
1335 }
1336
1337 }
1338
1339
1340 /**
1341 * get next step url
1342 *
1343 * @param \WC_Order $order
1344 * @param $step_id
1345 * @param $funnel_id
1346 * @return string
1347 */
1348 public function get_next_step_url_for_lms( $order , $step_id, $funnel_id ) {
1349 $current_page_id = $step_id;
1350 $next_step = Wpfnl_functions::get_next_step( $funnel_id, $current_page_id );
1351 $next_step = apply_filters( 'wpfunnels/next_step_data', $next_step );
1352
1353 if ( 'conditional' === $next_step['step_type'] ) {
1354 $condition = \WPFunnels\Conditions\Wpfnl_Condition_Checker::getInstance();
1355 $condition_identifier = strval($next_step['step_id']);
1356 $condition_matched = $condition->check_condition($funnel_id, $order, $condition_identifier, $current_page_id, $this->offerStatus);
1357 $node_found = Wpfnl_functions::get_node_id($funnel_id, $next_step['step_id']);
1358 if ($condition_matched) {
1359 $next_node = $this->go_to_output_1($funnel_id, $node_found);
1360 $next_step = Wpfnl_functions::get_step_by_node($funnel_id, $next_node);
1361 } else {
1362 $next_node = $this->go_to_output_2($funnel_id, $node_found);
1363 $next_step = Wpfnl_functions::get_step_by_node($funnel_id, $next_node);
1364 }
1365 } else {
1366 $next_step = $next_step['step_id'];
1367 }
1368 $redirect_url = get_post_permalink($next_step);
1369
1370
1371 $query_args = array(
1372 'wpfnl-order' => 'lms_order',
1373 'wpfnl-key' => 'lms_order',
1374 );
1375
1376
1377 return add_query_arg($query_args, $redirect_url);
1378 }
1379
1380
1381 /**
1382 * get next step url
1383 *
1384 * @param \WC_Order $order
1385 * @param $step_id
1386 * @param $funnel_id
1387 * @return string
1388 */
1389 public function get_next_step_url( \WC_Order $order , $step_id, $funnel_id ) {
1390 $next_step = Wpfnl_functions::get_next_conditional_step( $funnel_id, $step_id, $order, $this->offerStatus );
1391 $next_step = apply_filters( 'wpfunnels/next_step_data', $next_step );
1392 $next_step_id = isset($next_step['step_id']) ? $next_step['step_id'] : '';
1393
1394 if( $order ){
1395 $next_step = apply_filters( 'wpfunnels/modify_next_step_based_on_order', $next_step, $order );
1396 $next_step_id = isset($next_step['step_id']) ? $next_step['step_id'] : '';
1397 }
1398
1399 if( 'thankyou' === $next_step['step_type'] && Wpfnl_functions::is_global_thank_you_page_enabled() ){
1400 return Wpfnl_functions::redirect_to_deafult_thankyou();
1401 }
1402
1403 $redirect_url = $next_step_id ? get_post_permalink($next_step_id) : '';
1404 if( $order ){
1405 $query_args = array(
1406 'wpfnl-order' => $order->get_id(),
1407 'wpfnl-key' => $order->get_order_key(),
1408 'key' => $order->get_order_key(),
1409 );
1410 }else{
1411 $query_args = array(
1412 'wpfnl-order' => 'lms_order',
1413 'wpfnl-key' => 'lms_order',
1414 );
1415 }
1416 if( $redirect_url ){
1417 return add_query_arg($query_args, $redirect_url);
1418 }
1419 return false;
1420
1421 }
1422
1423
1424
1425 public function detect_upsell() {
1426 $type = '';
1427 if (get_post_type(get_the_ID()) == 'wpfunnel_steps') {
1428 $type = get_post_meta(get_the_ID(), '_step_type', true);
1429 if ($type == 'upsell' || $type == 'downsell') {
1430 if (isset($_GET['wpfnl-order']) && isset($_GET['wpfnl-key'])) {
1431 return;
1432 } else {
1433 $user = wp_get_current_user();
1434 if (!in_array('administrator', (array)$user->roles)) {
1435 echo '<p style="color:red;text-align:center;">You are not allowed to access this page directly</p>';
1436 wp_die();
1437 }
1438 }
1439 }
1440 }
1441 }
1442
1443
1444 public function wpfnl_modify_product_price($product_price) {
1445 if (class_exists('WOOMC\MultiCurrency\API')) {
1446 $api_obj = new WOOMC\MultiCurrency\API();
1447 $product_price = $api_obj->convert($product_price, get_woocommerce_currency(), get_option('woocommerce_currency'));
1448 }
1449
1450 return $product_price;
1451 }
1452
1453 public function funnel_upsell_shortcode_button() {
1454 global $post;
1455 return '
1456 <div class="wpfnl-upsell-btn-wrapper">
1457 <button type="button" id="upsell_funnel_target" data-id="' . $post->ID . '" name="button">
1458 BUY OFFER
1459 <span class="wpfnl-loader" id="wpfnl-loader-accept"></span>
1460 </button>
1461 <span class="wpfnl-alert" id="wpfnl-alert-accept"></span>
1462 </div>
1463 ';
1464 //wpfnl-success, wpfnl-error, box
1465 }
1466
1467 public function funnel_upsell_reject_shortcode_button() {
1468 global $post;
1469 return '
1470 <div class="wpfnl-upsell-btn-wrapper">
1471 <button type="button" id="upsell_funnel_reject" data-id="' . $post->ID . '" name="button">
1472 Not Interested
1473 <span class="wpfnl-loader" id="wpfnl-loader-reject"></span>
1474 </button>
1475 <span class="wpfnl-alert" id="wpfnl-alert-reject"></span>
1476 </div>
1477 ';
1478 //wpfnl-success, wpfnl-error, box
1479 }
1480
1481 public function go_to_output_1($funnel_id, $node_found) {
1482 $funnel_json = get_post_meta($funnel_id, '_funnel_data', true);
1483 if ($funnel_json) {
1484 // $funnel_data = json_decode($funnel_json, true);
1485 $node_data = $funnel_json['drawflow']['Home']['data'];
1486
1487 foreach ($node_data as $node_key => $node_value) {
1488 if ($node_value['id'] == $node_found) {
1489 $next_node = isset($node_value['outputs']['output_1']['connections'][0]['node']) ? $node_value['outputs']['output_1']['connections'][0]['node'] : '';
1490 return $next_node;
1491 }
1492 }
1493 return false;
1494 }
1495 }
1496
1497 public function go_to_output_2($funnel_id, $node_found) {
1498 $funnel_json = get_post_meta($funnel_id, '_funnel_data', true);
1499 if ($funnel_json) {
1500 // $funnel_data = json_decode($funnel_json, true);
1501 $node_data = $funnel_json['drawflow']['Home']['data'];
1502
1503 foreach ($node_data as $node_key => $node_value) {
1504 if ($node_value['id'] == $node_found) {
1505 $next_node = isset($node_value['outputs']['output_2']['connections'][0]['node']) ? $node_value['outputs']['output_2']['connections'][0]['node'] : '';
1506 return $next_node;
1507 }
1508 }
1509 return false;
1510 }
1511 }
1512
1513 public function get_conditional_redirect($step_id, $response, $type = 'accept') {
1514 $funnel_id = '';
1515 $condition_identifier = '';
1516 $group_conditions = get_post_meta($funnel_id, $condition_identifier, true);
1517 if ($group_conditions) {
1518
1519 // Loop through group condition.
1520 foreach ($group_conditions as $group) {
1521 if (empty($group)) {
1522 continue;
1523 }
1524
1525 $match_group = true;
1526 // Loop over rules and determine if all rules match.
1527 foreach ($group as $rule) {
1528 if (!$this->match_rule($rule, $order, $current_page_id)) {
1529 $match_group = false;
1530 break;
1531 }
1532 }
1533
1534 // If this group matches, show the field group.
1535 if ($match_group) {
1536 return true;
1537 }
1538 }
1539 }
1540
1541 // Return default.
1542 return false;
1543
1544 }
1545
1546 public function get_discount_data($step_id) {
1547 $data = [];
1548 $type = get_post_meta($step_id, '_step_type', true);
1549 if ($type == 'upsell') {
1550 $data['type'] = get_post_meta($step_id, '_wpfnl_upsell_discount_type', true);
1551 $data['value'] = get_post_meta($step_id, '_wpfnl_upsell_discount_value', true);
1552 } else {
1553 $data['type'] = get_post_meta($step_id, '_wpfnl_downsell_discount_type', true);
1554 $data['value'] = get_post_meta($step_id, '_wpfnl_downsell_discount_value', true);
1555 }
1556 return $data;
1557 }
1558
1559
1560 /**
1561 *
1562 */
1563 private function wpfnl_get_variation_id( $payload ){
1564 if( isset($payload['product_id'], $payload['data']) && is_array($payload['data']) ){
1565 $variation_id = (new \WC_Product_Data_Store_CPT())->find_matching_product_variation(
1566 new \WC_Product($payload['product_id']),
1567 $payload['data']
1568 );
1569 return $variation_id;
1570 }
1571 return false;
1572 }
1573
1574
1575 /**
1576 * @desc calculate shipping cost for offer steps (upsell/downsell)
1577 *
1578 * @param \WC_Order $order
1579 * @param $product_id
1580 * @param $total_price
1581 * @return int|mixed|void
1582 */
1583 private function calculate_offer_product_shipping_price( \WC_Order $order, $product_id, $total_price ) {
1584 if( !$product_id ) {
1585 return;
1586 }
1587 if( !WC()->shipping()->get_shipping_classes() ) {
1588 return;
1589 }
1590
1591 $product = wc_get_product( $product_id );
1592 if( $product && $product->is_virtual() ) {
1593 return;
1594 }
1595
1596 $shipping = [];
1597 $shipping_methods = $order->get_shipping_methods();
1598 $offer_settings = Wpfnl_functions::get_offer_settings();
1599 $offer_orders_option = isset( $offer_settings[ 'offer_orders' ] ) ? $offer_settings[ 'offer_orders' ] : '';
1600
1601 if( !empty( $shipping_methods ) ) {
1602 foreach( $shipping_methods as $key => $method ) {
1603 if( $method ){
1604 $shipping = [
1605 'name' => $method->get_name(),
1606 'method_title' => $method->get_method_title(),
1607 'method_id' => $method->get_method_id(),
1608 'instance_id' => $method->get_instance_id(),
1609 'total' => $method->get_total(),
1610 'total_tax' => $method->get_total_tax(),
1611 'taxes' => $method->get_taxes(),
1612 'tax_percentage' => ( $method->get_total() != 0 ) || ( $method->get_total() != 0.00 ) ? ( $method->get_total_tax() * 100 ) / $method->get_total() : 0,
1613 ];
1614 }
1615
1616 }
1617 }
1618
1619 if( 'main-order' === $offer_orders_option ) {
1620 $wc_shipping_zones = \WC_Shipping_Zones::get_zones();
1621
1622 if( !empty( $wc_shipping_zones ) && isset( $shipping[ 'method_id' ] ) && 'flat_rate' === $shipping[ 'method_id' ] ) {
1623 foreach( $wc_shipping_zones as $zone ) {
1624 if( isset( $zone[ 'shipping_methods' ] ) && !empty( $zone[ 'shipping_methods' ] ) ) {
1625 foreach( $zone[ 'shipping_methods' ] as $method ) {
1626 if( 'WC_Shipping_Flat_Rate' === get_class( $method ) ) {
1627 if( isset( $method->instance_id ) && isset( $shipping[ 'instance_id' ] ) ) {
1628 if( $method->instance_id == $shipping[ 'instance_id' ] ) {
1629 $product_shipping_class_id = $product->get_shipping_class_id();
1630 $instance_settings = isset( $method->instance_settings ) ? $method->instance_settings : [];
1631 $shipping_cost = isset( $instance_settings[ 'class_cost_' . $product_shipping_class_id ] ) ? $instance_settings[ 'class_cost_' . $product_shipping_class_id ] : $instance_settings[ 'no_class_cost' ];
1632 $shipping_cost = $shipping_cost ? $shipping_cost : 0;
1633 $tax_percentage = isset( $shipping[ 'tax_percentage' ] ) ? $shipping[ 'tax_percentage' ] : 0;
1634 $total_tax = ( $shipping_cost * $tax_percentage ) / 100;
1635 $shipping[ 'total' ] = $shipping_cost;
1636 $shipping[ 'total_tax' ] = $total_tax;
1637 $shipping[ 'taxes' ] = [
1638 'total' => [ $total_tax ]
1639 ];
1640 }
1641 }
1642 }
1643 }
1644 }
1645 }
1646 }
1647 }
1648 unset( $shipping[ 'tax_percentage' ] );
1649 return $shipping;
1650 }
1651
1652 /**
1653 * Check if current step is upsell or downsell
1654 *
1655 * @param bool $step_id
1656 * @return bool
1657 */
1658 public static function is_upsell_downsell_step( $step_id = false ) {
1659 $is_upsell_downsell = false;
1660 if ( Wpfnl_functions::is_funnel_step_page() ) {
1661 if ( ! $step_id ) {
1662 global $post;
1663 $step_id = $post->ID;
1664 }
1665 $step_type = get_post_meta( $step_id, '_step_type', true );
1666 if ( $step_type === 'upsell' || $step_type === 'downsell' ) {
1667 $is_upsell_downsell = true;
1668 }
1669 }
1670 return $is_upsell_downsell;
1671 }
1672
1673 /**
1674 * Get offer product data
1675 *
1676 * @param $step_id
1677 * @param string $selected_product_id
1678 * @param string $input_qty
1679 * @param int $order_id
1680 * @return array
1681 */
1682 public static function get_offer_product_data( $step_id, $selected_product_id = '', $input_qty = '', $order_id = 0 ) {
1683 // If pro plugin is available, use its method for full compatibility
1684 if ( class_exists( '\WPFunnelsPro\Wpfnl_Pro_functions' ) && method_exists( '\WPFunnelsPro\Wpfnl_Pro_functions', 'get_offer_product_data' ) ) {
1685 return \WPFunnelsPro\Wpfnl_Pro_functions::get_offer_product_data( $step_id, $selected_product_id, $input_qty, $order_id );
1686 }
1687
1688 $funnel_id = get_post_meta( $step_id, '_funnel_id', true );
1689 return apply_filters( 'wpfunnels/offer_product_data', self::get_offer_data( $step_id, $selected_product_id, $input_qty, $order_id ), $funnel_id, $step_id );
1690 }
1691
1692 /**
1693 * Get offer data
1694 *
1695 * @param $step_id
1696 * @param string $selected_product_id
1697 * @param string $input_qty
1698 * @param int $order_id
1699 * @return array
1700 */
1701 private static function get_offer_data( $step_id, $selected_product_id = '', $input_qty = '', $order_id = 0 ) {
1702 $data = array();
1703 $product_id = 0;
1704 $product_qty = 0;
1705
1706 $step_type = get_post_meta( $step_id, '_step_type', true );
1707 $offer_product = get_post_meta( $step_id, '_wpfnl_' . $step_type . '_products', true );
1708
1709 if ( isset( $offer_product[0] ) && ! $selected_product_id ) {
1710 $product_id = $offer_product[0]['id'];
1711 $product_qty = $offer_product[0]['quantity'];
1712 } else {
1713 $product_id = $selected_product_id;
1714 $product_qty = ! empty( $input_qty ) ? intval( $input_qty ) : ( isset( $offer_product[0]['quantity'] ) ? $offer_product[0]['quantity'] : 1 );
1715 }
1716
1717 if ( $product_id ) {
1718 $product = wc_get_product( $product_id );
1719 if ( $product ) {
1720 $data = self::prepare_offer_data( $step_id, $product_id, $product_qty, $step_type, array(), false, 0, $order_id );
1721 }
1722 }
1723 return $data;
1724 }
1725
1726 /**
1727 * Prepare offer data
1728 *
1729 * @param $step_id
1730 * @param $product_id
1731 * @param $product_qty
1732 * @param $step_type
1733 * @param $discount
1734 * @param $cancel_main_order
1735 * @param $amount_diff
1736 * @param $order_id
1737 * @return array
1738 */
1739 private static function prepare_offer_data( $step_id, $product_id, $product_qty, $step_type, $discount, $cancel_main_order, $amount_diff, $order_id ) {
1740 $product = wc_get_product( $product_id );
1741 if ( ! $product ) {
1742 return array();
1743 }
1744
1745 $original_price = $product->get_price( 'edit' );
1746 $custom_price = $original_price;
1747 $unit_price = $custom_price;
1748 $unit_price_tax = $custom_price;
1749 $product_price = floatval( $custom_price ) * intval( $product_qty );
1750
1751 $data = array(
1752 'step_id' => $step_id,
1753 'id' => $product_id,
1754 'name' => $product->get_title(),
1755 'desc' => $product->get_short_description(),
1756 'qty' => $product_qty,
1757 'original_price' => $original_price,
1758 'unit_price' => $unit_price,
1759 'unit_price_tax' => $unit_price_tax,
1760 'args' => array(
1761 'subtotal' => $custom_price,
1762 'total' => $custom_price,
1763 ),
1764 'price' => $product_price,
1765 'url' => $product->get_permalink(),
1766 'total' => $product_price,
1767 'cancel_main_order' => $cancel_main_order,
1768 );
1769
1770 return $data;
1771 }
1772
1773 /**
1774 * Update variation product details
1775 *
1776 * @param $step_id
1777 * @param $selected_product_id
1778 * @param $input_qty
1779 * @param $order_id
1780 * @return array
1781 */
1782 public static function update_variation_product_details( $step_id, $selected_product_id, $input_qty, $order_id ) {
1783 // If pro plugin is available, use its method for full compatibility
1784 if ( class_exists( '\WPFunnelsPro\Wpfnl_Pro_functions' ) && method_exists( '\WPFunnelsPro\Wpfnl_Pro_functions', 'update_variation_product_details' ) ) {
1785 return \WPFunnelsPro\Wpfnl_Pro_functions::update_variation_product_details( $step_id, $selected_product_id, $input_qty, $order_id );
1786 }
1787
1788 $step_type = get_post_meta( $step_id, '_step_type', true );
1789 $funnel_id = get_post_meta( $step_id, '_funnel_id', true );
1790 $discount = get_post_meta( $step_id, '_wpfnl_' . $step_type . '_discount', true );
1791
1792 $data = '';
1793 if ( $selected_product_id ) {
1794 $data = self::prepare_offer_data( $step_id, $selected_product_id, $input_qty, $step_type, $discount, false, 0, $order_id );
1795 }
1796
1797 return apply_filters( 'wpfunnels/offer_product_data', $data, $funnel_id, $step_id );
1798 }
1799
1800 /**
1801 * After offer charged
1802 *
1803 * @param $funnel_id
1804 * @param $step_id
1805 * @param $order_id
1806 * @param $order_key
1807 * @param $offer_product
1808 * @param bool $is_charge_success
1809 * @param null $attr
1810 * @param string $variation_id
1811 * @param string $input_qty
1812 * @param array $shipping_data
1813 * @return array
1814 */
1815 public static function after_offer_charged( $funnel_id, $step_id, $order_id, $order_key, $offer_product, $is_charge_success = false, $attr = null, $variation_id = '', $input_qty = '', $shipping_data = array() ) {
1816 // If pro plugin is available, use its method for full compatibility
1817 if ( class_exists( '\WPFunnelsPro\Wpfnl_Pro_functions' ) && method_exists( '\WPFunnelsPro\Wpfnl_Pro_functions', 'after_offer_charged' ) ) {
1818 return \WPFunnelsPro\Wpfnl_Pro_functions::after_offer_charged( $funnel_id, $step_id, $order_id, $order_key, $offer_product, $is_charge_success, $attr, $variation_id, $input_qty, $shipping_data );
1819 }
1820
1821 $result = array();
1822
1823 if ( $is_charge_success ) {
1824 $order = wc_get_order( $order_id );
1825 if ( ! $order ) {
1826 return array(
1827 'status' => 'error',
1828 'message' => __( 'Order not found', 'wpfnl' ),
1829 );
1830 }
1831
1832 // Simple implementation - add product to main order
1833 if ( $attr ) {
1834 $offer_product['args']['variation'] = $attr;
1835 }
1836
1837 $product = wc_get_product( $offer_product['id'] );
1838 if ( $product ) {
1839 $product->set_price( ( $offer_product['args']['total'] / $offer_product['qty'] ) );
1840 $item_id = $order->add_product( $product, $offer_product['qty'] );
1841
1842 if ( $item_id ) {
1843 $step_type = get_post_meta( $step_id, '_step_type', true );
1844 wc_add_order_item_meta( $item_id, "_wpfunnels_{$step_type}", 'yes' );
1845 wc_add_order_item_meta( $item_id, '_wpfunnels_step_id', $offer_product['step_id'] );
1846 $order->update_meta_data( '_wpfunnels_offer_' . $step_id, $step_id );
1847 $order->calculate_totals();
1848 $order->save();
1849
1850 $result = array(
1851 'status' => 'success',
1852 'message' => __( 'Product Added Successfully.', 'wpfnl' ),
1853 );
1854
1855 ob_start();
1856 do_action( 'wpfunnels/offer_accepted', $order, $offer_product );
1857 ob_get_clean();
1858 } else {
1859 $result = array(
1860 'status' => 'error',
1861 'message' => __( 'Failed to add product to order', 'wpfnl' ),
1862 );
1863 }
1864 }
1865 }
1866
1867 return $result;
1868 }
1869 }
1870