Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Bower components Debian packages RPM packages NuGet packages

jsarnowski / jsarnowski/cartflows-pro   php

Repository URL to install this package:

Version: 1.6.10 

/ gateways / class-cartflows-pro-gateway-stripe.php

<?php
/**
 * Stripe Gateway.
 *
 * @package cartflows
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

/**
 * Class Cartflows_Pro_Gateway_Stripe.
 */
class Cartflows_Pro_Gateway_Stripe {

	/**
	 * Member Variable
	 *
	 * @var instance
	 */
	private static $instance;

	/**
	 * Key name variable
	 *
	 * @var key
	 */
	public $key = 'stripe';

	/**
	 * Refund supported variable
	 *
	 * @var is_api_refund
	 */
	public $is_api_refund = true;


	/**
	 *  Initiator
	 */
	public static function get_instance() {
		if ( ! isset( self::$instance ) ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Constructor
	 */
	public function __construct() {

		add_filter( 'wc_stripe_force_save_source', array( $this, 'tokenize_if_required' ) );

		add_filter( 'wc_stripe_3ds_source', array( $this, 'save_3ds_source_for_later' ), 10, 2 );

		add_action( 'wc_gateway_stripe_process_response', array( $this, 'redirect_using_wc_function' ), 10, 2 );

		add_action( 'wp_ajax_wcf_stripe_sca_check', array( $this, 'check_stripe_sca' ) );
		add_action( 'wp_ajax_nopriv_wcf_stripe_sca_check', array( $this, 'check_stripe_sca' ) );

		add_action( 'cartflows_offer_subscription_created', array( $this, 'add_subscription_payment_meta' ), 10, 3 );

		add_action( 'cartflows_offer_before_main_order_cancel', array( $this, 'remove_the_stripe_refund_action' ), 10, 1 );

	}

	/**
	 * Verify 3DS and create intent accordingly.
	 *
	 * @return bool
	 */
	public function check_stripe_sca() {

		$nonce = filter_input( INPUT_POST, '_nonce', FILTER_SANITIZE_STRING );
		if ( ! wp_verify_nonce( $nonce, 'wcf_stripe_sca_check' ) ) {
			return;
		}

		$variation_id = '';
		$input_qty    = '';

		if ( isset( $_POST['variation_id'] ) ) {
			$variation_id = intval( $_POST['variation_id'] );
		}

		if ( isset( $_POST['input_qty'] ) && ! empty( $_POST['input_qty'] ) ) {
			$input_qty = intval( $_POST['input_qty'] );
		}

		$step_id       = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0;
		$order_id      = isset( $_POST['order_id'] ) ? sanitize_text_field( wp_unslash( $_POST['order_id'] ) ) : 0;
		$offer_type    = isset( $_POST['offer_type'] ) ? sanitize_text_field( wp_unslash( $_POST['offer_type'] ) ) : '';
		$offer_action  = isset( $_POST['offer_action'] ) ? sanitize_text_field( wp_unslash( $_POST['offer_action'] ) ) : '';
		$order         = wc_get_order( $order_id );
		$offer_product = wcf_pro()->utils->get_offer_data( $step_id, $variation_id, $input_qty, $order_id );

		if ( isset( $offer_product['price'] ) && ( floatval( 0 ) === floatval( $offer_product['price'] )
				|| '' === trim( $offer_product['price'] ) ) ) {
			wp_send_json(
				array(
					'result'  => 'fail',
					'message' => '0 value product',
				)
			);
		} else {

			$gateway = $this->get_wc_gateway();

			if ( $gateway ) {

				$order_source = $gateway->prepare_order_source( $order );
				$is_3ds       = isset( $order_source->source_object->card->three_d_secure ) ? $order_source->source_object->card->three_d_secure : false;
				// Check if 3DS required.
				if ( isset( $is_3ds ) && 'optional' !== $is_3ds && 'yes' === $offer_action ) {

					$intent = $this->create_intent( $order, $order_source, $offer_product );

					$main_settings = get_option( 'woocommerce_stripe_settings' );
					$testmode      = ( ! empty( $main_settings['testmode'] ) && 'yes' === $main_settings['testmode'] ) ? true : false;

					if ( $testmode ) {
						$publishable_key = ! empty( $main_settings['test_publishable_key'] ) ? $main_settings['test_publishable_key'] : '';
					} else {
						$publishable_key = ! empty( $main_settings['publishable_key'] ) ? $main_settings['publishable_key'] : '';
					}

					wp_send_json(
						array(
							'result'        => 'success',
							'redirect'      => $gateway->get_return_url( $order ),
							'intent_secret' => $intent->client_secret,
							'stripe_pk'     => $publishable_key,
						)
					);
				}
			}

			wp_send_json(
				array(
					'result'  => 'fail',
					'message' => 'No 3ds payment',
				)
			);
		}
	}

	/**
	 * Tokenize to save source of payment if required
	 *
	 * @param bool $save_source force save source.
	 */
	public function tokenize_if_required( $save_source ) {

		wcf()->logger->log( 'Started: ' . __CLASS__ . '::' . __FUNCTION__ );

		$checkout_id = wcf()->utils->get_checkout_id_from_post_data();
		$flow_id     = wcf()->utils->get_flow_id_from_post_data();

		if ( $checkout_id && $flow_id ) {

			$wcf_step_obj      = wcf_pro_get_step( $checkout_id );
			$next_step_id      = $wcf_step_obj->get_next_step_id();
			$wcf_next_step_obj = wcf_pro_get_step( $next_step_id );

			if ( $next_step_id && $wcf_next_step_obj->is_offer_page() ) {

				$save_source = true;
				wcf()->logger->log( 'Force save source enabled' );
			}
		}

		return $save_source;
	}

	/**
	 * Save 3d source.
	 *
	 * @param array $post_data Threads data.
	 * @param array $order order data.
	 */
	public function save_3ds_source_for_later( $post_data, $order ) {

		if ( $order && wcf_pro()->flow->is_upsell_exists( $order ) ) {

			$order->update_meta_data( '_cartflows_stripe_source_id', $post_data['three_d_secure']['card'] );

			$order->save();

			wcf()->logger->log( '3ds source saved for later use' );
		}

		return $post_data;
	}

	/**
	 * Redirection to order received URL.
	 *
	 * @param array $response response data.
	 * @param array $order order data.
	 */
	public function redirect_using_wc_function( $response, $order ) {

		wcf()->logger->log( 'Started: ' . __CLASS__ . '::' . __FUNCTION__ );

		if ( 1 === did_action( 'cartflows_order_started' ) && 1 === did_action( 'wc_gateway_stripe_process_redirect_payment' ) ) {

			$get_url = $order->get_checkout_order_received_url();
			wp_safe_redirect( $get_url );
			exit();
		}
	}

	/**
	 * Check if token is present.
	 *
	 * @param array $order order data.
	 */
	public function has_token( $order ) {

		$order_id = $order->get_id();

		$token = get_post_meta( $order_id, '_cartflows_stripe_source_id', true );

		if ( empty( $token ) ) {
			$token = get_post_meta( $order_id, '_stripe_source_id', true );
		}

		if ( ! empty( $token ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Get WooCommerce payment geteways.
	 *
	 * @return array
	 */
	public function get_wc_gateway() {

		global $woocommerce;

		$gateways = $woocommerce->payment_gateways->payment_gateways();

		return $gateways[ $this->key ];
	}

	/**
	 * After payment process.
	 *
	 * @param array $order order data.
	 * @param array $product product data.
	 * @return array
	 */
	public function process_offer_payment( $order, $product ) {

		$is_successful = false;

		if ( ! $this->has_token( $order ) ) {

			return $is_successful;
		}

		try {

			$gateway = $this->get_wc_gateway();

			$order_source = $gateway->prepare_order_source( $order );

			$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $order_source, $product ) );

			if ( ! is_wp_error( $response ) ) {

				if ( ! empty( $response->error ) ) {

					wcf()->logger->log( '\n' . '==== Product Start ==== \n' . print_r( $product, true ) . '==== Product End ==== \n ==== Error Response start ==== \n' . print_r( $response, true ) . '==== Error Response end ====\n' ); //phpcs:ignore

					$is_successful = false;
				} else {

					/** '_transaction_id', $response->id */
					$is_successful = true;

					$this->update_stripe_payout_details( $order, $response );
					$this->store_offer_transaction( $order, $response, $product );
				}
			}

			// @todo Show actual error if any.
		} catch ( Exception $e ) { //phpcs:ignore

			// @todo Exception catch to show actual error.
		}

		return $is_successful;
	}

	/**
	 * Store Offer Trxn Charge.
	 *
	 * @param WC_Order $order    The order that is being paid for.
	 * @param Object   $response The response that is send from the payment gateway.
	 * @param array    $product  The product data.
	 */
	public function store_offer_transaction( $order, $response, $product ) {

		$order->update_meta_data( 'cartflows_offer_txn_resp_' . $product['step_id'], $response->id );
		$order->update_meta_data( '_cartflows_offer_txn_stripe_source_id_' . $product['step_id'], $response->payment_method );
		$order->update_meta_data( '_cartflows_offer_txn_stripe_customer_id_' . $product['step_id'], $response->customer );
		$order->save();
	}

	/**
	 * Update Stripe Payout for the Offers.
	 *
	 * @param WC_Order $order           The order that is being paid for.
	 * @param object   $response        The response that is send from the payment gateway.
	 */
	public function update_stripe_payout_details( $order, $response ) {

		wcf()->logger->log( 'Started: ' . __CLASS__ . '::' . __FUNCTION__ );

		$fee = ! empty( $response->balance_transaction->fee ) ? WC_Stripe_Helper::format_balance_fee( $response->balance_transaction, 'fee' ) : 0;
		$net = ! empty( $response->balance_transaction->net ) ? WC_Stripe_Helper::format_balance_fee( $response->balance_transaction, 'net' ) : 0;

		$fee = $fee + WC_Stripe_Helper::get_stripe_fee( $order );
		$net = $net + WC_Stripe_Helper::get_stripe_net( $order );

		WC_Stripe_Helper::update_stripe_fee( $order, $fee );
		WC_Stripe_Helper::update_stripe_net( $order, $net );

		wcf()->logger->log( 'Stripe Payout updated.' );

		wcf()->logger->log( 'Ended: ' . __CLASS__ . '::' . __FUNCTION__ );
	}

	/**
	 * Create a new PaymentIntent.
	 *
	 * @param WC_Order $order           The order that is being paid for.
	 * @param object   $prepared_source The source that is used for the payment.
	 * @param object   $product offer product.
	 * @return object                   An intent or an error.
	 */
	public function create_intent( $order, $prepared_source, $product ) {
		// The request for a charge contains metadata for the intent.
		$full_request = $this->generate_payment_request( $order, $prepared_source, $product );

		$request = array(
			'source'               => $prepared_source->source,
			'amount'               => WC_Stripe_Helper::get_stripe_amount( $product['price'] ),
			'currency'             => strtolower( $order->get_currency() ),
			'description'          => $full_request['description'],
			'metadata'             => $full_request['metadata'],
			'statement_descriptor' => WC_Stripe_Helper::clean_statement_descriptor( $full_request['statement_descriptor'] ),
			'capture_method'       => ( 'true' === $full_request['capture'] ) ? 'automatic' : 'manual',
			'payment_method_types' => array(
				'card',
			),
			'customer'             => $prepared_source->customer,
		);

		if ( $prepared_source->customer ) {
			$request['customer'] = $prepared_source->customer;
		}
		// Create an intent that awaits an action.
		$intent = WC_Stripe_API::request( $request, 'payment_intents' );

		if ( ! empty( $intent->error ) ) {
			return $intent;
		}

		$order_id = $order->get_id();

		$step_id = filter_input( INPUT_POST, 'step_id', FILTER_VALIDATE_INT );
		// Save the intent ID to the order.
		update_post_meta( $order_id, '_stripe_intent_id_' . $step_id, $intent->id );

		return $intent;
	}

	/**
	 * Generate payment request.
	 *
	 * @param array  $order order data.
	 * @param string $order_source order source.
	 * @param array  $product product data.
	 * @return array
	 */
	protected function generate_payment_request( $order, $order_source, $product ) {

		$gateway               = $this->get_wc_gateway();
		$post_data             = array();
		$post_data['currency'] = strtolower( $order ? $order->get_currency() : get_woocommerce_currency() );
		$post_data['amount']   = WC_Stripe_Helper::get_stripe_amount( $product['price'], $post_data['currency'] );
		/* translators: %1s site name */
		$post_data['description'] = sprintf( __( '%1$s - Order %2$s - One Time offer', 'cartflows-pro' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), $order->get_order_number() );

		/* translators: %1s order number */
		$post_data['statement_descriptor'] = sprintf( __( 'Order %1$s-OTO', 'cartflows-pro' ), $order->get_order_number() );

		$post_data['capture'] = $gateway->capture ? 'true' : 'false';
		$billing_first_name   = $order->get_billing_first_name();
		$billing_last_name    = $order->get_billing_last_name();
		$billing_email        = $order->get_billing_email();

		if ( ! empty( $billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
			$post_data['receipt_email'] = $billing_email;
		}

		$metadata = array(
			__( 'customer_name', 'cartflows-pro' )  => sanitize_text_field( $billing_first_name ) . ' ' . sanitize_text_field( $billing_last_name ),
			__( 'customer_email', 'cartflows-pro' ) => sanitize_email( $billing_email ),
			'order_id'                              => $order->get_order_number() . '_' . $product['id'] . '_' . $product['step_id'],
		);

		$post_data['expand[]'] = 'balance_transaction';
		$post_data['metadata'] = apply_filters( 'wc_stripe_payment_metadata', $metadata, $order, $order_source );

		if ( $order_source->customer ) {
			$post_data['customer'] = $order_source->customer;
		}

		if ( $order_source->source ) {

			$source_3ds = $order->get_meta( '_cartflows_stripe_source_id', true );

			$post_data['source'] = ( '' !== $source_3ds ) ? $source_3ds : $order_source->source;
		}

		return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order, $order_source );
	}


	/**
	 * Process offer refund
	 *
	 * @param object $order Order Object.
	 * @param array  $offer_data offer data.
	 *
	 * @return string/bool.
	 */
	public function process_offer_refund( $order, $offer_data ) {

		$transaction_id = $offer_data['transaction_id'];
		$refund_amount  = $offer_data['refund_amount'];

		$order_currency = $order->get_currency( $order );

		$request     = array();
		$response_id = false;

		if ( ! is_null( $refund_amount ) && class_exists( 'WC_Stripe_Helper' ) && class_exists( 'WC_Stripe_API' ) ) {

			$request['amount'] = WC_Stripe_Helper::get_stripe_amount( $refund_amount, $order_currency );
			$request['charge'] = $transaction_id;
			$response          = WC_Stripe_API::request( $request, 'refunds' );

			if ( ! empty( $response->error ) || ! $response ) {
				$response_id = false;
			} else {
				/**
				 * Update sripe transaction amounts
				 */
				$this->get_wc_gateway()->update_fees( $order, $response->balance_transaction );

				$response_id = isset( $response->id ) ? $response->id : true;
			}
		}

		return $response_id;
	}

	/**
	 * Allow gateways to declare whether they support offer refund
	 *
	 * @return bool
	 */
	public function is_api_refund() {

		return $this->is_api_refund;
	}

	/**
	 * Setup the Payment data for Stripe's Automatic Subscription.
	 *
	 * @param WC_Subscription $subscription An instance of a subscription object.
	 * @param object          $order Object of order.
	 * @param array           $offer_product array of offer product.
	 */
	public function add_subscription_payment_meta( $subscription, $order, $offer_product ) {

		if ( 'stripe' === $order->get_payment_method() ) {

			$subscription_id = $subscription->get_id();

			update_post_meta( $subscription_id, '_stripe_source_id', $order->get_meta( '_stripe_source_id', true ) );
			update_post_meta( $subscription_id, '_stripe_customer_id', $order->get_meta( '_stripe_customer_id', true ) );
		}
	}

	/**
	 * Setup the Payment data for Stripe's Automatic Subscription.
	 *
	 * @param object $parent_order Object of order.
	 */
	public function remove_the_stripe_refund_action( $parent_order ) {

		// Stripe refund the order if status is cancelled hence need to avoid it by removing below action.
		if ( 'stripe' === $parent_order->get_payment_method() ) {
			remove_action( 'woocommerce_order_status_cancelled', array( WC_Stripe_Order_Handler::get_instance(), 'cancel_payment' ) );
		}
	}
}

/**
 *  Prepare if class 'Cartflows_Pro_Gateway_Stripe' exist.
 *  Kicking this off by calling 'get_instance()' method
 */
Cartflows_Pro_Gateway_Stripe::get_instance();