version = KKART_GATEWAY_YOCO_VERSION;
$this->credentials = $this->get_credential();
$this->id = 'yoco';
$this->enabled = $this->get_option( 'enabled' );
$this->icon = KKART_GATEWAY_YOCO_URL . '/assets/yoco-2024.svg';
$this->has_fields = false;
$this->init_form_fields();
$this->init_settings();
// Supported functionality.
$this->supports = array( 'products', 'pre-orders', 'refunds');
$this->title = $this->get_option( 'title', esc_html__( 'Yoco', 'kkart' ) );
$this->description = $this->get_option( 'description', esc_html__( 'Pay securely using a credit/debit card or other payment methods via Yoco.', 'kkart' ) );
$this->mode = $this->get_option( 'mode' );
$this->method_title = esc_html__( 'Yoco', 'kkart' );
$this->method_description = esc_html__( 'Yoco redirects customers to PayPal to enter their payment information.', 'kkart' );
$this->available_currencies = (array)apply_filters('kkart_gateway_yoco_available_currencies', array( 'ZAR' ) );
add_action( 'kkart_update_options_payment_gateways_' . $this->id, array( &$this, 'process_admin_options' ) );
add_action( 'admin_notices', array( $this, 'admin_notices' ) );
add_action( 'yoco_payment_gateway/checkout/created', array( $this, 'updateOrderCheckoutMeta' ), 10, 2 );
add_action( 'yoco_payment_gateway/order/refunded', array( $this, 'updateOrderRefundId' ), 10, 2 );
}
public function init_form_fields() {
$this->form_fields = array(
'enabled' => array(
'title' => __( 'Enable/Disable', 'kkart' ),
'label' => __( 'Enable Yoco Payments', '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' => __( 'Yoco', 'kkart' ),
'desc_tip' => true,
),
'description' => array(
'title' => __( 'Description', 'kkart' ),
'type' => 'text',
'description' => __( 'This controls the description which the user sees during checkout.', 'kkart' ),
'default' => __( 'Pay securely using a credit/debit card or other payment methods via Yoco.', 'kkart' ),
'css' => 'max-width:400px;',
),
'mode' => array(
'title' => __( 'Mode', 'kkart' ),
'label' => __( 'Mode', 'kkart' ),
'type' => 'select',
'description' => __( 'Test mode allow you to test the plugin without processing money.
Set the plugin to Live mode and click on "Save changes" for real customers to use it.', 'kkart' ),
'default' => 'Test',
'options' => array(
'live' => 'Live',
'test' => 'Test',
),
),
'live_secret_key' => array(
'title' => __( 'Live Secret Key', 'kkart' ),
'type' => 'password',
'description' => __( 'Live Secret Key', 'kkart' ),
'class' => 'input password-input',
),
'test_secret_key' => array(
'title' => __( 'Test Secret Key', 'kkart' ),
'type' => 'password',
'description' => __( 'Test Secret Key', 'kkart' ),
'class' => 'input password-input',
),
);
}
public function process_payment( $order_id ): ?array {
$order = kkart_get_order( $order_id );
try {
$checkoutUrl = $order->get_meta( 'kkart_yoco_order_checkout_url', true, 'kkart' );
if( ! empty( $checkoutUrl ) ){
return $this->createSuccessRedirectResponse( $checkoutUrl );
}
$response = $this->send($order, 'payment');
if( ! in_array( (int) $response['code'], array( 200, 201, 202 ), true )){
$error_message = isset( $response['body']['errorMessage'] ) ? $response['body']['errorMessage'] : '';
$error_code = isset( $response['body']['errorCode'] ) ? $response['body']['errorCode'] : '';
$response_message = isset( $response['message'] ) ? $response['message'] : '';
throw new Exception( sprintf( 'Failed to request checkout. %s', $response_message ) );
}
do_action( 'yoco_payment_gateway/checkout/created', $order, $response['body']);
return $this->createSuccessRedirectResponse( $response['body']['redirectUrl'] );
} catch ( \Throwable $th ){
kkart_add_notice( __( 'Your order could not be processed by Yoco - please try again later.', 'kkart' ), 'error' );
return null;
}
}
public function get_icon() {
$icon = '';
return apply_filters( 'kkart_gateway_icon', $icon, $this->id );
}
public function process_refund( $order_id, $amount = null, $reason = '' ){
$order = kkart_get_order( $order_id );
try{
$response = $this->send($order, 'refund');
if ( isset( $response['body']['description'] ) ) {
return new WP_Error( 400, $response['body']['description'] );
}
if ( isset( $response['body']['status'] ) && 'successful' === $response['body']['status'] ) {
do_action( 'yoco_payment_gateway/order/refunded', $order, $response['body'] );
}
return new WP_Error( 200, $response['body']['message'] ?? '' );
}catch( \Throwable $th ){
return new WP_Error( $th->getCode(), $th->getMessage() );
}
}
private function createSuccessRedirectResponse( string $redirectUrl ): array {
return array(
'result' => 'success',
'redirect' => $redirectUrl,
);
}
public function send($order, string $request_type): array {
try {
$url = $this->getUrl($order, $request_type);
$args = $this->getArgs($order,$request_type);
return $this->post( $url, $args );
} catch ( \Throwable $th ) {
throw $th;
}
}
public function updateOrderCheckoutMeta( KKART_Order $order, array $data ): void {
$this->updateOrderMeta( $order, 'kkart_yoco_order_checkout_id', $data['id'] );
$this->updateOrderMeta( $order, 'kkart_yoco_order_checkout_mode', $data['processingMode'] );
$this->updateOrderMeta( $order, 'kkart_yoco_order_checkout_url', $data['redirectUrl'] );
}
public function updateOrderPaymentId( KKART_Order $order, $payload ): void {
$this->updateOrderMeta( $order, 'kkart_yoco_order_payment_id', $payload->getPaymentId() );
}
public function updateOrderRefundId( KKART_Order $order, array $data ): void {
$this->updateOrderMeta( $order, 'kkart_yoco_order_refund_id', $data['refundId'] );
}
public function updateOrderMeta( KKART_Order $order, string $key, string $value ): void {
$order->update_meta_data( $key, $value );
$order->save_meta_data();
}
private function getUrl($order, string $request_type): string {
if($request_type === 'refund'){
$url = $this->getCheckoutApiUrl();
return trailingslashit( $url ) . $this->getOrderCheckoutId($order) . '/refund';
}
return $this->getCheckoutApiUrl();
}
public function getCheckoutApiUrl(): ?string {
if(! defined( 'KKART_YOCO_ONLINE_CHECKOUT_URL' ) ){
return '';
}
return KKART_YOCO_ONLINE_CHECKOUT_URL;
}
public function getOrderCheckoutId( KKART_Order $order ): string {
return $this->getOrderMeta( $order, 'kkart_yoco_order_checkout_id' );
}
public function getOrderMeta( KKART_Order $order, string $key ): string {
$meta = $order->get_meta( $key, true, 'yoco' );
return is_string( $meta ) ? $meta : '';
}
private function getArgs($order, string $request_type): array {
if($request_type === 'refund'){
return array(
'headers' => $this->getHeaders($request_type),
);
}
return array(
'headers' => $this->getHeaders($request_type),
'body' => $this->getBody($order)
);
}
public function getHeaders($request_type) {
$headers = array(
'Content-Type' => 'application/json',
'Authorization' => $this->getApiBearer(),
'X-Product' => 'kkart',
);
if($request_type === 'refund'){
return apply_filters( 'yoco_payment_gateway/refund/request/headers', $headers );
}
return apply_filters( 'yoco_payment_gateway/payment/request/headers', $headers );
}
public function getHeadersForMode($order) {
$headers = array(
'Content-Type' => 'application/json',
'Authorization' => $this->getApiBearer( $order->get_meta( 'kkart_yoco_order_payment_mode', true ) ),
'X-Product' => 'kkart',
);
return apply_filters( 'yoco_payment_gateway/payment/request/headers', $headers );
}
public function getApiBearer( string $mode = '' ): string {
return 'Bearer ' . $this->getSecretKey( $mode );
}
public function getSecretKey( string $mode = '' ) {
$mode = ( 'live' === $mode || 'test' === $mode ) ? $mode : $this->mode;
return $this->get_option($mode . '_secret_key' ) ? $this->get_option($mode . '_secret_key' ) : '';
}
public function getBody($order) {
$body = $this->buildPayload($order);
$body = apply_filters( 'yoco_payment_gateway/payment/request/body', $body );
return json_encode( $body, JSON_UNESCAPED_SLASHES );
}
public function getOrderCheckoutPaymentUrl( string $status, $order ): string {
return add_query_arg(
array(
'kkart_yoco_checkout_status' => $status,
),
$order->get_checkout_order_received_url()
);
}
// This function checked move on to the next
public function buildPayload($order){
$payload = array();
$payload['amount'] = $this->format($order->get_total());
$payload['currency'] = $order->get_currency();
$payload['successUrl'] = $order->get_checkout_order_received_url();
$payload['cancelUrl'] = $order->get_checkout_payment_url();
$payload['failureUrl'] = $this->getOrderCheckoutPaymentUrl( 'failed', $order );
$payload['totalDiscount'] = $order->get_total_discount();
$payload['totalTaxAmount'] = $order->get_total_tax();
$payload['subtotalAmount'] = $order->get_subtotal();
if (!isset($payload['metadata'])) {
$payload['metadata'] = array();
}
$metadata = $this->buildMetadata($order);
$payload['metadata'] = array_merge($payload['metadata'], $metadata);
$payload['productType'] = 'kkart';
return $payload;
}
public function buildMetadata( KKART_Order $order ){
$custom_info = array();
$note = join(' ',
array(
__( 'order', 'kkart' ),
$order->get_id(),
__( 'from', 'kkart' ),
$order->get_billing_first_name(),
$order->get_billing_last_name(),
'(' . $order->get_billing_email() . ')',
)
);
$custom_info['billNote'] = $note;
$custom_info['customerEmailAddress'] = $order->get_billing_email();
$custom_info['customerLastName'] = $order->get_billing_last_name();
$custom_info['customerFirstName'] = $order->get_billing_first_name();
return $custom_info;
}
public function post( string $url, array $args ){
if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
throw new Exception( __( 'Invalid URL for POST request.', 'yoco_wc_payment_gateway' ) );
}
$response = wp_remote_post( $url, $args );
if ( is_wp_error( $response ) ) {
throw new Exception( $response->get_error_message(), 0 );
}
$code = wp_remote_retrieve_response_code( $response );
//start from here
$message = wp_remote_retrieve_response_message( $response );
$body = wp_remote_retrieve_body( $response );
return array(
'code' => $code,
'message' => $message,
'body' => (array) json_decode( $body ),
);
}
public function format( $value, array $options = array() ): int {
$options = wp_parse_args(
$options,
array(
'decimals' => 2,
'rounding_mode' => PHP_ROUND_HALF_UP,
)
);
$decimals = absint( $options['decimals'] );
$rounding_mode = min( absint( $options['rounding_mode'] ), 4 );
return intval( round( ( (float) kkart_format_decimal( $value ) ) * ( 10 ** $decimals ), 0, $rounding_mode ) );
}
public function get_credential(){
$cred_arr = array();
$cred_arr['livePublic'] = $this->mode === "live" ? $this->get_option( 'live_public_key', '' ) : '';
$cred_arr['liveSecret'] = $this->mode === "live" ? $this->get_option( 'live_secret_key', '' ) : '';
$cred_arr['testPublic'] = $this->mode === "test" ? $this->get_option( 'test_public_key', '' ) : '';
$cred_arr['testSecret'] = $this->mode === "test" ? $this->get_option( 'test_secret_key', '' ) : '';
return $cred_arr;
}
public function check_requirements() {
$errors = [];
// Check if the store currency is supported by YOCO
if (!in_array(get_kkart_currency(), $this->available_currencies)) {
$errors[] = 'kkart-gateway-yoco-error-invalid-currency';
}
if ($this->mode === 'live' && empty($this->get_option('live_secret_key'))) {
$errors[] = 'kkart-gateway-yoco-error-missing-livekey';
}
if ($this->mode === 'test' && empty($this->get_option('test_secret_key'))) {
$errors[] = 'kkart-gateway-yoco-error-missing-testkey';
}
if ($this->mode === 'test' && !preg_match('/^sk_test/', $this->get_option('test_secret_key'))) {
$errors[] = 'kkart-gateway-yoco-error-wrong-testkey';
}
if ($this->mode === 'live' && !preg_match('/^sk_live/', $this->get_option('live_secret_key'))) {
$errors[] = 'kkart-gateway-yoco-error-wrong-livekey';
}
if($this->is_rest_api_enabled() === false){
$errors[] = 'rest-api-disabled';
}
return array_filter( $errors );
}
public function is_rest_api_enabled(){
$rest_api = get_option('sitepad_rest_api');
if(empty($rest_api)){
return false;
}
return true;
}
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-YOCO-admin-notice-transient' ) ) {
set_transient( 'kkart-gateway-YOCO-admin-notice-transient', 1, 1);
echo '
' . __( 'To use YOCO as a payment provider, you need to fix the problems below:', 'kkart' ) . '
' . '