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 / gateways / class-wpfnl-gateway-paypal-express.php

class-wpfnl-gateway-paypal-express.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 3.13.3, at includes/core/gateways/class-wpfnl-gateway-paypal-express.php

1,717 lines 68.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace WPFunnels\Gateway;
3
4 use WPFunnels\Wpfnl;
5 use WPFunnels\Wpfnl_functions;
6 use WPFunnels\Gateway\API\Wpfnl_Gateway;
7 use WPFunnels\Gateway\Exception\Wpfnl_Payment_Gateway_Exception;
8
9 class Wpfnl_Gateway_Paypal_Express extends Wpfnl_Gateway {
10
11 /**
12 * Live API url
13 */
14 const PRODUCTION_ENDPOINT = 'https://api-3t.paypal.com/nvp';
15
16 /**
17 * Sandbox api url
18 */
19 const SANDBOX_ENDPOINT = 'https://api-3t.sandbox.paypal.com/nvp';
20
21 protected $key = 'ppec_paypal';
22
23 /**
24 * The request parameters
25 *
26 * @var array
27 */
28 public $parameters = array();
29
30 public $response_params = array();
31
32
33 /**
34 * List of locales supported by PayPal.
35 *
36 * @var array
37 */
38 protected $_supported_locales = array(
39 'da_DK',
40 'de_DE',
41 'en_AU',
42 'en_GB',
43 'en_US',
44 'es_ES',
45 'fr_CA',
46 'fr_FR',
47 'he_IL',
48 'id_ID',
49 'it_IT',
50 'ja_JP',
51 'nl_NL',
52 'no_NO',
53 'pl_PL',
54 'pt_BR',
55 'pt_PT',
56 'ru_RU',
57 'sv_SE',
58 'th_TH',
59 'tr_TR',
60 'zh_CN',
61 'zh_HK',
62 'zh_TW',
63 );
64
65 public function __construct() {
66 add_filter( 'woocommerce_paypal_express_checkout_request_body', array( $this, 'modify_request_body' ), 999 );
67 add_action( 'wp_enqueue_scripts', array( $this, 'load_payment_scripts' ), 9999 );
68 // add_filter( 'woocommerce_paypal_refund_request', array( $this, 'wpfnl_offer_refund_request' ), 10, 3 );
69 }
70
71
72 /**
73 * modify paypal express request body params
74 *
75 * @param $request_body
76 * @return mixed
77 *
78 * @since 1.0.0
79 */
80 public function modify_request_body( $data ) {
81
82 if(!$this->is_enabled()) return $data;
83
84
85
86
87 if ( true === $this->is_reference_transaction_enabled() ) {
88
89 // // translators: blog name.
90 // $description = sprintf( _x( 'Orders with %s', 'data sent to PayPal', 'wpfunnels' ), get_bloginfo( 'name' ) );
91
92 // $description = html_entity_decode( $description, ENT_NOQUOTES, 'UTF-8' );
93
94 if ( $data && isset( $data['METHOD'] ) && 'SetExpressCheckout' == $data['METHOD'] && ! isset( $data['L_BILLINGTYPE0'] ) ) {
95 $data['RETURNURL'] = add_query_arg( array( 'create-billing-agreement' => true ), $data['RETURNURL'] );
96 $data['L_BILLINGTYPE0'] = 'MerchantInitiatedBillingSingleAgreement';
97 $data['L_BILLINGAGREEMENTDESCRIPTION0'] = $this->get_billing_agreement_description();
98 $data['L_BILLINGAGREEMENTCUSTOM0'] = '';
99 }
100
101 if ( $data && isset( $data['METHOD'] ) && 'DoReferenceTransaction' == $data['METHOD'] ) {
102
103 $step_id = isset( $_POST['step_id'] ) ? (int) $_POST['step_id'] : 0;
104 $order_id = isset( $_POST['order_id'] ) ? (int) $_POST['order_id'] : 0;
105
106 // Return if the step id is not in the query string.
107 if ( $step_id < 1 ) {
108 return $data;
109 }
110
111 // Return if the order id is not in the query string.
112 if ( $order_id < 1 ) {
113 return $data;
114 }
115
116 $order = wc_get_order( $order_id );
117 $variation_id = isset( $_POST['variation_id'] ) ? intval( $_POST['variation_id'] ) : '';
118 $input_qty = isset( $_POST['input_qty'] ) ? intval( $_POST['input_qty'] ) : '';
119 $offer_package = Wpfnl_functions::get_offer_product_data($step_id);
120
121 $data['AMT'] = $offer_package['total'];
122 $data['ITEMAMT'] = $offer_package['total'];
123
124 // shippingamt shoud be 0.
125 if ( ( isset( $offer_package['shipping'] ) && isset( $offer_package['shipping']['diff'] ) ) && 0 < $offer_package['shipping']['diff'] ) {
126 $data['SHIPPINGAMT'] = 0;
127 $data['SHIPDISCAMT'] = ( isset( $offer_package['shipping'] ) && isset( $offer_package['shipping']['diff'] ) ) ? $offer_package['shipping']['diff']['cost'] : 0;
128
129 } else {
130 $data['SHIPPINGAMT'] = ( isset( $offer_package['shipping'] ) && isset( $offer_package['shipping']['diff'] ) ) ? $offer_package['shipping']['diff']['cost'] : 0;
131 $data['SHIPDISCAMT'] = 0;
132 }
133
134 $data['TAXAMT'] = ( isset( $offer_package['taxes'] ) ) ? $offer_package['taxes'] : 0;
135 $data['INVNUM'] = 'WC-' . $order_id . '_' . $step_id;
136 $data['INSURANCEAMT'] = 0;
137 $data['HANDLINGAMT'] = 0;
138 $data = $this->remove_previous_line_items( $data );
139
140 $data['L_NAME0'] = $offer_package['name'];
141 $data['L_DESC0'] = $offer_package['desc'];
142 $data['L_AMT0'] = wc_format_decimal( $offer_package['unit_price_tax'], 2 );
143 $data['L_QTY0'] = $offer_package['qty'];
144
145 $item_amt = $offer_package['total'];
146
147 $data['ITEMAMT'] = $item_amt;
148 }
149
150 if ( isset( $data['METHOD'] ) && 'DoExpressCheckoutPayment' == $data['METHOD'] ) {
151
152 if ( isset( $data['PAYMENTREQUEST_0_CUSTOM'] ) ) {
153 $get_custom_attrs = json_decode( $data['PAYMENTREQUEST_0_CUSTOM'] );
154 if ( isset( $get_custom_attrs->order_id ) ) {
155 $get_order = wc_get_order( $get_custom_attrs->order_id );
156
157 try {
158 $checkout = wc_gateway_ppec()->checkout;
159 $checkout_details = $checkout->get_checkout_details( $data['TOKEN'] );
160
161 $checkout->create_billing_agreement( $get_order, $checkout_details );
162
163 $token = $get_order->get_meta( '_ppec_billing_agreement_id' );
164
165 if ( ! empty( $token ) ) {
166
167 // Saving meta by our own.
168 $get_order->update_meta_data('_ppec_billing_agreement_id', $token );
169 }
170 } catch ( \Exception $e ) {
171 print_r($e->getMessage());
172 }
173 }
174 }
175 }
176 }
177
178 return $data;
179 }
180
181
182 /**
183 * Remove line items
184 *
185 * @since 1.0.0
186 *
187 * @param array $array object.
188 *
189 * @return array
190 */
191 public function remove_previous_line_items( $array ) {
192
193 if ( is_array( $array ) && count( $array ) > 0 ) {
194 foreach ( $array as $key => $val ) {
195 if ( false !== strpos( strtoupper( $key ), 'L_' ) ) {
196 unset( $array[ $key ] );
197 }
198 }
199 }
200
201 return $array;
202 }
203
204
205 /**
206 * Get billing agreement description to be passed to PayPal.
207 *
208 * @return string Billing agreement description
209 * @since 1.0.0
210 *
211 */
212 protected function get_billing_agreement_description() {
213 /* translators: placeholder is blogname */
214 $description = sprintf( _x( 'Orders with %s', 'data sent to PayPal', 'wpfunnels' ), get_bloginfo( 'name' ) );
215
216 if ( strlen( $description ) > 127 ) {
217 $description = substr( $description, 0, 124 ) . '...';
218 }
219
220 return html_entity_decode( $description, ENT_NOQUOTES, 'UTF-8' );
221 }
222
223
224
225 /**
226 * check if paypal scripts should load or not
227 *
228 * @return bool
229 *
230 * @since 1.0.0
231 */
232 private function may_be_load_paypal_scripts() {
233
234 if ( (Wpfnl_functions::check_if_this_is_step_type('upsell') || Wpfnl_functions::check_if_this_is_step_type('downsell')) && $this->has_paypal_gateway()) {
235 return true;
236 }
237 return false;
238 }
239
240
241 /**
242 * load paypal payment js
243 */
244 public function load_payment_scripts() {
245
246 if ( $this->may_be_load_paypal_scripts() ) {
247 wp_enqueue_script(
248 'wpfnl-paypal-express-script',
249 'https://www.paypalobjects.com/api/checkout.js',
250 array( 'jquery' ),
251 WPFNL_PRO_VERSION,
252 true
253 );
254 $script = $this->paypal_script();
255 wp_add_inline_script( 'wpfnl-paypal-express-script', $script );
256 }
257 }
258
259 /**
260 * paypal script
261 *
262 */
263 public function paypal_script(){
264
265 ob_start();
266 ?>
267
268 (function($){ $( function($) {
269 var $wpfnl_paypal_checkout = {
270 init: function () {
271 var getButtons = [
272 'upsell_funnel_target',
273 ];
274 window.paypalCheckoutReady = function () {
275 paypal.checkout.setup(
276 '<?php echo esc_js($this->get_payer_id()); ?>',
277 {
278 environment: 'sandbox',
279 buttons: getButtons,
280 container: 'myContainer',
281 locale: '<?php echo esc_js( $this->get_paypal_locale() ); ?>',
282 click: function () {
283 var variation_id = 0;
284 var postData = {
285 step_id: window.WPFunnelsOfferVars.step_id,
286 funnel_id: window.WPFunnelsOfferVars.funnel_id,
287 order_id: window.WPFunnelsOfferVars.order_id,
288 order_key: window.WPFunnelsOfferVars.order_key,
289 variation_id: 0,
290 input_qty: 0,
291 action: 'wpfunnels_create_paypal_express_checkout_token'
292 };
293
294 paypal.checkout.initXO();
295 var action = $.post(window.WPFunnelsOfferVars.ajaxUrl, postData);
296
297 action.done(function (data) {
298 paypal.checkout.startFlow(data.token);
299 });
300
301 action.fail(function () {
302 paypal.checkout.closeFlow();
303 });
304 }
305 });
306 }
307 }
308 };
309
310 $wpfnl_paypal_checkout.init();
311 });
312 })(jQuery);
313 <?php return ob_get_clean();
314 }
315
316
317 /**
318 * Get locale data for PayPal.
319 *
320 * @return string
321 *
322 * @since 1.0.0
323 */
324 public function get_paypal_locale() {
325 $locale = get_locale();
326 if ( ! in_array( $locale, $this->_supported_locales, true ) ) {
327 $locale = 'en_US';
328 }
329
330 return $locale;
331 }
332
333 /**
334 * check id reference transaction is enabled
335 *
336 * @return bool
337 */
338 public function is_reference_transaction_enabled() {
339 return false;
340 }
341
342
343 /**
344 * check if api creds are saved or not
345 *
346 * @return bool
347 */
348 public function has_api_credentials_set() {
349 $credentials_are_set = false;
350 $environment = ('sandbox' === $this->get_wc_gateway()->environment) ? 'sandbox' : 'production';
351
352 $api_creds_prefix = '';
353 if ('sandbox' === $environment) {
354 $api_creds_prefix = 'sandbox_';
355 }
356
357 if ('' !== $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_username') && '' !== $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_password') && '' !== $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_signature')) {
358 $credentials_are_set = true;
359 }
360
361 return $credentials_are_set;
362 }
363
364
365 /**
366 * Check if current order has paypal gatway
367 *
368 * @return bool
369 */
370 public function has_paypal_gateway() {
371
372 $order_id = isset( $_GET['wpfnl-order'] ) ? $_GET['wpfnl-order'] : '';
373
374 if ( empty( $order_id ) ) {
375 return false;
376 }
377
378 $order = wc_get_order( $order_id );
379
380 $gateway = $order->get_payment_method();
381
382 if ( $this->get_key() === $gateway ) {
383 return true;
384 }
385
386 return false;
387 }
388
389
390 public function process_payment( $offer_product, $order_id, $order_key ) {
391
392 }
393
394 /**
395 * get payer id
396 *
397 * @return bool|mixed|void
398 */
399 public function get_payer_id() {
400 $environment = ('sandbox' === $this->get_wc_gateway()->environment) ? 'sandbox' : 'production';
401
402 $api_creds_prefix = '';
403 if ('sandbox' === $environment) {
404 $api_creds_prefix = 'sandbox_';
405 }
406
407 $option_key = 'woocommerce_ppec_payer_id_' . $environment . '_' . md5($this->get_wc_gateway()->get_option($api_creds_prefix . 'api_username') . ':' . $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_password'));
408 $payer_id = get_option($option_key);
409 if ($payer_id) {
410 return $payer_id;
411 } else {
412 $result = $this->get_pal_details();
413
414 if (!empty($result['PAL'])) {
415 update_option($option_key, wc_clean($result['PAL']));
416 return $payer_id;
417 }
418 }
419
420 return false;
421 }
422
423
424 /**
425 * get the paypal id, including the merchant account number
426 *
427 * @return object|API\SV_WC_API_Response
428 */
429 public function get_pal_details() {
430
431 $environment = ('sandbox' === $this->get_wc_gateway()->environment) ? 'sandbox' : 'production';
432 $api_creds_prefix = '';
433 if ('sandbox' === $environment) {
434 $api_creds_prefix = 'sandbox_';
435 }
436 $this->set_api_credentials(
437 $this->get_key(),
438 $environment,
439 $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_username'),
440 $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_password'),
441 $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_signature')
442 );
443 $this->add_parameter('METHOD', 'GetPalDetails');
444 $this->set_credentials_params(
445 $this->api_username,
446 $this->api_password,
447 $this->api_signature,
448 124
449 );
450
451
452 $request = new \stdClass();
453 $request->path = '';
454 $request->method = 'POST';
455 $request->body = $this->to_string();
456 return $this->perform_request($request);
457
458 }
459
460
461 /**
462 * Create express checkout token ajax action.
463 * It will return checkout token for express checkout
464 */
465 public function create_express_checkout_token() {
466
467 $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0;
468 $funnel_id = isset( $_POST['funnel_id'] ) ? intval( $_POST['funnel_id'] ) : 0;
469 $order_id = isset( $_POST['order_id'] ) ? intval( $_POST['order_id'] ) : 0;
470 $order_key = isset( $_POST['order_key'] ) ? sanitize_text_field( wp_unslash( $_POST['order_key'] ) ) : '';
471 $session_key = isset( $_POST['session_key'] ) ? sanitize_text_field( wp_unslash( $_POST['session_key'] ) ) : '';
472 $variation_id = isset( $_POST['variation_id'] ) ? intval( $_POST['variation_id'] ) : '';
473 $input_qty = isset( $_POST['input_qty'] ) ? intval( $_POST['input_qty'] ) : '';
474
475 $is_valid_order = true;
476
477 if ( $is_valid_order ) {
478 $order = wc_get_order( $order_id );
479 $response = $this->init_express_checkout(
480 array(
481 'currency' => $order ? $order->get_currency() : get_woocommerce_currency(),
482 'return_url' => $this->get_callback_url(
483 array(
484 'action' => 'wpfunnels_paypal_express_return',
485 'step_id' => $step_id,
486 'order_id' => $order->get_id(),
487 'funnel_id' => $funnel_id,
488 'order_key' => $order_key,
489 )
490 ),
491 'cancel_url' => $this->get_callback_url(
492 array(
493 'action' => 'wpfunnels_paypal_express_cancel',
494 'step_id' => $step_id,
495 'funnel_id' => $funnel_id,
496 'order_id' => $order->get_id(),
497 )
498 ),
499 'notify_url' => $this->get_callback_url( 'wpfunnels_notify_url' ),
500 'order' => $order,
501 'step_id' => $step_id,
502 'variation_id' => $variation_id,
503 'input_qty' => $input_qty,
504 ),
505 true
506 );
507
508
509 if ( isset( $response['TOKEN'] ) && '' !== $response['TOKEN'] ) {
510 wp_send_json(
511 array(
512 'result' => 'success',
513 'token' => $response['TOKEN'],
514 )
515 );
516 }
517 }
518
519 wp_send_json(
520 array(
521 'result' => 'error',
522 'response' => $response,
523 )
524 );
525 }
526
527
528 /**
529 * Initiate express checkout request
530 *
531 * @param $args
532 * @param bool $is_upsell
533 * @return object|API\SV_WC_API_Response
534 */
535 public function init_express_checkout( $args, $is_upsell = false ) {
536
537 $environment = ('sandbox' === $this->get_wc_gateway()->environment) ? 'sandbox' : 'production';
538
539 $api_creds_prefix = '';
540 if ('sandbox' === $environment) {
541 $api_creds_prefix = 'sandbox_';
542 }
543
544 $this->set_api_credentials(
545 $this->key,
546 $environment,
547 $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_username'),
548 $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_password'),
549 $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_signature')
550 );
551
552 $this->set_express_checkout_args( $args, $is_upsell );
553
554 $this->set_credentials_params(
555 $this->api_username,
556 $this->api_password,
557 $this->api_signature,
558 124
559 );
560 $request = new \stdClass();
561 $request->path = '';
562 $request->method = 'POST';
563 $request->body = $this->to_string();
564
565 WC()->session->set( 'paypal_request' , $this->get_parameters() );
566
567 return $this->perform_request($request);
568 }
569
570
571 /**
572 * Sets up DoExpressCheckoutPayment API Call arguments
573 *
574 * @param string $token Unique token of the payment initiated
575 * @param \WC_Order $order
576 * @param array $args
577 */
578 public function set_do_express_checkout_args($token, $order, $args)
579 {
580 $this->set_method('DoExpressCheckoutPayment');
581
582 // set base params
583 $this->add_parameters(array(
584 'TOKEN' => $token,
585 'PAYERID' => $args['payer_id'],
586 'BUTTONSOURCE' => 'WooThemes_Cart',
587 'RETURNFMFDETAILS' => 1,
588 ));
589
590 $this->add_payment_details_parameters($order, $args['payment_action']);
591 }
592
593
594 /**
595 * Do express checkout
596 *
597 * @param $token
598 * @param $order
599 * @param $args
600 * @return object|string
601 *
602 * @since 1.0.0
603 */
604 private function do_express_checkout($token, $order, $args) {
605 $environment = ('sandbox' === $this->get_wc_gateway()->environment) ? 'sandbox' : 'production';
606
607 $api_creds_prefix = '';
608 if ('sandbox' === $environment) {
609 $api_creds_prefix = 'sandbox_';
610 }
611
612 $this->set_api_credentials(
613 $this->key,
614 $environment,
615 $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_username'),
616 $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_password'),
617 $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_signature')
618 );
619
620 $this->set_do_express_checkout_args($token, $order, $args);
621
622 $this->set_credentials_params(
623 $this->api_username,
624 $this->api_password,
625 $this->api_signature,
626 124
627 );
628 $request = new \stdClass();
629 $request->path = '';
630 $request->method = 'POST';
631 $request->body = $this->to_string();
632 return $this->perform_request($request);
633 }
634
635
636 /**
637 * Sets up the API credentials for API request
638 *
639 * @param $gateway_id
640 * @param $api_environment
641 * @param $api_username
642 * @param $api_password
643 * @param $api_signature
644 *
645 * @since 1.0.0
646 */
647 private function set_api_credentials( $gateway_id, $api_environment, $api_username, $api_password, $api_signature ) {
648 // tie API to gateway
649 $this->gateway_id = $gateway_id;
650
651 // request URI does not vary per-request
652 $this->request_uri = ('production' === $api_environment) ? self::PRODUCTION_ENDPOINT : self::SANDBOX_ENDPOINT;
653
654 // PayPal requires HTTP 1.1
655 $this->request_http_version = '1.1';
656
657 $this->api_username = $api_username;
658 $this->api_password = $api_password;
659 $this->api_signature = $api_signature;
660 }
661
662
663 /**
664 * Sets up the express checkout transaction
665 *
666 * @link https://developer.paypal.com/docs/classic/express-checkout/integration-guide/ECGettingStarted/#id084RN060BPF
667 * @link https://developer.paypal.com/webapps/developer/docs/classic/api/merchant/SetExpressCheckout_API_Operation_NVP/
668 *
669 * @param array $args {
670 * @type string 'currency' (Optional) A 3-character currency code (default is store's currency).
671 * @type string 'billing_type' (Optional) Type of billing agreement for reference transactions. You must have permission from PayPal to use this field. This field must be set to one of the following values: MerchantInitiatedBilling - PayPal creates a billing agreement for each transaction associated with buyer. You must specify version 54.0 or higher to use this option; MerchantInitiatedBillingSingleAgreement - PayPal creates a single billing agreement for all transactions associated with buyer. Use this value unless you need per-transaction billing agreements. You must specify version 58.0 or higher to use this option.
672 * @type string 'billing_description' (Optional) Description of goods or services associated with the billing agreement. This field is required for each recurring payment billing agreement if using MerchantInitiatedBilling as the billing type, that means you can use a different agreement for each subscription/order. PayPal recommends that the description contain a brief summary of the billing agreement terms and conditions (but this only makes sense when the billing type is MerchantInitiatedBilling, otherwise the terms will be incorrectly displayed for all agreements). For example, buyer is billed at "9.99 per month for 2 years".
673 * @type string 'maximum_amount' (Optional) The expected maximum total amount of the complete order and future payments, including shipping cost and tax charges. If you pass the expected average transaction amount (default 25.00). PayPal uses this value to validate the buyer's funding source.
674 * @type string 'no_shipping' (Optional) Determines where or not PayPal displays shipping address fields on the PayPal pages. For digital goods, this field is required, and you must set it to 1. It is one of the following values: 0 – PayPal displays the shipping address on the PayPal pages; 1 – PayPal does not display shipping address fields whatsoever (default); 2 – If you do not pass the shipping address, PayPal obtains it from the buyer's account profile.
675 * @type string 'page_style' (Optional) Name of the Custom Payment Page Style for payment pages associated with this button or link. It corresponds to the HTML variable page_style for customizing payment pages. It is the same name as the Page Style Name you chose to add or edit the page style in your PayPal Account profile.
676 * @type string 'brand_name' (Optional) A label that overrides the business name in the PayPal account on the PayPal hosted checkout pages. Default: store name.
677 * @type string 'landing_page' (Optional) Type of PayPal page to display. It is one of the following values: 'login' – PayPal account login (default); 'Billing' – Non-PayPal account.
678 * @type string 'payment_action' (Optional) How you want to obtain payment. If the transaction does not include a one-time purchase, this field is ignored. Default 'Sale' – This is a final sale for which you are requesting payment (default). Alternative: 'Authorization' – This payment is a basic authorization subject to settlement with PayPal Authorization and Capture. You cannot set this field to Sale in SetExpressCheckout request and then change the value to Authorization or Order in the DoExpressCheckoutPayment request. If you set the field to Authorization or Order in SetExpressCheckout, you may set the field to Sale.
679 * @type string 'return_url' (Required) URL to which the buyer's browser is returned after choosing to pay with PayPal.
680 * @type string 'cancel_url' (Required) URL to which the buyer is returned if the buyer does not approve the use of PayPal to pay you.
681 * @type string 'custom' (Optional) A free-form field for up to 256 single-byte alphanumeric characters
682 * }
683 * @since 1.0.0
684 * @source https://github.com/wp-premium/woocommerce-subscriptions/blob/master/includes/gateways/paypal/includes/class-wcs-paypal-reference-transaction-api-request.php#L68
685 */
686 public function set_express_checkout_args( $args, $is_upsell = false ) {
687
688 // translators: placeholder is blogname
689 $default_description = sprintf( _x( 'Orders with %s', 'data sent to paypal', 'wpfnl-pro' ), get_bloginfo( 'name' ) );
690
691 $defaults = array(
692 'currency' => get_woocommerce_currency(),
693 'billing_type' => 'MerchantInitiatedBillingSingleAgreement',
694 // translators: placeholder is for blog name
695 'billing_description' => html_entity_decode( apply_filters( 'woocommerce_subscriptions_paypal_billing_agreement_description', $default_description, $args ), ENT_NOQUOTES, 'UTF-8' ),
696 'maximum_amount' => null,
697 'no_shipping' => 1,
698 'page_style' => null,
699 'brand_name' => html_entity_decode( get_bloginfo( 'name' ), ENT_NOQUOTES, 'UTF-8' ),
700 'landing_page' => 'login',
701 'payment_action' => 'Sale',
702 'custom' => '',
703 );
704
705 $args = wp_parse_args( $args, $defaults );
706
707 $this->set_method( 'SetExpressCheckout' );
708
709 $this->add_parameters( array(
710 'RETURNURL' => $args['return_url'],
711 'CANCELURL' => $args['cancel_url'],
712 'PAGESTYLE' => $args['page_style'],
713 'BRANDNAME' => $args['brand_name'],
714 'LANDINGPAGE' => ( 'login' == $args['landing_page'] ) ? 'Login' : 'Billing',
715 'NOSHIPPING' => $args['no_shipping'],
716 'MAXAMT' => $args['maximum_amount'],
717 ) );
718
719 if ( false === $is_upsell ) {
720 $this->add_parameter( 'L_BILLINGTYPE0', $args['billing_type'] );
721 $this->add_parameter( 'L_BILLINGAGREEMENTDESCRIPTION0', get_bloginfo( 'name' ) );
722 $this->add_parameter( 'L_BILLINGAGREEMENTCUSTOM0', '' );
723 }
724
725 // if we have an order, the request is to create a subscription/process a payment (not just check if the PayPal account supports Reference Transactions)
726 if ( isset( $args['order'] ) ) {
727
728 if ( true === $is_upsell ) {
729 $this->add_payment_details_parameters( $args['order'], $args['step_id'], $args['payment_action'], false, true, $args['variation_id'], $args['input_qty'] );
730
731 } else {
732 $this->add_payment_details_parameters( $args['order'], $args['payment_action'], false, false );
733
734 }
735 }
736
737 $args['no_shipping'] = 0;
738 if ( empty( $args['no_shipping'] ) ) {
739 $this->maybe_add_shipping_address_params( $args['order'] );
740
741 }
742 $set_express_checkout_params = apply_filters( 'wpfunnels/paypal_param_setexpresscheckout', $this->get_parameters(), $is_upsell );
743 if ( isset( $set_express_checkout_params['PAYMENTREQUEST_0_SHIPTOCOUNTRYCODE'] ) && 2 === strlen( $set_express_checkout_params['PAYMENTREQUEST_0_SHIPTOCOUNTRYCODE'] ) ) {
744 $set_express_checkout_params['ADDRESSOVERRIDE'] = '1';
745 }
746
747 $this->clean_params();
748 $this->add_parameters( $set_express_checkout_params );
749 }
750
751
752 /**
753 * Set the method for the request, currently using:
754 *
755 * + `SetExpressCheckout` - setup transaction
756 * + `GetExpressCheckout` - gets buyers info from PayPal
757 * + `DoExpressCheckoutPayment` - completes the transaction
758 * + `DoCapture` - captures a previously authorized transaction
759 *
760 * @param string $method
761 * @since 1.0.0
762 */
763 private function set_method( $method ) {
764 $this->add_parameter( 'METHOD', $method );
765 }
766
767
768 /**
769 * Set up the payment details for a DoExpressCheckoutPayment or DoReferenceTransaction request
770 *
771 * @param \WC_Order $order
772 * @param $step_id
773 * @param $type
774 * @param bool $use_deprecated_params whether to use deprecated PayPal NVP parameters (required for DoReferenceTransaction API calls)
775 * @param false $is_offer_charge
776 * @param string $variation_id
777 * @param string $input_qty
778 *
779 * @since 1.0.0
780 */
781 protected function add_payment_details_parameters( \WC_Order $order, $step_id, $type, $use_deprecated_params = false, $is_offer_charge = false, $variation_id = '', $input_qty = '' ) {
782
783 $calculated_total = 0;
784 $order_subtotal = 0;
785 $item_count = 0;
786 $order_items = array();
787
788 $offer_data = Wpfnl_functions::get_offer_product_data( $step_id );
789 if ( true === $is_offer_charge ) {
790 $order_items[] = array(
791 'NAME' => $offer_data['name'],
792 'DESC' => $offer_data['desc'],
793 'AMT' => $this->round( $offer_data['price'] ),
794 'QTY' => 1,
795 'ITEMURL' => $offer_data['url'],
796 );
797 $order_subtotal += $offer_data['total_unit_price_amount'];
798 }
799 else {
800 // add line items
801 foreach ( $order->get_items() as $item ) {
802
803 $product = new \WC_Product( $item['product_id'] );
804
805 $order_items[] = array(
806 'NAME' => $product->get_title(),
807 'DESC' => $this->get_item_description( $product ),
808 'AMT' => $this->round( $order->get_item_subtotal( $item ) ),
809 'QTY' => ( ! empty( $item['qty'] ) ) ? absint( $item['qty'] ) : 1,
810 'ITEMURL' => $product->get_permalink(),
811 );
812
813 $order_subtotal += $item['line_total'];
814 }
815
816 // add fees
817 foreach ( $order->get_fees() as $fee ) {
818
819 $order_items[] = array(
820 'NAME' => ( $fee['name'] ),
821 'AMT' => $this->round( $fee['line_total'] ),
822 'QTY' => 1,
823 );
824
825 $order_subtotal += $fee['line_total'];
826 }
827 if ( $order->get_total_discount() > 0 ) {
828
829 $order_items[] = array(
830 'NAME' => __( 'Total Discount', 'wpfnl' ),
831 'QTY' => 1,
832 'AMT' => - $this->round( $order->get_total_discount() ),
833 );
834 }
835 }
836
837
838
839
840 /**Do things for the main order **/
841 if ( false === $is_offer_charge ) {
842 if ( $this->skip_line_items( $order ) ) {
843
844 $total_amount = $this->round( $order->get_total() );
845
846 // calculate the total as PayPal would
847 $calculated_total += $this->round( $order_subtotal + $order->get_cart_tax() ) + $this->round( $order->get_total_shipping() + $order->get_shipping_tax() );
848
849 // offset the discrepancy between the WooCommerce cart total and PayPal's calculated total by adjusting the order subtotal
850 if ( $this->price_format( $total_amount ) !== $this->price_format( $calculated_total ) ) {
851 $order_subtotal = $order_subtotal - ( $calculated_total - $total_amount );
852 }
853
854 $item_names = array();
855
856 foreach ( $order_items as $item ) {
857 $item_names[] = sprintf( '%1$s x %2$s', $item['NAME'], $item['QTY'] );
858 }
859
860 // add a single item for the entire order
861 $this->add_line_item_parameters( array(
862 // translators: placeholder is blogname
863 'NAME' => sprintf( __( '%s - Order', 'wpfnl' ), get_option( 'blogname' ) ),
864 'DESC' => $this->get_item_description( implode( ', ', $item_names ) ),
865 'AMT' => $this->round( $order_subtotal + $order->get_cart_tax() ),
866 'QTY' => 1,
867 ), 0, $use_deprecated_params );
868
869 // add order-level parameters
870 // - Do not send the TAXAMT due to rounding errors
871 if ( $use_deprecated_params ) {
872 $this->add_parameters( array(
873 'AMT' => $total_amount,
874 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(),
875 'ITEMAMT' => $this->round( $order_subtotal + $order->get_cart_tax() ),
876 'SHIPPINGAMT' => $this->round( $order->get_total_shipping() + $order->get_shipping_tax() ),
877 'INVNUM' => $this->get_wc_gateway()->get_option( 'invoice_prefix' ) . Wpfnl_functions::str_to_ascii( ltrim( $order->get_order_number(), _x( '#', 'hash before the order number. Used as a character to remove from the actual order number', 'wpfnl-pro' ) ) ),
878 'PAYMENTACTION' => $type,
879 'PAYMENTREQUESTID' => $order->get_id(),
880 'CUSTOM' => wp_json_encode( array(
881 '_wpfunnels_o_id' => $order->get_id(),
882 ) ),
883 ) );
884 } else {
885 $this->add_payment_parameters( array(
886 'AMT' => $total_amount,
887 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(),
888 'ITEMAMT' => $this->round( $order_subtotal + $order->get_cart_tax() ),
889 'SHIPPINGAMT' => $this->round( $order->get_total_shipping() + $order->get_shipping_tax() ),
890 'INVNUM' => $this->get_wc_gateway()->get_option( 'invoice_prefix' ) . Wpfnl_functions::str_to_ascii( ltrim( $order->get_order_number(), _x( '#', 'hash before the order number. Used as a character to remove from the actual order number', 'wpfnl' ) ) ),
891 'PAYMENTACTION' => $type,
892 'PAYMENTREQUESTID' => $order->get_id(),
893 'CUSTOM' => wp_json_encode( array(
894 '_wpfunnels_o_id' => $order->get_id(),
895
896 ) ),
897 ) );
898 }
899 } else {
900
901 // add individual order items
902 foreach ( $order_items as $item ) {
903 $this->add_line_item_parameters( $item, $item_count ++, $use_deprecated_params );
904 }
905
906 $total_amount = $this->round( $order->get_total() );
907 // add order-level parameters
908 if ( $use_deprecated_params ) {
909 $this->add_parameters( array(
910 'AMT' => $total_amount,
911 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(),
912 'ITEMAMT' => $this->round( $order_subtotal ),
913 'SHIPPINGAMT' => $this->round( $order->get_total_shipping() ),
914 'TAXAMT' => $this->round( $order->get_total_tax() ),
915 'INVNUM' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']),
916 'PAYMENTACTION' => $type,
917 'PAYMENTREQUESTID' => $order->get_id(),
918
919 ) );
920 } else {
921 $this->add_payment_parameters( array(
922 'AMT' => $total_amount,
923 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(),
924 'ITEMAMT' => $this->round( $order_subtotal ),
925 'SHIPPINGAMT' => $this->round( $order->get_total_shipping() ),
926 'TAXAMT' => $this->round( $order->get_total_tax() ),
927 'INVNUM' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']),
928 'PAYMENTACTION' => $type,
929 'PAYMENTREQUESTID' => $order->get_id(),
930 'CUSTOM' => wp_json_encode( array(
931 '_wpfunnels_o_id' => $order->get_id(),
932 ) ),
933 ) );
934 }
935
936 }
937 } /** Handle paypal data setup for the offers */
938
939 else {
940
941 /**
942 * Code for reference transaction
943 */
944 $total_amount = $offer_data['total'];
945
946 $item_names = array();
947
948 foreach ( $order_items as $item ) {
949 $item_names[] = sprintf( '%1$s x %2$s', $item['NAME'], $item['QTY'] );
950 }
951 $item_count = 0;
952 // add individual order items
953 foreach ( $order_items as $item ) {
954 $this->add_line_item_parameters( $item, $item_count ++, $use_deprecated_params );
955 }
956
957 /**
958 * Check if this is a referencetransaction call then send depreceated params
959 */
960 if ( true === $use_deprecated_params && true === $is_offer_charge ) {
961 /**
962 * When shipping amount is a negative number, means user opted for free shipping offer
963 * In this case we setup shippingamt as 0 and shipping discount amount is that negative amount that is coming.
964 */
965 if ( ( isset( $offer_data['shipping'] ) && isset( $offer_data['shipping']['diff'] ) ) && 0 > $offer_data['shipping']['diff']['cost'] ) {
966 $this->add_parameters( array(
967 'AMT' => $total_amount,
968 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(),
969 'ITEMAMT' => $this->round( $order_subtotal ),
970 'SHIPPINGAMT' => 0,
971 'SHIPDISCAMT' => ( isset( $offer_data['shipping'] ) && isset( $offer_data['shipping']['diff'] ) ) ? $offer_data['shipping']['diff']['cost'] : 0,
972 'INVNUM' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']),
973 'PAYMENTACTION' => $type,
974 'PAYMENTREQUESTID' => $order->get_id(),
975 'TAXAMT' => ( isset( $offer_data['taxes'] ) ) ? $offer_data['taxes'] : 0,
976 'CUSTOM' => wp_json_encode( array(
977 '_wpfunnels_o_id' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']),
978 ) ),
979 ) );
980 } else {
981 $this->add_parameters( array(
982 'AMT' => $total_amount,
983 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(),
984 'ITEMAMT' => $this->round( $order_subtotal ),
985 'SHIPPINGAMT' => ( isset( $offer_data['shipping'] ) && isset( $offer_data['shipping']['diff'] ) ) ? $offer_data['shipping']['diff']['cost'] : 0,
986 'INVNUM' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']),
987 'PAYMENTACTION' => $type,
988 'PAYMENTREQUESTID' => $order->get_id(),
989 'TAXAMT' => ( isset( $offer_data['taxes'] ) ) ? $offer_data['taxes'] : 0,
990 'CUSTOM' => wp_json_encode( array(
991 '_wpfunnels_o_id' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']),
992 ) ),
993 ) );
994 }
995 } else {
996 if ( ( isset( $offer_data['shipping'] ) && isset( $offer_data['shipping']['diff'] ) ) && 0 > $offer_data['shipping']['diff']['cost'] ) {
997 $this->add_payment_parameters( array(
998 'AMT' => $total_amount,
999 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(),
1000 'ITEMAMT' => $this->round( $order_subtotal ),
1001 'SHIPPINGAMT' => 0,
1002 'SHIPDISCAMT' => ( isset( $offer_data['shipping'] ) && isset( $offer_data['shipping']['diff'] ) ) ? $offer_data['shipping']['diff']['cost'] : 0,
1003 'INVNUM' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']),
1004 'PAYMENTACTION' => $type,
1005 'PAYMENTREQUESTID' => $order->get_id(),
1006 'TAXAMT' => ( isset( $offer_data['taxes'] ) ) ? $offer_data['taxes'] : 0,
1007 'NOTIFYURL' => WC()->api_request_url( 'WC_Gateway_Paypal' ),
1008 'CUSTOM' => wp_json_encode( array(
1009 '_wpfunnels_o_id' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']),
1010 ) ),
1011 ) );
1012 } else {
1013 $this->add_payment_parameters( array(
1014 'AMT' => $total_amount,
1015 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(),
1016 'ITEMAMT' => $this->round( $order_subtotal ),
1017 'SHIPPINGAMT' => ( isset( $offer_data['shipping'] ) && isset( $offer_data['shipping']['diff'] ) ) ? $offer_data['shipping']['diff']['cost'] : 0,
1018 'INVNUM' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']),
1019 'PAYMENTACTION' => $type,
1020 'NOTIFYURL' => WC()->api_request_url( 'WC_Gateway_Paypal' ),
1021 'PAYMENTREQUESTID' => $order->get_id(),
1022 'TAXAMT' => ( isset( $offer_data['taxes'] ) ) ? $offer_data['taxes'] : 0,
1023 'CUSTOM' => wp_json_encode( array(
1024 '_wpfunnels_o_id' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']),
1025 ) ),
1026 ) );
1027 }
1028 }
1029 }
1030
1031
1032 }
1033
1034
1035 /**
1036 * Adds a line item parameters to the request, auto-prefixes the parameter key
1037 * with `L_PAYMENTREQUEST_0_` for convenience and readability
1038 *
1039 * @param array $params
1040 * @param int $item_count current item count
1041 *
1042 * @since 2.0
1043 */
1044 private function add_line_item_parameters( array $params, $item_count, $use_deprecated_params = false ) {
1045 foreach ( $params as $key => $value ) {
1046 if ( $use_deprecated_params ) {
1047 $this->add_parameter( "L_{$key}{$item_count}", $value );
1048 } else {
1049 $this->add_parameter( "L_PAYMENTREQUEST_0_{$key}{$item_count}", $value );
1050 }
1051 }
1052 }
1053
1054
1055 /**
1056 * Add payment parameters, auto-prefixes the parameter key with `PAYMENTREQUEST_0_`
1057 * for convenience and readability
1058 *
1059 * @param array $params
1060 *
1061 * @since 1.0.0
1062 */
1063 private function add_payment_parameters( array $params ) {
1064 foreach ( $params as $key => $value ) {
1065 $this->add_parameter( "PAYMENTREQUEST_0_{$key}", $value );
1066 }
1067 }
1068
1069
1070
1071 /**
1072 * PayPal cannot properly calculate order totals when prices include tax (due
1073 * to rounding issues), so line items are skipped and the order is sent as
1074 * a single item
1075 *
1076 * @since 2.0.9
1077 * @param \WC_Order $order Optional. The WC_Order object. Default null.
1078 * @return bool true if line items should be skipped, false otherwise
1079 */
1080 private function skip_line_items( $order = null, $order_items = null ) {
1081
1082 $skip_line_items = false;
1083
1084 // Also check actual totals add up just in case totals have been manually modified to amounts that can not round correctly, see https://github.com/Prospress/woocommerce-subscriptions/issues/2213
1085 if ( ! is_null( $order ) ) {
1086
1087 $rounded_total = 0;
1088 $items = array();
1089 foreach ( $order_items as $item ) {
1090 $amount = round( $item['line_subtotal'] / $item['qty'], 2 );
1091 $rounded_total += round( $amount * $item['qty'], 2 );
1092 $calculated_total += $this->round( $item['AMT'] * $item['QTY'] );
1093
1094 $amount = round( $item['line_subtotal'] / $item['qty'], 2 );
1095 $item = array(
1096 'name' => $item['name'],
1097 'quantity' => $item['qty'],
1098 'amount' => $amount,
1099 );
1100
1101 $items[] = $item;
1102 }
1103
1104 $discounts = $order->get_total_discount();
1105
1106 $details = array(
1107 'total_item_amount' => round( $order->get_subtotal(), 2 ) + $discounts,
1108 'order_tax' => round( $order->get_total_tax(), 2 ),
1109 'shipping' => round( $order->get_shipping_total(), 2 ),
1110 'items' => $items,
1111 );
1112 $details['order_total'] = round( $details['total_item_amount'] + $details['order_tax'] + $details['shipping'], 2 );
1113 if ( (float) $details['order_total'] !== (float) $order->get_total() ) {
1114 $skip_line_items = true;
1115 }
1116 if ( $details['total_item_amount'] !== $rounded_total ) {
1117 $skip_line_items = true;
1118 }
1119 }
1120
1121 /**
1122 * Filter whether line items should be skipped or not
1123 *
1124 * @since 3.3.0
1125 * @param bool $skip_line_items True if line items should be skipped, false otherwise
1126 * @param \WC_Order/null $order The WC_Order object or null.
1127 */
1128 return apply_filters( 'wpfunnels/paypal_skip_line_items', $skip_line_items, $order );
1129 }
1130
1131
1132 /**
1133 * @param \WC_Order $order
1134 */
1135 function maybe_add_shipping_address_params( \WC_Order $order, $prefix = 'PAYMENTREQUEST_0_SHIPTO' ) {
1136
1137 if ( $order->has_shipping_address() ) {
1138 $shipping_first_name = $order->get_shipping_first_name();
1139 $shipping_last_name = $order->get_shipping_last_name();
1140 $shipping_address_1 = $order->get_shipping_address_1();
1141 $shipping_address_2 = $order->get_shipping_address_2();
1142 $shipping_city = $order->get_shipping_city();
1143 $shipping_state = $order->get_shipping_state();
1144 $shipping_postcode = $order->get_shipping_postcode();
1145 $shipping_country = $order->get_shipping_country();
1146 } else {
1147 $shipping_first_name = $order->get_billing_first_name();
1148 $shipping_last_name = $order->get_billing_last_name();
1149 $shipping_address_1 = $order->get_billing_address_1();
1150 $shipping_address_2 = $order->get_billing_address_2();
1151 $shipping_city = $order->get_billing_city();
1152 $shipping_state = $order->get_billing_state();
1153 $shipping_postcode = $order->get_billing_postcode();
1154 $shipping_country = $order->get_billing_country();
1155 }
1156 if ( empty( $shipping_country ) ) {
1157 $shipping_country = WC()->countries->get_base_country();
1158 }
1159
1160 $shipping_phone = $order->get_billing_phone();
1161
1162 $params = array(
1163 $prefix . 'NAME' => $shipping_first_name . ' ' . $shipping_last_name,
1164 $prefix . 'STREET' => $shipping_address_1,
1165 $prefix . 'STREET2' => $shipping_address_2,
1166 $prefix . 'CITY' => $shipping_city,
1167 $prefix . 'STATE' => $shipping_state,
1168 $prefix . 'ZIP' => $shipping_postcode,
1169 $prefix . 'COUNTRYCODE' => $this->get_country( $shipping_country ),
1170 $prefix . 'PHONENUM' => $shipping_phone,
1171 );
1172 foreach ( $params as $key => $val ) {
1173 $this->add_parameter( $key, $val );
1174 }
1175
1176 }
1177
1178
1179 private function get_country( $country ) {
1180 if ( 2 === strlen( $country ) ) {
1181 return $country;
1182 }
1183
1184 return substr( $country, 0, 2 );
1185 }
1186
1187
1188 /**
1189 * Add api credentials parameters
1190 *
1191 * @param string $api_username API username.
1192 * @param string $api_password API password.
1193 * @param string $api_signature API signature.
1194 * @param string $api_version API version.
1195 * @return void
1196 */
1197 public function set_credentials_params( $api_username, $api_password, $api_signature, $api_version ) {
1198
1199 $this->add_parameters(
1200 array(
1201 'USER' => $api_username,
1202 'PWD' => $api_password,
1203 'SIGNATURE' => $api_signature,
1204 'VERSION' => $api_version,
1205 )
1206 );
1207 }
1208
1209
1210 /**
1211 * Add multiple parameters
1212 *
1213 * @param array $params
1214 *
1215 * @since 2.0
1216 */
1217 public function add_parameters(array $params)
1218 {
1219 foreach ($params as $key => $value) {
1220 $this->add_parameter($key, $value);
1221 }
1222 }
1223
1224
1225 /**
1226 * Add a parameter
1227 *
1228 * @param string $key
1229 * @param string|int $value
1230 *
1231 * @since 2.0
1232 */
1233 public function add_parameter($key, $value)
1234 {
1235 $this->parameters[$key] = $value;
1236 }
1237
1238
1239 /**
1240 * get call back url of paypal request
1241 *
1242 * @param $args
1243 * @return string
1244 *
1245 * @since 1.0.0
1246 */
1247 public function get_callback_url($args)
1248 {
1249 $api_request_url = WC()->api_request_url( 'wpfunnels_paypal_express' );
1250 if ( is_array( $args ) ) {
1251 return add_query_arg( $args, $api_request_url );
1252 } else {
1253 return add_query_arg( 'action', $args, $api_request_url );
1254 }
1255 }
1256
1257
1258 /**
1259 * get paypal payment params
1260 *
1261 * @return array|mixed|void
1262 * @throws \Exception
1263 */
1264 public function get_parameters() {
1265
1266 $this->parameters = apply_filters('wcs_paypal_request_params', $this->parameters, $this);
1267 // validate parameters.
1268 foreach ( $this->parameters as $key => $value ) {
1269
1270 // remove unused params.
1271 if ( '' === $value || is_null( $value ) ) {
1272 unset( $this->parameters[ $key ] );
1273 }
1274
1275 // format and check amounts.
1276 if ( false !== strpos( $key, 'AMT' ) ) {
1277
1278 // amounts must be 10,000.00 or less for USD.
1279 if ( isset( $this->parameters['PAYMENTREQUEST_0_CURRENCYCODE'] ) && 'USD' == $this->parameters['PAYMENTREQUEST_0_CURRENCYCODE'] && $value > 10000 ) {
1280
1281 throw new \Exception( sprintf( '%s amount of %s must be less than $10,000.00', $key, $value ) );
1282 }
1283
1284 $this->parameters[ $key ] = $this->price_format( $value );
1285 }
1286 }
1287 return $this->parameters;
1288 }
1289
1290
1291 /**
1292 * Return the parsed response object for the request
1293 *
1294 * @since 1.0.0
1295 *
1296 * @param string $raw_response_body response body.
1297 *
1298 * @return object
1299 */
1300 protected function get_parsed_response( $raw_response_body ) {
1301
1302 wp_parse_str( urldecode( $raw_response_body ), $this->response_params );
1303
1304 return $this->response_params;
1305 }
1306
1307
1308 /**
1309 * Get Express Checkout Details
1310 *
1311 * @param $token
1312 * @return object
1313 *
1314 * @since 1.0.0
1315 */
1316 public function get_express_checkout_details( $token ) {
1317
1318 $environment = ('sandbox' === $this->get_wc_gateway()->environment) ? 'sandbox' : 'production';
1319 $api_creds_prefix = '';
1320 if ('sandbox' === $environment) {
1321 $api_creds_prefix = 'sandbox_';
1322 }
1323 $this->set_api_credentials(
1324 $this->get_key(),
1325 $environment,
1326 $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_username'),
1327 $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_password'),
1328 $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_signature')
1329 );
1330 $this->set_express_checkout_details_args($token);
1331 $this->set_credentials_params(
1332 $this->api_username,
1333 $this->api_password,
1334 $this->api_signature,
1335 124
1336 );
1337 $request = new \stdClass();
1338 $request->path = '';
1339 $request->method = 'POST';
1340 $request->body = $this->to_string();
1341 return $this->perform_request($request);
1342 }
1343
1344
1345 /**
1346 * Sets up GetExpressCheckoutDetails API call arguments
1347 *
1348 * @param string $token
1349 *
1350 * @since 1.0.0
1351 */
1352 public function set_express_checkout_details_args($token)
1353 {
1354
1355 $this->set_method('GetExpressCheckoutDetails');
1356 $this->add_parameter('TOKEN', $token);
1357 }
1358
1359
1360 /**
1361 * @hooked woocommerce_api_wpfunnels_paypal_express
1362 */
1363 public function maybe_create_billing() {
1364 if (!isset($_GET['action'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1365 return;
1366 }
1367 $token = esc_attr( sanitize_text_field( wp_unslash( $_GET['token'] ) ) );
1368 $order_id = isset( $_GET['order_id'] ) ? intval( $_GET['order_id'] ) : 0;
1369 $step_id = isset( $_GET['step_id'] ) ? intval( $_GET['step_id'] ) : 0;
1370 $funnel_id = isset( $_GET['funnel_id'] ) ? intval( $_GET['funnel_id'] ) : 0;
1371
1372 switch ($_GET['action']) {
1373
1374 // called when the customer is returned from PayPal after authorizing their payment, used for retrieving the customer's checkout details
1375 case 'wpfunnels_paypal_express_create_billing_agreement':
1376
1377 // return if no token
1378 if (!isset($_GET['token'])) {
1379 return;
1380 }
1381
1382 // get token to retrieve checkout details with
1383 $token = wc_clean( $_GET['token'] );
1384 $order = null;
1385 try {
1386 $express_checkout_details_response = $this->get_express_checkout_details($token);
1387 $order = wc_get_order($order_id);
1388
1389 // Make sure the billing agreement was accepted
1390 if (1 === $express_checkout_details_response['BILLINGAGREEMENTACCEPTEDSTATUS'] || '1' === $express_checkout_details_response['BILLINGAGREEMENTACCEPTEDSTATUS']) {
1391
1392 if (is_null($order)) {
1393 throw new Wpfnl_Payment_Gateway_Exception(__('Unable to find order for PayPal billing agreement.', 'wpfnl-pro'), 101, $this->get_key());
1394 }
1395
1396 // we need to process an initial payment
1397 if (Wpfnl_functions::get_amount_for_comparisons($order->get_total()) > 0) {
1398 $billing_agreement_response = $this->do_express_checkout($token, $order, array(
1399 'payment_action' => 'Sale',
1400 'payer_id' => $this->get_value_from_response($express_checkout_details_response, 'PAYERID'),
1401 ));
1402 } else {
1403 throw new Wpfnl_Payment_Gateway_Exception(__('Order total is not greater than zero.', 'wpfnl'), 101, $this->get_key());
1404 }
1405
1406 if ($this->has_api_error($billing_agreement_response)) {
1407 throw new Wpfnl_Payment_Gateway_Exception($this->get_api_error($billing_agreement_response), 101, $this->get_key());
1408 }
1409
1410 $order->set_payment_method('paypal');
1411 $order->update_meta_data('_paypal_subscription_id', $this->get_value_from_response($billing_agreement_response, 'BILLINGAGREEMENTID'));
1412
1413 /**
1414 * mark primary payment as completed
1415 */
1416 $order->payment_complete($billing_agreement_response['PAYMENTINFO_0_TRANSACTIONID']);
1417
1418 $next_step = Wpfnl_functions::get_next_step( $funnel_id, $step_id );
1419 $custom_url = Wpfnl_functions::custom_url_for_thankyou_page( $next_step['step_id'] );
1420 if( $custom_url ){
1421 $redirect_url = $custom_url;
1422 }else{
1423 $redirect_url = add_query_arg('utm_nooverride', '1', get_permalink($next_step['step_id']));
1424 }
1425
1426 //redirect customer to order received page
1427 wp_safe_redirect(esc_url_raw($redirect_url));
1428 exit;
1429
1430 }
1431 else {
1432 $this->handle_api_failures($order);
1433 }
1434 }
1435 catch (\Exception $e) {
1436 $this->handle_api_failures($order, $e);
1437 }
1438 }
1439 }
1440
1441 /**
1442 * check if api response has any error
1443 *
1444 * @param $response
1445 * @return bool
1446 */
1447 public function has_api_error($response) {
1448 // assume something went wrong if ACK is missing
1449 if (!isset($response['ACK'])) {
1450 return true;
1451 }
1452
1453 // any non-success ACK is considered an error, see
1454 // https://developer.paypal.com/docs/classic/api/NVPAPIOverview/#id09C2F0K30L7
1455 return ('Success' !== $this->get_value_from_response($response, 'ACK') && 'SuccessWithWarning' !== $this->get_value_from_response($response, 'ACK'));
1456
1457 }
1458
1459
1460 /**
1461 * get order from response
1462 *
1463 * @param $response
1464 * @return bool|\WC_Order|\WC_Order_Refund
1465 */
1466 public function get_order_from_response($response) {
1467
1468 if ($response && isset($response['CUSTOM'])) {
1469 $getjson = json_decode($response['CUSTOM'], true);
1470 return wc_get_order($getjson['_wpfunnels_o_id']);
1471 }
1472 }
1473
1474
1475 /**
1476 * Handles Payment Gateway API error
1477 *
1478 * @param $order
1479 * @param $e
1480 */
1481 protected function handle_api_failures($order, $e = '') {
1482 if ($order instanceof \WC_Order) {
1483
1484 if ($e instanceof Wpfnl_Payment_Gateway_Exception) {
1485
1486 $order->add_order_note($e->getMessage());
1487 }
1488 $redirect = $order->get_checkout_order_received_url();
1489 wp_redirect($redirect);
1490 exit;
1491 }
1492 wp_die('Unable to process further. Please contact to the store admin & enquire about the status of your order.');
1493 exit;
1494 }
1495
1496
1497 public function handle_api_calls() {
1498
1499 if (!isset($_GET['action'])) {
1500 return;
1501 }
1502
1503 $step_id = isset( $_GET['step_id'] ) ? intval( $_GET['step_id'] ) : 0;
1504 $funnel_id = isset( $_GET['funnel_id'] ) ? intval( $_GET['funnel_id'] ) : 0;
1505 $order_id = isset( $_GET['order_id'] ) ? intval( $_GET['order_id'] ) : 0;
1506 $order_key = isset( $_GET['order_key'] ) ? sanitize_text_field( wp_unslash( $_GET['order_key'] ) ) : '';
1507 $variation_id = isset( $_GET['variation_id'] ) ? intval( $_GET['variation_id'] ) : '';
1508 $input_qty = isset( $_GET['input_qty'] ) ? intval( $_GET['input_qty'] ) : '';
1509
1510 $order = null;
1511 try {
1512 switch ($_GET['action']) {
1513 case 'wpfunnels_paypal_express_return':
1514
1515 $offer_product = Wpfnl_functions::get_offer_product_data($step_id);
1516
1517 $order = wc_get_order( $order_id );
1518
1519
1520 if ( isset( $_GET['token'] ) && ! empty( $_GET['token'] ) ) {
1521 $api_response_result = false;
1522 $express_checkout_details_response = $this->get_express_checkout_details( wp_unslash( $_GET['token'] ) ); //phpcs:ignore
1523
1524 $get_paypal_data = WC()->session->get('paypal_request');
1525
1526 /**
1527 * Check if product total is greater than 0.
1528 */
1529 if ( $offer_product['total'] > 0 ) {
1530 /**
1531 * Prepare DoExpessCheckout Call to finally charge the user.
1532 */
1533 $do_express_checkout_data = array(
1534 'TOKEN' => $express_checkout_details_response['TOKEN'],
1535 'PAYERID' => $express_checkout_details_response['PAYERID'],
1536 'METHOD' => 'DoExpressCheckoutPayment',
1537 );
1538
1539 $do_express_checkout_data = wp_parse_args( $do_express_checkout_data, $get_paypal_data );
1540 $environment = ( 'sandbox' === $this->get_wc_gateway()->environment ) ? 'sandbox' : 'live';
1541
1542 $api_prefix = '';
1543
1544 if ( 'sandbox' === $environment ) {
1545 $api_prefix = 'sandbox_';
1546 }
1547
1548 /**
1549 * Setup & perform DoExpressCheckout API Call.
1550 */
1551 $this->set_api_credentials(
1552 $this->key,
1553 $environment,
1554 $this->get_wc_gateway()->get_option( $api_prefix . 'api_username' ),
1555 $this->get_wc_gateway()->get_option( $api_prefix . 'api_password' ),
1556 $this->get_wc_gateway()->get_option( $api_prefix . 'api_signature' )
1557 );
1558
1559 $this->add_parameters( $do_express_checkout_data );
1560 $this->set_credentials_params( $this->api_username, $this->api_password, $this->api_signature, 124 );
1561
1562 $request = new \stdClass();
1563 $request->path = '';
1564 $request->method = 'POST';
1565 $request->body = $this->to_string();
1566 $response_checkout = $this->perform_request( $request );
1567
1568 if ( false === $this->has_api_error( $response_checkout ) ) {
1569 $api_response_result = true;
1570 $data = $this->handle_offer_charge($order, $api_response_result, $offer_product);
1571 }
1572 }
1573 else {
1574 $api_response_result = true;
1575 $data = $this->handle_offer_charge($order, $api_response_result, $offer_product);
1576 }
1577
1578 $result = Wpfnl_functions::after_offer_charged( $step_id, $order_id, $order_key, $api_response_result, $variation_id, $input_qty, $offer_product );
1579 $next_step = Wpfnl_functions::get_next_step($funnel_id, $step_id);
1580
1581 $next_step_url = get_permalink($next_step['step_id']);
1582 $custom_url = Wpfnl_functions::custom_url_for_thankyou_page( $next_step['step_id'] );
1583 if( $custom_url ){
1584 $redirect_url = $custom_url;
1585 }else{
1586 $redirect_url = add_query_arg(
1587 array(
1588 'wpfnl-order' => $order_id,
1589 'wpfnl-key' => $order_key,
1590 'key' => $order_key,
1591 ),
1592 $next_step_url
1593 );
1594 }
1595
1596 wp_safe_redirect( $redirect_url );
1597 exit;
1598
1599 } else {
1600
1601 $result = Wpfnl_functions::after_offer_charged( $step_id, $order_id, $order_key, $api_response_result, $variation_id, $input_qty, $offer_product );
1602 $next_step = Wpfnl_functions::get_next_step($funnel_id, $step_id);
1603
1604 $custom_url = Wpfnl_functions::custom_url_for_thankyou_page( $next_step['step_id'] );
1605 if( $custom_url ){
1606 $redirect_url = $custom_url;
1607 }else{
1608 $next_step_url = get_permalink($next_step['step_id']);
1609 $redirect_url = add_query_arg(
1610 array(
1611 'wpfnl-order' => $order_id,
1612 'wpfnl-key' => $order_key,
1613 'key' => $order_key,
1614 ),
1615 $next_step_url
1616 );
1617 }
1618
1619
1620 wp_safe_redirect( $redirect_url );
1621 exit;
1622 }
1623
1624 break;
1625
1626 case 'cancel_url':
1627
1628 /**
1629 * Getting the current URL from the session and loading the same offer again.
1630 * User needs to chose "no thanks" if he want to move to upsell/order received.
1631 */
1632 $url = get_permalink( $step_id );
1633
1634 $args = array(
1635 'wpfnl-order' => $order_id,
1636 'wpfnl-key' => $order_key,
1637 'key' => $order_key,
1638 );
1639
1640 $url = add_query_arg( $args, $url );
1641 wp_redirect($url);
1642 exit;
1643
1644 }
1645
1646 } catch (Exception $e) {
1647 $this->handle_api_failures($order, $e);
1648 }
1649 }
1650
1651
1652 /**
1653 * Store Offer Trxn Charge.
1654 *
1655 * @param \WC_Order $order The order that is being paid for.
1656 * @param Object $response The response that is send from the payment gateway.
1657 * @param array $product Product data.
1658 */
1659 public function handle_offer_charge( \WC_Order $order, $response, $product ) {
1660
1661 $order_id = $order->get_id();
1662 $txn_id = '';
1663
1664 if ( ! isset( $response['PAYMENTINFO_0_TRANSACTIONID'] ) ) {
1665 if(is_array($response)){
1666 $txn_id = $response['TRANSACTIONID'];
1667 }
1668
1669 } else {
1670 if(is_array($response)){
1671 $txn_id = $response['PAYMENTINFO_0_TRANSACTIONID'];
1672 }
1673 }
1674 $order->update_meta_data( '_wpfunnels_offer_txn_resp_' . $product['step_id'], $txn_id );
1675 $order->save();
1676 }
1677
1678
1679 /**
1680 * Modify offer refund data.
1681 *
1682 * @param array $request request.
1683 * @param object $order the order object.
1684 * @param float $amount refund amount.
1685 *
1686 * @return object
1687 */
1688 public function wpfnl_offer_refund_request( $request, $order, $amount ) {
1689
1690 $payment_method = $order->get_payment_method();
1691
1692 if ( $this->key !== $payment_method ) {
1693 return $request;
1694 }
1695
1696 if ( isset( $order->data['transaction_id'] ) && ! empty( $order->data['transaction_id'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Missing
1697 $request['TRANSACTIONID'] = wc_clean( $order->data['transaction_id'] ); //phpcs:ignore WordPress.Security.NonceVerification.Missing
1698
1699 $environment = ('sandbox' === $this->get_wc_gateway()->environment) ? 'sandbox' : 'live';
1700
1701 if ( 'live' === $environment ) {
1702 $request['USER'] = $this->get_wc_gateway()->get_option( 'api_username' );
1703 $request['PWD'] = $this->get_wc_gateway()->get_option( 'api_password' );
1704 $request['SIGNATURE'] = $this->get_wc_gateway()->get_option( 'api_signature' );
1705 } else {
1706 $request['USER'] = $this->get_wc_gateway()->get_option( 'sandbox_api_username' );
1707 $request['PWD'] = $this->get_wc_gateway()->get_option( 'sandbox_api_password' );
1708 $request['SIGNATURE'] = $this->get_wc_gateway()->get_option( 'sandbox_api_signature' );
1709
1710 }
1711 }
1712 return $request;
1713 }
1714
1715
1716 }
1717