OwlCyberSecurity - MANAGER
Edit File: class-give-stripe-gateway.php
<?php /** * Give - Stripe Core Gateway * * @since 2.5.0 * * @package Give * @subpackage Stripe Core * @copyright Copyright (c) 2019, GiveWP * @license https://opensource.org/licenses/gpl-license GNU Public License */ // Exit, if accessed directly. use Give\PaymentGateways\Stripe\ApplicationFee; use Give\ValueObjects\Money; if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Check for class Give_Stripe_Gateway exists. * * @since 2.5.0 */ if ( ! class_exists( 'Give_Stripe_Gateway' ) ) { class Give_Stripe_Gateway { /** * Default Gateway ID. * * @since 2.5.0 * @access public * * @var string */ public $id; /** * Set Latest Stripe Version. * * @since 2.5.0 * @access public * * @var string */ public $api_version = '2019-05-16'; /** * Payment Intent. * * @since 2.5.0 * @access public * * @var \Stripe\PaymentIntent */ public $payment_intent; /** * Stripe Payment Method Object. * * @since 2.5.0 * @access public * * @var Give_Stripe_Payment_Method */ public $payment_method; /** * Error Messages. * * @since 2.7.0 * @access public * * @var array $errorMessages List of error messages. */ public $errorMessages = []; /** * Give_Stripe_Gateway constructor. * * @since 2.5.0 * @access public * * @return bool|void */ public function __construct() { // Set API Version. $this->set_api_version(); // Call Payment Intent Class to utilize. $this->payment_intent = new Give_Stripe_Payment_Intent(); $this->payment_method = new Give_Stripe_Payment_Method(); add_action( "give_gateway_{$this->id}", [ $this, 'process_payment' ] ); } /** * This function is used to determine whether to show the payment fields or not. * * @since 2.7.0 * @access public * * @return bool */ public function canShowFields() { $status = true; $isConfigured = Give\Helpers\Gateways\Stripe::isAccountConfigured(); $isTestMode = give_is_test_mode(); $isSslActive = is_ssl(); if ( ! $isConfigured && ! $isSslActive && ! $isTestMode ) { // Account not configured, No SSL scenario. Give_Notices::print_frontend_notice( sprintf( '<strong>%1$s</strong> %2$s', esc_html__( 'Notice:', 'give' ), $this->errorMessages['accountNotConfiguredNoSsl'] ) ); $status = false; } elseif ( ! $isConfigured ) { // Account not configured scenario. Give_Notices::print_frontend_notice( sprintf( '<strong>%1$s</strong> %2$s', esc_html__( 'Notice:', 'give' ), $this->errorMessages['accountNotConfigured'] ) ); $status = false; } elseif ( ! $isTestMode && ! $isSslActive ) { // Account configured, No SSL scenario. Give_Notices::print_frontend_notice( sprintf( '<strong>%1$s</strong> %2$s', esc_html__( 'Notice:', 'give' ), $this->errorMessages['accountConfiguredNoSsl'] ) ); $status = false; } return $status; } /** * This function will help to set the latest Stripe API version. * * @since 2.5.0 * @access public * * @return void */ public function set_api_version() { // Set Application Info. give_stripe_set_app_info(); try { // Set API Version to latest. \Stripe\Stripe::setApiVersion( $this->api_version ); } catch ( \Stripe\Error\Base $e ) { // Log Error. $this->log_error( $e ); } catch ( Exception $e ) { // Something went wrong outside of Stripe. give_record_gateway_error( __( 'Stripe Error', 'give' ), sprintf( /* translators: %s Exception Message Body */ __( 'Unable to set Stripe API Version. Details: %s', 'give' ), $e->getMessage() ) ); give_set_error( 'stripe_error', __( 'An error occurred while processing the donation. Please try again.', 'give' ) ); // Send donor back to checkout page on error. $this->send_back_to_checkout(); } } /** * Send back to checkout based on the gateway id. * * @since 2.5.0 * @access public * * @return void */ public function send_back_to_checkout() { give_send_back_to_checkout( '?payment-mode=' . $this->id ); } /** * This function will be used to fetch token details from token id. * * @param string $id Stripe Token ID. * @param array $args Additional arguments. * * @since 2.5.0 * @access public * * @return \Stripe\Token */ public function get_token_details( $id, $args = [] ) { // Set Application Info. give_stripe_set_app_info(); try { $args = wp_parse_args( $args, give_stripe_get_connected_account_options() ); // Retrieve Token Object. return \Stripe\Token::retrieve( $id, $args ); } catch ( \Stripe\Error\Base $e ) { $this->log_error( $e ); } catch ( Exception $e ) { // Something went wrong outside of Stripe. give_record_gateway_error( __( 'Stripe Token Error', 'give' ), sprintf( /* translators: %s Exception Message Body */ __( 'Unable to retrieve token. Details: %s', 'give' ), $e->getMessage() ) ); give_set_error( 'stripe_error', __( 'An error occurred while processing the donation. Please try again.', 'give' ) ); // Send donor back to checkout page on error. $this->send_back_to_checkout(); } } /** * This function will be used to fetch source details from source id. * * @param string $id Stripe Source ID. * * @since 2.5.0 * @access public * * @return \Stripe\Source */ public function get_source_details( $id ) { // Set Application Info. give_stripe_set_app_info(); try { // Retrieve Source Object. return \Stripe\Source::retrieve( $id, give_stripe_get_connected_account_options() ); } catch ( \Stripe\Error\Base $e ) { $this->log_error( $e ); } catch ( Exception $e ) { // Something went wrong outside of Stripe. give_record_gateway_error( __( 'Stripe Source Error', 'give' ), sprintf( /* translators: %s Exception Message Body */ __( 'Unable to retrieve source. Details: %s', 'give' ), $e->getMessage() ) ); give_set_error( 'stripe_error', __( 'An error occurred while processing the donation. Please try again.', 'give' ) ); // Send donor back to checkout page on error. $this->send_back_to_checkout(); } } /** * This function will prepare source based on the parameters provided. * * @param array $args List of arguments \Stripe\Source::create() supports. * * @since 2.5.0 * @access public * * @return \Stripe\Source */ public function prepare_source( $args ) { // Set Application Info. give_stripe_set_app_info(); try { // Create Source Object. return \Stripe\Source::create( $args, give_stripe_get_connected_account_options() ); } catch ( \Stripe\Error\Base $e ) { $this->log_error( $e ); } catch ( Exception $e ) { // Something went wrong outside of Stripe. give_record_gateway_error( __( 'Stripe Error', 'give' ), sprintf( /* translators: %s Exception Message Body */ __( 'Unable to create source. Details: %s', 'give' ), $e->getMessage() ) ); give_set_error( 'stripe_error', __( 'An error occurred while processing the donation. Please try again.', 'give' ) ); // Send donor back to checkout page on error. $this->send_back_to_checkout(); } } /** * Get Customer's card. * * @param \Stripe\Customer $stripe_customer Stripe Customer Object. * @param string $id Source or Token ID. * * @since 2.5.0 * * @return \Stripe\Source|bool */ public function get_customer_card( $stripe_customer, $id ) { $card_exists = false; $all_sources = $stripe_customer->sources->all(); $card = $this->get_source_details( $id ); $source_list = wp_list_pluck( $all_sources->data, 'id' ); // Check whether the source is already attached to customer or not. if ( in_array( $id, $source_list, true ) ) { $card_exists = true; } // Create the card if none found above. if ( ! $card_exists ) { try { // Attach Source to existing Customer. $card = $stripe_customer->sources->create( [ 'source' => $id, ] ); } catch ( \Stripe\Error\Base $e ) { // Log Error. $this->log_error( $e ); } catch ( Exception $e ) { give_record_gateway_error( __( 'Stripe Card Error', 'give' ), sprintf( /* translators: %s Exception Error Message */ __( 'The Stripe Gateway returned an error while processing a donation. Details: %s', 'give' ), $e->getMessage() ) ); // Send donor back to checkout page on error. $this->send_back_to_checkout(); } } // Return Card Details, if exists. if ( ! empty( $card->id ) ) { return $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' ) ); // Send donor back to checkout page on error. $this->send_back_to_checkout(); return false; } } /** * Save Stripe Customer ID. * * @param string $stripe_customer_id Customer ID. * @param int $payment_id Payment ID. * * @since 2.5.0 */ public function save_stripe_customer_id( $stripe_customer_id, $payment_id ) { // Update customer meta. if ( class_exists( 'Give_DB_Donor_Meta' ) ) { $donor_id = give_get_payment_donor_id( $payment_id ); // Get the Give donor. $donor = new Give_Donor( $donor_id ); // Update donor meta. $donor->update_meta( give_stripe_get_customer_key(), $stripe_customer_id ); } elseif ( is_user_logged_in() ) { // Support saving to legacy method of user method. update_user_meta( get_current_user_id(), give_stripe_get_customer_key(), $stripe_customer_id ); } } /** * Log a Stripe Error. * * Logs in the Give db the error and also displays the error message to the donor. * * @param \Stripe\Error\Base|\Stripe\Error\Card $exception Exception. * * @since 2.5.0 * * @return bool */ public function log_error( $exception ) { $log_message = __( 'The Stripe payment gateway returned an error while processing the donation.', 'give' ) . '<br><br>'; $exception_message = $exception->getMessage(); // Bad Request of some sort. if ( ! empty( $exception_message ) ) { $log_message .= sprintf( /* translators: %s Exception Message */ __( 'Message: %s', 'give' ), $exception_message ) . '<br><br>'; $trace_string = $exception->getTraceAsString(); if ( ! empty( $trace_string ) ) { $log_message .= sprintf( /* translators: %s Trace String */ __( 'Code: %s', 'give' ), $trace_string ); } give_set_error( 'stripe_request_error', $exception_message ); } else { give_set_error( 'stripe_request_error', __( 'The Stripe API request was invalid, please try again.', 'give' ) ); } // Log it with DB. give_record_gateway_error( __( 'Stripe Error', 'give' ), $log_message ); // Send donor back to checkout page on error. $this->send_back_to_checkout(); return false; } /** * Format currency for Stripe. * * @see https://support.stripe.com/questions/which-zero-decimal-currencies-does-stripe-support * * @param float $amount Donation amount. * * @return mixed */ public function format_amount( $amount ) { return Money::of( $amount, give_get_currency() )->getMinorAmount(); } /** * Verify Payment. * * @param int $payment_id Payment ID. * @param string $stripe_customer_id Customer ID. * @param \Stripe\Charge $charge Stripe Charge Object. */ public function verify_payment( $payment_id, $stripe_customer_id, $charge ) { // Sanity checks: verify all vars exist. if ( $payment_id && ( ! empty( $stripe_customer_id ) || ! empty( $charge ) ) ) { /** * This action hook is used to perform some additional steps to verify the payment. * * @param int $payment_id Payment ID. * @param string $stripe_customer_id Customer ID. * @param \Stripe\Charge $charge Stripe Charge Object. * * @since 2.5.0 */ do_action( 'give_stripe_verify_payment', $payment_id, $stripe_customer_id, $charge ); // @TODO use Stripe's API here to retrieve the invoice then confirm it has been paid. // Regular payment, publish it. give_update_payment_status( $payment_id, 'publish' ); // Save Stripe customer id. $this->save_stripe_customer_id( $stripe_customer_id, $payment_id ); // Send them to success page. give_send_to_success_page(); } else { give_set_error( 'payment_not_recorded', __( 'Your donation could not be recorded, please contact the site administrator.', 'give' ) ); // If errors are present, send the user back to the purchase page so they can be corrected. $this->send_back_to_checkout(); } // End if(). } /** * This function will prepare metadata to send to Stripe. * * @param int $donation_id Donation ID. * @param array $donation_data Donation Data. * * @since 2.5.0 * @access public * * @return array */ public function prepare_metadata( $donation_id, $donation_data = [] ) { return give_stripe_prepare_metadata( $donation_id, $donation_data ); } /** * This function will help to charge with Stripe. * * @param int $donation_id Donation ID with pending status. * @param array $charge_args List of charge arguments. * * @since 2.5.0 * @access public * * @return \Stripe\Charge */ public function create_charge( $donation_id, $charge_args ) { // Set App Info to Stripe. give_stripe_set_app_info(); try { $charge_args = apply_filters( "give_{$this->id}_create_charge_args", $charge_args ); // Charge application fee, only if the Stripe premium add-on is not active. if ( ApplicationFee::canAddfee() ) { // Set Application Fee Amount. $charge_args['application_fee_amount'] = give_stripe_get_application_fee_amount( $charge_args['amount'] ); } $charge = \Stripe\Charge::create( $charge_args, give_stripe_get_connected_account_options() ); // Add note for the charge. // Save Stripe's charge ID to the transaction. if ( ! empty( $charge ) ) { give_insert_payment_note( $donation_id, 'Stripe Charge ID: ' . $charge->id ); give_set_payment_transaction_id( $donation_id, $charge->id ); } return $charge; } catch ( \Stripe\Error\Base $e ) { Give_Stripe_Logger::log_error( $e, $this->id ); } catch ( Exception $e ) { give_record_gateway_error( __( 'Stripe Charge Error', 'give' ), sprintf( /* translators: %s Exception Error Message */ __( 'Unable to create a successful charge. Details: %s', 'give' ), $e ) ); give_set_error( 'stripe_charge_error', __( 'Error processing donation with Stripe. Please try again.', 'give' ) ); return false; } // End try(). } /** * Create Source for Stripe 3D Secure Payments. * * @param int $donation_id Donation ID. * @param int $source_id Source ID/Object. * * @since 1.6 * @access public * * @return bool|\Stripe\Source */ public function create_3d_secure_source( $donation_id, $source_id ) { $form_id = give_get_payment_form_id( $donation_id ); $customer_id = give_get_payment_meta( $donation_id, '_give_stripe_customer_id', true ); $donation_amount = give_donation_amount( $donation_id ); // Prepare basic source args. $source_args = [ 'amount' => $this->format_amount( $donation_amount ), 'currency' => give_get_currency( $form_id ), 'type' => 'three_d_secure', 'three_d_secure' => [ 'card' => $source_id, ], 'statement_descriptor' => give_stripe_get_statement_descriptor(), 'redirect' => [ 'return_url' => esc_url_raw( add_query_arg( [ 'give-listener' => 'stripe_three_d_secure', 'donation_id' => $donation_id, ], give_get_success_page_uri() ) ), ], ]; $source = $this->prepare_source( $source_args ); // Add donation note for 3D secure source ID. if ( ! empty( $source->id ) ) { give_insert_payment_note( $donation_id, 'Stripe 3D Secure Source ID: ' . $source->id ); } // Save 3D secure source id to donation. give_update_payment_meta( $donation_id, '_give_stripe_3dsecure_source_id', $source->id ); return $source; } /** * Is 3D secure payment required? * * @param \Stripe\Source $source_object Stripe Source Object. * * @since 1.6 * @access public * * @return bool */ public function is_3d_secure_required( $source_object ) { $is_3d_secure_enabled = give_is_setting_enabled( give_get_option( 'stripe_enable_three_d_secure_payments', '' ) ); if ( $is_3d_secure_enabled ) { return apply_filters( 'give_stripe_3d_secure_required', ( ! empty( $source_object->type ) && 'card' === $source_object->type && 'required' === $source_object->card->three_d_secure ), $source_object ); } return false; } /** * Process One Time Charge. * * @param array $donation_data List of donation data. * @param string $stripe_customer_id Customer ID. * * @return bool|\Stripe\Charge */ public function process_charge( $donation_data, $stripe_customer_id ) { $form_id = ! empty( $donation_data['post_data']['give-form-id'] ) ? intval( $donation_data['post_data']['give-form-id'] ) : 0; $donation_id = ! empty( $donation_data['donation_id'] ) ? intval( $donation_data['donation_id'] ) : 0; $source_id = ! empty( $donation_data['source_id'] ) ? $donation_data['source_id'] : 0; $description = ! empty( $donation_data['description'] ) ? $donation_data['description'] : false; // Process the charge. $amount = $this->format_amount( $donation_data['price'] ); $charge_args = [ 'amount' => $amount, 'currency' => give_get_currency( $form_id ), 'customer' => $stripe_customer_id, 'description' => html_entity_decode( $description, ENT_COMPAT, 'UTF-8' ), 'statement_descriptor' => give_stripe_get_statement_descriptor( $donation_data ), 'metadata' => $this->prepare_metadata( $donation_id ), 'source' => $source_id, ]; // Create charge with general gateway fn. $charge = $this->create_charge( $donation_id, $charge_args ); // Return charge if set. if ( isset( $charge ) ) { return $charge; } else { return false; } } } }