Hacked By AnonymousFox
<?php
/**
* REST API YOCO PAYMENTS controller
*
* Handles requests to the yoco/webhook endpoint.
*
* @package Kkart\RestApi
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API Order Notes controller class.
*
* @package Kkart\RestApi
*/
class KKART_REST_Yoco_Webhook_Controller extends KKART_REST_Controller {
protected $namespace = 'kkart/v3';
protected $rest_base = 'webhook';
public function register_routes(){
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
'methods'=> 'GET, POST',
'callback'=> array( $this, 'yoco_webhook_handler' ),
'permission_callback' => array( $this, 'permit' ),
) );
}
public function yoco_webhook_handler($request){
$body = $request->get_params();
$eventType = isset( $body['type'] ) && ! empty( $body['type'] ) ? $body['type'] : '';
$checkout_id = isset( $body['payload']['metadata']['checkoutId'] ) ? $body['payload']['metadata']['checkoutId'] : '';
$payment_id = isset( $body['payload']['id'] ) ? $body['payload']['id'] : '';
if($eventType == 'payment.succeeded'){
$this->update_payment_status($checkout_id, $payment_id);
}
if($eventType === 'refund.failed'){
$this->update_refund_failed($checkout_id, $payment_id);
}
if($eventType === 'refund.succeeded'){
$this->update_refund_succeeded($checkout_id, $payment_id);
}
}
public function update_payment_status($checkout_id, $payment_id){
$args = array(
'meta_key' => 'kkart_yoco_order_checkout_id',
'meta_value' => $checkout_id,
'meta_compare' => "=",
);
$orders = kkart_get_orders($args);
if( empty( $orders ) ){
return null;
}
$order = array_shift( $orders );
$order_status = is_a( $order, KKART_Order::class ) ? $order : null;
if( null === $order_status ){
return new WP_REST_Response(
array(
'description' => sprintf( 'No order found for CheckoutId %s.', $checkout_id ),
),
404,
);
}
if( true === $order_status->update_status( 'processing' ) ){
$order->update_meta_data( 'kkart_yoco_order_payment_id', $payment_id );
$order->save_meta_data();
return new WP_REST_Response();
}else{
return new WP_REST_Response(
array(
'description' => sprintf( 'Failed to complete payment of order #%s.', $order->get_id() ),
),
500,
);
}
}
public function update_refund_succeeded($checkout_id, $payment_id){
$args = array(
'meta_key' => 'kkart_yoco_order_checkout_id',
'meta_value' => $checkout_id,
'meta_compare' => "=",
);
$orders = kkart_get_orders($args);
if( empty( $orders ) ){
return null;
}
$order = array_shift( $orders );
$order_status = is_a( $order, KKART_Order::class ) ? $order : null;
if( null === $order_status ){
return new WP_REST_Response(
array(
'description' => sprintf( 'Could not find the order for checkout id %s.', $checkout_id ),
),
403,
);
}
if( 'refunded' === $order_status->get_status() ){
return new WP_REST_Response(
array(
'description' => sprintf( 'Order for checkout id %s is already refunded.', $checkout_id ),
),
403,
);
}
try{
$refund = $this->refund_amc($order_status);
if( null === $refund ){
return new WP_REST_Response();
}
if( 'completed' === $refund->get_status() ){
$order_status->update_meta_data( 'kkart_yoco_order_payment_id', $payment_id );
$order_status->save_meta_data();
return new WP_REST_Response();
}
return new WP_REST_Response(
array(
'description' => sprintf( 'Failed to complete refund of order #%s - wrong order status.', $order_status->get_id() ),
),
403,
);
}catch (\Throwable $th){
return new WP_REST_Response(
array(
'description' => sprintf('Could not find the order for checkout id %s.', $checkout_id),
),
403
);
}
}
public function update_refund_failed($checkout_id, $payment_id){
$args = array(
'meta_key' => 'kkart_yoco_order_checkout_id',
'meta_value' => $checkout_id,
'meta_compare' => "=",
);
$orders = kkart_get_orders($args);
if( empty( $orders ) ){
return null;
}
$order = array_shift( $orders );
$order_status = is_a( $order, KKART_Order::class ) ? $order : null;
if( null === $order_status ){
return new WP_REST_Response(
array(
'description' => sprintf( 'Could not find the order for checkout id %s.', $checkout_id ),
),
403,
);
}
if ( 'refunded' === $order_status->get_status() ) {
return new WP_REST_Response(
array(
'description' => sprintf( 'Order for checkout id %s is already refunded.', $checkout_id ),
),
403,
);
}
return new WP_REST_Response();
}
public function permit($request) {
$headers = array(
'webhook_id' => $request->get_header('webhook_id'),
'webhook_timestamp' => $request->get_header('webhook_timestamp'),
'webhook_signature' => $request->get_header('webhook_signature'),
);
return $this->validate($request->get_body(), $headers);
}
public function validate(string $payload, array $webhookHeaders){
try{
$this->verify($payload, $webhookHeaders);
return true;
}catch(\Throwable $th){
return false;
}
}
public function verify($payload, $headers){
if(
!isset($headers['webhook_id']) ||
!isset($headers['webhook_timestamp']) ||
!isset($headers['webhook_signature'])
){
throw new Exception('Webhook Signature Validator is missing required headers');
}
$msgId = $headers['webhook_id'];
$msgTimestamp = $headers['webhook_timestamp'];
$msgSignature = $headers['webhook_signature'];
$timestamp = $this->verifyTimestamp($msgTimestamp);
$signature = $this->sign($msgId, $timestamp, $payload);
$expectedSignature = explode(',', $signature, 2)[1];
$passedSignatures = explode(' ', $msgSignature);
foreach($passedSignatures as $versionedSignature){
$sigParts = explode(',', $versionedSignature, 2);
$version = $sigParts[0];
$passedSignature = $sigParts[1];
if(0 !== strcmp($version, 'v1')){
continue;
}
if(hash_equals($expectedSignature, $passedSignature)){
return json_decode($payload, true);
}
}
throw new Exception('Webhook no matching signature found');
}
public function sign(string $msgId, int $timestamp, string $payload): string {
if (!is_int($timestamp)) {
throw new Exception('Invalid timestamp format');
}
$toSign = "{$msgId}.{$timestamp}.{$payload}";
$secret = $this->secret();
$hex_hash = hash_hmac('sha256', $toSign, $secret);
$signature = base64_encode(pack('H*', $hex_hash));
return "v1,{$signature}";
}
public function secret(){
$settings = get_option( 'kkart_yoco_settings', null );
if( ! isset( $settings['mode'] ) ){
return '';
}
$key = 'live' === $settings['mode'] ? 'yoco_payment_gateway_live_webhook_secret' : 'yoco_payment_gateway_test_webhook_secret';
$SECRET_PREFIX = 'whsec_';
$secret = get_option( $key, '' );
if(substr($secret, 0, strlen($SECRET_PREFIX)) === $SECRET_PREFIX){
$secret = substr($secret, strlen($SECRET_PREFIX));
return base64_decode($secret);
}
}
public function verifyTimestamp($timestampHeader): int{
$now = time();
$timestamp = intval($timestampHeader, 10);
if ($timestamp < ($now - 5 * 60)) {
throw new Exception('Webhook timestamp is too old');
}
if ($timestamp > ($now + 5 * 60)) {
throw new Exception('Webhook timestamp is too new');
}
return $timestamp;
}
public function refund_amc($order){
if( ! empty( $refunds = $order->get_refunds() ) ){
return array_shift( $refunds );
}
$args = array(
'amount' => $order->get_total(),
'reason' => __( 'Refund requested via webhook.', 'kkart' ),
'order_id' => $order->get_id(),
'refund_payment_method' => 'yoco',
'line_items' => $order->get_items(),
);
$refund = kkart_create_refund( apply_filters( 'yoco_payment_gateway/request/refund/args', $args ) );
if( is_wp_error( $refund ) ){
throw new Error( $refund->get_error_message(), (int) $refund->get_error_code() );
}
return $refund;
}
}
?>
Hacked By AnonymousFox1.0, Coded By AnonymousFox