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 '
'
. __( 'To use ikhokha as a payment provider, you need to fix the problems below:', 'kkart' ) . '
'
. '
'
. array_reduce( $errors_to_show, function( $errors_list, $error_item ) {
$errors_list = $errors_list . PHP_EOL . ( '- ' . $this->get_error_message($error_item) . '
' );
return $errors_list;
}, '' )
. '
';
}
}
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 = '';
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.');
}
}
}
}