PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.9.2
WooCommerce v10.9.2
10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / includes / gateways / paypal / class-wc-gateway-paypal.php
woocommerce / includes / gateways / paypal Last commit date
assets 4 years ago includes 5 months ago class-wc-gateway-paypal-buttons.php 5 months ago class-wc-gateway-paypal.php 1 month ago
class-wc-gateway-paypal.php
1078 lines
1 <?php
2 /**
3 * PayPal Standard Payment Gateway.
4 *
5 * Provides a PayPal Standard Payment Gateway.
6 *
7 * @class WC_Gateway_Paypal
8 * @extends WC_Payment_Gateway
9 * @version 2.3.0
10 * @package WooCommerce\Classes\Payment
11 */
12
13 use Automattic\Jetpack\Constants;
14 use Automattic\WooCommerce\Enums\PaymentGatewayFeature;
15 use Automattic\Jetpack\Connection\Manager as Jetpack_Connection_Manager;
16 use Automattic\WooCommerce\Gateways\PayPal\Buttons as PayPalButtons;
17 use Automattic\WooCommerce\Gateways\PayPal\Constants as PayPalConstants;
18 use Automattic\WooCommerce\Gateways\PayPal\Helper as PayPalHelper;
19 use Automattic\WooCommerce\Gateways\PayPal\Notices as PayPalNotices;
20 use Automattic\WooCommerce\Gateways\PayPal\Request as PayPalRequest;
21 use Automattic\WooCommerce\Gateways\PayPal\TransactAccountManager as PayPalTransactAccountManager;
22
23 if ( ! defined( 'ABSPATH' ) ) {
24 exit;
25 }
26
27 // Require the deprecated classes for backward compatibility.
28 // This will be removed in 11.0.0.
29 if ( ! class_exists( 'WC_Gateway_Paypal_Constants' ) ) {
30 require_once __DIR__ . '/includes/class-wc-gateway-paypal-constants.php';
31 }
32
33 if ( ! class_exists( 'WC_Gateway_Paypal_Helper' ) ) {
34 require_once __DIR__ . '/includes/class-wc-gateway-paypal-helper.php';
35 }
36
37 if ( ! class_exists( 'WC_Gateway_Paypal_Notices' ) ) {
38 require_once __DIR__ . '/includes/class-wc-gateway-paypal-notices.php';
39 }
40
41 if ( ! class_exists( 'WC_Gateway_Paypal_Buttons' ) ) {
42 require_once __DIR__ . '/class-wc-gateway-paypal-buttons.php';
43 }
44
45 /**
46 * WC_Gateway_Paypal Class.
47 */
48 class WC_Gateway_Paypal extends WC_Payment_Gateway {
49
50 /**
51 * Unique ID for this gateway.
52 *
53 * @var string
54 */
55 const ID = 'paypal';
56
57 /**
58 * Whether or not logging is enabled
59 *
60 * @var bool
61 */
62 public static $log_enabled = null;
63
64 /**
65 * Logger instance
66 *
67 * @var WC_Logger
68 */
69 public static $log = false;
70
71 /**
72 * Whether the test mode is enabled.
73 *
74 * @var bool
75 */
76 public $testmode;
77
78 /**
79 * Whether the debug mode is enabled.
80 *
81 * @var bool
82 */
83 public $debug;
84
85 /**
86 * The intent of the payment (capture or authorize).
87 *
88 * @var string
89 */
90 public $intent;
91
92 /**
93 * Email address to send payments to.
94 *
95 * @var string
96 */
97 public $email;
98
99 /**
100 * Receiver email.
101 *
102 * @var string
103 */
104 public $receiver_email;
105
106 /**
107 * Identity token.
108 *
109 * @var string
110 */
111 public $identity_token;
112
113 /**
114 * Jetpack connection manager.
115 *
116 * @var Jetpack_Connection_Manager
117 */
118 private $jetpack_connection_manager;
119
120 /**
121 * Whether the Transact onboarding is complete.
122 *
123 * @var bool
124 */
125 private $transact_onboarding_complete;
126
127 /**
128 * The *Singleton* instance of this class
129 *
130 * @var WC_Gateway_Paypal
131 */
132 private static $instance;
133
134 /**
135 * Returns the *Singleton* instance of this class.
136 *
137 * @return WC_Gateway_Paypal The *Singleton* instance.
138 */
139 public static function get_instance() {
140 if ( null === self::$instance ) {
141 self::$instance = new self();
142 }
143 return self::$instance;
144 }
145
146 /**
147 * Set the instance of the gateway.
148 *
149 * @param WC_Gateway_Paypal $instance The instance of the gateway.
150 * @return void
151 */
152 public static function set_instance( $instance ) {
153 self::$instance = $instance;
154 }
155
156 /**
157 * Constructor for the gateway.
158 */
159 public function __construct() {
160 $this->id = self::ID;
161 $this->has_fields = false;
162 $this->order_button_text = __( 'Proceed to PayPal', 'woocommerce' );
163 $this->method_title = __( 'PayPal Standard', 'woocommerce' );
164 /* translators: %s: Link to WC system status page */
165 $this->method_description = __( 'PayPal Standard redirects customers to PayPal to enter their payment information.', 'woocommerce' );
166 $this->supports = array(
167 PaymentGatewayFeature::PRODUCTS,
168 PaymentGatewayFeature::REFUNDS,
169 );
170
171 // Load the settings.
172 $this->init_form_fields();
173 $this->init_settings();
174
175 // Define user set variables.
176 $this->title = $this->get_option( 'title' );
177 $this->description = $this->get_option( 'description' );
178 $this->testmode = 'yes' === $this->get_option( 'testmode', 'no' );
179 $this->intent = 'sale' === $this->get_option( 'paymentaction', 'sale' ) ? 'capture' : 'authorize';
180 $this->debug = 'yes' === $this->get_option( 'debug', 'no' );
181 $this->email = $this->get_option( 'email' );
182 $this->receiver_email = $this->get_option( 'receiver_email', $this->email );
183 $this->identity_token = $this->get_option( 'identity_token' );
184 $this->transact_onboarding_complete = 'yes' === $this->get_option( 'transact_onboarding_complete', 'no' );
185 self::$log_enabled = $this->debug;
186
187 if ( $this->testmode ) {
188 /* translators: 1: Link to PayPal sandbox testing guide page, 2: Link to PayPal info page */
189 $this->description .= '<br>' . sprintf( __( '<strong>Sandbox mode enabled</strong>. Only sandbox test accounts can be used. See the <a href="%1$s">PayPal Sandbox Testing Guide</a> for more details. <a href="%2$s" target="_blank">What is PayPal?</a>', 'woocommerce' ), 'https://developer.paypal.com/tools/sandbox/', esc_url( 'https://www.paypal.com/digital-wallet/how-paypal-works' ) );
190 $this->description = trim( $this->description );
191 }
192
193 // Actions.
194 add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
195 add_action( 'woocommerce_order_status_processing', array( $this, 'capture_payment' ) );
196 add_action( 'woocommerce_order_status_completed', array( $this, 'capture_payment' ) );
197 add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
198
199 if ( ! $this->is_valid_for_use() ) {
200 $this->enabled = 'no';
201 } else {
202 include_once __DIR__ . '/includes/class-wc-gateway-paypal-ipn-handler.php';
203 new WC_Gateway_Paypal_IPN_Handler( $this->testmode, $this->receiver_email );
204
205 if ( $this->identity_token ) {
206 include_once __DIR__ . '/includes/class-wc-gateway-paypal-pdt-handler.php';
207 $pdt_handler = new WC_Gateway_Paypal_PDT_Handler( $this->testmode, $this->identity_token );
208 $pdt_handler->set_receiver_email( $this->receiver_email );
209 }
210 }
211
212 if ( 'yes' === $this->enabled ) {
213 add_filter( 'woocommerce_thankyou_order_received_text', array( $this, 'order_received_text' ), 10, 2 );
214 // Hide action buttons for pending orders as they take a while to be captured with orders v2.
215 add_filter( 'woocommerce_my_account_my_orders_actions', array( $this, 'hide_action_buttons' ), 10, 2 );
216
217 add_filter( 'woocommerce_settings_api_form_fields_paypal', array( $this, 'maybe_remove_fields' ), 15 );
218
219 // Hook for plugin upgrades.
220 add_action( 'woocommerce_updated', array( $this, 'maybe_onboard_with_transact' ) );
221
222 if ( $this->should_use_orders_v2() ) {
223 // Hook for updating the shipping information on order approval (Orders v2).
224 add_action( 'woocommerce_before_thankyou', array( $this, 'update_addresses_in_order' ), 10 );
225
226 // Hook for PayPal order responses to manage account restriction notices.
227 add_action( 'woocommerce_paypal_standard_order_created_response', array( $this, 'manage_account_restriction_status' ), 10, 3 );
228
229 $buttons = new PayPalButtons( $this );
230 if ( $buttons->is_enabled() && ! $this->needs_setup() ) {
231 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
232 add_filter( 'wp_script_attributes', array( $this, 'add_paypal_sdk_attributes' ) );
233
234 // Render the buttons container to load the buttons via PayPal JS SDK.
235 // Classic checkout page.
236 add_action( 'woocommerce_checkout_before_customer_details', array( $this, 'render_buttons_container' ) );
237 // Classic cart page.
238 add_action( 'woocommerce_after_cart_totals', array( $this, 'render_buttons_container' ) );
239 // Product page.
240 add_action( 'woocommerce_after_add_to_cart_form', array( $this, 'render_buttons_container' ) );
241 }
242 }
243 }
244 }
245
246 /**
247 * Update the shipping and billing information for the order.
248 * Hooked on 'woocommerce_before_thankyou'.
249 *
250 * @param int $order_id The order ID.
251 * @return void
252 */
253 public function update_addresses_in_order( $order_id ) {
254 $order = wc_get_order( $order_id );
255
256 // Bail early if the order is not a PayPal order.
257 if ( ! $order instanceof WC_Order || $order->get_payment_method() !== $this->id ) {
258 return;
259 }
260
261 // Bail early if not on Orders v2.
262 if ( ! $this->should_use_orders_v2() ) {
263 return;
264 }
265
266 $paypal_order_id = $order->get_meta( PayPalConstants::PAYPAL_ORDER_META_ORDER_ID );
267 if ( empty( $paypal_order_id ) ) {
268 return;
269 }
270
271 /**
272 * Bail early if the addresses update already have been attempted (whether successful or not).
273 * Prevent duplicate address update attempts from the thankyou page.
274 *
275 * Address updates are primarily handled by the PayPal webhook when the order is approved.
276 * This method serves as a fallback if the webhook hasn't fired yet,
277 * but we want to show the correct addresses to the customer on the thankyou page.
278 * Once an attempt is made (meta exists), we skip to prevent repeated API calls on page reloads.
279 * The webhook handler will always update the addresses.
280 */
281 $addresses_update_attempted = $order->meta_exists( PayPalConstants::PAYPAL_ORDER_META_ADDRESSES_UPDATED );
282 if ( $addresses_update_attempted ) {
283 return;
284 }
285
286 try {
287 $paypal_request = new PayPalRequest( $this );
288 $paypal_order_details = $paypal_request->get_paypal_order_details( $paypal_order_id );
289
290 // Update the addresses in the order with the addresses from the PayPal order details.
291 PayPalHelper::update_addresses_in_order( $order, $paypal_order_details );
292 } catch ( Exception $e ) {
293 self::log( 'Error updating addresses for order #' . $order_id . ': ' . $e->getMessage(), 'error' );
294 $order->update_meta_data( PayPalConstants::PAYPAL_ORDER_META_ADDRESSES_UPDATED, 'no' );
295 $order->save();
296 }
297 }
298
299 /**
300 * Onboard the merchant with the Transact platform.
301 *
302 * @return void
303 */
304 public function maybe_onboard_with_transact() {
305 if ( ! is_admin() || ! current_user_can( 'manage_woocommerce' ) ) {
306 return;
307 }
308
309 // Do not run if PayPal Standard is not enabled.
310 if ( 'yes' !== $this->enabled ) {
311 return;
312 }
313
314 /**
315 * Filters whether the gateway should use Orders v2 API.
316 *
317 * @param bool $use_orders_v2 Whether the gateway should use Orders v2 API.
318 *
319 * @since 10.2.0
320 */
321 $use_orders_v2 = apply_filters(
322 'woocommerce_paypal_use_orders_v2',
323 PayPalHelper::is_orders_v2_migration_eligible()
324 );
325
326 // If the conditions are met, but there is an override to not use Orders v2,
327 // respect the override. Bail early -- we don't need to onboard if not using Orders v2.
328 if ( ! $use_orders_v2 ) {
329 return;
330 }
331
332 $transact_account_manager = new PayPalTransactAccountManager( $this );
333 $transact_account_manager->do_onboarding();
334 }
335
336 /**
337 * Check if the gateway is available for use.
338 *
339 * @return bool
340 */
341 public function is_available() {
342 if ( 'yes' !== $this->enabled ) {
343 return false;
344 }
345
346 // For Orders v2, require a valid email address to be set up in the gateway settings.
347 if ( $this->should_use_orders_v2() && $this->needs_setup() ) {
348 return false;
349 }
350
351 return parent::is_available();
352 }
353
354 /**
355 * Return whether or not this gateway still requires setup to function.
356 *
357 * When this gateway is toggled on via AJAX, if this returns true a
358 * redirect will occur to the settings page instead.
359 *
360 * @since 3.4.0
361 * @return bool
362 */
363 public function needs_setup() {
364 return empty( $this->email ) || ! is_email( $this->email );
365 }
366
367 /**
368 * Logging method.
369 *
370 * @param string $message Log message.
371 * @param string $level Optional. Default 'info'. Possible values:
372 * emergency|alert|critical|error|warning|notice|info|debug.
373 */
374 public static function log( $message, $level = 'info' ) {
375 if ( is_null( self::$log_enabled ) ) {
376 $settings = get_option( 'woocommerce_paypal_settings' );
377 self::$log_enabled = 'yes' === ( $settings['debug'] ?? 'no' );
378 }
379
380 if ( self::$log_enabled ) {
381 if ( empty( self::$log ) ) {
382 self::$log = wc_get_logger();
383 }
384 self::$log->log( $level, $message, array( 'source' => self::ID ) );
385 }
386 }
387
388 /**
389 * Processes and saves options.
390 * If there is an error thrown, will continue to save and validate fields, but will leave the erroring field out.
391 *
392 * @return bool was anything saved?
393 */
394 public function process_admin_options() {
395 $saved = parent::process_admin_options();
396
397 // Maybe clear logs.
398 if ( 'yes' !== $this->get_option( 'debug', 'no' ) ) {
399 if ( empty( self::$log ) ) {
400 self::$log = wc_get_logger();
401 }
402 if ( self::$log instanceof WC_Logger ) {
403 self::$log->clear( self::ID );
404 }
405 }
406
407 // Trigger Transact onboarding when settings are saved.
408 if ( $saved ) {
409 $this->maybe_onboard_with_transact();
410 }
411
412 return $saved;
413 }
414
415 /**
416 * Get gateway icon.
417 *
418 * @return string
419 */
420 public function get_icon() {
421 $icon = $this->get_paypal_icon_image();
422 $icon_html = '<img src="' . esc_attr( $icon ) . '" alt="' . esc_attr__( 'PayPal acceptance mark', 'woocommerce' ) . '" />';
423
424 return apply_filters( 'woocommerce_gateway_icon', $icon_html, $this->id );
425 }
426
427 /**
428 * Get the link for an icon based on country.
429 *
430 * @param string $country Country two letter code.
431 * @return string
432 */
433 protected function get_icon_url( $country ) {
434 $url = 'https://www.paypal.com/' . strtolower( $country );
435 $home_counties = array( 'BE', 'CZ', 'DK', 'HU', 'IT', 'JP', 'NL', 'NO', 'ES', 'SE', 'TR', 'IN' );
436 $countries = array( 'DZ', 'AU', 'BH', 'BQ', 'BW', 'CA', 'CN', 'CW', 'FI', 'FR', 'DE', 'GR', 'HK', 'ID', 'JO', 'KE', 'KW', 'LU', 'MY', 'MA', 'OM', 'PH', 'PL', 'PT', 'QA', 'IE', 'RU', 'BL', 'SX', 'MF', 'SA', 'SG', 'SK', 'KR', 'SS', 'TW', 'TH', 'AE', 'GB', 'US', 'VN' );
437
438 if ( in_array( $country, $home_counties, true ) ) {
439 return $url . '/webapps/mpp/home';
440 } elseif ( in_array( $country, $countries, true ) ) {
441 return $url . '/webapps/mpp/paypal-popup';
442 } else {
443 return $url . '/cgi-bin/webscr?cmd=xpt/Marketing/general/WIPaypal-outside';
444 }
445 }
446
447 /**
448 * Get PayPal icon image.
449 *
450 * @return string The PayPal icon image.
451 */
452 protected function get_paypal_icon_image() {
453 $icon = WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/paypal.png' );
454
455 /**
456 * Filters the PayPal icon image.
457 *
458 * @param string $icon The PayPal icon image.
459 * @return string The PayPal icon image.
460 * @since 10.6.0
461 */
462 return apply_filters( 'woocommerce_paypal_icon', $icon );
463 }
464
465 /**
466 * Get PayPal images for a country.
467 *
468 * @param string $country Country code.
469 * @return array of image URLs
470 * @deprecated 10.6.0 Use get_paypal_icon_image() instead.
471 */
472 protected function get_icon_image( $country ) {
473 wc_deprecated_function( __METHOD__, '10.6.0', 'get_paypal_icon_image()' );
474 switch ( $country ) {
475 case 'US':
476 case 'NZ':
477 case 'CZ':
478 case 'HU':
479 case 'MY':
480 $icon = 'https://www.paypalobjects.com/webstatic/mktg/logo/AM_mc_vs_dc_ae.jpg';
481 break;
482 case 'TR':
483 $icon = 'https://www.paypalobjects.com/webstatic/mktg/logo-center/logo_paypal_odeme_secenekleri.jpg';
484 break;
485 case 'GB':
486 $icon = 'https://www.paypalobjects.com/webstatic/mktg/Logo/AM_mc_vs_ms_ae_UK.png';
487 break;
488 case 'MX':
489 $icon = array(
490 'https://www.paypal.com/es_XC/Marketing/i/banner/paypal_visa_mastercard_amex.png',
491 'https://www.paypal.com/es_XC/Marketing/i/banner/paypal_debit_card_275x60.gif',
492 );
493 break;
494 case 'FR':
495 $icon = 'https://www.paypalobjects.com/webstatic/mktg/logo-center/logo_paypal_moyens_paiement_fr.jpg';
496 break;
497 case 'AU':
498 $icon = 'https://www.paypalobjects.com/webstatic/en_AU/mktg/logo/Solutions-graphics-1-184x80.jpg';
499 break;
500 case 'DK':
501 $icon = 'https://www.paypalobjects.com/webstatic/mktg/logo-center/logo_PayPal_betalingsmuligheder_dk.jpg';
502 break;
503 case 'RU':
504 $icon = 'https://www.paypalobjects.com/webstatic/ru_RU/mktg/business/pages/logo-center/AM_mc_vs_dc_ae.jpg';
505 break;
506 case 'NO':
507 $icon = 'https://www.paypalobjects.com/webstatic/mktg/logo-center/banner_pl_just_pp_319x110.jpg';
508 break;
509 case 'CA':
510 $icon = 'https://www.paypalobjects.com/webstatic/en_CA/mktg/logo-image/AM_mc_vs_dc_ae.jpg';
511 break;
512 case 'HK':
513 $icon = 'https://www.paypalobjects.com/webstatic/en_HK/mktg/logo/AM_mc_vs_dc_ae.jpg';
514 break;
515 case 'SG':
516 $icon = 'https://www.paypalobjects.com/webstatic/en_SG/mktg/Logos/AM_mc_vs_dc_ae.jpg';
517 break;
518 case 'TW':
519 $icon = 'https://www.paypalobjects.com/webstatic/en_TW/mktg/logos/AM_mc_vs_dc_ae.jpg';
520 break;
521 case 'TH':
522 $icon = 'https://www.paypalobjects.com/webstatic/en_TH/mktg/Logos/AM_mc_vs_dc_ae.jpg';
523 break;
524 case 'JP':
525 $icon = 'https://www.paypal.com/ja_JP/JP/i/bnr/horizontal_solution_4_jcb.gif';
526 break;
527 case 'IN':
528 $icon = 'https://www.paypalobjects.com/webstatic/mktg/logo/AM_mc_vs_dc_ae.jpg';
529 break;
530 default:
531 $icon = WC_HTTPS::force_https_url( WC()->plugin_url() . '/includes/gateways/paypal/assets/images/paypal.png' );
532 break;
533 }
534 return apply_filters( 'woocommerce_paypal_icon', $icon );
535 }
536
537 /**
538 * Check if this gateway is available in the user's country based on currency.
539 *
540 * @return bool
541 */
542 public function is_valid_for_use() {
543 if ( $this->should_use_orders_v2() ) {
544 $valid_currencies = PayPalConstants::SUPPORTED_CURRENCIES;
545 } else {
546 $valid_currencies = array( 'AUD', 'BRL', 'CAD', 'MXN', 'NZD', 'HKD', 'SGD', 'USD', 'EUR', 'JPY', 'TRY', 'NOK', 'CZK', 'DKK', 'HUF', 'ILS', 'MYR', 'PHP', 'PLN', 'SEK', 'CHF', 'TWD', 'THB', 'GBP', 'RMB', 'RUB', 'INR' );
547 }
548 return in_array(
549 get_woocommerce_currency(),
550 apply_filters(
551 'woocommerce_paypal_supported_currencies',
552 $valid_currencies
553 ),
554 true
555 );
556 }
557
558 /**
559 * Admin Panel Options.
560 * - Options for bits like 'title' and availability on a country-by-country basis.
561 *
562 * @since 1.0.0
563 */
564 public function admin_options() {
565 if ( $this->is_valid_for_use() ) {
566 parent::admin_options();
567 } elseif ( ! $this->should_use_orders_v2() ) {
568 ?>
569 <div class="inline error">
570 <p>
571 <strong><?php esc_html_e( 'Gateway disabled', 'woocommerce' ); ?></strong>: <?php esc_html_e( 'PayPal Standard does not support your store currency.', 'woocommerce' ); ?>
572 </p>
573 </div>
574 <?php
575 }
576 }
577
578 /**
579 * Initialise Gateway Settings Form Fields.
580 */
581 public function init_form_fields() {
582 $this->form_fields = include __DIR__ . '/includes/settings-paypal.php';
583 }
584
585 /**
586 * Filter to remove fields for Orders v2.
587 *
588 * @param array $form_fields Form fields.
589 * @return array
590 */
591 public function maybe_remove_fields( $form_fields ) {
592 // Remove legacy setting fiels when using Orders v2.
593 if ( $this->should_use_orders_v2() ) {
594 foreach ( $form_fields as $key => $field ) {
595 if ( isset( $field['is_legacy'] ) && $field['is_legacy'] ) {
596 unset( $form_fields[ $key ] );
597 }
598 }
599 }
600
601 if ( ! $this->should_use_orders_v2() ) {
602 unset( $form_fields['paypal_buttons'] );
603 }
604 return $form_fields;
605 }
606
607 /**
608 * Get the transaction URL.
609 *
610 * @param WC_Order $order Order object.
611 * @return string
612 */
613 public function get_transaction_url( $order ) {
614 if ( $this->testmode ) {
615 $this->view_transaction_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=%s';
616 } else {
617 $this->view_transaction_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=%s';
618 }
619 return parent::get_transaction_url( $order );
620 }
621
622 /**
623 * Process the payment and return the result.
624 *
625 * @param int $order_id Order ID.
626 * @return array
627 * @throws Exception If the PayPal order creation fails.
628 */
629 public function process_payment( $order_id ) {
630 $order = wc_get_order( $order_id );
631
632 if ( ! $order || ! $order instanceof WC_Order ) {
633 return array();
634 }
635
636 if ( $this->should_use_orders_v2() ) {
637 $paypal_request = new PayPalRequest( $this );
638
639 $paypal_order = $paypal_request->create_paypal_order( $order );
640 if ( ! $paypal_order || empty( $paypal_order['id'] ) || empty( $paypal_order['redirect_url'] ) ) {
641 throw new Exception(
642 esc_html__( 'We are unable to process your PayPal payment at this time. Please try again or use a different payment method.', 'woocommerce' )
643 );
644 }
645
646 $redirect_url = $paypal_order['redirect_url'];
647 } else {
648 include_once __DIR__ . '/includes/class-wc-gateway-paypal-request.php';
649
650 $paypal_request = new WC_Gateway_Paypal_Request( $this );
651 $redirect_url = $paypal_request->get_request_url( $order, $this->testmode );
652 }
653
654 return array(
655 'result' => 'success',
656 'redirect' => $redirect_url,
657 );
658 }
659
660 /**
661 * Can the order be refunded via PayPal?
662 *
663 * @param WC_Order $order Order object.
664 * @return bool
665 */
666 public function can_refund_order( $order ) {
667 $has_api_creds = false;
668
669 if ( $this->testmode ) {
670 $has_api_creds = $this->get_option( 'sandbox_api_username' ) && $this->get_option( 'sandbox_api_password' ) && $this->get_option( 'sandbox_api_signature' );
671 } else {
672 $has_api_creds = $this->get_option( 'api_username' ) && $this->get_option( 'api_password' ) && $this->get_option( 'api_signature' );
673 }
674
675 return $order && $order->get_transaction_id() && $has_api_creds;
676 }
677
678 /**
679 * Init the API class and set the username/password etc.
680 */
681 protected function init_api() {
682 include_once __DIR__ . '/includes/class-wc-gateway-paypal-api-handler.php';
683
684 WC_Gateway_Paypal_API_Handler::$api_username = $this->testmode ? $this->get_option( 'sandbox_api_username' ) : $this->get_option( 'api_username' );
685 WC_Gateway_Paypal_API_Handler::$api_password = $this->testmode ? $this->get_option( 'sandbox_api_password' ) : $this->get_option( 'api_password' );
686 WC_Gateway_Paypal_API_Handler::$api_signature = $this->testmode ? $this->get_option( 'sandbox_api_signature' ) : $this->get_option( 'api_signature' );
687 WC_Gateway_Paypal_API_Handler::$sandbox = $this->testmode;
688 }
689
690 /**
691 * Process a refund if supported.
692 *
693 * @param int $order_id Order ID.
694 * @param float $amount Refund amount.
695 * @param string $reason Refund reason.
696 * @return bool|WP_Error
697 */
698 public function process_refund( $order_id, $amount = null, $reason = '' ) {
699 $order = wc_get_order( $order_id );
700
701 if ( ! $this->can_refund_order( $order ) ) {
702 return new WP_Error( 'error', __( 'Refund failed.', 'woocommerce' ) );
703 }
704
705 $this->init_api();
706
707 $result = WC_Gateway_Paypal_API_Handler::refund_transaction( $order, $amount, $reason );
708
709 if ( is_wp_error( $result ) ) {
710 static::log( 'Refund Failed: ' . $result->get_error_message(), 'error' );
711 return new WP_Error( 'error', $result->get_error_message() );
712 }
713
714 static::log( 'Refund Result: ' . wc_print_r( $result, true ) );
715
716 switch ( strtolower( $result->ACK ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
717 case 'success':
718 case 'successwithwarning':
719 $order->add_order_note(
720 /* translators: 1: Refund amount, 2: Refund ID */
721 sprintf( __( 'Refunded %1$s - Refund ID: %2$s', 'woocommerce' ), $result->GROSSREFUNDAMT, $result->REFUNDTRANSACTIONID ) // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
722 );
723 return true;
724 }
725
726 return isset( $result->L_LONGMESSAGE0 ) ? new WP_Error( 'error', $result->L_LONGMESSAGE0 ) : false; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
727 }
728
729 /**
730 * Capture payment when the order is changed from on-hold to complete or processing
731 *
732 * @param int $order_id Order ID.
733 */
734 public function capture_payment( $order_id ) {
735 $order = wc_get_order( $order_id );
736 if ( ! $order || ! $order instanceof WC_Order ) {
737 return;
738 }
739
740 // Bail if the order is not a PayPal order.
741 if ( self::ID !== $order->get_payment_method() ) {
742 return;
743 }
744
745 // If the order is authorized via legacy API, the '_paypal_status' meta will be 'pending'.
746 $is_authorized_via_legacy_api = 'pending' === $order->get_meta( PayPalConstants::PAYPAL_ORDER_META_STATUS, true );
747
748 if ( $this->should_use_orders_v2() && ! $is_authorized_via_legacy_api ) {
749 $paypal_request = new PayPalRequest( $this );
750 $paypal_request->capture_authorized_payment( $order );
751 return;
752 }
753
754 if ( 'pending' === $order->get_meta( PayPalConstants::PAYPAL_ORDER_META_STATUS, true ) && $order->get_transaction_id() ) {
755 $this->init_api();
756 $result = WC_Gateway_Paypal_API_Handler::do_capture( $order );
757
758 if ( is_wp_error( $result ) ) {
759 static::log( 'Capture Failed: ' . $result->get_error_message(), 'error' );
760 /* translators: %s: Paypal gateway error message */
761 $order->add_order_note( sprintf( __( 'Payment could not be captured: %s', 'woocommerce' ), $result->get_error_message() ) );
762 return;
763 }
764
765 static::log( 'Capture Result: ' . wc_print_r( $result, true ) );
766
767 // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
768 if ( ! empty( $result->PAYMENTSTATUS ) ) {
769 switch ( $result->PAYMENTSTATUS ) {
770 case 'Completed':
771 /* translators: 1: Amount, 2: Authorization ID, 3: Transaction ID */
772 $order->add_order_note( sprintf( __( 'Payment of %1$s was captured - Auth ID: %2$s, Transaction ID: %3$s', 'woocommerce' ), $result->AMT, $result->AUTHORIZATIONID, $result->TRANSACTIONID ) );
773 $order->update_meta_data( PayPalConstants::PAYPAL_ORDER_META_STATUS, $result->PAYMENTSTATUS );
774 $order->set_transaction_id( $result->TRANSACTIONID );
775 $order->save();
776 break;
777 default:
778 /* translators: 1: Authorization ID, 2: Payment status */
779 $order->add_order_note( sprintf( __( 'Payment could not be captured - Auth ID: %1$s, Status: %2$s', 'woocommerce' ), $result->AUTHORIZATIONID, $result->PAYMENTSTATUS ) );
780 break;
781 }
782 }
783 // phpcs:enable
784 }
785 }
786
787 /**
788 * Load admin scripts.
789 *
790 * @since 3.3.0
791 */
792 public function admin_scripts() {
793 $screen = get_current_screen();
794 $screen_id = $screen ? $screen->id : '';
795
796 if ( 'woocommerce_page_wc-settings' !== $screen_id ) {
797 return;
798 }
799
800 $suffix = Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min';
801 $version = Constants::get_constant( 'WC_VERSION' );
802
803 wp_enqueue_script( 'woocommerce_paypal_admin', WC()->plugin_url() . '/includes/gateways/paypal/assets/js/paypal-admin' . $suffix . '.js', array(), $version, true );
804 }
805
806 /**
807 * Enqueue scripts.
808 */
809 public function enqueue_scripts() {
810 if ( 'no' === $this->enabled ) {
811 return;
812 }
813
814 $version = Constants::get_constant( 'WC_VERSION' );
815 $is_page_supported = is_checkout() || is_cart() || is_product();
816 $buttons = new PayPalButtons( $this );
817 $options = $buttons->get_common_options();
818
819 if ( empty( $options['client-id'] ) || ! $is_page_supported ) {
820 return;
821 }
822
823 $sdk_host = $this->testmode ? 'https://www.sandbox.paypal.com/sdk/js' : 'https://www.paypal.com/sdk/js';
824
825 // Add PayPal JS SDK script.
826 // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
827 wp_register_script( 'paypal-standard-sdk', add_query_arg( $options, $sdk_host ), array(), null, false );
828 wp_enqueue_script( 'paypal-standard-sdk' );
829
830 wp_register_script( 'wc-paypal-frontend', WC()->plugin_url() . '/client/legacy/js/gateways/paypal.js', array( 'jquery', 'wp-api-fetch' ), $version, true );
831
832 wp_localize_script(
833 'wc-paypal-frontend',
834 'paypal_standard',
835 array(
836 'gateway_id' => $this->id,
837 'is_product_page' => is_product(),
838 'app_switch_request_origin' => $buttons->get_current_page_for_app_switch(),
839 'wc_store_api_nonce' => wp_create_nonce( 'wc_store_api' ),
840 'create_order_nonce' => wp_create_nonce( 'wc_gateway_paypal_standard_create_order' ),
841 'cancel_payment_nonce' => wp_create_nonce( 'wc_gateway_paypal_standard_cancel_payment' ),
842 'generic_error_message' => __( 'An unknown error occurred', 'woocommerce' ),
843 )
844 );
845
846 wp_enqueue_script( 'wc-paypal-frontend' );
847 }
848
849 /**
850 * Add PayPal SDK attributes to the script.
851 *
852 * @param array $attrs Attributes.
853 * @return array
854 */
855 public function add_paypal_sdk_attributes( $attrs ) {
856 if ( 'paypal-standard-sdk-js' === $attrs['id'] ) {
857 $buttons = new PayPalButtons( $this );
858 $page_type = $buttons->get_page_type();
859
860 $attrs['data-page-type'] = $page_type;
861 $attrs['data-partner-attribution-id'] = 'Woo_Cart_CoreUpgrade';
862 }
863
864 return $attrs;
865 }
866
867 /**
868 * Builds the PayPal payment fields area.
869 *
870 * @since 10.3.0
871 */
872 public function render_buttons_container() {
873 echo '<div id="paypal-standard-container"></div>';
874 }
875
876 /**
877 * Custom PayPal order received text.
878 *
879 * @since 3.9.0
880 * @param string $text Default text.
881 * @param WC_Order $order Order data.
882 * @return string
883 */
884 public function order_received_text( $text, $order ) {
885 if ( $order && $this->id === $order->get_payment_method() ) {
886 return esc_html__( 'Thank you for your payment. Your transaction has been completed, and a receipt for your purchase has been emailed to you. Log into your PayPal account to view transaction details.', 'woocommerce' );
887 }
888
889 return $text;
890 }
891
892 /**
893 * Hide "Pay" and "Cancel" action buttons for pending orders as orders v2 takes a while to be captured.
894 *
895 * @param array $actions An array with the default actions.
896 * @param WC_Order $order The order.
897 * @return array
898 */
899 public function hide_action_buttons( $actions, $order ) {
900 if ( $this->should_use_orders_v2() && $this->id === $order->get_payment_method() ) {
901 unset( $actions['pay'], $actions['cancel'] );
902 }
903 return $actions;
904 }
905
906 /**
907 * Determines whether PayPal Standard should be loaded or not.
908 *
909 * By default PayPal Standard isn't loaded on new installs or on existing sites which haven't set up the gateway.
910 *
911 * @since 5.5.0
912 *
913 * @return bool Whether PayPal Standard should be loaded.
914 */
915 public function should_load() {
916 $option_key = '_should_load';
917 $should_load = $this->get_option( $option_key );
918
919 if ( '' === $should_load ) {
920 // Set default `_should_load` to 'yes' on existing stores with PayPal Standard enabled or with existing PayPal Standard orders.
921 $should_load = 'yes' === $this->enabled || $this->has_paypal_orders();
922
923 $this->update_option( $option_key, wc_bool_to_string( $should_load ) );
924 } else {
925 // Enabled always takes precedence over the option.
926 $should_load = wc_string_to_bool( $this->enabled ) || wc_string_to_bool( $should_load );
927 }
928
929 return $should_load;
930 }
931
932 /**
933 * Checks if the store has at least one PayPal Standand order.
934 *
935 * @return bool
936 */
937 public function has_paypal_orders() {
938 $paypal_orders = wc_get_orders(
939 array(
940 'limit' => 1,
941 'return' => 'ids',
942 'payment_method' => self::ID,
943 )
944 );
945
946 return is_countable( $paypal_orders ) ? 1 === count( $paypal_orders ) : false;
947 }
948
949 /**
950 * Check if the gateway should use Orders v2 API.
951 *
952 * @return bool
953 */
954 public function should_use_orders_v2() {
955 /**
956 * Filters whether the gateway should use Orders v2 API.
957 *
958 * @param bool $use_orders_v2 Whether the gateway should use Orders v2 API.
959 *
960 * @since 10.2.0
961 */
962 $use_orders_v2 = apply_filters(
963 'woocommerce_paypal_use_orders_v2',
964 PayPalHelper::is_orders_v2_migration_eligible()
965 );
966
967 // If the conditions are met, but there is an override to not use Orders v2,
968 // respect the override.
969 if ( ! $use_orders_v2 ) {
970 return false;
971 }
972
973 // If the gateway is not onboarded, bail early.
974 if ( ! $this->is_transact_onboarding_complete() ) {
975 return false;
976 }
977
978 // We need a Jetpack connection to be able to send authenticated requests to the proxy.
979 $jetpack_connection_manager = $this->get_jetpack_connection_manager();
980 if ( ! $jetpack_connection_manager || ! $jetpack_connection_manager->is_connected() ) {
981 return false;
982 }
983
984 // We need merchant and provider accounts with Transact to be able to use the proxy.
985 $transact_account_manager = new PayPalTransactAccountManager( $this );
986 $merchant_account_data = $transact_account_manager->get_transact_account_data( 'merchant' );
987 if ( empty( $merchant_account_data ) ) {
988 return false;
989 }
990
991 $provider_account_data = $transact_account_manager->get_transact_account_data( 'provider' );
992 if ( empty( $provider_account_data ) ) {
993 return false;
994 }
995
996 return true;
997 }
998
999 /**
1000 * Get the Jetpack connection manager.
1001 *
1002 * @return Jetpack_Connection_Manager
1003 */
1004 public function get_jetpack_connection_manager() {
1005 if ( ! $this->jetpack_connection_manager ) {
1006 $this->jetpack_connection_manager = new Jetpack_Connection_Manager( 'woocommerce' );
1007 }
1008 return $this->jetpack_connection_manager;
1009 }
1010
1011 /**
1012 * Whether the Transact onboarding is complete.
1013 *
1014 * @return bool
1015 */
1016 public function is_transact_onboarding_complete() {
1017 return $this->transact_onboarding_complete;
1018 }
1019
1020 /**
1021 * Set the Transact onboarding as complete.
1022 *
1023 * @return void
1024 */
1025 public function set_transact_onboarding_complete() {
1026 if ( $this->transact_onboarding_complete ) {
1027 return;
1028 }
1029
1030 $this->update_option( 'transact_onboarding_complete', 'yes' );
1031 $this->transact_onboarding_complete = true;
1032 }
1033
1034 /**
1035 * Handle PayPal order response to manage account restriction notices.
1036 *
1037 * This method is called via the 'woocommerce_paypal_standard_order_created_response' hook
1038 * and manages the account restriction flag based on PayPal API responses.
1039 *
1040 * Extensions can disable this feature using the filter:
1041 * add_filter( 'woocommerce_paypal_account_restriction_notices_enabled', '__return_false' );
1042 *
1043 * @param int|string $http_code The HTTP status code from the PayPal API response.
1044 * @param array $response_data The decoded response data from the PayPal API.
1045 * @param WC_Order $order The WooCommerce order object.
1046 * @return void
1047 */
1048 public function manage_account_restriction_status( $http_code, $response_data, $order ): void {
1049 /**
1050 * Filters whether account restriction notices should be enabled.
1051 *
1052 * This filter allows extensions to opt out of the account restriction notice functionality.
1053 *
1054 * @since 10.4.0
1055 *
1056 * @param bool $enabled Whether account restriction notices are enabled. Default true.
1057 */
1058 if ( ! apply_filters( 'woocommerce_paypal_account_restriction_notices_enabled', true ) ) {
1059 return;
1060 }
1061
1062 PayPalNotices::manage_account_restriction_flag_for_notice( $http_code, $response_data, $order );
1063 }
1064 }
1065
1066 // Initialize PayPal admin notices handler on 'init' hook to ensure the class loads before admin_init and admin_notices hooks fire.
1067 add_action(
1068 'init',
1069 function () {
1070 if ( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
1071 return;
1072 }
1073
1074 include_once __DIR__ . '/includes/class-wc-gateway-paypal-notices.php';
1075 new PayPalNotices();
1076 }
1077 );
1078