OwlCyberSecurity - MANAGER
Edit File: class-kkart-gateway-ikhokha.php
<?php /** * IKHOKHA Payment Gateway * @class KKART_Gateway_Ikhokha * @package Kkart * @version 1.0.0 * @category Payment Gateways * @author Kkart */ if ( ! defined( 'ABSPATH' ) ) { exit; } define( 'KKART_GATEWAY_IKHOKHA_URL', untrailingslashit( plugin_dir_url(__FILE__) ) ); define( 'KKART_IKHOKHA_API_ENDPOINT', 'https://api.ikhokha.com/ecomm/v1/' ); class KKART_Gateway_Ikhokha extends KKART_Payment_Gateway { public function __construct() { $this->version = KKART_VERSION; $this->id = 'ikhokha'; $this->icon = KKART_GATEWAY_IKHOKHA_URL . '/assets/kkart_ikhokha.png'; $this->has_fields = false; // in case you need a custom credit card form $this->method_title = 'iKhokha (beta)'; // Default Title $this->method_description = __('Secure credit, debit card and Instant EFT payments with iKhokha.'); $this->supports = array( 'products', 'refunds' ); $this->init_form_fields(); $this->init_settings(); // Setup default merchant data. $this->title = $this->get_option('title'); $this->description = $this->get_option('description'); $this->enabled = $this->get_option('enabled'); $this->testmode = 'yes' === $this->get_option('testmode'); $this->application_id = $this->get_option('application_id'); $this->application_secret = $this->get_option('application_secret'); $this->site_name = get_bloginfo('name'); $this->available_currencies = (array)apply_filters('kkart_gateway_ikhokha_available_currencies', array( 'ZAR' ) ); // save admin options add_action('kkart_update_options_payment_gateways_' . $this->id, array($this, 'process_admin_options')); add_action( 'admin_notices', array( $this, 'admin_notices' ) ); add_action( 'kkart_receipt_'.$this->id, array( $this, 'receipt_page' ), 10, 1 ); add_action('kkart_api_' . strtolower(get_class($this)), array($this, 'ikhokha_process_response')); } /** * Initialise Gateway Settings Form Fields * * @since 1.0.0 */ public function init_form_fields() { $this->form_fields = array( 'enabled' => array( 'title' => __( 'Enable/Disable', 'kkart' ), 'label' => __( 'Enable iKhokha Payment Gateway', 'kkart' ), 'type' => 'checkbox', 'description' => '', 'default' => 'no', ), 'title' => array( 'title' => __('Title', 'kkart'), 'type' => 'text', 'description' => __('This controls the title which the user sees during checkout.', 'kkart'), 'default' => __('iKhokha', 'kkart'), 'desc_tip' => true, ), 'description' => array( 'title' => __('Description', 'kkart'), 'type' => 'textarea', 'description' => __('This controls the description which the user sees during checkout.', 'kakrt'), 'default' => __('Secure credit, debit card and Instant EFT payments with iKhokha.', 'kakrt'), ), 'testmode' => array( 'title' => __('Test mode', 'kkart'), 'label' => __('Enable Test Mode (Use Card Number: 1111 1111 1111 1111 Expiry Month: 11 Expiry Year: 25 CVV: 111)', 'kkart'), 'type' => 'checkbox', 'description' => __('Place the payment gateway in test mode and use the displayed test card details to conduct a test transaction. Note: Your website users will NOT be able to transact if this setting is enabled.', 'kkart'), 'default' => __('no', 'kkart'), 'desc_tip' => true, ), 'application_id' => array( 'title' => __('Application ID', 'kkart'), 'type' => 'text', ), 'application_secret' => array( 'title' => __('Application Secret', 'kkart'), 'type' => 'password', ), ); } public function check_requirements() { $errors = []; // Add more error if needed if (!in_array(get_kkart_currency(), $this->available_currencies)) { $errors[] = 'kkart-gateway-ikhokha-error-invalid-currency'; } if (empty($this->get_option('application_id'))) { $errors[] = 'kkart-gateway-ikhokha-error-missing-application_id'; } if (empty($this->get_option('application_secret'))) { $errors[] = 'kkart-gateway-ikhokha-error-missing-application_secret'; } return array_filter( $errors ); } public function is_available() { if ( 'yes' === $this->enabled ) { $errors = $this->check_requirements(); return 0 === count( $errors ); } return parent::is_available(); } public function admin_notices() { // Get requirement errors. $errors_to_show = $this->check_requirements(); // If everything is in place, don't display it. if( ! count( $errors_to_show ) ){ return; } // If the gateway isn't enabled, don't show it. if ( "no" === $this->enabled ) { return; } // Use transients to display the admin notice once after saving values. if ( ! get_transient( 'kkart-gateway-ikhokha-admin-notice-transient' ) ) { set_transient( 'kkart-gateway-ikhokha-admin-notice-transient', 1, 1); echo '<div class="notice notice-error is-dismissible"><p>' . __( 'To use ikhokha 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;">' . array_reduce( $errors_to_show, function( $errors_list, $error_item ) { $errors_list = $errors_list . PHP_EOL . ( '<li>' . $this->get_error_message($error_item) . '</li>' ); return $errors_list; }, '' ) . '</ul></p></div>'; } } public function get_error_message( $key ) { $error_msg = ''; switch ( $key ) { case 'kkart-gateway-ikhokha-error-invalid-currency': $error_msg = __( 'Your store uses a currency that IKHOKHA doesnt support yet.', 'kkart' ); break; case 'kkart-gateway-ikhokha-error-missing-application_id': $error_msg = __( 'You forgot to fill your Application ID.', 'kkart' ); break; case 'kkart-gateway-ikhokha-error-missing-application_secret': $error_msg = __( 'You forgot to fill your Application secret.', 'kkart' ); break; } return $error_msg; } public function process_payment($order_id) { global $kkart; $order = new KKART_Order($order_id); return array( 'result' => 'success', 'redirect' => $order->get_checkout_payment_url(true), ); } public function receipt_page($order) { echo $this->generate_post_form($order); } public function generate_post_form($order_id) { $order = new KKART_Order($order_id); $payment_page = $order->get_checkout_payment_url(); $cart_page_id = kkart_get_page_id('cart'); $cart_page_url = $cart_page_id ? get_permalink($cart_page_id) : ''; /* Define test or live mode */ if ($this->get_option('testmode') == "yes") { $mode = true; } else { $mode = false; } $client_details = array( "platformName" => "Kkart", "platformVersion" => KKART_VERSION, "pluginVersion" => KKART_VERSION, "website" => get_site_url(), ); /* Amount Validation */ $getTotal = $order->get_total(); $getDecimal = kkart_get_price_decimal_separator(); $resetDecimal = str_replace($getDecimal, '.', $getTotal); $orderAmount = number_format($resetDecimal, 2, '.', ''); /* Payload Info */ $payload = array( "amount" => round($orderAmount * 100), "callbackUrl" => str_replace('http:', 'https:', add_query_arg(array('kkart-api' => 'KKART_Gateway_Ikhokha', 'reference' => $order_id), home_url('/'))), "successUrl" => $this->get_return_url($order), "failUrl" => $payment_page, "test" => $mode, "customerEmail" => $order->get_billing_email(), "customerPhone" => $order->get_billing_phone(), "customerName" => $order->get_billing_first_name() . ' ' . $order->get_billing_last_name(), "client" => $client_details, ); $auth = $this->ikhokha_order_auth($payload); // replace once we have an actual auth api if (is_array($auth) && array_key_exists('paymentUrl', $auth)) { // save payment link to order $order->update_meta_data('kkart_ikhokha_payment_url', esc_url_raw($auth['paymentUrl'])); $order->save(); // Enqueue Script for loading Form & Auto submit kkart_enqueue_js(' $.blockUI({ message: "' . esc_js(__('Thank you for your order. We are now redirecting you to iKhokha to make payment.', 'kkart')) . '", baseZ: 99999, overlayCSS: { background: "#fff", opacity: 0.6 }, css: { padding: "20px", zindex: "9999999", textAlign: "center", color: "#555", border: "3px solid #aaa", backgroundColor:"#fff", cursor: "wait", lineHeight: "24px", } }); window.location.href = "' . esc_attr($auth['paymentUrl']) . '"; '); $html = ''; }else{ // make ssl if needed if (get_option('kkart_force_ssl_checkout') == 'yes') { $payment_page = str_replace('http:', 'https:', $payment_page); } // display an HTML error briefly before redirecting to payment page $html = '<div class="ikhokha-payment-notice" style="border:2px solid #ff0000;padding:10px;font-weight: 700;color: #ff0000;">Invalid connection to iKhokha - <a href="' . $payment_page . '">Try again.</a></div>'; wp_redirect($payment_page); } return $html; } public function generate_signature($payload) { $signature = false; if(isset($payload) && !empty($payload) && $this->application_secret !== ''){ $string = "/ecomm/v1/paymentlinks" . addslashes(json_encode($payload, JSON_UNESCAPED_SLASHES)); $signature = hash_hmac("sha256", $string, $this->application_secret); } return $signature; } public function ikhokha_order_auth($payload) { $signature = $this->generate_signature($payload); if(!$signature){ return false; } $args = [ 'method' => 'POST', 'sslverify' => true, 'headers' => [ 'Cache-Control' => 'no-cache', 'Content-Type' => 'application/json', 'IK-APPID' => $this->application_id, 'IK-SIGN' => $signature, ], 'body' => json_encode($payload), ]; $uri = KKART_IKHOKHA_API_ENDPOINT . 'paymentlinks'; $request = wp_remote_post($uri, $args); $response = wp_remote_retrieve_body($request); $response = json_decode($response, true); if (is_wp_error($response)) { $error_message = $response->get_error_message(); return $error_message; } else { return $response; } } public function generate_callback_signature($payload) { $signature = false; if (isset($payload) && !empty($payload) && $this->application_secret !== '') { $string = "/" . addslashes(json_encode($payload, JSON_UNESCAPED_SLASHES)); $signature = hash_hmac("sha256", $string, $this->application_secret); } return $signature; } public function ikhokha_process_response(){ global $kkart; $check = false; // Get data via post if (isset($_POST['status']) && !empty($_POST['status'])) { $data = array('status' => sanitize_text_field($_POST['status'])); // extra data if (isset($_POST['transactionId']) && !empty($_POST['transactionId'])) { $data['transactionId'] = sanitize_key($_POST['transactionId']); } if (isset($_POST['responseCode']) && !empty($_POST['responseCode'])) { $data['responseCode'] = sanitize_text_field($_POST['responseCode']); } if (isset($_POST['responseMessage']) && !empty($_POST['responseMessage'])) { $data['responseMessage'] = sanitize_text_field($_POST['responseMessage']); } } else { $data = json_decode(file_get_contents("php://input"), true); } // if we getting endpoint data if (isset($data) && isset($_GET['reference']) && isset($data['status'])) { // get headers $headers = getallheaders(); $keys = array(); foreach ($headers as $headerKey => $headerValue) { $keys[strtoupper($headerKey)] = $headerValue; } // generate signature $signature = $this->generate_callback_signature($data); // if the generated signature does not match the signature received in header return 500 error if ($signature !== $keys['IK-SIGN']) { status_header(500, "HTTP/1.1 500 Internal Server Error"); die(); } if($this->application_id !== $keys['IK-APPID']) { status_header(500, "HTTP/1.1 500 Internal Server Error"); die(); } // set reference and ensure it returns as a number $reference = sanitize_key($_GET['reference']); if (!is_numeric($reference)) { $reference = +$reference; } $order = new KKART_Order($reference); $orderStatus = $order->get_status(); $transactionStatus = strtolower($data['status']); // if we waiting for a payment // if order status is success from api endpoint we mark the order as processing in WC if ($orderStatus != 'processing' && $transactionStatus == 'success') { status_header(200); $order->update_status('processing', sprintf(__('%s %s was successfully processed through iKhokha', 'kkart'), get_kkart_currency(), $order->get_total())); $order->payment_complete(); kkart_reduce_stock_levels($order->get_id()); $order->update_meta_data('kkart_ikhokha_data', $data); $order->save(); KKART()->cart->empty_cart(); $check = true; } else if ($orderStatus != 'processing' && $transactionStatus == 'failed') { status_header(200); $order->update_status('failed', sprintf(__('%s %s failed to connect through iKhokha', 'kkart'), get_kkart_currency(), $order->get_total())); } } else { // if no api data status_header(500, "HTTP/1.1 500 Internal Server Error"); die(); } if (!$check) { status_header(500, "HTTP/1.1 500 Internal Server Error"); die(); } $return = array( 'status' => $check, ); print_r($return); die(); } public function generate_refund_signature($refund_payload, $order_id){ $refund_signature = false; // default to false if(isset($refund_payload) && !empty($refund_payload) && $this->application_secret !== ''){ $transaction_id = null; $getData = get_post_meta($order_id, 'kkart_ikhokha_data', true) ? get_post_meta($order_id, 'kkart_ikhokha_data', true) : ''; if(isset($getData['transactionId']) && !empty($getData['transactionId'])){ $transaction_id = $getData['transactionId']; } if(empty($transaction_id)){ $payment_url = get_post_meta($order_id, 'kkart_ikhokha_payment_url', true); $arr_payment = explode("/", $payment_url); $arr_payment_reversed = array_reverse($arr_payment); $transaction_id = $arr_payment_reversed[0]; } $string = "/ecomm" . "/" . $transaction_id . "/refunds" . addslashes(json_encode($refund_payload, JSON_UNESCAPED_SLASHES)); $refund_signature = hash_hmac("sha256", $string, $this->application_secret); } return $refund_signature; } public function process_refund($order_id, $amount = null, $reason = ''){ $convert = round($amount * 100); //Payload Info $refund_payload = array( "amount" => $convert, "reason" => $reason, ); $refund_signature = $this->generate_refund_signature($refund_payload, $order_id); if(!$refund_signature){ return false; } $transaction_id = null; $getData = get_post_meta($order_id, 'kkart_ikhokha_data', true) ? get_post_meta($order_id, 'kkart_ikhokha_data', true) : ''; if(isset($getData['transactionId']) && !empty($getData['transactionId'])){ $transaction_id = $getData['transactionId']; } if(empty($transaction_id)){ $payment_url = get_post_meta($order_id, 'kkart_ikhokha_payment_url', true); $arr_payment = explode("/", $payment_url); $arr_payment_reversed = array_reverse($arr_payment); $transaction_id = $arr_payment_reversed[0]; } $refund_url_full = "https://api.ikhokha.com/ecomm/$transaction_id/refunds"; $args = [ 'method' => 'POST', 'sslverify' => true, 'timeout' => 30, 'headers' => [ 'Cache-Control' => 'no-cache', 'Content-Type' => 'application/json', 'IK-APPID' => $this->application_id, 'IK-SIGN' => $refund_signature, ], 'body' => json_encode($refund_payload), ]; $request = wp_remote_post($refund_url_full, $args); $response = wp_remote_retrieve_body($request); $response = json_decode($response, true); if(is_wp_error($response)){ $error_message = $response->get_error_message(); return false; }else{ if (isset($response['status']) && $response['status'] == "SUCCESS") { $order = new KKART_Order($order_id); $order->add_order_note(sprintf(__('%s %s was successfully refunded through iKhokha', 'kkart'), get_kkart_currency(), $amount)); return true; } else if (isset($response['status']) && $response['status'] == "FAILURE") { return new WP_Error('kkart_' . $order_id . '_refund_failed', $response['responseMessage']); } else { return new WP_Error('kkart_' . $order_id . '_refund_failed', 'Unable to process a refund.'); } } } }