OwlCyberSecurity - MANAGER
Edit File: class-give-stripe-customer.php
<?php /** * Give - Stripe Customer * * @package Give * @subpackage Stripe Core * @copyright Copyright (c) 2019, GiveWP * @license https://opensource.org/licenses/gpl-license GNU Public License */ // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Class Give_Stripe_Customer. * * @since 2.5.0 */ class Give_Stripe_Customer { /** * Stripe Customer ID. * * @since 2.5.0 * @access private * * @var string */ private $id = ''; /** * Stripe Payment Method ID. * * @since 2.5.0 * @access private * * @var string */ private $payment_method_id = ''; /** * Donor Email. * * @since 2.5.0 * @access private * * @var string */ private $donor_email = ''; /** * Stripe Customer Data. * * @since 2.5.0 * @access private * * @var \Stripe\Customer */ public $customer_data = []; /** * Stripe Gateway Object. * * @since 2.5.0 * @access public * * @var array|Give_Stripe_Gateway */ public $stripe_gateway = []; /** * Attached Payment Method to Customer. * * @since 2.5.0 * @access public * * @var array|\Stripe\PaymentMethod */ public $attached_payment_method = []; /** * Check for card existence for customer. * * @since 2.5.0 * @access public * * @var bool */ public $is_card_exists = false; /** * Give_Stripe_Customer constructor. * * @param string $email Donor Email. * @param string $payment_method_id Stripe Payment Method ID. * * @since 2.5.0 * @access public */ public function __construct( $email, $payment_method_id = '' ) { $this->donor_email = $email; $this->payment_method_id = $payment_method_id; $this->stripe_gateway = new Give_Stripe_Gateway(); $this->set_id( give_stripe_get_customer_id( $email ) ); $this->get_or_create_customer(); } /** * Get Stripe customer ID. * * @since 2.5.0 * @access public * * @return string */ public function get_id() { return $this->id; } /** * Set Stripe customer ID. * * @param string $id Stripe Customer ID. * * @since 2.5.0 * @access public */ public function set_id( $id ) { $this->id = give_clean( $id ); } /** * Store data from the Stripe API about this customer * * @param /Stripe/Customer $data Stripe Customer Object. * * @since 2.5.0 * @access public */ public function set_customer_data( $data ) { $this->customer_data = $data; } /** * Get the Stripe customer object. If not found, create the customer with Stripe's API. * Save the customer ID appropriately in the database. * * @since 2.5.0 * @access public * * @return bool|\Stripe\Customer */ public function get_or_create_customer() { $customer = false; // No customer ID found, look up based on the email. $stripe_customer_id = give_stripe_get_customer_id( $this->donor_email ); // There is a customer ID. Check if it is active still in Stripe. if ( ! empty( $stripe_customer_id ) ) { // Set Application Info. give_stripe_set_app_info(); try { // Retrieve the customer to ensure the customer has not been deleted. $customer = \Stripe\Customer::retrieve( $stripe_customer_id, give_stripe_get_connected_account_options() ); if ( isset( $customer->deleted ) && $customer->deleted ) { // This customer was deleted. $customer = false; } } catch ( \Stripe\Error\InvalidRequest $e ) { $error_object = $e->getJsonBody(); if ( $this->is_no_such_customer_error( $error_object['error'] ) ) { $customer = $this->create_customer(); } else { // Record Log. give_stripe_record_log( __( 'Stripe - Customer Creation Error', 'give' ), $e->getMessage() ); } } catch ( Exception $e ) { $customer = false; } } // Create the Stripe customer if not present. if ( ! $customer ) { $customer = $this->create_customer(); } $this->set_id( $customer->id ); $this->set_customer_data( $customer ); // Proceed only, if the source is not empty. if ( ! empty( $this->payment_method_id ) ) { // Attach source/payment method to customer. if ( give_stripe_is_source_type( $this->payment_method_id, 'pm' ) ) { $this->attach_payment_method(); } else { $this->attach_source(); } } return $customer; } /** * This function is used to update the customer records in Stripe. * * @param string $id Customer ID provided by Stripe. * @param array $args List of arguments to update customer details. * * @since 2.5.10 * * @return bool|\Stripe\Customer */ public function update_customer( $id, $args ) { give_stripe_set_app_info(); $customer = false; try { $customer = \Stripe\Customer::update( $id, $args ); } catch ( Exception $e ) { // Record Log. give_stripe_record_log( __( 'Stripe - Customer Update Error', 'give' ), $e->getMessage() ); } return $customer; } /** * This function is used to set the payment method as default. * * @param string $id Payment Method ID provided by Stripe. * @param string $customer_id Customer ID provided by Stripe. * * @since 2.5.10 * @see https://stripe.com/docs/api/payment_methods/attach * * @return \Stripe\Customer */ public function set_default_payment_method( $id, $customer_id ) { $customer = null; try { $payment_method = $this->stripe_gateway->payment_method->retrieve( $id ); // Add card only if not added before. if ( $customer_id !== $payment_method->customer ) { $payment_method->attach( [ 'customer' => $customer_id, ] ); } $update_args = [ 'invoice_settings' => [ 'default_payment_method' => $id, ], ]; $customer = $this->update_customer( $customer_id, $update_args ); } catch ( Exception $e ) { give_set_error( 'stripe_error', $e->getMessage() ); give_record_gateway_error( __( 'Stripe Payment Method Error', 'give' ), sprintf( '%s%s', __( 'Error: ', 'give' ), $e->getMessage() ) ); give_send_back_to_checkout( '?payment-mode=stripe' ); } return $customer; } /** * Create a Customer in Stripe. * * @since 2.5.0 * @access public * * @return bool|\Stripe\Customer */ public function create_customer() { $customer = false; $post_data = give_clean( $_POST ); $payment_mode = ! empty( $post_data['give-gateway'] ) ? $post_data['give-gateway'] : ''; $form_id = ! empty( $post_data['give-form-id'] ) ? $post_data['give-form-id'] : 0; $first_name = ! empty( $post_data['give_first'] ) ? $post_data['give_first'] : ''; $last_name = ! empty( $post_data['give_last'] ) ? $post_data['give_last'] : ''; $full_name = ! empty( $last_name ) ? "{$first_name} {$last_name}" : $first_name; // Set Application Info. give_stripe_set_app_info(); try { $metadata = [ 'first_name' => $first_name, 'last_name' => $last_name, 'created_by' => $post_data['give-form-title'], ]; // Add address to customer metadata if present. if ( ! empty( $post_data['billing_country'] ) ) { $metadata['address_line1'] = isset( $post_data['card_address'] ) ? $post_data['card_address'] : ''; $metadata['address_line2'] = isset( $post_data['card_address_2'] ) ? $post_data['card_address_2'] : ''; $metadata['address_city'] = isset( $post_data['card_city'] ) ? $post_data['card_city'] : ''; $metadata['address_state'] = isset( $post_data['card_state'] ) ? $post_data['card_state'] : ''; $metadata['address_country'] = isset( $post_data['billing_country'] ) ? $post_data['billing_country'] : ''; $metadata['address_zip'] = isset( $post_data['card_zip'] ) ? $post_data['card_zip'] : ''; } // Add company name to customer metadata. if ( give_is_company_field_enabled( $form_id ) ) { $metadata['company_name'] = ! empty( $post_data['give_company_name'] ) ? $post_data['give_company_name'] : ''; } /** * This filter will be used to modify customer arguments based on the need. * * @param array $args List of customer arguments from Stripe. * * @since 2.5.0 */ $args = apply_filters( 'give_stripe_customer_args', [ 'description' => sprintf( /* translators: %s Site URL */ __( 'Stripe Customer generated by GiveWP via %s', 'give' ), get_bloginfo( 'url' ) ), 'name' => $full_name, 'email' => $this->donor_email, 'metadata' => apply_filters( 'give_stripe_customer_metadata', $metadata, $post_data ), ] ); // Add these parameters when payment method/source id exists. if ( ! empty( $this->payment_method_id ) ) { if ( give_stripe_is_source_type( $this->payment_method_id, 'pm' ) ) { $args['payment_method'] = $this->payment_method_id; } else { $args['source'] = $this->payment_method_id; } } // Create a customer first so we can retrieve them later for future payments. $customer = \Stripe\Customer::create( $args, give_stripe_get_connected_account_options() ); } catch ( Exception $e ) { // Something else happened. give_record_gateway_error( esc_html__( 'Stripe Error', 'give' ), sprintf( '%1$s %2$s', esc_html__( 'The Stripe Gateway returned an error while creating the customer. Details:', 'give' ), $e->getTraceAsString() ) ); give_set_error( 'stripe_error', $e->getMessage() ); give_send_back_to_checkout( "?payment-mode={$payment_mode}&form_id={$form_id}" ); } if ( ! empty( $customer->id ) ) { // Return obj. return $customer; } else { return false; } } /** * This function is used to attach source to the customer, if not exists. * * @since 2.1 * @since 2.11.0 Use Give_Stripe_Customer::doesSourceFingerPrintMatch function to verify source finger print * @access public * * @return void */ public function attach_source() { if ( ! empty( $this->payment_method_id ) && ! empty( $this->customer_data ) ) { $card = ''; $card_exists = false; $newSourcePaymentMethodDetail = ''; $all_sources = $this->customer_data->sources->all(); // Fetch the new card or source object to match with customer attached card fingerprint. if ( give_stripe_is_source_type( $this->payment_method_id, 'tok' ) ) { $token_details = $this->stripe_gateway->get_token_details( $this->payment_method_id ); $newSourcePaymentMethodDetail = $token_details->bank_account; } elseif ( give_stripe_is_source_type( $this->payment_method_id, 'src' ) ) { $source_details = $this->stripe_gateway->get_source_details( $this->payment_method_id ); $newSourcePaymentMethodDetail = $source_details->card; } /** * This filter hook is used to get new card details. * * @since 2.5.0 */ $newSourcePaymentMethodDetail = apply_filters( 'give_stripe_get_new_card_details', $newSourcePaymentMethodDetail, $this->payment_method_id, $this->stripe_gateway ); // Check to ensure that new card is already attached with customer or not. if ( count( $all_sources->data ) > 0 ) { foreach ( $all_sources->data as $source_item ) { if ( $this->isSourceFingerPrintMatch( $source_item, $newSourcePaymentMethodDetail ) ) { // Set the existing card as default source. $this->customer_data->default_source = $source_item->id; $this->customer_data->save(); $card = $source_item; $card_exists = true; $this->is_card_exists = true; break; } } } // Create the card, if none found above. if ( ! $card_exists ) { try { $card = $this->customer_data->sources->create( [ 'source' => $this->payment_method_id ] ); $this->customer_data->default_source = $card->id; $this->customer_data->save(); } catch ( \Stripe\Error\Base $e ) { Give_Stripe_Logger::log_error( $e, 'stripe' ); } catch ( Exception $e ) { give_record_gateway_error( __( 'Stripe Error', 'give' ), sprintf( /* translators: %s Exception Message Body */ __( 'The Stripe Gateway returned an error while creating the customer. Details: %s', 'give' ), $e->getMessage() ) ); give_set_error( 'stripe_error', __( 'An occurred while processing the donation with the gateway. Please try your donation again.', 'give' ) ); give_send_back_to_checkout( '?payment-mode=' . give_clean( $_GET['payment-mode'] ) ); return; } } // Return Card Details, if exists. if ( ! empty( $card->id ) ) { $this->attached_payment_method = $card; } else { give_set_error( 'stripe_error', __( 'An error occurred while processing the donation. Please try again.', 'give' ) ); give_record_gateway_error( __( 'Stripe Error', 'give' ), __( 'An error occurred retrieving or creating the ', 'give' ) ); give_send_back_to_checkout( '?payment-mode=' . give_clean( $_GET['payment-mode'] ) ); $this->attached_payment_method = false; } } // End if(). } /** * This function is used to attach source to the customer, if not exists. * * @since 2.5.0 * @access public * * @return void */ public function attach_payment_method() { if ( ! empty( $this->payment_method_id ) && ! empty( $this->customer_data ) ) { $payment_method = ''; $payment_methods = $this->stripe_gateway->payment_method->list_all( $this->id ); // All payment methods. $new_payment_method = $this->stripe_gateway->payment_method->retrieve( $this->payment_method_id ); /** * This filter hook is used to get new card details. * * @since 2.5.0 */ $new_payment_method = apply_filters( 'give_stripe_get_new_card_details', $new_payment_method, $this->payment_method_id, $this->stripe_gateway ); // Check to ensure that new card is already attached with customer's payment methods or not. if ( count( $payment_methods->data ) > 0 ) { foreach ( $payment_methods->data as $card_details ) { // If fingerprint of new and existing payment method doesn't match then continue to next iteration. if ( $card_details->card->fingerprint !== $new_payment_method->card->fingerprint ) { continue; } if ( $card_details->card->exp_month !== $new_payment_method->card->exp_month || $card_details->card->exp_year !== $new_payment_method->card->exp_year ) { // Set updated expiry date to the existing card. $this->stripe_gateway->payment_method->update( $card_details->id, [ 'card' => [ 'exp_month' => $new_payment_method->card->exp_month, 'exp_year' => $new_payment_method->card->exp_year, ], ] ); } // Set existing card as default payment method. $this->set_default_payment_method( $card_details->id, $this->id ); $payment_method = $card_details; $this->is_card_exists = true; } } // Create the card, if none found above. if ( ! $this->is_card_exists ) { // Set new card as default payment method. $this->set_default_payment_method( $this->payment_method_id, $this->id ); // Assign the new payment method. $payment_method = $new_payment_method; } // Return Card Details, if exists. if ( ! empty( $payment_method->id ) ) { $this->attached_payment_method = $payment_method; } else { give_set_error( 'stripe_error', __( 'An error occurred while processing the donation. Please try again.', 'give' ) ); give_record_gateway_error( __( 'Stripe Error', 'give' ), __( 'An error occurred retrieving or creating the ', 'give' ) ); give_send_back_to_checkout( '?payment-mode=stripe' ); $this->attached_payment_method = false; } } // End if(). } /** * This function will check whether the error says no such customer. * * @param \Stripe\Error\InvalidRequest $error Invalid Request Error. * * @since 2.5.0 * @access public * * @return bool */ public function is_no_such_customer_error( $error ) { return ( $error && 'invalid_request_error' === $error['type'] && preg_match( '/No such customer/i', $error['message'] ) ); } /** * This function will check whether the ID provided is Card ID? * * @param string $id Card ID. * * @since 2.5.0 * @access public * * @return bool */ public function is_card( $id ) { return give_stripe_is_source_type( $id, 'card' ); } /** * This function will check whether the ID provided is Source ID? * * @param string $id Source ID. * * @since 2.5.0 * @access public * * @return bool */ public function is_source( $id ) { return give_stripe_is_source_type( $id, 'src' ); } /** * This function will check whether the ID provided is Bank Account ID? * * @param string $id Source ID. * * @since 2.5.0 * @access public * * @return bool */ public function is_bank_account( $id ) { return give_stripe_is_source_type( $id, 'ba' ); } /** * @since 2.11.0 * * @param stdClass $stripeSource * @param stdClass $newStripeSource */ private function isSourceFingerPrintMatch( $stripeSource, $newStripeSource ) { if ( isset( $stripeSource->fingerprint ) ) { return $stripeSource->fingerprint === $newStripeSource->fingerprint; } if ( property_exists( $stripeSource, 'card' ) && isset( $stripeSource->card->fingerprint ) ) { return $stripeSource->card->fingerprint === $newStripeSource->fingerprint; } return false; } }