PK
Current Path : /proc/self/cwd/wp-content/plugins/checkout-plugins-stripe-woo/inc/traits/ |
Current File : //proc/self/cwd/wp-content/plugins/checkout-plugins-stripe-woo/inc/traits/subscriptions.php |
<?php /** * Subscriptions Trait. * * @package checkout-plugins-stripe-woo */ namespace CPSW\Inc\Traits; use CPSW\Inc\Traits\Subscription_Helper as SH; use CPSW\Gateway\Stripe\Stripe_Api; use CPSW\Inc\Logger; use WC_Emails; use WC_Subscriptions_Change_Payment_Gateway; use Exception; use WC_AJAX; /** * Trait for Subscriptions utility functions. */ trait Subscriptions { use SH; /** * Initialize subscription support and hooks. */ public function maybe_init_subscriptions() { if ( ! $this->is_subscriptions_enabled() ) { return; } $this->supports = $this->add_subscription_filters( $this->supports ); add_action( 'woocommerce_scheduled_subscription_payment_' . $this->id, [ $this, 'scheduled_subscription_payment' ], 10, 2 ); add_action( 'woocommerce_subscription_failing_payment_method_updated_' . $this->id, [ $this, 'update_failing_payment_method' ], 10, 2 ); add_action( 'wcs_resubscribe_order_created', [ $this, 'delete_resubscribe_meta' ], 10, 1 ); add_action( 'wcs_renewal_order_created', [ $this, 'delete_renewal_meta' ], 10 ); add_action( 'cpsw_payment_fields_' . $this->id, [ $this, 'display_update_subs_payment_checkout' ] ); add_action( 'cpsw_add_payment_method_' . $this->id . '_success', [ $this, 'handle_add_payment_method_success' ], 10, 2 ); add_action( 'woocommerce_subscriptions_change_payment_before_submit', [ $this, 'differentiate_change_payment_method_form' ] ); add_filter( 'woocommerce_subscription_payment_meta', [ $this, 'add_subscription_payment_meta' ], 10, 2 ); add_filter( 'woocommerce_subscription_validate_payment_meta', [ $this, 'validate_subscription_payment_meta' ], 10, 2 ); add_filter( 'cpsw_display_save_payment_method_checkbox', [ $this, 'display_save_payment_method_checkbox' ] ); add_action( 'template_redirect', [ $this, 'remove_order_pay_var' ], 99 ); add_action( 'template_redirect', [ $this, 'restore_order_pay_var' ], 101 ); } /** * Checkbox to update all subcription to new payment method */ public function display_update_subs_payment_checkout() { $subs_statuses = apply_filters( 'cpsw_update_subs_payment_method_card_status', [ 'active' ] ); if ( apply_filters( 'cpsw_display_update_subs_payment_method_card_checkbox', true ) && wcs_user_has_subscription( get_current_user_id(), '', $subs_statuses ) && is_add_payment_method_page() ) { $id = sprintf( '%1$s-update-subs-payment-method-card', $this->id ); echo '<span class="cpsw-save-cards"><label><input type="checkbox" name="' . esc_attr( $id ) . '" value="1"/>' . wp_kses_post( apply_filters( 'cpsw_save_to_subs_text', __( 'Update the payment method used for all of my active subscriptions.', 'checkout-plugins-stripe-woo' ) ) ) . '</label></span>'; } } /** * Updates all active subscriptions payment method. * * @param string $source_id source id. * @param object $source_object source object. */ public function handle_add_payment_method_success( $source_id, $source_object ) { if ( isset( $_POST[ $this->id . '-update-subs-payment-method-card' ] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Missing $all_subs = wcs_get_users_subscriptions(); $subs_statuses = apply_filters( 'cpsw_update_subs_payment_method_card_status', [ 'active' ] ); $stripe_customer = $this->get_customer_id(); if ( ! empty( $all_subs ) ) { foreach ( $all_subs as $sub ) { if ( $sub->has_status( $subs_statuses ) ) { WC_Subscriptions_Change_Payment_Gateway::update_payment_method( $sub, $this->id, [ 'post_meta' => [ '_cpsw_source_id' => [ 'value' => $source_id ], '_cpsw_customer_id' => [ 'value' => $stripe_customer ], ], ] ); } } } } } /** * Renders hidden element. */ public function differentiate_change_payment_method_form() { echo '<input type="hidden" id="wc-cpsw_stripe-change-payment-method" />'; } /** * Maybe process payment method change for subscriptions. * * @param int $order_id current order id. * @return bool */ public function maybe_change_subscription_payment_method( $order_id ) { return ( $this->is_subscriptions_enabled() && $this->has_subscription( $order_id ) && $this->is_changing_payment_method_for_subscription() ); } /** * Process the payment method change for subscriptions. * * @param int $order_id current order id. * @return array|null */ public function process_change_subscription_payment_method( $order_id ) { try { $subscription = wc_get_order( $order_id ); $prepared_source = $this->prepare_source( $order_id, get_current_user_id(), true ); $this->save_payment_method_to_order( $subscription, $prepared_source ); do_action( 'cpsw_change_subs_payment_method_success', $prepared_source->source, $prepared_source ); return [ 'result' => 'success', 'redirect' => $this->get_return_url( $subscription ), ]; } catch ( Exception $e ) { Logger::error( $e->getMessage(), true ); } } /** * Scheduled_subscription_payment function. * * @param float $amount_to_charge float The amount to charge. * @param object $renewal_order WC_Order A WC_Order object created to record the renewal payment. */ public function scheduled_subscription_payment( $amount_to_charge, $renewal_order ) { $this->process_subscription_payment( $amount_to_charge, $renewal_order, true, false ); } /** * Process_subscription_payment function. * * @param float $amount order amount. * @param mixed $renewal_order renewal order object. * @param bool $retry Should we retry the process. * @param object $previous_error previous error for same order. * @throws Exception Stripe Exception. * @return void */ public function process_subscription_payment( $amount, $renewal_order, $retry = true, $previous_error = false ) { try { $order_id = $renewal_order->get_id(); if ( isset( $_REQUEST['process_early_renewal'] ) && 'cpsw_stripe' === $this->id ) { // phpcs:ignore WordPress.Security.NonceVerification $response = $this->process_payment( $order_id, true, false, $previous_error, true ); if ( 'success' === $response['result'] && isset( $response['payment_intent_secret'] ) ) { $verification_url = add_query_arg( [ 'order' => $order_id, 'nonce' => wp_create_nonce( 'wc_stripe_confirm_pi' ), 'redirect_to' => remove_query_arg( [ 'process_early_renewal', 'subscription_id', 'wcs_nonce' ] ), 'early_renewal' => true, ], WC_AJAX::get_endpoint( 'wc_stripe_verify_intent' ) ); echo wp_json_encode( [ 'stripe_sca_required' => true, 'intent_secret' => $response['payment_intent_secret'], 'redirect_url' => $verification_url, ] ); exit; } // Hijack all other redirects in order to do the redirection in JavaScript. add_action( 'wp_redirect', [ $this, 'redirect_after_early_renewal' ], 100 ); return; } // Check for an existing intent, which is associated with the order. if ( $this->has_authentication_already_failed( $renewal_order ) ) { return; } // Get source from order. $prepared_source = $this->prepare_order_source( $renewal_order ); $source_object = $prepared_source->source_object; if ( ! $prepared_source->customer ) { throw new Exception( 'Failed to process renewal for order ' . $renewal_order->get_id() . '. Stripe customer id is missing in the order', __( 'Customer not found', 'checkout-plugins-stripe-woo' ) ); } Logger::info( "Begin processing subscription payment for order {$order_id} for the amount of {$amount}" ); $response = $this->create_and_confirm_intent_for_off_session( $renewal_order, $prepared_source, $amount ); $is_authentication_required = $this->is_authentication_required_for_payment( $response ); // error not of the type 'authentication_required'. if ( isset( $response['error'] ) && ! $is_authentication_required ) { if ( $this->is_retryable_error( $response['error'] ) ) { // We want to retry. if ( $retry ) { // Don't do anymore retries after this. if ( 5 <= $this->retry_interval ) { return $this->process_subscription_payment( $amount, $renewal_order, false, $response['error'] ); } sleep( $this->retry_interval ); $this->retry_interval++; return $this->process_subscription_payment( $amount, $renewal_order, true, $response['error'] ); } else { $localized_message = __( 'Sorry, we are unable to process your payment at this time. Please retry later.', 'checkout-plugins-stripe-woo' ); $renewal_order->add_order_note( $localized_message ); } } $renewal_order->add_order_note( $response['message'] ); } // Either the charge was successfully captured, or it requires further authentication. if ( $is_authentication_required ) { do_action( 'cpsw_stripe_process_payment_authentication_required', $renewal_order, $response ); $error_message = __( 'This transaction requires authentication.', 'checkout-plugins-stripe-woo' ); $renewal_order->add_order_note( $error_message ); $charge = end( $response['error']->payment_intent->charges->data ); $id = $charge->id; $order_id = $renewal_order->get_id(); $renewal_order->set_transaction_id( $id ); /* translators: %s is the stripe charge Id */ $renewal_order->update_status( 'failed', sprintf( __( 'Stripe charge awaiting authentication by user: %s.', 'checkout-plugins-stripe-woo' ), $id ) ); if ( is_callable( [ $renewal_order, 'save' ] ) ) { $renewal_order->save(); } } else { // The charge was successfully captured. do_action( 'cpsw_stripe_process_payment', $response, $renewal_order ); if ( isset( $response['error'] ) ) { $renewal_order->update_status( 'failed', $response['message'] ); return; } if ( $response && 'cpsw_sepa' === $this->id ) { set_transient( 'cpsw_stripe_sepa_client_secret', $response->client_secret, 1 * MINUTE_IN_SECONDS ); } // Use the last charge within the intent or the full response body in case of SEPA. $this->process_response( ( isset( $response->charges ) && ! empty( $response->charges->data ) ) ? end( $response->charges->data ) : $response, $renewal_order ); } } catch ( Exception $e ) { Logger::error( $e->getMessage(), true ); do_action( 'cpsw_stripe_process_payment_error', $e, $renewal_order ); /* translators: error message */ $renewal_order->update_status( 'failed' ); } } /** * Create and confirm intents for subscriptions * * @param WC_Orde $order current order. * @param Object $source prepared source to be charged. * @param string $amount amount of order. * @return object */ public function create_and_confirm_intent_for_off_session( $order, $source, $amount ) { $order_id = $order->get_id(); $request = [ 'amount' => $amount ? $this->get_formatted_amount( $order->get_total() ) : 0, 'currency' => $order->get_currency(), 'payment_method_types' => [ $this->payment_method_types ], 'description' => $this->get_order_description( $order ), 'confirmation_method' => 'automatic', 'customer' => $source->customer, 'metadata' => $this->get_metadata( $order_id ), 'payment_method' => $source->source, ]; if ( 'cpsw_sepa' !== $this->id ) { $request['off_session'] = 'true'; $request['confirm'] = 'true'; } if ( ! empty( trim( $this->statement_descriptor ) ) ) { $request['statement_descriptor_suffix'] = $this->statement_descriptor; } if ( ! empty( $this->capture_method ) ) { $request['capture_method'] = $this->capture_method; } Logger::info( "Stripe Payment initiated for order $order_id" ); $stripe_api = new Stripe_Api(); $response = $stripe_api->payment_intents( 'create', [ apply_filters( 'cpsw_create_and_confirm_intent_post_data', $request ) ] ); $intent = $response['success'] ? $response['data'] : false; if ( $intent ) { $intent_data = [ 'id' => $intent->id, 'client_secret' => $intent->client_secret, ]; $order->update_meta_data( '_cpsw_intent_secret', $intent_data ); $order->save(); } else { $intent = [ 'message' => isset( $response['message'] ) ? $response['message'] : __( 'Payment processing failed. Please retry.', 'checkout-plugins-stripe-woo' ), 'type' => $response['type'], 'code' => $response['code'], 'error' => $response['error'], ]; } return $intent; } /** * Check if authentication is required for payment * * @param object $response intent response. * @return boolean */ public function is_authentication_required_for_payment( $response ) { return ( ! empty( $response['code'] ) && 'authentication_required' === $response['code'] ); } /** * Prepare Sorce for current order * * @param WC_Order $order Current order. * @return object */ public function prepare_order_source( $order = null ) { $stripe_customer = [ 'id' => '' ]; $stripe_source = false; $token_id = false; $source_object = false; if ( $order ) { $order_id = $order->get_id(); $stripe_customer_id = $this->get_cpsw_customer_id( $order ); if ( $stripe_customer_id ) { $stripe_customer['id'] = $stripe_customer_id; } $source_id = $order->get_meta( '_cpsw_source_id', true ); if ( $source_id ) { $stripe_source = $source_id; $stripe_api = new Stripe_Api(); $response = $stripe_api->payment_methods( 'retrieve', [ $stripe_source ] ); $source_object = $response['success'] ? $response['data'] : false; } elseif ( apply_filters( 'cpsw_use_default_customer_source', true ) ) { // Attempting with empty source id. $stripe_source = ''; } } return (object) [ 'token_id' => $token_id, 'customer' => $stripe_customer ? $stripe_customer['id'] : false, 'source' => $stripe_source, 'source_object' => $source_object, 'payment_method' => null, ]; } /** * Get customer id from meta for current order. * * @param WC_Order $order current woocommerce order. * @return string */ public function get_cpsw_customer_id( $order ) { // Try to get it via the order first. $customer = $order->get_meta( '_cpsw_customer_id', true ); if ( empty( $customer ) ) { $customer = get_user_option( '_cpsw_customer_id', $order->get_customer_id() ); } return $customer; } /** * Updates other subscription sources * * @param WC_Order $order Current order. * @param Stripe_Source $source payment source to be used. * @return void */ public function maybe_update_source_on_subscription_order( $order, $source ) { if ( ! $this->is_subscriptions_enabled() ) { return; } $order_id = $order->get_id(); // Also store it on the subscriptions being purchased or paid for in the order. if ( function_exists( 'wcs_order_contains_subscription' ) && wcs_order_contains_subscription( $order_id ) ) { $subscriptions = wcs_get_subscriptions_for_order( $order_id ); } elseif ( function_exists( 'wcs_order_contains_renewal' ) && wcs_order_contains_renewal( $order_id ) ) { $subscriptions = wcs_get_subscriptions_for_renewal_order( $order_id ); } else { $subscriptions = []; } foreach ( $subscriptions as $subscription ) { $subscription->update_meta_data( '_cpsw_customer_id', $source->customer ); if ( ! empty( $source->payment_method ) ) { $subscription->update_meta_data( '_cpsw_source_id', $source->payment_method ); } else { $subscription->update_meta_data( '_cpsw_source_id', $source->source ); } $subscription->save(); } } /** * Don't transfer Stripe customer/token meta to resubscribe orders. * * @param object $resubscribe_order The order created for the customer to resubscribe to the old expired/cancelled subscription. */ public function delete_resubscribe_meta( $resubscribe_order ) { $resubscribe_order->delete_meta_data( '_cpsw_customer_id' ); $resubscribe_order->delete_meta_data( '_cpsw_source_id' ); if ( is_callable( [ $resubscribe_order, 'save' ] ) ) { $resubscribe_order->save(); } $this->delete_renewal_meta( $resubscribe_order ); } /** * Don't transfer Stripe fee/ID meta to renewal orders. * * @param object $renewal_order The order created for the customer to resubscribe to the old expired/cancelled subscription. * @return object */ public function delete_renewal_meta( $renewal_order ) { $this->delete_stripe_fee( $renewal_order ); $this->delete_stripe_net( $renewal_order ); // Delete payment intent ID. $renewal_order->delete_meta_data( '_stripe_intent_id' ); if ( is_callable( [ $renewal_order, 'save' ] ) ) { $renewal_order->save(); } return $renewal_order; } /** * Deleting stripe fee meta * * @param WC_Order $order Current woocommerce order. * @return bool | void */ public function delete_stripe_fee( $order = null ) { if ( is_null( $order ) ) { return false; } $order->delete_meta_data( '_stripe_fee' ); $order->delete_meta_data( 'Stripe Fee' ); if ( is_callable( [ $order, 'save' ] ) ) { $order->save(); } } /** * Deleting Stripe renewal meta * * @param WC_Order $order Current woocommerce order. * @return bool | void */ public static function delete_stripe_net( $order = null ) { if ( is_null( $order ) ) { return false; } $order->delete_meta_data( 'stripe_net' ); $order->delete_meta_data( 'Net Revenue From Stripe' ); if ( is_callable( [ $order, 'save' ] ) ) { $order->save(); } } /** * Update the customer_id for after subscription completion * * @param WC_Subscription $subscription The subscription for which the failing payment method relates. * @param WC_Order $renewal_order The order which recorded the successful payment (to make up for the failed automatic payment). * @return void */ public function update_failing_payment_method( $subscription, $renewal_order ) { $subscription->update_meta_data( '_cpsw_customer_id', $renewal_order->get_meta( '_cpsw_customer_id', true ) ); $subscription->update_meta_data( '_cpsw_source_id', $renewal_order->get_meta( '_cpsw_source_id', true ) ); $subscription->save(); } /** * Saves the payment meta data required to process automatic recurring payments * * @param array $payment_meta associative array of meta data required for automatic payments. * @param WC_Subscription $subscription An instance of a subscription object. * @return array */ public function add_subscription_payment_meta( $payment_meta, $subscription ) { $source_id = $subscription->get_meta( '_cpsw_source_id' ); $payment_meta[ $this->id ] = [ 'post_meta' => [ '_cpsw_customer_id' => [ 'value' => $subscription->get_meta( '_cpsw_customer_id' ), 'label' => 'Stripe Customer ID', ], '_cpsw_source_id' => [ 'value' => $source_id, 'label' => 'Stripe Source ID', ], ], ]; return $payment_meta; } /** * Validate the payment meta data * * @param string $payment_method_id The ID of the payment method to validate. * @param array $payment_meta associative array of meta data required for automatic payments. * @throws Exception Stripe Exception. * @return void */ public function validate_subscription_payment_meta( $payment_method_id, $payment_meta ) { //phpcs:ignore WordPressVIPMinimum.Hooks.AlwaysReturnInFilter.MissingReturnStatement if ( $this->id === $payment_method_id ) { if ( ! isset( $payment_meta['post_meta']['_cpsw_customer_id']['value'] ) || empty( $payment_meta['post_meta']['_cpsw_customer_id']['value'] ) ) { // Allow empty stripe customer id during subscription renewal. It will be added when processing payment if required. if ( ! isset( $_POST['wc_order_action'] ) || 'wcs_process_renewal' !== $_POST['wc_order_action'] ) { //phpcs:ignore WordPress.Security.NonceVerification.Missing throw new Exception( __( 'A "Stripe Customer ID" value is required.', 'checkout-plugins-stripe-woo' ) ); } } elseif ( 0 !== strpos( $payment_meta['post_meta']['_cpsw_customer_id']['value'], 'cus_' ) ) { throw new Exception( __( 'Invalid customer ID. A valid "Stripe Customer ID" must begin with "cus_".', 'checkout-plugins-stripe-woo' ) ); } if ( ! empty( $payment_meta['post_meta']['_cpsw_source_id']['value'] ) && ( 0 !== strpos( $payment_meta['post_meta']['_cpsw_source_id']['value'], 'card_' ) && 0 !== strpos( $payment_meta['post_meta']['_cpsw_source_id']['value'], 'src_' ) && 0 !== strpos( $payment_meta['post_meta']['_cpsw_source_id']['value'], 'pm_' ) ) ) { throw new Exception( __( 'Invalid source ID. A valid source "Stripe Source ID" must begin with "src_", "pm_", or "card_".', 'checkout-plugins-stripe-woo' ) ); } } } /** * Removes sca variable if not required. */ public function remove_order_pay_var() { global $wp; if ( isset( $_GET['wc-stripe-confirmation'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification $this->order_pay_var = $wp->query_vars['order-pay']; $wp->query_vars['order-pay'] = null; } } /** * Restore the variable that was removed in remove_order_pay_var() */ public function restore_order_pay_var() { global $wp; if ( isset( $this->order_pay_var ) ) { $wp->query_vars['order-pay'] = $this->order_pay_var; } } /** * Checks if a renewal already failed because a manual authentication is required. * * @param WC_Order $renewal_order The renewal order. * @return boolean */ public function has_authentication_already_failed( $renewal_order ) { $existing_intent = $this->get_intent_from_order( $renewal_order ); if ( ! $existing_intent || 'requires_payment_method' !== $existing_intent->status || empty( $existing_intent->last_payment_error ) || 'authentication_required' !== $existing_intent->last_payment_error->code ) { return false; } // Make sure all emails are instantiated. WC_Emails::instance(); /** * Action when authentication already failed. * * @param WC_Order $renewal_order The order that is being renewed. */ do_action( 'cpsw_has_authentication_already_failed', $renewal_order ); // Fail the payment attempt (order would be currently pending because of retry rules). $charge = end( $existing_intent->charges->data ); $charge_id = $charge->id; /* translators: %s is the stripe charge Id */ $renewal_order->update_status( 'failed', sprintf( __( 'Stripe charge awaiting authentication by user: %s.', 'checkout-plugins-stripe-woo' ), $charge_id ) ); return true; } /** * Get intent from order * * @param WC_Order $order order object. * @return mixed intent id or false. */ public function get_intent_from_order( $order ) { $intent_id = $order->get_meta( '_stripe_intent_id' ); if ( $intent_id ) { return $this->get_intent( 'payment_intents', $intent_id ); } // The order doesn't have a payment intent, but it may have a setup intent. $intent_id = $order->get_meta( '_stripe_setup_intent' ); if ( $intent_id ) { return $this->get_intent( 'setup_intents', $intent_id ); } return false; } /** * Hijacks `wp_redirect` in order to generate a JS-friendly object with the URL. * * @param string $url The URL that Subscriptions attempts a redirect to. * @return void */ public function redirect_after_early_renewal( $url ) { echo wp_json_encode( [ 'stripe_sca_required' => false, 'redirect_url' => $url, ] ); exit; } /** * Once an intent has been verified, perform some final actions for early renewals. * * @param WC_Order $order The renewal order. * @param stdClass $intent The Payment Intent object. */ protected function maybe_process_subscription_early_renewal_success( $order, $intent ) { if ( $this->is_subscriptions_enabled() && isset( $_GET['early_renewal'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification wcs_update_dates_after_early_renewal( wcs_get_subscription( $order->get_meta( '_subscription_renewal' ) ), $order ); } } /** * Process early renewal. * * @param WC_Order $order The renewal order. * @param stdClass $intent The Payment Intent object (unused). */ protected function maybe_process_subscription_early_renewal_failure( $order, $intent ) { if ( $this->is_subscriptions_enabled() && isset( $_GET['early_renewal'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification $order->delete( true ); $renewal_url = wcs_get_early_renewal_url( wcs_get_subscription( $order->get_meta( '_subscription_renewal' ) ) ); wp_safe_redirect( $renewal_url ); exit; } } /** * Prepare source for user * * @param int $order_id current woocommerce order id. * @param int $user_id current user id. * @param boolean $force_save_source if saved a source is required. * @param string $existing_customer_id stripe customer id if available. * @throws Exception Stripe Exceptions. * @return mixed */ public function prepare_source( $order_id, $user_id, $force_save_source = false, $existing_customer_id = null ) { // Nonce verification already done in parent woocommerce function process_checkout. $order = wc_get_order( $order_id ); $customer_id = ( ! empty( $existing_customer_id ) ) ? $existing_customer_id : $this->get_customer_id( $order ); $source_object = ''; $source_id = ''; $wc_token_id = false; $payment_method = isset( $_POST['payment_method'] ) ? wc_clean( wp_unslash( $_POST['payment_method'] ) ) : 'cpsw_stripe'; //phpcs:ignore WordPress.Security.NonceVerification.Missing $is_token = false; // New CC info was entered and we have a new source to process. if ( ! empty( $_POST['payment_method_created'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Missing $stripe_source = wc_clean( wp_unslash( $_POST['payment_method_created'] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Missing $stripe_api = new Stripe_Api(); $response = $stripe_api->payment_methods( 'retrieve', [ $stripe_source ] ); $source_object = $response['success'] ? $response['data'] : false; if ( ! $source_object ) { return; } $source_id = $source_object->id; // This checks to see if customer opted to save the payment method to file. $maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ); //phpcs:ignore WordPress.Security.NonceVerification.Missing // Either saved card is enabled or forced by flow. if ( ( $user_id && $this->enable_saved_cards && $maybe_saved_card && 'reusable' === $source_object->usage ) || $force_save_source ) { $stripe_api->payment_methods( 'attach', [ $source_id, [ 'customer' => $customer_id ] ] ); $this->create_payment_token_for_user( $user_id, $source_object ); if ( ! empty( $response->error ) ) { throw new Exception( print_r( $response, true ), $this->get_localized_error_message_from_response( $response ) ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r } if ( is_wp_error( $response ) ) { throw new Exception( $response->get_error_message(), $response->get_error_message() ); } } } elseif ( $this->is_using_saved_payment_method() ) { // Use an existing token, and then process the payment. $wc_token = $this->get_token_from_request( $_POST ); //phpcs:ignore WordPress.Security.NonceVerification.Missing $wc_token_id = $wc_token->get_id(); if ( ! $wc_token || $wc_token->get_user_id() !== get_current_user_id() ) { WC()->session->set( 'refresh_totals', true ); throw new Exception( 'Invalid payment method', __( 'Invalid payment method. Please input a new card number.', 'checkout-plugins-stripe-woo' ) ); } $source_id = $wc_token->get_token(); if ( ! empty( $source_id ) ) { $is_token = true; } // nonce verification already done in parent woocommerce function process_checkout. } elseif ( isset( $_POST['stripe_token'] ) && 'new' !== $_POST['stripe_token'] ) { //phpcs:ignore WordPress.Security.NonceVerification.Missing $stripe_token = wc_clean( wp_unslash( $_POST['stripe_token'] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Missing $maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ); //phpcs:ignore WordPress.Security.NonceVerification.Missing // This is true if the user wants to store the card to their account. if ( ( $user_id && $this->enable_saved_cards && $maybe_saved_card ) || $force_save_source ) { $response = $customer->attach_source( $stripe_token ); if ( ! empty( $response->error ) ) { throw new Exception( print_r( $response, true ), $response->error->message ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r } if ( is_wp_error( $response ) ) { throw new Exception( $response->get_error_message(), $response->get_error_message() ); } $source_id = $response->id; } else { $source_id = $stripe_token; $is_token = true; } } if ( ! $customer_id ) { $customer = $this->create_stripe_customer( $source_id, $order_id, $user_email ); } if ( empty( $source_object ) && ! $is_token ) { $source_object = $this->get_source_object( $source_id ); } return (object) [ 'token_id' => $wc_token_id, 'customer' => $customer_id, 'source' => $source_id, 'source_object' => $source_object, 'payment_method' => null, ]; } /** * Get source object by source id. * * @since 1.4.5 * * @param string $source_id The source ID to get source object for. */ public function get_source_object( $source_id = '' ) { if ( empty( $source_id ) ) { return ''; } $stripe_api = new Stripe_Api(); $response = $stripe_api->payment_methods( 'retrieve', [ $source_id ] ); $source_object = $response['success'] ? $response['data'] : false; return $source_object; } }