Skip to content

Commit

Permalink
Update XEveryPayWebhookAction.php
Browse files Browse the repository at this point in the history
  • Loading branch information
erikuus committed Jan 9, 2025
1 parent 2cfcdea commit 1e46d76
Showing 1 changed file with 68 additions and 64 deletions.
132 changes: 68 additions & 64 deletions components/everypay/XEveryPayWebhookAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
/**
* XEveryPayWebhookAction class file.
*
* XEveryPayWebhookAction handles webhook POST requests from EveryPay
* and passes the decoded data to a specified callback function in the controller.
* This action handles EveryPay webhook POST requests (form-encoded data),
* validates the payment status using the specified EveryPay component,
* and calls success or failure callbacks based on the result.
*
* Usage example in your controller:
*
Expand All @@ -12,131 +13,134 @@
* return array(
* 'everypayWebhook'=>array(
* 'class'=>'ext.components.everypay.XEveryPayWebhookAction',
* 'successCallback'=>'handleEveryPaySuccess',
* 'failureCallback'=>'handleEveryPayFailure',
* // optional secret token for verifying the request
* // 'secretToken' => 'abc123',
* 'log'=>true,
* 'logCategory'=>'payment.everypay.webhook'
* )
* 'componentName'=>'creditcard', // name of the EveryPay component in Yii config
* 'successCallback'=>'handleEveryPayPaymentSuccess', // method in controller to call on success
* 'failureCallback'=>'handleEveryPayPaymentFailure', // method in controller to call on failure
* ),
* );
* }
*
* Then define handleEveryPaySuccess($data) and handleEveryPayFailure() in your controller.
* public function handleEveryPayPaymentSuccess($data)
* {
* // Handle successful payment logic here.
* }
*
* @link https://support.every-pay.com/
* @author Erik Uus <[email protected]>
* @version 1.0.0
* public function handleEveryPayPaymentFailure()
* {
* // Handle failure logic here.
* }
*/
class XEveryPayWebhookAction extends CAction
{
/**
* @var string $successCallback The name of the controller method called on success.
* The method should accept 1 parameter: an associative array of the decoded request data.
* @var string The name of the EveryPay component as configured in Yii (e.g., 'everypay').
*/
public $componentName='everypay';

/**
* @var string $successCallback The controller method called on successful validation.
* This method should accept one parameter: the array of parsed POST data.
*/
public $successCallback;

/**
* @var string $failureCallback The name of the controller method called on failure.
* @var string $failureCallback The controller method called on failure.
*/
public $failureCallback;

/**
* @var bool $log Whether to log events and errors.
* Defaults to false.
*/
public $log=false;

/**
* @var string $logLevel The level for log messages (trace, info, warning, error, etc.).
* Defaults to 'error'.
* @var string $logLevel The log level (e.g., 'trace', 'info', 'error').
*/
public $logLevel='error';

/**
* @var string $logCategory The category for log messages.
* Defaults to 'ext.components.everypay.XEveryPayWebhookAction'.
*/
public $logCategory='ext.components.everypay.XEveryPayWebhookAction';

/**
* @var string|null $secretToken (Optional) If set, we check for a matching header, param, or
* something else as a basic form of verification that this request is from EveryPay.
* Adjust usage below to match how you want to verify authenticity.
* @var string $logCategory The category for logging messages.
*/
public $secretToken;
public $logCategory = 'ext.components.everypay.XEveryPayWebhookAction';

/**
* Run the action: parse EveryPay webhook request, call success or failure callback.
* Main entry point: parse POST data, validate payment, call success/failure callbacks.
*/
public function run()
{
// Read the request body
$rawBody = @file_get_contents('php://input');
$this->log('EveryPay webhook raw body: ' . $rawBody, $this->logLevel);
// Read the raw POST body
$rawBody=@file_get_contents('php://input');
$this->log('EveryPay webhook raw body: ' . $rawBody, 'trace');

// Optionally verify the request came from EveryPay checking a "secret token" in GET param
if($this->secretToken!==null)
{
$token=Yii::app()->request->getParam('token');
if($token!==$this->secretToken)
{
$this->log('Invalid token in EveryPay webhook request!', $this->logLevel);
$this->handleFailure();
http_response_code(400);
return;
}
}
// Parse the form-encoded data
$decoded=array();
parse_str($rawBody, $decoded);
$this->log('EveryPay webhook parsed form data: ' . var_export($decoded, true), 'trace');

// Decode JSON
$decoded=json_decode($rawBody, true);
if(json_last_error()!==JSON_ERROR_NONE)
// Check for required parameters
if(empty($decoded['payment_reference']) && empty($decoded['order_reference']))
{
$this->log('EveryPay webhook JSON parse error: ' . json_last_error_msg(), $this->logLevel);
$this->log('EveryPay webhook missing payment_reference/order_reference', 'error');
$this->handleFailure();
// Send an HTTP status code
http_response_code(400);
return;
}

// Additional checks on the structure
if(empty($decoded['payment_reference']) && empty($decoded['order_reference']))
// Set request parameters so validatePayment() can find them
if(isset($decoded['payment_reference']))
$_REQUEST['payment_reference'] = $decoded['payment_reference'];

if(isset($decoded['order_reference']))
$_REQUEST['order_reference'] = $decoded['order_reference'];

// Get the configured EveryPay component
$everyPay=Yii::app()->getComponent($this->componentName);
if(!$everyPay)
{
$this->log('EveryPay webhook missing payment_reference/order_reference', $this->logLevel);
$this->log("No EveryPay component found with name '{$this->componentName}'!", 'error');
$this->handleFailure();
http_response_code(400);
http_response_code(500);
return;
}

// Call success callback
if($this->successCallback && method_exists($this->controller, $this->successCallback))
// Validate payment using the component
if ($everyPay->validatePayment())
{
$this->controller->{$this->successCallback}($decoded);
// Payment is settled
if($this->successCallback && method_exists($this->controller, $this->successCallback))
$this->controller->{$this->successCallback}($everyPay->statusResponse);
else
$this->log('No successCallback defined or not callable', $this->logLevel);

http_response_code(200);
}
else
{
$this->log('No success callback defined or not callable', $this->logLevel);
// Typically return 200 so EveryPay does not keep retrying infinitely.
// Payment validation failed or not settled
$this->log('EveryPay payment validation failed: ' . $everyPay->errorMessage, $this->logLevel);
$this->handleFailure();
http_response_code(200);
}
}

/**
* Logs a message if logging is enabled.
* Helper to log messages if logging is enabled.
* @param string $message
* @param string $level
*/
protected function log($message, $level='info')
protected function log($message, $level = 'info')
{
if($this->log===true)
if($this->log === true)
Yii::log($message, $level, $this->logCategory);
}

/**
* Handle failures by calling the failure callback if defined.
* Calls the failure callback if defined.
*/
protected function handleFailure()
{
if($this->failureCallback && method_exists($this->controller, $this->failureCallback))
if ($this->failureCallback && method_exists($this->controller, $this->failureCallback)) {
$this->controller->{$this->failureCallback}();
}
}
}

0 comments on commit 1e46d76

Please sign in to comment.