blob: 0aa6fbbf2d81794eb2670f93a43b605882b05241 [file] [log] [blame]
swissChilif0cbdc32023-01-05 17:21:38 -05001<?php
2use Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType;
3use Automattic\WooCommerce\Blocks\Payments\PaymentResult;
4use Automattic\WooCommerce\Blocks\Payments\PaymentContext;
5
6defined( 'ABSPATH' ) || exit;
7
8/**
9 * WC_Stripe_Blocks_Support class.
10 *
11 * @extends AbstractPaymentMethodType
12 */
13final class WC_Stripe_Blocks_Support extends AbstractPaymentMethodType {
14 /**
15 * Payment method name defined by payment methods extending this class.
16 *
17 * @var string
18 */
19 protected $name = 'stripe';
20
21 /**
22 * The Payment Request configuration class used for Shortcode PRBs. We use it here to retrieve
23 * the same configurations.
24 *
25 * @var WC_Stripe_Payment_Request
26 */
27 private $payment_request_configuration;
28
29 /**
30 * Constructor
31 *
32 * @param WC_Stripe_Payment_Request The Stripe Payment Request configuration used for Payment
33 * Request buttons.
34 */
35 public function __construct( $payment_request_configuration = null ) {
36 add_action( 'woocommerce_rest_checkout_process_payment_with_context', [ $this, 'add_payment_request_order_meta' ], 8, 2 );
37 add_action( 'woocommerce_rest_checkout_process_payment_with_context', [ $this, 'add_stripe_intents' ], 9999, 2 );
38 $this->payment_request_configuration = null !== $payment_request_configuration ? $payment_request_configuration : new WC_Stripe_Payment_Request();
39 }
40
41 /**
42 * Initializes the payment method type.
43 */
44 public function initialize() {
45 $this->settings = get_option( 'woocommerce_stripe_settings', [] );
46 }
47
48 /**
49 * Returns if this payment method should be active. If false, the scripts will not be enqueued.
50 *
51 * @return boolean
52 */
53 public function is_active() {
54 return ! empty( $this->settings['enabled'] ) && 'yes' === $this->settings['enabled'];
55 }
56
57 /**
58 * Returns an array of scripts/handles to be registered for this payment method.
59 *
60 * @return array
61 */
62 public function get_payment_method_script_handles() {
63 // Ensure Stripe JS is enqueued
64 wp_register_script(
65 'stripe',
66 'https://js.stripe.com/v3/',
67 [],
68 '3.0',
69 true
70 );
71
72 if ( WC_Stripe_Feature_Flags::is_upe_checkout_enabled() ) {
73 $this->register_upe_payment_method_script_handles();
74 } else {
75 $this->register_legacy_payment_method_script_handles();
76 }
77
78 return [ 'wc-stripe-blocks-integration' ];
79 }
80
81 /**
82 * Registers the UPE JS scripts.
83 */
84 private function register_upe_payment_method_script_handles() {
85 $asset_path = WC_STRIPE_PLUGIN_PATH . '/build/upe_blocks.asset.php';
86 $version = WC_STRIPE_VERSION;
87 $dependencies = [];
88 if ( file_exists( $asset_path ) ) {
89 $asset = require $asset_path;
90 $version = is_array( $asset ) && isset( $asset['version'] )
91 ? $asset['version']
92 : $version;
93 $dependencies = is_array( $asset ) && isset( $asset['dependencies'] )
94 ? $asset['dependencies']
95 : $dependencies;
96 }
97
98 wp_enqueue_style(
99 'wc-stripe-blocks-checkout-style',
100 WC_STRIPE_PLUGIN_URL . '/build/upe_blocks.css',
101 [],
102 $version
103 );
104
105 wp_register_script(
106 'wc-stripe-blocks-integration',
107 WC_STRIPE_PLUGIN_URL . '/build/upe_blocks.js',
108 array_merge( [ 'stripe' ], $dependencies ),
109 $version,
110 true
111 );
112 wp_set_script_translations(
113 'wc-stripe-blocks-integration',
114 'woocommerce-gateway-stripe'
115 );
116 }
117
118 /**
119 * Registers the classic JS scripts.
120 */
121 private function register_legacy_payment_method_script_handles() {
122 $asset_path = WC_STRIPE_PLUGIN_PATH . '/build/index.asset.php';
123 $version = WC_STRIPE_VERSION;
124 $dependencies = [];
125 if ( file_exists( $asset_path ) ) {
126 $asset = require $asset_path;
127 $version = is_array( $asset ) && isset( $asset['version'] )
128 ? $asset['version']
129 : $version;
130 $dependencies = is_array( $asset ) && isset( $asset['dependencies'] )
131 ? $asset['dependencies']
132 : $dependencies;
133 }
134 wp_register_script(
135 'wc-stripe-blocks-integration',
136 WC_STRIPE_PLUGIN_URL . '/build/index.js',
137 array_merge( [ 'stripe' ], $dependencies ),
138 $version,
139 true
140 );
141 wp_set_script_translations(
142 'wc-stripe-blocks-integration',
143 'woocommerce-gateway-stripe'
144 );
145 }
146
147 /**
148 * Returns an array of key=>value pairs of data made available to the payment methods script.
149 *
150 * @return array
151 */
152 public function get_payment_method_data() {
153 // We need to call array_merge_recursive so the blocks 'button' setting doesn't overwrite
154 // what's provided from the gateway or payment request configuration.
155 return array_replace_recursive(
156 $this->get_gateway_javascript_params(),
157 $this->get_payment_request_javascript_params(),
158 // Blocks-specific options
159 [
160 'icons' => $this->get_icons(),
161 'supports' => $this->get_supported_features(),
162 'showSavedCards' => $this->get_show_saved_cards(),
163 'showSaveOption' => $this->get_show_save_option(),
164 'isAdmin' => is_admin(),
165 'shouldShowPaymentRequestButton' => $this->should_show_payment_request_button(),
166 'button' => [
167 'customLabel' => $this->payment_request_configuration->get_button_label(),
168 ],
169 ]
170 );
171 }
172
173 /**
174 * Returns true if the PRB should be shown on the current page, false otherwise.
175 *
176 * Note: We use `has_block()` in this function, which isn't supported until WP 5.0. However,
177 * WooCommerce Blocks hasn't supported a WP version lower than 5.0 since 2019. Since this
178 * function is only called when the WooCommerce Blocks extension is available, it should be
179 * safe to call `has_block()` here.
180 * That said, we only run those checks if the `has_block()` function exists, just in case.
181 *
182 * @return boolean True if PRBs should be displayed, false otherwise
183 */
184 private function should_show_payment_request_button() {
185 // TODO: Remove the `function_exists()` check once the minimum WP version has been bumped
186 // to version 5.0.
187 if ( function_exists( 'has_block' ) ) {
188 // Don't show if PRBs are supposed to be hidden on the cart page.
189 if (
190 has_block( 'woocommerce/cart' )
191 && ! $this->payment_request_configuration->should_show_prb_on_cart_page()
192 ) {
193 return false;
194 }
195
196 // Don't show if PRBs are supposed to be hidden on the checkout page.
197 if (
198 has_block( 'woocommerce/checkout' )
199 && ! $this->payment_request_configuration->should_show_prb_on_checkout_page()
200 ) {
201 return false;
202 }
203
204 // Don't show PRB if there are unsupported products in the cart.
205 if (
206 ( has_block( 'woocommerce/checkout' ) || has_block( 'woocommerce/cart' ) )
207 && ! $this->payment_request_configuration->allowed_items_in_cart()
208 ) {
209 return false;
210 }
211 }
212
213 return $this->payment_request_configuration->should_show_payment_request_button();
214 }
215
216 /**
217 * Returns the Stripe Payment Gateway JavaScript configuration object.
218 *
219 * @return array the JS configuration from the Stripe Payment Gateway.
220 */
221 private function get_gateway_javascript_params() {
222 $js_configuration = [];
223
224 $gateways = WC()->payment_gateways->get_available_payment_gateways();
225 if ( isset( $gateways['stripe'] ) ) {
226 $js_configuration = $gateways['stripe']->javascript_params();
227 }
228
229 return apply_filters(
230 'wc_stripe_params',
231 $js_configuration
232 );
233 }
234
235 /**
236 * Returns the Stripe Payment Request JavaScript configuration object.
237 *
238 * @return array the JS configuration for Stripe Payment Requests.
239 */
240 private function get_payment_request_javascript_params() {
241 return apply_filters(
242 'wc_stripe_payment_request_params',
243 $this->payment_request_configuration->javascript_params()
244 );
245 }
246
247 /**
248 * Determine if store allows cards to be saved during checkout.
249 *
250 * @return bool True if merchant allows shopper to save card (payment method) during checkout.
251 */
252 private function get_show_saved_cards() {
253 return isset( $this->settings['saved_cards'] ) ? 'yes' === $this->settings['saved_cards'] : false;
254 }
255
256 /**
257 * Determine if the checkbox to enable the user to save their payment method should be shown.
258 *
259 * @return bool True if the save payment checkbox should be displayed to the user.
260 */
261 private function get_show_save_option() {
262 $saved_cards = $this->get_show_saved_cards();
263 // This assumes that Stripe supports `tokenization` - currently this is true, based on
264 // https://github.com/woocommerce/woocommerce-gateway-stripe/blob/master/includes/class-wc-gateway-stripe.php#L95 .
265 // See https://github.com/woocommerce/woocommerce-gateway-stripe/blob/ad19168b63df86176cbe35c3e95203a245687640/includes/class-wc-gateway-stripe.php#L271 and
266 // https://github.com/woocommerce/woocommerce/wiki/Payment-Token-API .
267 return apply_filters( 'wc_stripe_display_save_payment_method_checkbox', filter_var( $saved_cards, FILTER_VALIDATE_BOOLEAN ) );
268 }
269
270 /**
271 * Returns the title string to use in the UI (customisable via admin settings screen).
272 *
273 * @return string Title / label string
274 */
275 private function get_title() {
276 return isset( $this->settings['title'] ) ? $this->settings['title'] : __( 'Credit / Debit Card', 'woocommerce-gateway-stripe' );
277 }
278
279 /**
280 * Return the icons urls.
281 *
282 * @return array Arrays of icons metadata.
283 */
284 private function get_icons() {
285 $icons_src = [
286 'visa' => [
287 'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/visa.svg',
288 'alt' => __( 'Visa', 'woocommerce-gateway-stripe' ),
289 ],
290 'amex' => [
291 'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/amex.svg',
292 'alt' => __( 'American Express', 'woocommerce-gateway-stripe' ),
293 ],
294 'mastercard' => [
295 'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/mastercard.svg',
296 'alt' => __( 'Mastercard', 'woocommerce-gateway-stripe' ),
297 ],
298 ];
299
300 if ( 'USD' === get_woocommerce_currency() ) {
301 $icons_src['discover'] = [
302 'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/discover.svg',
303 'alt' => __( 'Discover', 'woocommerce-gateway-stripe' ),
304 ];
305 $icons_src['jcb'] = [
306 'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/jcb.svg',
307 'alt' => __( 'JCB', 'woocommerce-gateway-stripe' ),
308 ];
309 $icons_src['diners'] = [
310 'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/diners.svg',
311 'alt' => __( 'Diners', 'woocommerce-gateway-stripe' ),
312 ];
313 }
314 return $icons_src;
315 }
316
317 /**
318 * Add payment request data to the order meta as hooked on the
319 * woocommerce_rest_checkout_process_payment_with_context action.
320 *
321 * @param PaymentContext $context Holds context for the payment.
322 * @param PaymentResult $result Result object for the payment.
323 */
324 public function add_payment_request_order_meta( PaymentContext $context, PaymentResult &$result ) {
325 $data = $context->payment_data;
326 if ( ! empty( $data['payment_request_type'] ) && 'stripe' === $context->payment_method ) {
327 $this->add_order_meta( $context->order, $data['payment_request_type'] );
328 }
329
330 // hook into stripe error processing so that we can capture the error to
331 // payment details (which is added to notices and thus not helpful for
332 // this context).
333 if ( 'stripe' === $context->payment_method ) {
334 add_action(
335 'wc_gateway_stripe_process_payment_error',
336 function( $error ) use ( &$result ) {
337 $payment_details = $result->payment_details;
338 $payment_details['errorMessage'] = wp_strip_all_tags( $error->getLocalizedMessage() );
339 $result->set_payment_details( $payment_details );
340 }
341 );
342 }
343 }
344
345 /**
346 * Handles any potential stripe intents on the order that need handled.
347 *
348 * This is configured to execute after legacy payment processing has
349 * happened on the woocommerce_rest_checkout_process_payment_with_context
350 * action hook.
351 *
352 * @param PaymentContext $context Holds context for the payment.
353 * @param PaymentResult $result Result object for the payment.
354 */
355 public function add_stripe_intents( PaymentContext $context, PaymentResult &$result ) {
356 if ( 'stripe' === $context->payment_method
357 && (
358 ! empty( $result->payment_details['payment_intent_secret'] )
359 || ! empty( $result->payment_details['setup_intent_secret'] )
360 )
361 ) {
362 $payment_details = $result->payment_details;
363 $verification_endpoint = add_query_arg(
364 [
365 'order' => $context->order->get_id(),
366 'nonce' => wp_create_nonce( 'wc_stripe_confirm_pi' ),
367 'redirect_to' => rawurlencode( $result->redirect_url ),
368 ],
369 home_url() . \WC_Ajax::get_endpoint( 'wc_stripe_verify_intent' )
370 );
371
372 if ( ! empty( $payment_details['save_payment_method'] ) ) {
373 $verification_endpoint = add_query_arg(
374 [ 'save_payment_method' => true ],
375 $verification_endpoint
376 );
377 }
378
379 $payment_details['verification_endpoint'] = $verification_endpoint;
380 $result->set_payment_details( $payment_details );
381 $result->set_status( 'success' );
382 }
383 }
384
385 /**
386 * Handles adding information about the payment request type used to the order meta.
387 *
388 * @param \WC_Order $order The order being processed.
389 * @param string $payment_request_type The payment request type used for payment.
390 */
391 private function add_order_meta( \WC_Order $order, $payment_request_type ) {
392 if ( 'apple_pay' === $payment_request_type ) {
393 $order->set_payment_method_title( 'Apple Pay (Stripe)' );
394 $order->save();
395 } elseif ( 'google_pay' === $payment_request_type ) {
396 $order->set_payment_method_title( 'Google Pay (Stripe)' );
397 $order->save();
398 } elseif ( 'payment_request_api' === $payment_request_type ) {
399 $order->set_payment_method_title( 'Payment Request (Stripe)' );
400 $order->save();
401 }
402 }
403
404 /**
405 * Returns an array of supported features.
406 *
407 * @return string[]
408 */
409 public function get_supported_features() {
410 $gateways = WC()->payment_gateways->get_available_payment_gateways();
411 if ( isset( $gateways['stripe'] ) ) {
412 $gateway = $gateways['stripe'];
413 return array_filter( $gateway->supports, [ $gateway, 'supports' ] );
414 }
415 return [];
416 }
417}