blob: d8319156eeeecee0b25f2912f3c1921cd5588c5a [file] [log] [blame]
swissChilif0cbdc32023-01-05 17:21:38 -05001<?php
2if ( ! defined( 'ABSPATH' ) ) {
3 exit;
4}
5
6// phpcs:disable WordPress.Files.FileName
7
8/**
9 * Abstract class that will be inherited by all payment methods.
10 *
11 * @extends WC_Payment_Gateway_CC
12 *
13 * @since 4.0.0
14 */
15abstract class WC_Stripe_Payment_Gateway extends WC_Payment_Gateway_CC {
16
17 use WC_Stripe_Subscriptions_Trait;
18 use WC_Stripe_Pre_Orders_Trait;
19
20 /**
21 * The delay between retries.
22 *
23 * @var int
24 */
25 protected $retry_interval = 1;
26
27 /**
28 * Fallback method to be inherited by all payment methods. Stripe UPE will override it.
29 *
30 * @return string[]
31 */
32 public function get_upe_enabled_payment_method_ids() {
33 return [ 'card' ];
34 }
35
36 /**
37 * Fallback method to be inherited by all payment methods. Stripe UPE will override it.
38 *
39 * @return string[]
40 */
41 public function get_upe_available_payment_methods() {
42 return [ 'card' ];
43 }
44
45 /**
46 * Checks whether the gateway is enabled.
47 *
48 * @return bool The result.
49 */
50 public function is_enabled() {
51 return 'yes' === $this->get_option( 'enabled' );
52 }
53
54 /**
55 * Disables gateway.
56 */
57 public function disable() {
58 $this->update_option( 'enabled', 'no' );
59 }
60
61 /**
62 * Enables gateway.
63 */
64 public function enable() {
65 $this->update_option( 'enabled', 'yes' );
66 }
67
68 /**
69 * Displays the admin settings webhook description.
70 *
71 * @since 4.1.0
72 * @version 5.0.0
73 * @return mixed
74 */
75 public function display_admin_settings_webhook_description() {
76 /* translators: 1) webhook url */
77 $description = sprintf( __( 'You must add the following webhook endpoint <strong style="background-color:#ddd;">&nbsp;%s&nbsp;</strong> to your <a href="https://dashboard.stripe.com/account/webhooks" target="_blank">Stripe account settings</a> (if there isn\'t one already enabled). This will enable you to receive notifications on the charge statuses.', 'woocommerce-gateway-stripe' ), WC_Stripe_Helper::get_webhook_url() );
78
79 $webhook_status = WC_Stripe_Webhook_State::get_webhook_status_message();
80
81 return $description . '<br><br>' . $webhook_status;
82 }
83
84 /**
85 * Prints the admin options for the gateway.
86 * Inserts an empty placeholder div feature flag is enabled.
87 */
88 public function admin_options() {
89 $form_fields = $this->get_form_fields();
90
91 echo '<h2>' . esc_html( $this->get_method_title() );
92 wc_back_link( __( 'Return to payments', 'woocommerce-gateway-stripe' ), admin_url( 'admin.php?page=wc-settings&tab=checkout' ) );
93 echo '</h2>';
94
95 $this->render_upe_settings();
96 }
97
98 /**
99 * Inserts an empty placeholder div for new account card when Stripe is not connected.
100 * Inserts an empty placeholder div for UPE opt-in banner within the existing form fields, otherwise.
101 */
102 public function render_upe_settings() {
103 global $hide_save_button;
104 $hide_save_button = true;
105 $is_stripe_connected = woocommerce_gateway_stripe()->connect->is_connected();
106
107 echo $is_stripe_connected ? '<div id="wc-stripe-payment-gateway-container"></div>' : '<div id="wc-stripe-new-account-container"></div>';
108 }
109
110 /**
111 * Displays the save to account checkbox.
112 *
113 * @since 4.1.0
114 * @version 5.6.0
115 */
116 public function save_payment_method_checkbox( $force_checked = false ) {
117 $id = 'wc-' . $this->id . '-new-payment-method';
118 ?>
119 <fieldset <?php echo $force_checked ? 'style="display:none;"' : ''; /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ ?>>
120 <p class="form-row woocommerce-SavedPaymentMethods-saveNew">
121 <input id="<?php echo esc_attr( $id ); ?>" name="<?php echo esc_attr( $id ); ?>" type="checkbox" value="true" style="width:auto;" <?php echo $force_checked ? 'checked' : ''; /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ ?> />
122 <label for="<?php echo esc_attr( $id ); ?>" style="display:inline;">
123 <?php echo esc_html( apply_filters( 'wc_stripe_save_to_account_text', __( 'Save payment information to my account for future purchases.', 'woocommerce-gateway-stripe' ) ) ); ?>
124 </label>
125 </p>
126 </fieldset>
127 <?php
128 }
129
130 /**
131 * Checks to see if request is invalid and that
132 * they are worth retrying.
133 *
134 * @since 4.0.5
135 * @param array $error
136 */
137 public function is_retryable_error( $error ) {
138 return (
139 'invalid_request_error' === $error->type ||
140 'idempotency_error' === $error->type ||
141 'rate_limit_error' === $error->type ||
142 'api_connection_error' === $error->type ||
143 'api_error' === $error->type
144 );
145 }
146
147 /**
148 * Checks to see if error is of same idempotency key
149 * error due to retries with different parameters.
150 *
151 * @since 4.1.0
152 * @param array $error
153 */
154 public function is_same_idempotency_error( $error ) {
155 return (
156 $error &&
157 'idempotency_error' === $error->type &&
158 preg_match( '/Keys for idempotent requests can only be used with the same parameters they were first used with./i', $error->message )
159 );
160 }
161
162 /**
163 * Checks to see if error is of invalid request
164 * error and it is no such customer.
165 *
166 * @since 4.1.0
167 * @param array $error
168 */
169 public function is_no_such_customer_error( $error ) {
170 return (
171 $error &&
172 'invalid_request_error' === $error->type &&
173 preg_match( '/No such customer/i', $error->message )
174 );
175 }
176
177 /**
178 * Checks to see if error is of invalid request
179 * error and it is no such token.
180 *
181 * @since 4.1.0
182 * @param array $error
183 */
184 public function is_no_such_token_error( $error ) {
185 return (
186 $error &&
187 'invalid_request_error' === $error->type &&
188 preg_match( '/No such token/i', $error->message )
189 );
190 }
191
192 /**
193 * Checks to see if error is of invalid request
194 * error and it is no such source.
195 *
196 * @since 4.1.0
197 * @param array $error
198 */
199 public function is_no_such_source_error( $error ) {
200 return (
201 $error &&
202 'invalid_request_error' === $error->type &&
203 preg_match( '/No such (source|PaymentMethod)/i', $error->message )
204 );
205 }
206
207 /**
208 * Checks to see if error is of invalid request
209 * error and it is no such source linked to customer.
210 *
211 * @since 4.1.0
212 * @param array $error
213 */
214 public function is_no_linked_source_error( $error ) {
215 return (
216 $error &&
217 'invalid_request_error' === $error->type &&
218 preg_match( '/does not have a linked source with ID/i', $error->message )
219 );
220 }
221
222 /**
223 * Check to see if we need to update the idempotency
224 * key to be different from previous charge request.
225 *
226 * @since 4.1.0
227 * @param object $source_object
228 * @param object $error
229 * @return bool
230 */
231 public function need_update_idempotency_key( $source_object, $error ) {
232 return (
233 $error &&
234 1 < $this->retry_interval &&
235 ! empty( $source_object ) &&
236 'chargeable' === $source_object->status &&
237 self::is_same_idempotency_error( $error )
238 );
239 }
240
241 /**
242 * Checks if keys are set and valid.
243 *
244 * @since 4.0.6
245 * @return bool True if the keys are set *and* valid, false otherwise (for example, if keys are empty or the secret key was pasted as publishable key).
246 */
247 public function are_keys_set() {
248 // NOTE: updates to this function should be added to are_keys_set()
249 // in includes/payment-methods/class-wc-stripe-payment-request.php
250
251 if ( $this->testmode ) {
252 return preg_match( '/^pk_test_/', $this->publishable_key )
253 && preg_match( '/^[rs]k_test_/', $this->secret_key );
254 } else {
255 return preg_match( '/^pk_live_/', $this->publishable_key )
256 && preg_match( '/^[rs]k_live_/', $this->secret_key );
257 }
258 }
259
260 /**
261 * Check if we need to make gateways available.
262 *
263 * @since 4.1.3
264 */
265 public function is_available() {
266 if ( 'yes' === $this->enabled ) {
267 return $this->are_keys_set();
268 }
269
270 return parent::is_available();
271 }
272
273 public function save_payment_method_requested() {
274 $payment_method = isset( $_POST['payment_method'] ) ? wc_clean( wp_unslash( $_POST['payment_method'] ) ) : 'stripe';
275
276 return isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] );
277 }
278
279 /**
280 * Customer param wrong? The user may have been deleted on stripe's end. Remove customer_id. Can be retried without.
281 *
282 * @since 4.2.0
283 * @param object $error The error that was returned from Stripe's API.
284 * @param WC_Order $order The order those payment is being processed.
285 * @return bool A flag that indicates that the customer does not exist and should be removed.
286 */
287 public function maybe_remove_non_existent_customer( $error, $order ) {
288 if ( ! $this->is_no_such_customer_error( $error ) ) {
289 return false;
290 }
291
292 delete_user_option( $order->get_customer_id(), '_stripe_customer_id' );
293 $order->delete_meta_data( '_stripe_customer_id' );
294 $order->save();
295
296 return true;
297 }
298
299 /**
300 * All payment icons that work with Stripe. Some icons references
301 * WC core icons.
302 *
303 * @since 4.0.0
304 * @since 4.1.0 Changed to using img with svg (colored) instead of fonts.
305 * @return array
306 */
307 public function payment_icons() {
308 return apply_filters(
309 'wc_stripe_payment_icons',
310 [
311 'alipay' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/alipay.svg" class="stripe-alipay-icon stripe-icon" alt="Alipay" />',
312 'wechat' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/wechat.svg" class="stripe-wechat-icon stripe-icon" alt="Wechat Pay" />',
313 'bancontact' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/bancontact.svg" class="stripe-bancontact-icon stripe-icon" alt="Bancontact" />',
314 'ideal' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/ideal.svg" class="stripe-ideal-icon stripe-icon" alt="iDEAL" />',
315 'p24' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/p24.svg" class="stripe-p24-icon stripe-icon" alt="P24" />',
316 'giropay' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/giropay.svg" class="stripe-giropay-icon stripe-icon" alt="giropay" />',
317 'eps' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/eps.svg" class="stripe-eps-icon stripe-icon" alt="EPS" />',
318 'multibanco' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/multibanco.svg" class="stripe-multibanco-icon stripe-icon" alt="Multibanco" />',
319 'sofort' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/sofort.svg" class="stripe-sofort-icon stripe-icon" alt="Sofort" />',
320 'sepa' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/sepa.svg" class="stripe-sepa-icon stripe-icon" alt="SEPA" />',
321 'boleto' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/boleto.svg" class="stripe-boleto-icon stripe-icon" alt="Boleto" />',
322 'oxxo' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/oxxo.svg" class="stripe-oxxo-icon stripe-icon" alt="OXXO" />',
323 ]
324 );
325 }
326
327 /**
328 * Validates that the order meets the minimum order amount
329 * set by Stripe.
330 *
331 * @since 4.0.0
332 * @version 4.0.0
333 * @param object $order
334 */
335 public function validate_minimum_order_amount( $order ) {
336 if ( $order->get_total() * 100 < WC_Stripe_Helper::get_minimum_amount() ) {
337 /* translators: 1) amount (including currency symbol) */
338 throw new WC_Stripe_Exception( 'Did not meet minimum amount', sprintf( __( 'Sorry, the minimum allowed order total is %1$s to use this payment method.', 'woocommerce-gateway-stripe' ), wc_price( WC_Stripe_Helper::get_minimum_amount() / 100 ) ) );
339 }
340 }
341
342 /**
343 * Gets the transaction URL linked to Stripe dashboard.
344 *
345 * @since 4.0.0
346 * @version 4.0.0
347 */
348 public function get_transaction_url( $order ) {
349 if ( $this->testmode ) {
350 $this->view_transaction_url = 'https://dashboard.stripe.com/test/payments/%s';
351 } else {
352 $this->view_transaction_url = 'https://dashboard.stripe.com/payments/%s';
353 }
354
355 return parent::get_transaction_url( $order );
356 }
357
358 /**
359 * Gets the saved customer id if exists.
360 *
361 * @since 4.0.0
362 * @version 4.0.0
363 */
364 public function get_stripe_customer_id( $order ) {
365 // Try to get it via the order first.
366 $customer = $order->get_meta( '_stripe_customer_id', true );
367
368 if ( empty( $customer ) ) {
369 $customer = get_user_option( '_stripe_customer_id', $order->get_customer_id() );
370 }
371
372 return $customer;
373 }
374
375 /**
376 * Builds the return URL from redirects.
377 *
378 * @since 4.0.0
379 * @version 4.0.0
380 * @param object $order
381 * @param int $id Stripe session id.
382 */
383 public function get_stripe_return_url( $order = null, $id = null ) {
384 if ( is_object( $order ) ) {
385 if ( empty( $id ) ) {
386 $id = uniqid();
387 }
388
389 $order_id = $order->get_id();
390
391 $args = [
392 'utm_nooverride' => '1',
393 'order_id' => $order_id,
394 ];
395
396 return wp_sanitize_redirect( esc_url_raw( add_query_arg( $args, $this->get_return_url( $order ) ) ) );
397 }
398
399 return wp_sanitize_redirect( esc_url_raw( add_query_arg( [ 'utm_nooverride' => '1' ], $this->get_return_url() ) ) );
400 }
401
402 /**
403 * Generate the request for the payment.
404 *
405 * @since 3.1.0
406 * @version 4.5.4
407 * @param WC_Order $order
408 * @param object $prepared_payment_method Stripe Payment Method or Source.
409 * @return array()
410 */
411 public function generate_payment_request( $order, $prepared_payment_method ) {
412 $settings = get_option( 'woocommerce_stripe_settings', [] );
413 $statement_descriptor = ! empty( $settings['statement_descriptor'] ) ? str_replace( "'", '', $settings['statement_descriptor'] ) : '';
414 $short_statement_descriptor = ! empty( $settings['short_statement_descriptor'] ) ? str_replace( "'", '', $settings['short_statement_descriptor'] ) : '';
415 $is_short_statement_descriptor_enabled = ! empty( $settings['is_short_statement_descriptor_enabled'] ) && 'yes' === $settings['is_short_statement_descriptor_enabled'];
416 $capture = ! empty( $settings['capture'] ) && 'yes' === $settings['capture'] ? true : false;
417 $post_data = [];
418 $post_data['currency'] = strtolower( $order->get_currency() );
419 $post_data['amount'] = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $post_data['currency'] );
420 /* translators: 1) blog name 2) order number */
421 $post_data['description'] = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), $order->get_order_number() );
422 $billing_email = $order->get_billing_email();
423 $billing_first_name = $order->get_billing_first_name();
424 $billing_last_name = $order->get_billing_last_name();
425
426 if ( ! empty( $billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
427 $post_data['receipt_email'] = $billing_email;
428 }
429
430 switch ( $order->get_payment_method() ) {
431 case 'stripe':
432 if ( $is_short_statement_descriptor_enabled && ! ( empty( $short_statement_descriptor ) && empty( $statement_descriptor ) ) ) {
433 $post_data['statement_descriptor'] = WC_Stripe_Helper::get_dynamic_statement_descriptor( $short_statement_descriptor, $order, $statement_descriptor );
434 } elseif ( ! empty( $statement_descriptor ) ) {
435 $post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor );
436 }
437
438 $post_data['capture'] = $capture ? 'true' : 'false';
439 break;
440 case 'stripe_sepa':
441 if ( ! empty( $statement_descriptor ) ) {
442 $post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor );
443 }
444 // other payment methods error if we try to add a statement descriptor in the request
445 }
446
447 if ( method_exists( $order, 'get_shipping_postcode' ) && ! empty( $order->get_shipping_postcode() ) ) {
448 $post_data['shipping'] = [
449 'name' => trim( $order->get_shipping_first_name() . ' ' . $order->get_shipping_last_name() ),
450 'address' => [
451 'line1' => $order->get_shipping_address_1(),
452 'line2' => $order->get_shipping_address_2(),
453 'city' => $order->get_shipping_city(),
454 'country' => $order->get_shipping_country(),
455 'postal_code' => $order->get_shipping_postcode(),
456 'state' => $order->get_shipping_state(),
457 ],
458 ];
459 }
460
461 $post_data['expand[]'] = 'balance_transaction';
462
463 $metadata = [
464 __( 'customer_name', 'woocommerce-gateway-stripe' ) => sanitize_text_field( $billing_first_name ) . ' ' . sanitize_text_field( $billing_last_name ),
465 __( 'customer_email', 'woocommerce-gateway-stripe' ) => sanitize_email( $billing_email ),
466 'order_id' => $order->get_order_number(),
467 'site_url' => esc_url( get_site_url() ),
468 ];
469
470 if ( $this->has_subscription( $order->get_id() ) ) {
471 $metadata += [
472 'payment_type' => 'recurring',
473 ];
474 }
475
476 $post_data['metadata'] = apply_filters( 'wc_stripe_payment_metadata', $metadata, $order, $prepared_payment_method );
477
478 if ( $prepared_payment_method->customer ) {
479 $post_data['customer'] = $prepared_payment_method->customer;
480 }
481
482 if ( ! empty( $prepared_payment_method->source ) ) {
483 $post_data['source'] = $prepared_payment_method->source;
484 }
485
486 if ( ! empty( $prepared_payment_method->payment_method ) ) {
487 $post_data['payment_method'] = $prepared_payment_method->payment_method;
488 }
489
490 /**
491 * Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request.
492 *
493 * @since 3.1.0
494 * @param array $post_data
495 * @param WC_Order $order
496 * @param object $source
497 */
498 return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order, $prepared_payment_method );
499 }
500
501 /**
502 * Store extra meta data for an order from a Stripe Response.
503 */
504 public function process_response( $response, $order ) {
505 WC_Stripe_Logger::log( 'Processing response: ' . print_r( $response, true ) );
506
507 $order_id = $order->get_id();
508 $captured = ( isset( $response->captured ) && $response->captured ) ? 'yes' : 'no';
509
510 // Store charge data.
511 $order->update_meta_data( '_stripe_charge_captured', $captured );
512
513 if ( isset( $response->balance_transaction ) ) {
514 $this->update_fees( $order, is_string( $response->balance_transaction ) ? $response->balance_transaction : $response->balance_transaction->id );
515 }
516
517 if ( 'yes' === $captured ) {
518 /**
519 * Charge can be captured but in a pending state. Payment methods
520 * that are asynchronous may take couple days to clear. Webhook will
521 * take care of the status changes.
522 */
523 if ( 'pending' === $response->status ) {
524 $order_stock_reduced = $order->get_meta( '_order_stock_reduced', true );
525
526 if ( ! $order_stock_reduced ) {
527 wc_reduce_stock_levels( $order_id );
528 }
529
530 $order->set_transaction_id( $response->id );
531 /* translators: transaction id */
532 $order->update_status( 'on-hold', sprintf( __( 'Stripe charge awaiting payment: %s.', 'woocommerce-gateway-stripe' ), $response->id ) );
533 }
534
535 if ( 'succeeded' === $response->status ) {
536 $order->payment_complete( $response->id );
537
538 /* translators: transaction id */
539 $message = sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $response->id );
540 $order->add_order_note( $message );
541 }
542
543 if ( 'failed' === $response->status ) {
544 $localized_message = __( 'Payment processing failed. Please retry.', 'woocommerce-gateway-stripe' );
545 $order->add_order_note( $localized_message );
546 throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
547 }
548 } else {
549 $order->set_transaction_id( $response->id );
550
551 if ( $order->has_status( [ 'pending', 'failed' ] ) ) {
552 wc_reduce_stock_levels( $order_id );
553 }
554
555 /* translators: transaction id */
556 $order->update_status( 'on-hold', sprintf( __( 'Stripe charge authorized (Charge ID: %s). Process order to take payment, or cancel to remove the pre-authorization. Attempting to refund the order in part or in full will release the authorization and cancel the payment.', 'woocommerce-gateway-stripe' ), $response->id ) );
557 }
558
559 if ( is_callable( [ $order, 'save' ] ) ) {
560 $order->save();
561 }
562
563 do_action( 'wc_gateway_stripe_process_response', $response, $order );
564
565 return $response;
566 }
567
568 /**
569 * Sends the failed order email to admin.
570 *
571 * @since 3.1.0
572 * @version 4.0.0
573 * @param int $order_id
574 * @return null
575 */
576 public function send_failed_order_email( $order_id ) {
577 $emails = WC()->mailer()->get_emails();
578 if ( ! empty( $emails ) && ! empty( $order_id ) ) {
579 $emails['WC_Email_Failed_Order']->trigger( $order_id );
580 }
581 }
582
583 /**
584 * Get owner details.
585 *
586 * @since 4.0.0
587 * @version 4.0.0
588 * @param object $order
589 * @return object $details
590 */
591 public function get_owner_details( $order ) {
592 $billing_first_name = $order->get_billing_first_name();
593 $billing_last_name = $order->get_billing_last_name();
594
595 $details = [];
596
597 $name = $billing_first_name . ' ' . $billing_last_name;
598 $email = $order->get_billing_email();
599 $phone = $order->get_billing_phone();
600
601 if ( ! empty( $phone ) ) {
602 $details['phone'] = $phone;
603 }
604
605 if ( ! empty( $name ) ) {
606 $details['name'] = $name;
607 }
608
609 if ( ! empty( $email ) ) {
610 $details['email'] = $email;
611 }
612
613 $details['address']['line1'] = $order->get_billing_address_1();
614 $details['address']['line2'] = $order->get_billing_address_2();
615 $details['address']['state'] = $order->get_billing_state();
616 $details['address']['city'] = $order->get_billing_city();
617 $details['address']['postal_code'] = $order->get_billing_postcode();
618 $details['address']['country'] = $order->get_billing_country();
619
620 return (object) apply_filters( 'wc_stripe_owner_details', $details, $order );
621 }
622
623 /**
624 * Get source object by source id.
625 *
626 * @since 4.0.3
627 * @param string $source_id The source ID to get source object for.
628 */
629 public function get_source_object( $source_id = '' ) {
630 if ( empty( $source_id ) ) {
631 return '';
632 }
633
634 $source_object = WC_Stripe_API::retrieve( 'sources/' . $source_id );
635
636 if ( ! empty( $source_object->error ) ) {
637 throw new WC_Stripe_Exception( print_r( $source_object, true ), $source_object->error->message );
638 }
639
640 return $source_object;
641 }
642
643 /**
644 * Checks if card is a prepaid card.
645 *
646 * @since 4.0.6
647 * @param object $source_object
648 * @return bool
649 */
650 public function is_prepaid_card( $source_object ) {
651 return (
652 $source_object
653 && in_array( $source_object->object, [ 'token', 'source', 'payment_method' ], true )
654 && 'prepaid' === $source_object->card->funding
655 );
656 }
657
658 /**
659 * Checks if a payment method object represents a prepaid credit card and
660 * throws an exception if it is one, but that is not allowed.
661 *
662 * @since 4.2.0
663 * @param object $prepared_source The object with source details.
664 * @throws WC_Stripe_Exception An exception if the card is prepaid, but prepaid cards are not allowed.
665 */
666 public function maybe_disallow_prepaid_card( $payment_method ) {
667 // Check if we don't allow prepaid credit cards.
668 if ( apply_filters( 'wc_stripe_allow_prepaid_card', true ) || ! $this->is_prepaid_card( $payment_method ) ) {
669 return;
670 }
671
672 $localized_message = __( 'Sorry, we\'re not accepting prepaid cards at this time. Your credit card has not been charged. Please try with alternative payment method.', 'woocommerce-gateway-stripe' );
673 throw new WC_Stripe_Exception( print_r( $payment_method, true ), $localized_message );
674 }
675
676 /**
677 * Checks if source is of legacy type card.
678 *
679 * @since 4.0.8
680 * @param string $source_id
681 * @return bool
682 */
683 public function is_type_legacy_card( $source_id ) {
684 return ( preg_match( '/^card_/', $source_id ) );
685 }
686
687 /**
688 * Checks if source is payment method (pm_).
689 *
690 * @since 5.6.0
691 * @param string $source_id
692 * @return bool
693 */
694 public function is_type_payment_method( $source_id ) {
695 return ( preg_match( '/^pm_/', $source_id ) );
696 }
697
698 /**
699 * Checks if payment is via saved payment source.
700 *
701 * @since 4.1.0
702 * @return bool
703 */
704 public function is_using_saved_payment_method() {
705 $payment_method = isset( $_POST['payment_method'] ) ? wc_clean( wp_unslash( $_POST['payment_method'] ) ) : 'stripe';
706
707 return ( isset( $_POST[ 'wc-' . $payment_method . '-payment-token' ] ) && 'new' !== $_POST[ 'wc-' . $payment_method . '-payment-token' ] );
708 }
709
710 /**
711 * Get payment source. This can be a new token/source or existing WC token.
712 * If user is logged in and/or has WC account, create an account on Stripe.
713 * This way we can attribute the payment to the user to better fight fraud.
714 *
715 * @since 3.1.0
716 * @version 4.0.0
717 * @param string $user_id
718 * @param bool $force_save_source Should we force save payment source.
719 *
720 * @throws Exception When card was not added or for and invalid card.
721 * @return object
722 */
723 public function prepare_source( $user_id, $force_save_source = false, $existing_customer_id = null ) {
724 $customer = new WC_Stripe_Customer( $user_id );
725 if ( ! empty( $existing_customer_id ) ) {
726 $customer->set_id( $existing_customer_id );
727 }
728
729 $force_save_source = apply_filters( 'wc_stripe_force_save_source', $force_save_source, $customer );
730 $source_object = '';
731 $source_id = '';
732 $wc_token_id = false;
733 $payment_method = isset( $_POST['payment_method'] ) ? wc_clean( wp_unslash( $_POST['payment_method'] ) ) : 'stripe';
734 $is_token = false;
735
736 // New CC info was entered and we have a new source to process.
737 if ( ! empty( $_POST['stripe_source'] ) ) {
738 $source_object = self::get_source_object( wc_clean( wp_unslash( $_POST['stripe_source'] ) ) );
739 $source_id = $source_object->id;
740
741 // This checks to see if customer opted to save the payment method to file.
742 $maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] );
743
744 /**
745 * This is true if the user wants to store the card to their account.
746 * Criteria to save to file is they are logged in, they opted to save or product requirements and the source is
747 * actually reusable. Either that or force_save_source is true.
748 */
749 if ( ( $user_id && $this->saved_cards && $maybe_saved_card && 'reusable' === $source_object->usage ) || $force_save_source ) {
750 $response = $customer->attach_source( $source_object->id );
751
752 if ( ! empty( $response->error ) ) {
753 throw new WC_Stripe_Exception( print_r( $response, true ), $this->get_localized_error_message_from_response( $response ) );
754 }
755 if ( is_wp_error( $response ) ) {
756 throw new WC_Stripe_Exception( $response->get_error_message(), $response->get_error_message() );
757 }
758 }
759 } elseif ( $this->is_using_saved_payment_method() ) {
760 // Use an existing token, and then process the payment.
761 $wc_token_id = isset( $_POST[ 'wc-' . $payment_method . '-payment-token' ] ) ? wc_clean( wp_unslash( $_POST[ 'wc-' . $payment_method . '-payment-token' ] ) ) : '';
762 $wc_token = WC_Payment_Tokens::get( $wc_token_id );
763
764 if ( ! $wc_token || $wc_token->get_user_id() !== get_current_user_id() ) {
765 WC()->session->set( 'refresh_totals', true );
766 throw new WC_Stripe_Exception( 'Invalid payment method', __( 'Invalid payment method. Please input a new card number.', 'woocommerce-gateway-stripe' ) );
767 }
768
769 $source_id = $wc_token->get_token();
770
771 if ( $this->is_type_legacy_card( $source_id ) || $this->is_type_payment_method( $source_id ) ) {
772 $is_token = true;
773 }
774 } elseif ( isset( $_POST['stripe_token'] ) && 'new' !== $_POST['stripe_token'] ) {
775 $stripe_token = wc_clean( wp_unslash( $_POST['stripe_token'] ) );
776 $maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] );
777
778 // This is true if the user wants to store the card to their account.
779 if ( ( $user_id && $this->saved_cards && $maybe_saved_card ) || $force_save_source ) {
780 $response = $customer->attach_source( $stripe_token );
781
782 if ( ! empty( $response->error ) ) {
783 throw new WC_Stripe_Exception( print_r( $response, true ), $response->error->message );
784 }
785 if ( is_wp_error( $response ) ) {
786 throw new WC_Stripe_Exception( $response->get_error_message(), $response->get_error_message() );
787 }
788 $source_id = $response->id;
789 } else {
790 $source_id = $stripe_token;
791 $is_token = true;
792 }
793 }
794
795 $customer_id = $customer->get_id();
796 if ( ! $customer_id ) {
797 $customer->set_id( $customer->create_customer() );
798 $customer_id = $customer->get_id();
799 } else {
800 $customer_id = $customer->update_customer();
801 }
802
803 if ( empty( $source_object ) && ! $is_token ) {
804 $source_object = self::get_source_object( $source_id );
805 }
806
807 return (object) [
808 'token_id' => $wc_token_id,
809 'customer' => $customer_id,
810 'source' => $source_id,
811 'source_object' => $source_object,
812 'payment_method' => null,
813 ];
814 }
815
816 /**
817 * Get payment source from an order. This could be used in the future for
818 * a subscription as an example, therefore using the current user ID would
819 * not work - the customer won't be logged in :)
820 *
821 * Not using 2.6 tokens for this part since we need a customer AND a card
822 * token, and not just one.
823 *
824 * @since 3.1.0
825 * @version 4.0.0
826 * @param object $order
827 * @return object
828 */
829 public function prepare_order_source( $order = null ) {
830 $stripe_customer = new WC_Stripe_Customer();
831 $stripe_source = false;
832 $token_id = false;
833 $source_object = false;
834
835 if ( $order ) {
836 $order_id = $order->get_id();
837
838 $stripe_customer_id = $this->get_stripe_customer_id( $order );
839
840 if ( $stripe_customer_id ) {
841 $stripe_customer->set_id( $stripe_customer_id );
842 }
843
844 $source_id = $order->get_meta( '_stripe_source_id', true );
845
846 // Since 4.0.0, we changed card to source so we need to account for that.
847 if ( empty( $source_id ) ) {
848 $source_id = $order->get_meta( '_stripe_card_id', true );
849
850 // Take this opportunity to update the key name.
851 $order->update_meta_data( '_stripe_source_id', $source_id );
852
853 if ( is_callable( [ $order, 'save' ] ) ) {
854 $order->save();
855 }
856 }
857
858 if ( $source_id ) {
859 $stripe_source = $source_id;
860 $source_object = WC_Stripe_API::retrieve( 'sources/' . $source_id );
861 } elseif ( apply_filters( 'wc_stripe_use_default_customer_source', true ) ) {
862 /*
863 * We can attempt to charge the customer's default source
864 * by sending empty source id.
865 */
866 $stripe_source = '';
867 }
868 }
869
870 return (object) [
871 'token_id' => $token_id,
872 'customer' => $stripe_customer ? $stripe_customer->get_id() : false,
873 'source' => $stripe_source,
874 'source_object' => $source_object,
875 'payment_method' => null,
876 ];
877 }
878
879 /**
880 * Checks whether a source exists.
881 *
882 * @since 4.2.0
883 * @param object $prepared_source The source that should be verified.
884 * @throws WC_Stripe_Exception An exception if the source ID is missing.
885 */
886 public function check_source( $prepared_source ) {
887 if ( empty( $prepared_source->source ) ) {
888 $localized_message = __( 'Payment processing failed. Please retry.', 'woocommerce-gateway-stripe' );
889 throw new WC_Stripe_Exception( print_r( $prepared_source, true ), $localized_message );
890 }
891 }
892
893 /**
894 * Save source to order.
895 *
896 * @since 3.1.0
897 * @version 4.0.0
898 * @param WC_Order $order For to which the source applies.
899 * @param stdClass $source Source information.
900 */
901 public function save_source_to_order( $order, $source ) {
902 // Store source in the order.
903 if ( $source->customer ) {
904 $order->update_meta_data( '_stripe_customer_id', $source->customer );
905 }
906
907 if ( $source->source ) {
908 $order->update_meta_data( '_stripe_source_id', $source->source );
909 }
910
911 if ( is_callable( [ $order, 'save' ] ) ) {
912 $order->save();
913 }
914
915 $this->maybe_update_source_on_subscription_order( $order, $source );
916 }
917
918 /**
919 * Updates Stripe fees/net.
920 * e.g usage would be after a refund.
921 *
922 * @since 4.0.0
923 * @version 4.0.6
924 * @param object $order The order object
925 * @param int $balance_transaction_id
926 */
927 public function update_fees( $order, $balance_transaction_id ) {
928 $balance_transaction = WC_Stripe_API::retrieve( 'balance/history/' . $balance_transaction_id );
929
930 if ( empty( $balance_transaction->error ) ) {
931 if ( isset( $balance_transaction ) && isset( $balance_transaction->fee ) ) {
932 // Fees and Net needs to both come from Stripe to be accurate as the returned
933 // values are in the local currency of the Stripe account, not from WC.
934 $fee_refund = ! empty( $balance_transaction->fee ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'fee' ) : 0;
935 $net_refund = ! empty( $balance_transaction->net ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'net' ) : 0;
936
937 // Current data fee & net.
938 $fee_current = WC_Stripe_Helper::get_stripe_fee( $order );
939 $net_current = WC_Stripe_Helper::get_stripe_net( $order );
940
941 // Calculation.
942 $fee = (float) $fee_current + (float) $fee_refund;
943 $net = (float) $net_current + (float) $net_refund;
944
945 WC_Stripe_Helper::update_stripe_fee( $order, $fee );
946 WC_Stripe_Helper::update_stripe_net( $order, $net );
947
948 $currency = ! empty( $balance_transaction->currency ) ? strtoupper( $balance_transaction->currency ) : null;
949 WC_Stripe_Helper::update_stripe_currency( $order, $currency );
950
951 if ( is_callable( [ $order, 'save' ] ) ) {
952 $order->save();
953 }
954 }
955 } else {
956 WC_Stripe_Logger::log( 'Unable to update fees/net meta for order: ' . $order->get_id() );
957 }
958 }
959
960 /**
961 * Refund a charge.
962 *
963 * @since 3.1.0
964 * @version 4.9.0
965 * @param int $order_id
966 * @param float $amount
967 *
968 * @return bool
969 * @throws Exception Throws exception when charge wasn't captured.
970 */
971 public function process_refund( $order_id, $amount = null, $reason = '' ) {
972 $order = wc_get_order( $order_id );
973
974 if ( ! $order ) {
975 return false;
976 }
977
978 $request = [];
979
980 $order_currency = $order->get_currency();
981 $captured = $order->get_meta( '_stripe_charge_captured', true );
982 $charge_id = $order->get_transaction_id();
983
984 if ( ! $charge_id ) {
985 return false;
986 }
987
988 if ( ! is_null( $amount ) ) {
989 $request['amount'] = WC_Stripe_Helper::get_stripe_amount( $amount, $order_currency );
990 }
991
992 // If order is only authorized, don't pass amount.
993 if ( 'yes' !== $captured ) {
994 unset( $request['amount'] );
995 }
996
997 if ( $reason ) {
998 // Trim the refund reason to a max of 500 characters due to Stripe limits: https://stripe.com/docs/api/metadata.
999 if ( strlen( $reason ) > 500 ) {
1000 $reason = function_exists( 'mb_substr' ) ? mb_substr( $reason, 0, 450 ) : substr( $reason, 0, 450 );
1001 // Add some explainer text indicating where to find the full refund reason.
1002 $reason = $reason . '... [See WooCommerce order page for full text.]';
1003 }
1004
1005 $request['metadata'] = [
1006 'reason' => $reason,
1007 ];
1008 }
1009
1010 $request['charge'] = $charge_id;
1011 WC_Stripe_Logger::log( "Info: Beginning refund for order {$charge_id} for the amount of {$amount}" );
1012
1013 $request = apply_filters( 'wc_stripe_refund_request', $request, $order );
1014
1015 $intent = $this->get_intent_from_order( $order );
1016 $intent_cancelled = false;
1017 if ( $intent ) {
1018 // If the order has a Payment Intent pending capture, then the Intent itself must be refunded (cancelled), not the Charge.
1019 if ( ! empty( $intent->error ) ) {
1020 $response = $intent;
1021 $intent_cancelled = true;
1022 } elseif ( 'requires_capture' === $intent->status ) {
1023 $result = WC_Stripe_API::request(
1024 [],
1025 'payment_intents/' . $intent->id . '/cancel'
1026 );
1027 $intent_cancelled = true;
1028
1029 if ( ! empty( $result->error ) ) {
1030 $response = $result;
1031 } else {
1032 $charge = end( $result->charges->data );
1033 $response = end( $charge->refunds->data );
1034 }
1035 }
1036 }
1037
1038 if ( ! $intent_cancelled && 'yes' === $captured ) {
1039 $response = WC_Stripe_API::request( $request, 'refunds' );
1040 }
1041
1042 if ( ! empty( $response->error ) ) {
1043 WC_Stripe_Logger::log( 'Error: ' . $response->error->message );
1044
1045 return new WP_Error(
1046 'stripe_error',
1047 sprintf(
1048 /* translators: %1$s is a stripe error message */
1049 __( 'There was a problem initiating a refund: %1$s', 'woocommerce-gateway-stripe' ),
1050 $response->error->message
1051 )
1052 );
1053
1054 } elseif ( ! empty( $response->id ) ) {
1055 $formatted_amount = wc_price( $response->amount / 100 );
1056 if ( in_array( strtolower( $order->get_currency() ), WC_Stripe_Helper::no_decimal_currencies(), true ) ) {
1057 $formatted_amount = wc_price( $response->amount );
1058 }
1059
1060 // If charge wasn't captured, skip creating a refund and cancel order.
1061 if ( 'yes' !== $captured ) {
1062 /* translators: amount (including currency symbol) */
1063 $order->add_order_note( sprintf( __( 'Pre-Authorization for %s voided.', 'woocommerce-gateway-stripe' ), $formatted_amount ) );
1064 $order->update_status( 'cancelled' );
1065 // If amount is set, that means this function was called from the manual refund form.
1066 if ( ! is_null( $amount ) ) {
1067 // Throw an exception to provide a custom message on why the refund failed.
1068 throw new Exception( __( 'The authorization was voided and the order cancelled. Click okay to continue, then refresh the page.', 'woocommerce-gateway-stripe' ) );
1069 } else {
1070 // If refund was initiaded by changing order status, prevent refund without errors.
1071 return false;
1072 }
1073 }
1074
1075 $order->update_meta_data( '_stripe_refund_id', $response->id );
1076
1077 if ( isset( $response->balance_transaction ) ) {
1078 $this->update_fees( $order, $response->balance_transaction );
1079 }
1080
1081 /* translators: 1) amount (including currency symbol) 2) transaction id 3) refund message */
1082 $refund_message = sprintf( __( 'Refunded %1$s - Refund ID: %2$s - Reason: %3$s', 'woocommerce-gateway-stripe' ), $formatted_amount, $response->id, $reason );
1083
1084 $order->add_order_note( $refund_message );
1085 WC_Stripe_Logger::log( 'Success: ' . html_entity_decode( wp_strip_all_tags( $refund_message ) ) );
1086
1087 return true;
1088 }
1089 }
1090
1091 /**
1092 * Add payment method via account screen.
1093 * We don't store the token locally, but to the Stripe API.
1094 *
1095 * @since 3.0.0
1096 * @version 4.0.0
1097 */
1098 public function add_payment_method() {
1099 $error = false;
1100 $error_msg = __( 'There was a problem adding the payment method.', 'woocommerce-gateway-stripe' );
1101 $source_id = '';
1102
1103 if ( empty( $_POST['stripe_source'] ) && empty( $_POST['stripe_token'] ) || ! is_user_logged_in() ) {
1104 $error = true;
1105 }
1106
1107 $stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
1108
1109 $source = ! empty( $_POST['stripe_source'] ) ? wc_clean( wp_unslash( $_POST['stripe_source'] ) ) : '';
1110
1111 $source_object = WC_Stripe_API::retrieve( 'sources/' . $source );
1112
1113 if ( isset( $source_object ) ) {
1114 if ( ! empty( $source_object->error ) ) {
1115 $error = true;
1116 }
1117
1118 $source_id = $source_object->id;
1119 } elseif ( isset( $_POST['stripe_token'] ) ) {
1120 $source_id = wc_clean( wp_unslash( $_POST['stripe_token'] ) );
1121 }
1122
1123 $response = $stripe_customer->add_source( $source_id );
1124
1125 if ( ! $response || is_wp_error( $response ) || ! empty( $response->error ) ) {
1126 $error = true;
1127 }
1128
1129 if ( $error ) {
1130 wc_add_notice( $error_msg, 'error' );
1131 WC_Stripe_Logger::log( 'Add payment method Error: ' . $error_msg );
1132 return;
1133 }
1134
1135 do_action( 'wc_stripe_add_payment_method_' . ( isset( $_POST['payment_method'] ) ? wc_clean( wp_unslash( $_POST['payment_method'] ) ) : '' ) . '_success', $source_id, $source_object );
1136
1137 return [
1138 'result' => 'success',
1139 'redirect' => wc_get_endpoint_url( 'payment-methods' ),
1140 ];
1141 }
1142
1143 /**
1144 * Gets the locale with normalization that only Stripe accepts.
1145 *
1146 * @since 4.0.6
1147 * @return string $locale
1148 */
1149 public function get_locale() {
1150 $locale = get_locale();
1151
1152 /*
1153 * Stripe expects Norwegian to only be passed NO.
1154 * But WP has different dialects.
1155 */
1156 if ( 'NO' === substr( $locale, 3, 2 ) ) {
1157 $locale = 'no';
1158 } else {
1159 $locale = substr( get_locale(), 0, 2 );
1160 }
1161
1162 return $locale;
1163 }
1164
1165 /**
1166 * Change the idempotency key so charge can
1167 * process order as a different transaction.
1168 *
1169 * @since 4.0.6
1170 * @param string $idempotency_key
1171 * @param array $request
1172 */
1173 public function change_idempotency_key( $idempotency_key, $request ) {
1174 $customer = ! empty( $request['customer'] ) ? $request['customer'] : '';
1175 $source = ! empty( $request['source'] ) ? $request['source'] : $customer;
1176 $count = $this->retry_interval;
1177
1178 return $request['metadata']['order_id'] . '-' . $count . '-' . $source;
1179 }
1180
1181 /**
1182 * Checks if request is the original to prevent double processing
1183 * on WC side. The original-request header and request-id header
1184 * needs to be the same to mean its the original request.
1185 *
1186 * @since 4.0.6
1187 * @param array $headers
1188 */
1189 public function is_original_request( $headers ) {
1190 if ( $headers['original-request'] === $headers['request-id'] ) {
1191 return true;
1192 }
1193
1194 return false;
1195 }
1196
1197 /**
1198 * Generates the request when creating a new payment intent.
1199 *
1200 * @param WC_Order $order The order that is being paid for.
1201 * @param object $prepared_source The source that is used for the payment.
1202 * @return array The arguments for the request.
1203 */
1204 public function generate_create_intent_request( $order, $prepared_source ) {
1205 // The request for a charge contains metadata for the intent.
1206 $full_request = $this->generate_payment_request( $order, $prepared_source );
1207
1208 $payment_method_types = [ 'card' ];
1209 if ( WC_Stripe_Feature_Flags::is_upe_checkout_enabled() ) {
1210 $payment_method_types = $this->get_upe_enabled_at_checkout_payment_method_ids();
1211 } elseif ( isset( $prepared_source->source_object->type ) ) {
1212 $payment_method_types = [ $prepared_source->source_object->type ];
1213 }
1214
1215 $request = [
1216 'source' => $prepared_source->source,
1217 'amount' => WC_Stripe_Helper::get_stripe_amount( $order->get_total() ),
1218 'currency' => strtolower( $order->get_currency() ),
1219 'description' => $full_request['description'],
1220 'metadata' => $full_request['metadata'],
1221 'capture_method' => ( 'true' === $full_request['capture'] ) ? 'automatic' : 'manual',
1222 'payment_method_types' => $payment_method_types,
1223 ];
1224
1225 $force_save_source = apply_filters( 'wc_stripe_force_save_source', false, $prepared_source->source );
1226
1227 if ( $this->save_payment_method_requested() || $this->has_subscription( $order->get_id() ) || $force_save_source ) {
1228 $request['setup_future_usage'] = 'off_session';
1229 $request['metadata']['save_payment_method'] = 'true';
1230 }
1231
1232 if ( $prepared_source->customer ) {
1233 $request['customer'] = $prepared_source->customer;
1234 }
1235
1236 if ( isset( $full_request['statement_descriptor'] ) ) {
1237 $request['statement_descriptor'] = $full_request['statement_descriptor'];
1238 }
1239
1240 if ( isset( $full_request['shipping'] ) ) {
1241 $request['shipping'] = $full_request['shipping'];
1242 }
1243
1244 if ( isset( $full_request['receipt_email'] ) ) {
1245 $request['receipt_email'] = $full_request['receipt_email'];
1246 }
1247
1248 /**
1249 * Filter the return value of the WC_Payment_Gateway_CC::generate_create_intent_request.
1250 *
1251 * @since 3.1.0
1252 * @param array $request
1253 * @param WC_Order $order
1254 * @param object $source
1255 */
1256 return apply_filters( 'wc_stripe_generate_create_intent_request', $request, $order, $prepared_source );
1257 }
1258
1259 /**
1260 * Create the level 3 data array to send to Stripe when making a purchase.
1261 *
1262 * @param WC_Order $order The order that is being paid for.
1263 * @return array The level 3 data to send to Stripe.
1264 */
1265 public function get_level3_data_from_order( $order ) {
1266 // Get the order items. Don't need their keys, only their values.
1267 // Order item IDs are used as keys in the original order items array.
1268 $order_items = array_values( $order->get_items( [ 'line_item', 'fee' ] ) );
1269 $currency = $order->get_currency();
1270
1271 $stripe_line_items = array_map(
1272 function( $item ) use ( $currency ) {
1273 if ( is_a( $item, 'WC_Order_Item_Product' ) ) {
1274 $product_id = $item->get_variation_id()
1275 ? $item->get_variation_id()
1276 : $item->get_product_id();
1277 $subtotal = $item->get_subtotal();
1278 } else {
1279 $product_id = substr( sanitize_title( $item->get_name() ), 0, 12 );
1280 $subtotal = $item->get_total();
1281 }
1282 $product_description = substr( $item->get_name(), 0, 26 );
1283 $quantity = $item->get_quantity();
1284 $unit_cost = WC_Stripe_Helper::get_stripe_amount( ( $subtotal / $quantity ), $currency );
1285 $tax_amount = WC_Stripe_Helper::get_stripe_amount( $item->get_total_tax(), $currency );
1286 $discount_amount = WC_Stripe_Helper::get_stripe_amount( $subtotal - $item->get_total(), $currency );
1287
1288 return (object) [
1289 'product_code' => (string) $product_id, // Up to 12 characters that uniquely identify the product.
1290 'product_description' => $product_description, // Up to 26 characters long describing the product.
1291 'unit_cost' => $unit_cost, // Cost of the product, in cents, as a non-negative integer.
1292 'quantity' => $quantity, // The number of items of this type sold, as a non-negative integer.
1293 'tax_amount' => $tax_amount, // The amount of tax this item had added to it, in cents, as a non-negative integer.
1294 'discount_amount' => $discount_amount, // The amount an item was discounted—if there was a sale,for example, as a non-negative integer.
1295 ];
1296 },
1297 $order_items
1298 );
1299
1300 $level3_data = [
1301 'merchant_reference' => $order->get_id(), // An alphanumeric string of up to characters in length. This unique value is assigned by the merchant to identify the order. Also known as an “Order ID”.
1302 'shipping_amount' => WC_Stripe_Helper::get_stripe_amount( (float) $order->get_shipping_total() + (float) $order->get_shipping_tax(), $currency ), // The shipping cost, in cents, as a non-negative integer.
1303 'line_items' => $stripe_line_items,
1304 ];
1305
1306 // The customer’s U.S. shipping ZIP code.
1307 $shipping_address_zip = $order->get_shipping_postcode();
1308 if ( $this->is_valid_us_zip_code( $shipping_address_zip ) ) {
1309 $level3_data['shipping_address_zip'] = $shipping_address_zip;
1310 }
1311
1312 // The merchant’s U.S. shipping ZIP code.
1313 $store_postcode = get_option( 'woocommerce_store_postcode' );
1314 if ( $this->is_valid_us_zip_code( $store_postcode ) ) {
1315 $level3_data['shipping_from_zip'] = $store_postcode;
1316 }
1317
1318 return $level3_data;
1319 }
1320
1321 /**
1322 * Create a new PaymentIntent.
1323 *
1324 * @param WC_Order $order The order that is being paid for.
1325 * @param object $prepared_source The source that is used for the payment.
1326 * @return object An intent or an error.
1327 */
1328 public function create_intent( $order, $prepared_source ) {
1329 $request = $this->generate_create_intent_request( $order, $prepared_source );
1330
1331 // Create an intent that awaits an action.
1332 $intent = WC_Stripe_API::request( $request, 'payment_intents' );
1333 if ( ! empty( $intent->error ) ) {
1334 return $intent;
1335 }
1336
1337 $order_id = $order->get_id();
1338 WC_Stripe_Logger::log( "Stripe PaymentIntent $intent->id initiated for order $order_id" );
1339
1340 // Save the intent ID to the order.
1341 $this->save_intent_to_order( $order, $intent );
1342
1343 return $intent;
1344 }
1345
1346 /**
1347 * Updates an existing intent with updated amount, source, and customer.
1348 *
1349 * @param object $intent The existing intent object.
1350 * @param WC_Order $order The order.
1351 * @param object $prepared_source Currently selected source.
1352 * @return object An updated intent.
1353 */
1354 public function update_existing_intent( $intent, $order, $prepared_source ) {
1355 $request = [];
1356
1357 if ( $prepared_source->source !== $intent->source ) {
1358 $request['source'] = $prepared_source->source;
1359 }
1360
1361 $new_amount = WC_Stripe_Helper::get_stripe_amount( $order->get_total() );
1362 if ( $intent->amount !== $new_amount ) {
1363 $request['amount'] = $new_amount;
1364 }
1365
1366 if ( $prepared_source->customer && $intent->customer !== $prepared_source->customer ) {
1367 $request['customer'] = $prepared_source->customer;
1368 }
1369
1370 $request['payment_method_types'] = [ 'card' ];
1371
1372 if ( $this->has_subscription( $order->get_id() ) ) {
1373 // If this is a failed subscription order payment, the intent should be
1374 // prepared for future usage.
1375 $request['setup_future_usage'] = 'off_session';
1376 }
1377
1378 if ( empty( $request ) ) {
1379 return $intent;
1380 }
1381
1382 /**
1383 * Filter the value of the request.
1384 *
1385 * @since 6.1.0
1386 * @param array $request Request to send to Stripe API.
1387 * @param WC_Order $order Order that the intent is associated with.
1388 * @param object $source Currently selected source.
1389 */
1390 $request = apply_filters( 'wc_stripe_update_existing_intent_request', $request, $order, $prepared_source );
1391
1392 $level3_data = $this->get_level3_data_from_order( $order );
1393 return WC_Stripe_API::request_with_level3_data(
1394 $request,
1395 "payment_intents/$intent->id",
1396 $level3_data,
1397 $order
1398 );
1399 }
1400
1401 /**
1402 * Confirms an intent if it is the `requires_confirmation` state.
1403 *
1404 * @since 4.2.1
1405 * @param object $intent The intent to confirm.
1406 * @param WC_Order $order The order that the intent is associated with.
1407 * @param object $prepared_source The source that is being charged.
1408 * @return object Either an error or the updated intent.
1409 */
1410 public function confirm_intent( $intent, $order, $prepared_source ) {
1411 if ( 'requires_confirmation' !== $intent->status ) {
1412 return $intent;
1413 }
1414
1415 // Try to confirm the intent & capture the charge (if 3DS is not required).
1416 $confirm_request = [
1417 'source' => $prepared_source->source,
1418 ];
1419
1420 $level3_data = $this->get_level3_data_from_order( $order );
1421 $confirmed_intent = WC_Stripe_API::request_with_level3_data(
1422 $confirm_request,
1423 "payment_intents/$intent->id/confirm",
1424 $level3_data,
1425 $order
1426 );
1427
1428 if ( ! empty( $confirmed_intent->error ) ) {
1429 return $confirmed_intent;
1430 }
1431
1432 // Save a note about the status of the intent.
1433 $order_id = $order->get_id();
1434 if ( 'succeeded' === $confirmed_intent->status ) {
1435 WC_Stripe_Logger::log( "Stripe PaymentIntent $intent->id succeeded for order $order_id" );
1436 } elseif ( 'requires_action' === $confirmed_intent->status ) {
1437 WC_Stripe_Logger::log( "Stripe PaymentIntent $intent->id requires authentication for order $order_id" );
1438 }
1439
1440 return $confirmed_intent;
1441 }
1442
1443 /**
1444 * Saves intent to order.
1445 *
1446 * @since 3.2.0
1447 * @param WC_Order $order For to which the source applies.
1448 * @param stdClass $intent Payment intent information.
1449 */
1450 public function save_intent_to_order( $order, $intent ) {
1451 if ( 'payment_intent' === $intent->object ) {
1452 WC_Stripe_Helper::add_payment_intent_to_order( $intent->id, $order );
1453 } elseif ( 'setup_intent' === $intent->object ) {
1454 $order->update_meta_data( '_stripe_setup_intent', $intent->id );
1455 }
1456
1457 if ( is_callable( [ $order, 'save' ] ) ) {
1458 $order->save();
1459 }
1460 }
1461
1462 /**
1463 * Retrieves the payment intent, associated with an order.
1464 *
1465 * @since 4.2
1466 * @param WC_Order $order The order to retrieve an intent for.
1467 * @return obect|bool Either the intent object or `false`.
1468 */
1469 public function get_intent_from_order( $order ) {
1470 $intent_id = $order->get_meta( '_stripe_intent_id' );
1471
1472 if ( $intent_id ) {
1473 return $this->get_intent( 'payment_intents', $intent_id );
1474 }
1475
1476 // The order doesn't have a payment intent, but it may have a setup intent.
1477 $intent_id = $order->get_meta( '_stripe_setup_intent' );
1478
1479 if ( $intent_id ) {
1480 return $this->get_intent( 'setup_intents', $intent_id );
1481 }
1482
1483 return false;
1484 }
1485
1486 /**
1487 * Retrieves intent from Stripe API by intent id.
1488 *
1489 * @param string $intent_type Either 'payment_intents' or 'setup_intents'.
1490 * @param string $intent_id Intent id.
1491 * @return object|bool Either the intent object or `false`.
1492 * @throws Exception Throws exception for unknown $intent_type.
1493 */
1494 private function get_intent( $intent_type, $intent_id ) {
1495 if ( ! in_array( $intent_type, [ 'payment_intents', 'setup_intents' ], true ) ) {
1496 throw new Exception( "Failed to get intent of type $intent_type. Type is not allowed" );
1497 }
1498
1499 $response = WC_Stripe_API::request( [], "$intent_type/$intent_id?expand[]=payment_method", 'GET' );
1500
1501 if ( $response && isset( $response->{ 'error' } ) ) {
1502 $error_response_message = print_r( $response, true );
1503 WC_Stripe_Logger::log( "Failed to get Stripe intent $intent_type/$intent_id." );
1504 WC_Stripe_Logger::log( "Response: $error_response_message" );
1505 return false;
1506 }
1507
1508 return $response;
1509 }
1510
1511 /**
1512 * Locks an order for payment intent processing for 5 minutes.
1513 *
1514 * @since 4.2
1515 * @param WC_Order $order The order that is being paid.
1516 * @param stdClass $intent The intent that is being processed.
1517 * @return bool A flag that indicates whether the order is already locked.
1518 */
1519 public function lock_order_payment( $order, $intent = null ) {
1520 $order_id = $order->get_id();
1521 $transient_name = 'wc_stripe_processing_intent_' . $order_id;
1522 $processing = get_transient( $transient_name );
1523
1524 // Block the process if the same intent is already being handled.
1525 if ( '-1' === $processing || ( isset( $intent->id ) && $processing === $intent->id ) ) {
1526 return true;
1527 }
1528
1529 // Save the new intent as a transient, eventually overwriting another one.
1530 set_transient( $transient_name, empty( $intent ) ? '-1' : $intent->id, 5 * MINUTE_IN_SECONDS );
1531
1532 return false;
1533 }
1534
1535 /**
1536 * Unlocks an order for processing by payment intents.
1537 *
1538 * @since 4.2
1539 * @param WC_Order $order The order that is being unlocked.
1540 */
1541 public function unlock_order_payment( $order ) {
1542 $order_id = $order->get_id();
1543 delete_transient( 'wc_stripe_processing_intent_' . $order_id );
1544 }
1545
1546 /**
1547 * Given a response from Stripe, check if it's a card error where authentication is required
1548 * to complete the payment.
1549 *
1550 * @param object $response The response from Stripe.
1551 * @return boolean Whether or not it's a 'authentication_required' error
1552 */
1553 public function is_authentication_required_for_payment( $response ) {
1554 return ( ! empty( $response->error ) && 'authentication_required' === $response->error->code )
1555 || ( ! empty( $response->last_payment_error ) && 'authentication_required' === $response->last_payment_error->code );
1556 }
1557
1558 /**
1559 * Creates a SetupIntent for future payments, and saves it to the order.
1560 *
1561 * @param WC_Order $order The ID of the (free/pre- order).
1562 * @param object $prepared_source The source, entered/chosen by the customer.
1563 * @return string|null The client secret of the intent, used for confirmation in JS.
1564 */
1565 public function setup_intent( $order, $prepared_source ) {
1566 // SEPA Direct Debit payments do not require any customer action after the source has been created.
1567 // Once the customer has provided their IBAN details and accepted the mandate, no further action is needed and the resulting source is directly chargeable.
1568 if ( 'sepa_debit' === $prepared_source->source_object->type ) {
1569 return;
1570 }
1571
1572 $order_id = $order->get_id();
1573 $setup_intent = WC_Stripe_API::request(
1574 [
1575 'payment_method' => $prepared_source->source,
1576 'customer' => $prepared_source->customer,
1577 'confirm' => 'true',
1578 ],
1579 'setup_intents'
1580 );
1581
1582 if ( is_wp_error( $setup_intent ) ) {
1583 WC_Stripe_Logger::log( "Unable to create SetupIntent for Order #$order_id: " . print_r( $setup_intent, true ) );
1584 } elseif ( 'requires_action' === $setup_intent->status ) {
1585 $order->update_meta_data( '_stripe_setup_intent', $setup_intent->id );
1586 $order->save();
1587
1588 return $setup_intent->client_secret;
1589 }
1590 }
1591
1592 /**
1593 * Create and confirm a new PaymentIntent.
1594 *
1595 * @param WC_Order $order The order that is being paid for.
1596 * @param object $prepared_source The source that is used for the payment.
1597 * @param float $amount The amount to charge. If not specified, it will be read from the order.
1598 * @return object An intent or an error.
1599 */
1600 public function create_and_confirm_intent_for_off_session( $order, $prepared_source, $amount = null ) {
1601 // The request for a charge contains metadata for the intent.
1602 $full_request = $this->generate_payment_request( $order, $prepared_source );
1603
1604 $payment_method_types = [ 'card' ];
1605 if ( WC_Stripe_Feature_Flags::is_upe_checkout_enabled() ) {
1606 $payment_method_types = $this->get_upe_enabled_at_checkout_payment_method_ids();
1607 } elseif ( isset( $prepared_source->source_object->type ) ) {
1608 $payment_method_types = [ $prepared_source->source_object->type ];
1609 }
1610
1611 $request = [
1612 'amount' => $amount ? WC_Stripe_Helper::get_stripe_amount( $amount, $full_request['currency'] ) : $full_request['amount'],
1613 'currency' => $full_request['currency'],
1614 'description' => $full_request['description'],
1615 'metadata' => $full_request['metadata'],
1616 'payment_method_types' => $payment_method_types,
1617 'off_session' => 'true',
1618 'confirm' => 'true',
1619 'confirmation_method' => 'automatic',
1620 ];
1621
1622 if ( isset( $full_request['statement_descriptor'] ) ) {
1623 $request['statement_descriptor'] = $full_request['statement_descriptor'];
1624 }
1625
1626 if ( isset( $full_request['customer'] ) ) {
1627 $request['customer'] = $full_request['customer'];
1628 }
1629
1630 if ( isset( $full_request['source'] ) ) {
1631 $is_source = 'src_' === substr( $full_request['source'], 0, 4 );
1632 $request[ $is_source ? 'source' : 'payment_method' ] = $full_request['source'];
1633 }
1634
1635 /**
1636 * Filter the value of the request.
1637 *
1638 * @since 4.5.0
1639 * @param array $request
1640 * @param WC_Order $order
1641 * @param object $source
1642 */
1643 $request = apply_filters( 'wc_stripe_generate_create_intent_request', $request, $order, $prepared_source );
1644
1645 if ( isset( $full_request['shipping'] ) ) {
1646 $request['shipping'] = $full_request['shipping'];
1647 }
1648
1649 $level3_data = $this->get_level3_data_from_order( $order );
1650 $intent = WC_Stripe_API::request_with_level3_data(
1651 $request,
1652 'payment_intents',
1653 $level3_data,
1654 $order
1655 );
1656 $is_authentication_required = $this->is_authentication_required_for_payment( $intent );
1657
1658 if ( ! empty( $intent->error ) && ! $is_authentication_required ) {
1659 return $intent;
1660 }
1661
1662 $intent_id = ( ! empty( $intent->error )
1663 ? $intent->error->payment_intent->id
1664 : $intent->id
1665 );
1666 $payment_intent = ( ! empty( $intent->error )
1667 ? $intent->error->payment_intent
1668 : $intent
1669 );
1670 $order_id = $order->get_id();
1671 WC_Stripe_Logger::log( "Stripe PaymentIntent $intent_id initiated for order $order_id" );
1672
1673 // Save the intent ID to the order.
1674 $this->save_intent_to_order( $order, $payment_intent );
1675
1676 return $intent;
1677 }
1678
1679 /** Verifies whether a certain ZIP code is valid for the US, incl. 4-digit extensions.
1680 *
1681 * @param string $zip The ZIP code to verify.
1682 * @return boolean
1683 */
1684 public function is_valid_us_zip_code( $zip ) {
1685 return ! empty( $zip ) && preg_match( '/^\d{5,5}(-\d{4,4})?$/', $zip );
1686 }
1687
1688 /**
1689 * Gets a localized message for an error from a response, adds it as a note to the order, and throws it.
1690 *
1691 * @since 4.2.0
1692 * @param stdClass $response The response from the Stripe API.
1693 * @param WC_Order $order The order to add a note to.
1694 * @throws WC_Stripe_Exception An exception with the right message.
1695 */
1696 public function throw_localized_message( $response, $order ) {
1697 $localized_message = $this->get_localized_error_message_from_response( $response );
1698
1699 $order->add_order_note( $localized_message );
1700
1701 throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
1702 }
1703
1704 /**
1705 * Generates a localized message for an error from a response.
1706 *
1707 * @since 4.3.2
1708 *
1709 * @param stdClass $response The response from the Stripe API.
1710 *
1711 * @return string The localized error message.
1712 */
1713 public function get_localized_error_message_from_response( $response ) {
1714 $localized_messages = WC_Stripe_Helper::get_localized_messages();
1715
1716 if ( 'card_error' === $response->error->type ) {
1717 $localized_message = isset( $localized_messages[ $response->error->code ] ) ? $localized_messages[ $response->error->code ] : $response->error->message;
1718 } else {
1719 $localized_message = isset( $localized_messages[ $response->error->type ] ) ? $localized_messages[ $response->error->type ] : $response->error->message;
1720 }
1721
1722 return $localized_message;
1723 }
1724
1725 /**
1726 * Payment_scripts function.
1727 *
1728 * Outputs scripts used for stripe payment
1729 *
1730 * @since 3.1.0
1731 * @version 4.0.0
1732 */
1733 public function payment_scripts() {
1734 if (
1735 ! is_product()
1736 && ! WC_Stripe_Helper::has_cart_or_checkout_on_current_page()
1737 && ! isset( $_GET['pay_for_order'] ) // wpcs: csrf ok.
1738 && ! is_add_payment_method_page()
1739 && ! isset( $_GET['change_payment_method'] ) // wpcs: csrf ok.
1740 && ! ( ! empty( get_query_var( 'view-subscription' ) ) && is_callable( 'WCS_Early_Renewal_Manager::is_early_renewal_via_modal_enabled' ) && WCS_Early_Renewal_Manager::is_early_renewal_via_modal_enabled() )
1741 || ( is_order_received_page() )
1742 ) {
1743 return;
1744 }
1745
1746 if ( is_product() && ! WC_Stripe_Helper::should_load_scripts_on_product_page() ) {
1747 return;
1748 }
1749
1750 if ( is_cart() && ! WC_Stripe_Helper::should_load_scripts_on_cart_page() ) {
1751 return;
1752 }
1753
1754 // If Stripe is not enabled bail.
1755 if ( 'no' === $this->enabled ) {
1756 return;
1757 }
1758
1759 // If keys are not set bail.
1760 if ( ! $this->are_keys_set() ) {
1761 WC_Stripe_Logger::log( 'Keys are not set correctly.' );
1762 return;
1763 }
1764
1765 // If no SSL bail.
1766 if ( ! $this->testmode && ! is_ssl() ) {
1767 WC_Stripe_Logger::log( 'Stripe live mode requires SSL.' );
1768 return;
1769 }
1770
1771 $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
1772
1773 wp_register_style( 'stripe_styles', plugins_url( 'assets/css/stripe-styles.css', WC_STRIPE_MAIN_FILE ), [], WC_STRIPE_VERSION );
1774 wp_enqueue_style( 'stripe_styles' );
1775
1776 wp_register_script( 'stripe', 'https://js.stripe.com/v3/', '', '3.0', true );
1777 wp_register_script( 'woocommerce_stripe', plugins_url( 'assets/js/stripe' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), [ 'jquery-payment', 'stripe' ], WC_STRIPE_VERSION, true );
1778
1779 wp_localize_script(
1780 'woocommerce_stripe',
1781 'wc_stripe_params',
1782 apply_filters( 'wc_stripe_params', $this->javascript_params() )
1783 );
1784
1785 $this->tokenization_script();
1786 wp_enqueue_script( 'woocommerce_stripe' );
1787 }
1788
1789 /**
1790 * Returns the JavaScript configuration object used on the product, cart, and checkout pages.
1791 *
1792 * @return array The configuration object to be loaded to JS.
1793 */
1794 public function javascript_params() {
1795 global $wp;
1796
1797 $order_id = absint( get_query_var( 'order-pay' ) );
1798
1799 $stripe_params = [
1800 'title' => $this->title,
1801 'key' => $this->publishable_key,
1802 'i18n_terms' => __( 'Please accept the terms and conditions first', 'woocommerce-gateway-stripe' ),
1803 'i18n_required_fields' => __( 'Please fill in required checkout fields first', 'woocommerce-gateway-stripe' ),
1804 'updateFailedOrderNonce' => wp_create_nonce( 'wc_stripe_update_failed_order_nonce' ),
1805 'updatePaymentIntentNonce' => wp_create_nonce( 'wc_stripe_update_payment_intent_nonce' ),
1806 'orderId' => $order_id,
1807 'checkout_url' => WC_AJAX::get_endpoint( 'checkout' ),
1808 ];
1809
1810 // If we're on the pay page we need to pass stripe.js the address of the order.
1811 if ( isset( $_GET['pay_for_order'] ) && 'true' === $_GET['pay_for_order'] ) { // wpcs: csrf ok.
1812 $order_id = wc_clean( $wp->query_vars['order-pay'] ); // wpcs: csrf ok, sanitization ok, xss ok.
1813 $order = wc_get_order( $order_id );
1814
1815 if ( is_a( $order, 'WC_Order' ) ) {
1816 $stripe_params['billing_first_name'] = $order->get_billing_first_name();
1817 $stripe_params['billing_last_name'] = $order->get_billing_last_name();
1818 $stripe_params['billing_address_1'] = $order->get_billing_address_1();
1819 $stripe_params['billing_address_2'] = $order->get_billing_address_2();
1820 $stripe_params['billing_state'] = $order->get_billing_state();
1821 $stripe_params['billing_city'] = $order->get_billing_city();
1822 $stripe_params['billing_postcode'] = $order->get_billing_postcode();
1823 $stripe_params['billing_country'] = $order->get_billing_country();
1824 }
1825 }
1826
1827 $sepa_elements_options = apply_filters(
1828 'wc_stripe_sepa_elements_options',
1829 [
1830 'supportedCountries' => [ 'SEPA' ],
1831 'placeholderCountry' => WC()->countries->get_base_country(),
1832 'style' => [ 'base' => [ 'fontSize' => '15px' ] ],
1833 ]
1834 );
1835
1836 $stripe_params['stripe_locale'] = WC_Stripe_Helper::convert_wc_locale_to_stripe_locale( get_locale() );
1837 $stripe_params['no_prepaid_card_msg'] = __( 'Sorry, we\'re not accepting prepaid cards at this time. Your credit card has not been charged. Please try with alternative payment method.', 'woocommerce-gateway-stripe' );
1838 $stripe_params['no_sepa_owner_msg'] = __( 'Please enter your IBAN account name.', 'woocommerce-gateway-stripe' );
1839 $stripe_params['no_sepa_iban_msg'] = __( 'Please enter your IBAN account number.', 'woocommerce-gateway-stripe' );
1840 $stripe_params['payment_intent_error'] = __( 'We couldn\'t initiate the payment. Please try again.', 'woocommerce-gateway-stripe' );
1841 $stripe_params['sepa_mandate_notification'] = apply_filters( 'wc_stripe_sepa_mandate_notification', 'email' );
1842 $stripe_params['allow_prepaid_card'] = apply_filters( 'wc_stripe_allow_prepaid_card', true ) ? 'yes' : 'no';
1843 $stripe_params['inline_cc_form'] = ( isset( $this->inline_cc_form ) && $this->inline_cc_form ) ? 'yes' : 'no';
1844 $stripe_params['is_checkout'] = ( is_checkout() && empty( $_GET['pay_for_order'] ) ) ? 'yes' : 'no'; // wpcs: csrf ok.
1845 $stripe_params['return_url'] = $this->get_stripe_return_url();
1846 $stripe_params['ajaxurl'] = WC_AJAX::get_endpoint( '%%endpoint%%' );
1847 $stripe_params['stripe_nonce'] = wp_create_nonce( '_wc_stripe_nonce' );
1848 $stripe_params['statement_descriptor'] = $this->statement_descriptor;
1849 $stripe_params['elements_options'] = apply_filters( 'wc_stripe_elements_options', [] );
1850 $stripe_params['sepa_elements_options'] = $sepa_elements_options;
1851 $stripe_params['invalid_owner_name'] = __( 'Billing First Name and Last Name are required.', 'woocommerce-gateway-stripe' );
1852 $stripe_params['is_change_payment_page'] = isset( $_GET['change_payment_method'] ) ? 'yes' : 'no'; // wpcs: csrf ok.
1853 $stripe_params['is_add_payment_page'] = is_wc_endpoint_url( 'add-payment-method' ) ? 'yes' : 'no';
1854 $stripe_params['is_pay_for_order_page'] = is_wc_endpoint_url( 'order-pay' ) ? 'yes' : 'no';
1855 $stripe_params['elements_styling'] = apply_filters( 'wc_stripe_elements_styling', false );
1856 $stripe_params['elements_classes'] = apply_filters( 'wc_stripe_elements_classes', false );
1857 $stripe_params['add_card_nonce'] = wp_create_nonce( 'wc_stripe_create_si' );
1858 $stripe_params['create_payment_intent_nonce'] = wp_create_nonce( 'wc_stripe_create_payment_intent_nonce' );
1859 $stripe_params['cpf_cnpj_required_msg'] = __( 'CPF/CNPJ is a required field', 'woocommerce-gateway-stripe' );
1860
1861 // Merge localized messages to be use in JS.
1862 $stripe_params = array_merge( $stripe_params, WC_Stripe_Helper::get_localized_messages() );
1863
1864 return $stripe_params;
1865 }
1866}