OwlCyberSecurity - MANAGER
Edit File: class-kkart-gateway-stripe.php
<?php /** * Stripe Payment Gateway * * Provides a Stripe Payment Gateway. * * @class kkart_gateway_stripe_checkout * @package Kkart * @category Payment Gateways * @author Kkart */ if( ! defined( 'ABSPATH' ) ) { exit; } require_once dirname( __FILE__ ) . '/stripe-php/init.php'; use Stripe\Stripe; class KKART_Gateway_Stripe extends KKART_Payment_Gateway { //log instance public static $log = false; public static $log_enabled = false; function __construct() { //NOTE:: Update payment method description according to supported payment methods $this->id = 'stripe'; $this->method_title = esc_html__( 'Stripe', 'kkart' ); $this->method_description = esc_html__( 'Stripe redirects you to the Stripe hosted secure payment page.', 'kkart' ); $this->init_form_fields(); $this->init_settings(); // Define user set options $this->title = $this->get_option( 'title', esc_html__( 'Stripe', 'kkart' ) ); $this->description = $this->get_option( 'description', esc_html__( 'Pay via Stripe. Accept Credit Cards, Debit Cards, Alipay and more', 'kkart' ) ); $this->enabled = $this->get_option( 'enabled' ); $this->apiKey = $this->get_option( 'apiKey' ); $this->payment_method = $this->get_option( 'payment_method' ); $this->business_name = $this->get_option( 'business_name' ); $this->webhook = $this->get_option( 'webhook' ); $this->webhook_secret = $this->get_option( 'webhook_secret' ); self::$log_enabled = $this->get_option( 'log_enabled' ); if( $this->webhook ) { add_action( 'kkart_api_kkart_gateway_'. $this->id, array( &$this, 'webhook_response' ) ); } add_action( 'kkart_update_options_payment_gateways_' . $this->id, array( &$this, 'process_admin_options' ) ); add_action( 'admin_notices', array( $this, 'admin_notices' ) ); } public function init_form_fields() { $this->form_fields = array(); $this->form_fields['enabled'] = array( 'title' => esc_html__( 'Enable/Disable', 'kkart' ), 'type' => 'checkbox', 'label' => esc_html__( 'Enable Stripe Payment Gateway', 'kkart' ), 'default' => 'no' ); $this->form_fields['title'] = array( 'title' => esc_html__( 'Title', 'kkart' ), 'type' => 'text', 'description' => esc_html__( 'This controls the title which the user sees during checkout.', 'kkart' ), 'default' => esc_html__( 'Stripe', 'kkart' ), 'desc_tip' => true ); $this->form_fields['description'] = array( 'title' => esc_html__( 'Description', 'kkart' ), 'type' => 'textarea', 'description' => esc_html__( 'This controls the description which the user sees during checkout.', 'kkart' ), 'default' => esc_html__( 'Pay via Stripe. Accept Credit Cards, Debit Cards, Alipay and more', 'kkart' ) ); $this->form_fields['business_name'] = array( 'title' => esc_html__( 'Business Name', 'kkart' ), 'type' => 'text', 'description' => esc_html__( 'This Will Be Shown on Payment page.', 'kkart' ), 'default' => '', ); $this->form_fields['apiKey'] = array( 'title' => esc_html__( 'API Secret Key', 'kkart' ), 'type' => 'password', 'description' => __( 'Please enter the <strong>Secret key</strong> from <a href="https://dashboard.stripe.com/" target="_blank">Stripe Dashboard</a>', 'kkart' ), 'default' => '' ); $this->form_fields['payment_method'] = array( 'title' => esc_html__( 'Payment Method', 'kkart' ), 'type' => 'multiselect', 'class' => 'chosen_select', 'css' => 'width: 350px;', 'options' => array( 'card' => 'Card', 'alipay' => 'AliPay', 'ideal' => 'iDEAL', 'bacs_debit' => 'Bacs Direct Debit', 'bancontact' => 'Bancontact', 'giropay' => 'Giropay', 'p24' => 'Przelewy24', 'eps' => 'EPS', 'sofort' => 'Sofort', 'sepa_debit' => 'SEPA Direct Debit', 'grabpay' => 'GrabPay', 'afterpay_clearpay' => 'Afterpay / Clearpay', 'acss_debit' => 'ACSS Debit', 'wechat_pay' => 'WeChat Pay', 'boleto' => 'Boleto', 'oxxo' => 'OXXO', ), 'default' => array( 'card' ), 'description' => sprintf( __( 'Please make sure the selected gateways are enabled in ', 'kkart' ). '<strong><a href="https://dashboard.stripe.com/account/payments/settings" target="_blank">Stripe Payment Method Settings</a></strong>' ) ); $this->form_fields['log_enabled'] = array( 'title' => esc_html__( 'Logging', 'kkart' ), 'type' => 'checkbox', 'label' => esc_html__( 'Enable Logging', 'kkart' ), 'description' => sprintf( __( 'Log Stripe events, <strong>DON\'T ALWAYS ENABLE THIS.</strong> You can check this log in %s.', 'kkart' ), '<a target="_blank" href="' . esc_url( admin_url( 'admin.php?page=kkart-status&tab=logs&log_file=' . esc_attr( $this->id ) . '-' . sanitize_file_name( wp_hash( $this->id ) ) . '.log' ) ) . '">' . esc_html__( 'System Status > Logs', 'kkart' ) . '</a>' ), 'default' => 'no' ); $this->form_fields['webhook_title'] = array( 'title' => esc_html__( 'Webhook Options' ), 'type' => 'title', 'class'=> 'kkart-css-class' ); $this->form_fields['webhook'] = array( 'title' => esc_html__( 'Enable Webhook', 'kkart' ), 'type' => 'checkbox', 'description' => sprintf( __('WebHook URL <code>','kkart'). add_query_arg( 'kkart-api','KKART_Gateway_Stripe', home_url( '/' ) ).'</code><br/>Add Webhook here :- <a href="https://dashboard.stripe.com/webhooks" target="_blank">Stripe Webhook</a>' ), 'default' => 'no' ); $this->form_fields['webhook_secret'] = array( 'title' => esc_html__( 'Webhook Secret Key', 'kkart' ), 'type' => 'password', 'description' => esc_html__( 'Webhook secret key is used to authenticate webhooks', 'kkart' ), 'default' => '' ); } public function admin_options() { kkart_enqueue_js(" jQuery( function( $ ) { $('.kkart-css-class').css({'border-top': 'dashed 1px #ccc','padding-top': '15px','width': '68%'}); })" ); parent::admin_options(); } public function process_payment( $order_id ) { $order = kkart_get_order( $order_id ); \Stripe\Stripe::setApiKey( $this->apiKey ); $current_user = wp_get_current_user(); $current_user = (string) $current_user->user_email; $session_data = array( 'customer_email' => $current_user, 'payment_method_types' => $this->payment_method, 'line_items' => [[ 'price_data' => [ 'currency' => get_kkart_currency(), 'product_data' => [ 'name' => $this->business_name ? $this->business_name : get_bloginfo('name'), ], 'unit_amount_decimal' => $this->get_stripe_amount( $order->get_total(), get_kkart_currency() ), ], 'quantity' => 1, ]], 'mode' => 'payment', 'success_url' => esc_url_raw( add_query_arg( 'utm_nooverride', '1', $this->get_return_url( $order ) ) ), 'cancel_url' => $order->get_cancel_order_url(), ); //setting payment_method_options foreach( $this->payment_method as $payment_method ) { if( $this->setPaymentMethodOptions( $payment_method ) ) { $session_data['payment_method_options'] = $this->setPaymentMethodOptions( $payment_method ); } } //setting payment_intent_data if( in_array('bacs_debit', $this->payment_method ) ) { $session_data['payment_intent_data'] = [ 'setup_future_usage' => 'off_session', ]; } $session = \Stripe\Checkout\Session::create( $session_data ); if( isset( $session['id'] ) && isset( $session['url'] ) ) { self::log( '============Payment Initiated ==========', 'debug' ); self::log( $session ); self::log( '========Session Data Ends Here=======', 'debug' ); return array( 'result' => 'success', 'redirect' => $session['url'] ); } self::log( 'Payment Initiation Failed', 'error' ); return array( 'result' => 'failed', //'message' => $['message'] ); } //Processing webhook response public function webhook_response() { if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || ( 'POST' !== $_SERVER['REQUEST_METHOD'] ) || ! isset( $_GET['kkart-api'] ) || ( 'KKART_Gateway_Stripe' !== $_GET['kkart-api'] ) ) { return; } $endpoint_secret = $this->webhook_secret; $payload = @file_get_contents('php://input'); $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE']; $event = null; try { $event = \Stripe\Webhook::constructEvent( $payload, $sig_header, $endpoint_secret ); } catch(\UnexpectedValueException $e) { // Invalid payload self::log( 'Webhook Source authentication Unexpected Value'. $e ); http_response_code(400); exit(); } catch(\Stripe\Exception\SignatureVerificationException $e) { // Invalid signature self::log( 'Webhook Source authentication failed Invalid Signature'. $e ); http_response_code(400); exit(); } $this->process_webhook( $event ); return; } public function setPaymentMethodOptions( $payment_method ) { switch ( $payment_method ) { case 'wechat_pay': return array( 'wechat_pay' => array( 'client' => "web" ), ); } return; } //Icon to be displayed on checkout page public function get_icon() { $icon_url = untrailingslashit( plugin_dir_url( __FILE__ ) ). '/assets/poweredbystripe.svg' ; $icons_str = sprintf( '<img src="%s" alt="Powered By Stripe" />', $icon_url ); return apply_filters( 'kkart_gateway_icon', $icons_str, $this->id ); } /** * Get Stripe amount to pay * * @param float $total Amount due. * @param string $currency Accepted currency. * * @return float|int */ public static function get_stripe_amount( $total, $currency = '' ) { if ( ! $currency ) { $currency = get_kkart_currency(); } if ( in_array( strtolower( $currency ), self::no_decimal_currencies() ) ) { return absint( $total ); } else { return absint( kkart_format_decimal( ( (float) $total * 100 ), kkart_get_price_decimals() ) ); // In cents. } } /** * List of currencies supported by Stripe that has no decimals * https://stripe.com/docs/currencies#zero-decimal from https://stripe.com/docs/currencies#presentment-currencies * * @return array $currencies */ public static function no_decimal_currencies() { return ['bif', 'clp', 'djf', 'gnf', 'jpy', 'kmf', 'krw', 'mga', 'pyg', 'rwf', 'ugx', 'vnd', 'vuv', 'xaf', 'xof', 'xpf']; } //Processes the incoming webhook. public function process_webhook( $event ) { $checkout = $event->data->object; switch ( $event->type ) { case 'checkout.session.async_payment_failed': case 'checkout.session.expired': $this->process_webhook_checkout_failed( $checkout ); break; case 'checkout.session.async_payment_succeeded': $this->process_webhook_checkout_succeeded( $checkout ); break; case 'checkout.session.completed': $this->process_webhook_checkout_completed( $checkout ); // ... handle other event types default: self::log( 'Received unknown event type \n' . $event->type, 'warning' ); } } //Parses order_id from success url and returns order using ID public function get_order_by_order_id( $checkout ) { //parses order_id from success url $parsed_url = parse_url( $checkout->success_url ); parse_str( $parsed_url['query'], $param ); $order_id = absint( $param['order_id'] ); $order = kkart_get_order( $order_id ); return $order; } /* * Checkout Succeeded when the payment has been made * but yet to be confirmed from the payment gateway */ public function process_webhook_checkout_succeeded( $checkout ) { $order = $this->get_order_by_order_id( $checkout ); // If order status is already in on-hold status don't continue. if ( $order->has_status( 'on-hold' ) ) { return; } $message = __( 'We are waiting for Payment confirmation from the gateway.', 'kkart' ); self::log( $message ); $order->update_status( 'on-hold', $message ); } //Payment Failed public function process_webhook_checkout_failed( $checkout ) { $order = $this->get_order_by_order_id( $checkout ); // If order status is already in failed status don't continue. if ( $order->has_status( 'failed' ) ) { return; } $message = __( 'This payment failed to clear.', 'kkart' ); self::log( $message ); $order->update_status( 'failed', $message ); } //Payment completion confirmed by thr gateway public function process_webhook_checkout_completed( $checkout ) { $order = $this->get_order_by_order_id( $checkout ); // If order status is already in on-hold status don't continue. if ( $order->has_status( 'processing' ) ) { return; } // Store other data such as fees $order->set_transaction_id( $checkout->id ); $order->payment_complete( $checkout->id ); $order->update_status( 'processing', $message ); self::log( 'Order payment Success Confirmed' ); } public function process_refund( $order_id, $amount = null, $reason = '' ) { $order = kkart_get_order( $order_id ); if ( ! $order or ! $order->get_transaction_id() ) { return new WP_Error('error', __('Refund failed: No transaction ID', 'kkart')); } $paymentId = $order->get_transaction_id(); $stripe = new \Stripe\StripeClient( $this->apiKey ); $refund_data = array( 'charge' => $paymentId, ); //checks if amount is given else the default is full amount of the order if ( $amount ) { $refund_data['amount'] = $this->get_stripe_amount( $amount, get_kkart_currency() ); } if ( !empty( $reason ) ) { $refund_data['reason'] = $reason; } try{ $refund = $stripe->refunds->create( $refund_data ); $order->add_order_note( __( 'Refund Id: ' . $refund->id, 'kkart' ) ); return true; } catch(Exception $e) { self::log( 'Refund Failed' . $e->getMessage() ); return new WP_Error('error', __($e->getMessage(), 'kkart')); } } public function admin_notices() { // If the gateway isn't enabled, don't show it. if ( "no" === $this->enabled ) { return; } $errors = $this->check_requirements(); foreach( $errors as $error ) { if( ! $error ){ continue; } $this->update_option( 'enabled', 'no' ); $error_message = $this->get_error_message( $error ); $this->admin_notice_html( $error_message ); } if( !empty( $this->payment_method ) ) { array_walk( $this->payment_method, array( $this, 'supported_currency' ) ); } } public function get_error_message( $key ) { switch ( $key ) { case 'kkart-gateway-stripe-error-missing-apiKey': return __( 'You forgot to fill your API Key.', 'kkart' ); case 'kkart-gateway-stripe-error-missing-payment-method': return __( 'You forgot to fill your Payment Method.', 'kkart' ); case 'kkart-gateway-stripe-error-missing-business-name': return __( 'You forgot to fill your Business Name.', 'kkart' ); case 'kkart-gateway-stripe-error-missing-webhook-secret': return __( 'You forgot to fill your Webhook Secret.', 'kkart' ); } } public function check_requirements() { $errors = [ empty( $this->get_option( 'apiKey' ) ) ? 'kkart-gateway-stripe-error-missing-apiKey' : null, empty( $this->get_option( 'payment_method' ) ) ? 'kkart-gateway-stripe-error-missing-payment-method' : null, empty( $this->get_option( 'business_name' ) ) ? 'kkart-gateway-stripe-error-missing-business-name' : null, ('yes' === $this->get_option( 'webhook' ) && empty( $this->get_option( 'webhook_secret' ) ) ) ?'kkart-gateway-stripe-error-missing-webhook-secret' : null, ]; return $errors; } // Checks for supported currencies by the payment method public function supported_currency( $gateway, $key ) { if( is_callable( array( $this, 'is_supported_currency_by_'. $gateway ) ) ){ $is_supported = call_user_func( array( $this, 'is_supported_currency_by_'. $gateway ) ); if( $is_supported ) { return; } $gateway_name = str_replace( '_', ' ', ucwords( $gateway, '_' ) ); $this->update_payment_method( $key ); $this->admin_notice_html( $gateway_name . ' does not support the store\'s currency that is ' . get_kkart_currency() ); $this->update_option( 'enabled', 'no' ); } } //Checks if the currency is supported by the gateway public function is_supported_currency( $supported_currencies ) { $store_currency = get_kkart_currency(); if( in_array( $store_currency, $supported_currencies ) ) { return true; } return false; } public function is_supported_currency_by_giropay() { $supported_currencies = [ 'EUR' ]; return $this->is_supported_currency( $supported_currencies ); } public function is_supported_currency_by_ideal() { $supported_currencies = [ 'EUR' ]; return $this->is_supported_currency( $supported_currencies ); } public function is_supported_currency_by_p24() { $supported_currencies = [ 'EUR', 'PLN' ]; return $this->is_supported_currency( $supported_currencies ); } public function is_supported_currency_by_sofort() { $supported_currencies = [ 'EUR' ]; return $this->is_supported_currency( $supported_currencies ); } public function is_supported_currency_by_eps() { $supported_currencies = [ 'EUR' ]; return $this->is_supported_currency( $supported_currencies ); } public function is_supported_currency_by_bancontact() { $supported_currencies = [ 'EUR' ]; return $this->is_supported_currency( $supported_currencies ); } public function is_supported_currency_by_grabpay() { $supported_currencies = [ 'SGD', 'MYR' ]; return $this->is_supported_currency( $supported_currencies ); } public function is_supported_currency_by_wechat_pay() { $supported_currencies = [ 'CNY', 'AUD', 'CAD', 'EUR', 'GBP', 'HKD', 'JPY', 'SGD', 'USD', 'DKK', 'NOK', 'SEK', 'CHF' ]; return $this->is_supported_currency( $supported_currencies ); } public function is_supported_currency_by_oxxo() { $supported_currencies = [ 'MXN' ]; return $this->is_supported_currency( $supported_currencies ); } public function is_supported_currency_by_boleto() { $supported_currencies = [ 'BRL' ]; return $this->is_supported_currency( $supported_currencies ); } public function is_supported_currency_by_sepa_debit() { $supported_currencies = [ 'EUR' ]; return $this->is_supported_currency( $supported_currencies ); } public function is_supported_currency_by_bacs_debit() { $supported_currencies = [ 'GBP' ]; return $this->is_supported_currency( $supported_currencies ); } public function is_supported_currency_by_afterpay_clearpay() { $supported_currencies = [ 'USD', 'CAD', 'GBP', 'AUD', 'NZD' ]; return $this->is_supported_currency( $supported_currencies ); } //Updates Payment Method in Stripe public function update_payment_method( $index ) { unset( $this->payment_method[$index] ); $this->payment_method = array_values( $this->payment_method ); $this->update_option( 'payment_method', $this->payment_method ); return; } //Echos Admin Notice HTML public function admin_notice_html( $error ) { echo '<div class="notice notice-error is-dismissible"><p>' . __( 'To use Stripe as a payment provider, you need to fix the problems below:', 'kkart' ) . '</p>' . '<ul style="list-style-type: disc; list-style-position: inside; padding-left: 2em;">' . $error . '</ul></p></div>'; } public static function log( $message, $level = 'info' ) { if ( self::$log_enabled ) { if ( empty( self::$log ) ) { self::$log = kkart_get_logger(); } self::$log->log( $level, $message, array( 'source' => 'stripe-checkout' ) ); } } }