PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.3
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.3
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.13.3, at includes/core/offer/class-wpfnl-offer.php

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