JezK
Edit File: class-cartflows-pro-gateway-woocommerce-payments.php
<?php /** * Stripe Gateway. * * @package cartflows */ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } use WCPay\Payment_Information; use WCPay\Constants\Payment_Type; use WCPay\Constants\Payment_Initiated_By; use WCPay\Core\Server\Request\Refund_Charge; /** * Class Cartflows_Pro_Gateway_Woocommerce_Payments. */ class Cartflows_Pro_Gateway_Woocommerce_Payments { /** * Member Variable * * @var instance */ private static $instance; /** * Key name variable * * @var key */ public $key = 'woocommerce_payments'; /** * Refund supported variable * * @var is_api_refund */ public $is_api_refund = true; /** * Refund supported variable * * @var gateway_service_objects */ public $gateway_service_objects = array(); /** * Initiator */ public static function get_instance() { if ( ! isset( self::$instance ) ) { self::$instance = new self(); } return self::$instance; } /** * Constructor */ public function __construct() { add_filter( 'wc_payments_display_save_payment_method_checkbox', array( $this, 'tokenize_if_required' ), 11, 1 ); add_action( 'cartflows_order_started', array( $this, 'save_3ds_source_for_later' ), 10, 1 ); add_action( 'woocommerce_checkout_after_customer_details', array( $this, 'add_payment_gateway_option_hidden_field' ), 99 ); add_action( 'wp_ajax_wcf_woop_create_payment_intent', array( $this, 'create_payment_intent' ) ); add_action( 'wp_ajax_nopriv_wcf_woop_create_payment_intent', array( $this, 'create_payment_intent' ) ); add_action( 'cartflows_offer_child_order_created_' . $this->key, array( $this, 'add_required_meta_to_child_order' ), 10, 3 ); add_action( 'cartflows_offer_subscription_created', array( $this, 'add_subscription_payment_meta' ), 10, 3 ); $this->prepare_gateway_objects(); } /** * Prepare the array of all the required classes of payment gateway to use it. * * @return void */ public function prepare_gateway_objects() { $api_client = ''; $api_account_service = ''; $database_cache = ''; $customer_services = ''; $token_service = ''; $api_session_service = ''; if ( class_exists( 'WC_Payments' ) && class_exists( 'WC_Payments_Customer_Service' ) && class_exists( 'WC_Payments_Token_Service' ) ) { $api_client = WC_Payments::get_payments_api_client(); $api_account_service = WC_Payments::get_account_service(); $database_cache = class_exists( 'WCPay\Database_Cache' ) ? WC_Payments::get_database_cache() : null; /** * In the latest version of WooCommerce Payments, they have introduced a new parameter to the WC_Payments_Customer_Service class, * So, adding a backward compatibility for the older version which is less than 6.7.0. */ if ( version_compare( '6.7.1', WCPAY_VERSION_NUMBER, '<=' ) ) { // Execute it for the WCPAY version is greater than 6.7.0. $customer_services = WC_Payments::get_customer_service(); // Fallback if in case the customer service is empty. if ( empty( $customer_services ) ) { $api_session_service = WC_Payments::get_session_service(); $customer_services = new WC_Payments_Customer_Service( $api_client, $api_account_service, $database_cache, $api_session_service ); } } else { // Execute it for the WCPAY version is less than 6.7.0. $customer_services = new WC_Payments_Customer_Service( $api_client, $api_account_service, $database_cache ); } $token_service = new WC_Payments_Token_Service( $api_client, $customer_services ); } $this->gateway_service_objects = array( 'api_client' => $api_client ? $api_client : '', 'api_account_service' => $api_account_service ? $api_account_service : '', 'database_cache' => $database_cache ? $database_cache : '', 'customer_services' => $customer_services ? $customer_services : '', 'token_service' => $token_service ? $token_service : '', ); } /** * Create intent for the offer order. */ public function create_payment_intent() { wcf()->logger->log( 'Started: ' . __CLASS__ . '::' . __FUNCTION__ ); $nonce = isset( $_POST['wcf_wc_payment_nonce'] ) && ! empty( $_POST['wcf_wc_payment_nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['wcf_wc_payment_nonce'] ) ) : ''; if ( ! wp_verify_nonce( $nonce, 'wcf_woop_create_payment_intent' ) ) { $response_data = array( 'message' => __( 'Nonce validation failed', 'cartflows-pro' ) ); wp_send_json_error( $response_data ); } $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; $order_key = isset( $_POST['order_key'] ) ? sanitize_text_field( wp_unslash( $_POST['order_key'] ) ) : ''; $order = wc_get_order( $order_id ); // Verify order ownership via order_key. if ( ! $order instanceof WC_Order || ! $order->key_is_valid( $order_key ) ) { wp_send_json_error( array( 'message' => __( 'Invalid order', 'cartflows-pro' ) ) ); return; } $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' => 'Zero value product', ) ); } else { $gateway = $this->get_wc_gateway(); if ( $gateway ) { wcf()->logger->log( 'Gateway object found : ' . wp_json_encode( $gateway ) ); try { $token = $this->retrieve_token_from_order( $order ); wcf()->logger->log( 'payment_token from order : ' . PHP_EOL . wp_json_encode( $token ) ); $payment_information = new Payment_Information( '', $order, Payment_Type::SINGLE(), $token, Payment_Initiated_By::CUSTOMER(), null, null, '', 'card' ); $response = $this->process_payment_against_intent( $order, $offer_product, $payment_information ); } catch ( Exception $e ) { wcf()->logger->log( '==== Gateway Response start ====' . PHP_EOL . wcf_print_r( $response, true ) . PHP_EOL . '==== Error Gateway response end ====' ); wp_send_json( array( 'result' => 'fail', 'message' => 'Payment Failed', ) ); } // Process the response for the order. if ( ! empty( $response ) ) { $intent_id = $response->get_id() ? $response->get_id() : 0; $status = $response->get_status(); $charge = $response->get_charge(); $charge_id = ! empty( $charge ) ? $charge->get_id() : null; $client_secret = $response->get_client_secret(); wcf()->logger->log( 'Intent i:e Txn response : ' . wp_json_encode( $response ) ); wcf()->logger->log( 'Charge ID : ' . $charge_id ); $extra_data = array( 'step_id' => $step_id, 'order_id' => $order_id, 'intent_id' => $intent_id, 'charge_id' => $charge_id, ); // Save the intent ID to the order. $this->store_order_payment_meta( $order, $payment_information, $extra_data ); wcf()->logger->log( 'Newly generated Intent ID : ' . $intent_id ); wp_send_json( array( 'result' => 'success', 'status' => $status, 'client_secret' => $client_secret, 'client_intend' => $intent_id, ) ); } wcf()->logger->log( 'Final APi response : ' . wp_json_encode( $response ) ); } wp_send_json( array( 'result' => 'fail', 'message' => __( 'No payment. No gateway found', 'cartflows-pro' ), ) ); } wcf()->logger->log( 'Ended: ' . __CLASS__ . '::' . __FUNCTION__ ); } /** * Store the payment meta in the parent order to use it in the child order. * * @param object $order Parent object. * @param object $payment_information Object of payment information with all data. * @param array $extra_data Any extra data that may require. */ public function store_order_payment_meta( $order, $payment_information, $extra_data ) { $payment_method = $payment_information->get_payment_method(); $order_id = $order->get_id(); $step_id = $extra_data['step_id']; $order->update_meta_data( '_wcf_woop_offer_stripe_intent_id_' . $step_id, $extra_data['intent_id'] ); $order->update_meta_data( '_wcf_woop_offer_charge_id_' . $step_id . '_' . $order_id, $extra_data['charge_id'] ); $order->update_meta_data( '_wcf_woop_offer_payment_method_id', $payment_method ); $order->update_meta_data( '_wcf_woop_offer_wcpay_mode', WC_Payments::mode()->is_test() ? 'test' : 'prod' ); $order->save(); } /** * Retrieve the token from the main order. * * @param object $order Object of current order. * @param bool $has_token Separator used to log the logs. * * @return WC_Payment_Tokens|mix $token The retrieved order token on success & false otherwise. */ public function retrieve_token_from_order( $order, $has_token = false ) { wcf()->logger->log( 'Started: ' . __CLASS__ . '::' . __FUNCTION__ ); if ( $has_token ) { wcf()->logger->log( 'Checking for token in the order to confirm the order' ); } $order_tokens = $order->get_payment_tokens(); $token_id = end( $order_tokens ); $token = $token_id ? WC_Payment_Tokens::get( $token_id ) : null; wcf()->logger->log( 'Tokens found: ' . wp_json_encode( $token ) ); wcf()->logger->log( 'Started: ' . __CLASS__ . '::' . __FUNCTION__ ); return $token; } /** * Tokenize to save source of payment if required * * @param bool $save_source force save source. * @return bool $save_source modified save value. */ 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_pro()->flow->is_upsell_exist_in_flow( $flow_id, $checkout_id ) ) { $save_source = false; wcf()->logger->log( 'Force save source enabled' ); } wcf()->logger->log( 'Ended: ' . __CLASS__ . '::' . __FUNCTION__ ); return $save_source; } /** * Save 3d source. * * @param int $order_id current order ID. * @return void */ public function save_3ds_source_for_later( $order_id ) { wcf()->logger->log( 'Started: ' . __CLASS__ . '::' . __FUNCTION__ ); $order = wc_get_order( $order_id ); if ( $order && $this->key === $order->get_payment_method() && wcf_pro()->flow->check_if_next_step_is_offer( $order ) ) { try { $payment_method_id = $order->get_meta( '_payment_method_id' ); wcf()->logger->log( '_payment_method_id : ' . $payment_method_id ); // Save the payment method (token) to the user's meta. $token = $this->gateway_service_objects['token_service']->add_payment_method_to_user( $payment_method_id, wp_get_current_user() ); wcf()->logger->log( 'Our Token : ' . PHP_EOL . $token ); // Add the token to the order for safety. $this->get_wc_gateway()->add_token_to_order( $order, $token ); $order->update_meta_data( '_cartflows_' . $this->key . '_source_id', $payment_method_id ); $order->update_meta_data( '_cartflows_' . $this->key . '_token', $token ); // Clear the DB cache if any. $order->read_meta_data( true ); wcf()->logger->log( '3DS source saved for later use' ); } catch ( Exception $e ) { wcf()->logger->log( $order->get_id() . ' Unable to save token for 3ds.' . $e->getMessage() ); } } wcf()->logger->log( 'Leaving: ' . __CLASS__ . '::' . __FUNCTION__ ); } /** * Render checkout ID hidden field. * * @param array $checkout checkout session data. * @return void */ public function add_payment_gateway_option_hidden_field( $checkout ) { if ( ! _is_wcf_checkout_type() ) { return; } $available_gateways = array(); foreach ( WC()->payment_gateways->get_available_payment_gateways() as $payment_gateway_id => $payment_gateway_item ) { array_push( $available_gateways, $payment_gateway_id ); } $checkout_id = _get_wcf_checkout_id(); $flow_id = wcf()->utils->get_flow_id_from_step_id( $checkout_id ); $is_upsell = wcf_pro()->flow->is_upsell_exist_in_flow( $flow_id, $checkout_id ); if ( ( is_array( $available_gateways ) && ! in_array( $this->key, $available_gateways, true ) ) || ! $is_upsell ) { return; } echo '<input type="hidden" class="input-hidden wc-woocommerce_payments-new-payment-method" name="wc-woocommerce_payments-new-payment-method" value="true" />'; } /** * Check if token is present. * * @param object $order order data. */ public function has_token( $order ) { $order_id = $order->get_id(); $token = $this->retrieve_token_from_order( $order, true ); if ( empty( $token ) ) { $source_id = $order->get_meta( '_cartflows_' . $this->key . '_source_id' ); $token = $order->get_meta( '_cartflows_' . $this->key . '_token' ); } if ( ! empty( $token ) ) { return true; } return false; } /** * Get WooCommerce payment geteways. * * @return object */ public function get_wc_gateway() { global $woocommerce; $gateways = $woocommerce->payment_gateways->payment_gateways(); return $gateways[ $this->key ]; } /** * After payment process. * * @param object $order order data. * @param array $product product data. * @return array */ public function process_offer_payment( $order, $product ) { wcf()->logger->log( 'Started: ' . __CLASS__ . '::' . __FUNCTION__ ); $is_successful = false; $nonce = isset( $_POST['_nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_nonce'] ) ) : ''; if ( ! wp_verify_nonce( $nonce, $product['action'] ) ) { return $is_successful; } if ( ! $this->has_token( $order ) ) { return $is_successful; } if ( isset( $_POST['woop_intent_id'] ) ) { $stored_intent_id = $order->get_meta( '_wcf_woop_offer_stripe_intent_id_' . $product['step_id'] ); $payment_method = $order->get_meta( '_cartflows_' . $this->key . '_source_id' ); $intent_id = sanitize_text_field( wp_unslash( $_POST['woop_intent_id'] ) ); wcf()->logger->log( 'Intent ID found: ' . $intent_id ); wcf()->logger->log( 'Stored Intent ID found: ' . $stored_intent_id ); $confirm_intent = ( $intent_id === $stored_intent_id ) ? true : false; if ( $confirm_intent ) { wcf()->logger->log( 'Intent ID confirmed' ); $is_successful = true; $data = array( 'id' => $intent_id, 'payment_method' => $payment_method, 'customer' => $order->get_meta( '_stripe_customer_id', true ), ); $this->store_offer_transaction( $order, $data, $product ); } else { wcf()->logger->log( 'Intent ID not matched. Offer order failed ' ); } } wcf()->logger->log( 'Ended: ' . __CLASS__ . '::' . __FUNCTION__ ); 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(); } /** * Create a new PaymentIntent against the new order. * * @param object $order The order that is being paid for. * @param array $offer_product Offer product. * @param object $payment_information The source that is used for the payment. * @return object $intent An intent or an error. */ public function process_payment_against_intent( $order, $offer_product, $payment_information ) { $f_name = $order->get_billing_first_name(); $l_name = $order->get_billing_last_name(); $email = $order->get_billing_email(); $customer_id = $order->get_meta( '_stripe_customer_id' ); $offer_amount = WC_Payments_Utils::prepare_amount( $offer_product['price'], $order->get_currency() ); $step_id = $offer_product['step_id']; $payment_source = WC_Payments::get_gateway()->get_payment_method_ids_enabled_at_checkout( null, true ); $product = wc_get_product( $offer_product['id'] ); $metadata = array( 'customer_name' => sanitize_text_field( $f_name ) . ' ' . sanitize_text_field( $l_name ), 'customer_email' => sanitize_email( $email ), 'site_url' => esc_url( get_site_url() ), 'payment_type' => $payment_information->get_payment_type(), 'order_number' => $order->get_order_number() . '_' . $offer_product['id'] . '_' . $step_id, 'statement_descriptor' => apply_filters( 'cartflows_' . $this->key . '_offer_statement_descriptor', substr( trim( /* translators: %1s order number */ sprintf( __( 'Order %1$s-OTO', 'cartflows-pro' ), $order->get_order_number() ) ), 0, 22 ) ), ); wcf()->logger->log( 'Current Installed version : ' . WCPAY_VERSION_NUMBER ); /** * Use this in case to add the backward compatibility of the payment gateway. * We don't need to add the backward compatibility as we are adding the support from scratch newly. */ $request = WCPay\Core\Server\Request\Create_And_Confirm_Intention::create(); $request->set_amount( $offer_amount ); $request->set_currency_code( strtolower( $order->get_currency() ) ); $request->set_payment_method( $payment_information->get_payment_method() ); $request->set_customer( $customer_id ); $request->set_capture_method( $payment_information->is_using_manual_capture() ); $request->set_metadata( $metadata ); $request->set_level3( $this->get_level3_data_for_offer_product( $order, $offer_product ) ); $request->set_off_session( $payment_information->is_merchant_initiated() ); $request->set_payment_methods( $payment_source ); $request->set_cvc_confirmation( $payment_information->get_cvc_confirmation() ); // Make sure that setting fingerprint is performed after setting metadata because metadata will override any values you set before for metadata param. $request->set_fingerprint( $payment_information->get_fingerprint() ); $is_subscription_product = $product->is_type( 'subscription' ) || $product->is_type( 'variable-subscription' ) || $product->is_type( 'subscription_variation' ); if ( $is_subscription_product ) { $mandate = $order->get_meta( '_stripe_mandate_id', true ); if ( $mandate ) { $request->set_mandate( $mandate ); } } $request->assign_hook( 'wcpay_create_and_confirm_intent_request' ); $request->set_hook_args( $payment_information ); $intent = $request->send(); return $intent; } /** * 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 ) { wcf()->logger->log( 'Start- ' . __CLASS__ . '::' . __FUNCTION__ ); $transaction_id = $offer_data['transaction_id']; $refund_amount = $offer_data['refund_amount']; $refund_reason = $offer_data['refund_reason']; $order_currency = $order->get_currency( $order ); $response_id = false; if ( ! is_null( $refund_amount ) && ! empty( $transaction_id ) ) { $intent = $this->gateway_service_objects['api_client']->get_intent( $transaction_id ); $charge_id = $intent->get_charge()->get_id(); $refund_request = Refund_Charge::create(); $refund_request->set_charge( $charge_id ); if ( null !== $refund_amount ) { $refund_request->set_amount( WC_Payments_Utils::prepare_amount( $refund_amount, $order_currency ) ); } // Specify the refund error if the reason has the following words. This is a requirement from the Stripe. Read more: https://stripe.com/docs/api/refunds/create#create_refund-reason. if ( in_array( $refund_reason, array( 'duplicate', 'fraudulent', 'requested_by_customer' ), true ) ) { $refund_request->set_reason( $refund_reason ); } $response = $refund_request->send(); wcf()->logger->log( 'Refund response : ' . wp_json_encode( $response, true ) ); if ( ! empty( $response->error ) || ! $response ) { $response_id = false; } else { $response_id = isset( $response->id ) ? $response->id : true; wcf()->logger->log( 'Refund response ID: ' . $response_id ); } } wcf()->logger->log( 'Ended- ' . __CLASS__ . '::' . __FUNCTION__ ); 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 ( $this->key === $order->get_payment_method() ) { $txn_id = $order->get_meta( 'cartflows_offer_txn_resp_' . $offer_product['step_id'], true ); $intent_id = $order->get_meta( '_wcf_woop_offer_stripe_intent_id_' . $offer_product['step_id'], true ); $subscription->update_meta_data( '_payment_method_id', $txn_id ); $subscription->update_meta_data( '_payment_tokens', $order->get_payment_tokens() ); $subscription->update_meta_data( '_stripe_customer_id', $order->get_meta( '_stripe_customer_id', true ) ); $subscription->update_meta_data( '_charge_id', $order->get_meta( '_wcf_woop_offer_charge_id_' . $offer_product['step_id'] . '_' . $order->get_id(), true ) ); $subscription->update_meta_data( '_intent_id', $intent_id ); $subscription->save_meta_data(); $subscription->save(); } } /** * Save the parent payment meta to child order. * * @param object $parent_order Object of order. * @param object $child_order Object of order. * @param int $transaction_id transaction id. */ public function add_required_meta_to_child_order( $parent_order, $child_order, $transaction_id ) { wcf()->logger->log( 'Start- ' . __CLASS__ . '::' . __FUNCTION__ ); $offer_intent_id = array( 'id' => $transaction_id, ); // Get order/intent/payment data from the parent order object. $step_id = $child_order->get_meta( '_cartflows_offer_step_id', true ); $intent_id = $parent_order->get_meta( '_wcf_woop_offer_stripe_intent_id_' . $step_id, true ); $charge_id = $parent_order->get_meta( '_wcf_woop_offer_charge_id_' . $step_id . '_' . $parent_order->get_id(), true ); $payment_method_id = $parent_order->get_meta( '_wcf_woop_offer_payment_method_id', true ); $payment_mode = $parent_order->get_meta( '_wcf_woop_offer_wcpay_mode', true ); $customer_id = $parent_order->get_meta( '_cartflows_offer_txn_stripe_customer_id_' . $step_id ); $intent = $this->gateway_service_objects['api_client']->get_intent( $intent_id ); $intent_status = $intent->get_status(); // Update order/intent/payment data child order object. This data is required for refund/subscription/cancellation of orders. $child_order->update_meta_data( '_wcf_woop_offer_stripe_intent_id_' . $step_id, $offer_intent_id ); $child_order->set_transaction_id( $intent_id ); $child_order->update_meta_data( '_intent_id', $intent_id ); $child_order->update_meta_data( '_charge_id', $charge_id ); $child_order->update_meta_data( '_intention_status', $intent_status ); $child_order->update_meta_data( '_payment_method_id', $payment_method_id ); $child_order->update_meta_data( '_stripe_customer_id', $customer_id ); WC_Payments_Utils::set_order_intent_currency( $child_order, $intent->get_currency() ); $child_order->save(); wcf()->logger->log( 'Ended- ' . __CLASS__ . '::' . __FUNCTION__ ); } /** * Prepare offer data to sent in the payment request. * * @param object $order Current Order Object. * @param array $offer_product Selected offer Product Data. * * @return array $level3_data modified data for the payment request. */ public function get_level3_data_for_offer_product( $order, $offer_product ) { $level3_data = array( 'merchant_reference' => (string) $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”. 'customer_reference' => (string) $order->get_id(), 'shipping_amount' => WC_Payments_Utils::prepare_amount( (float) $offer_product['shipping_fee_tax'], $order->get_currency() ), // The shipping cost, in cents, as a non-negative integer. 'line_items' => (object) array( 'product_code' => (string) substr( $offer_product['name'], 0, 12 ), // Up to 12 characters that uniquely identify the product. 'product_description' => substr( $offer_product['desc'], 0, 26 ), // Up to 26 characters long describing the product. 'unit_cost' => WC_Payments_Utils::prepare_amount( $offer_product['unit_price'], $order->get_currency() ), // Cost of the product, in cents, as a non-negative integer. 'quantity' => $offer_product['qty'], // The number of items of this type sold, as a non-negative integer. 'tax_amount' => ! empty( $offer_product['unit_price_tax'] ) ? ( $offer_product['unit_price_tax'] - $offer_product['unit_price'] ) : 0, // The amount of tax this item had added to it, in cents, as a non-negative integer. 'discount_amount' => WC_Payments_Utils::prepare_amount( $offer_product['original_price'] - $offer_product['price'], $order->get_currency() ), // The amount an item was discounted—if there was a sale,for example, as a non-negative integer. ), ); return $level3_data; } } /** * Prepare if class 'Cartflows_Pro_Gateway_Woocommerce_Payments' exist. * Kicking this off by calling 'get_instance()' method */ Cartflows_Pro_Gateway_Woocommerce_Payments::get_instance();