Skip to content

Commit

Permalink
Release 2.1.0
Browse files Browse the repository at this point in the history
- adding inline single step checkout
- adding sha2/3 support to IPN and API
  • Loading branch information
Craig Christenson committed Feb 12, 2024
1 parent 0591f5b commit b6b991c
Show file tree
Hide file tree
Showing 9 changed files with 588 additions and 475 deletions.
8 changes: 5 additions & 3 deletions extras/twocheckout_api/ipn.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@
require_once( DIR_WS_MODULES . 'payment/twocheckout/twocheckout_api_helper.php' );
$helper = new twocheckout_api_helper();
$params = $_POST;
$hash = $helper->extractHashFromParams($params);
$secretKey = MODULE_PAYMENT_TWOCHECKOUT_API_SECRET_KEY;

if (!isset($params['REFNOEXT']) || (!isset($params['REFNO']) || empty($params['REFNO']))) {
throw new Exception(sprintf('Cannot identify order: "%s".', $params['REFNOEXT']));
}
// ignore all other payment methods
if ($helper->is_2payjs_order($params['REFNO'])) {
if (!$helper->isIpnResponseValid($params)) {
throw new Exception(sprintf('MD5 hash mismatch for 2Checkout IPN with date: "%s".', $params['IPN_DATE']));
if (!$helper->isIpnResponseValid($params, $secretKey, $hash)) {
exit(sprintf('Hash mismatch for 2Checkout IPN with date: "%s".', $params['IPN_DATE']));
}
$helper->processOrderStatus($params);
echo $helper->calculateIpnResponse($params);
echo $helper->calculateIpnResponse($params, $secretKey, $hash['algo']);
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,26 @@
if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST') {
exit('Not allowed');
}
chdir('../..');
require_once('includes/application_top.php');
require_once('includes/modules/payment/twocheckout/twocheckout_cplus_helper.php');
$helper = new twocheckout_cplus_helper();
$params = $_POST;
$hash = $helper->extractHashFromParams($params);
$secretKey = MODULE_PAYMENT_TWOCHECKOUT_CONVERT_PLUS_SECRET_KEY;

if (!isset($params['REFNOEXT']) || (!isset($params['REFNO']) || empty($params['REFNO']))) {
throw new Exception(sprintf('Cannot identify order: "%s".', $params['REFNOEXT']));
}
// ignore all other payment methods
if ($helper->is_cplus_order($params['REFNO'])) {

if (!$helper->isIpnResponseValid($params)) {
throw new Exception(sprintf('MD5 hash mismatch for 2Checkout IPN with date: "%s".', $params['IPN_DATE']));
if (!$helper->isIpnResponseValid($params, $secretKey, $hash)) {
throw new Exception(sprintf('Hash mismatch for 2Checkout IPN with date: "%s".', $params['IPN_DATE']));
}

$helper->processOrderStatus($params);

echo $helper->calculateIpnResponse($params);
echo $helper->calculateIpnResponse($params, $secretKey, $hash['algo']);
}

10 changes: 6 additions & 4 deletions twocheckout_inline_ipn.php → extras/twocheckout_inline/ipn.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,27 @@
if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST') {
exit('Not allowed');
}

chdir('../..');
require_once('includes/application_top.php');
require_once('includes/modules/payment/twocheckout/twocheckout_inline_helper.php');
$helper = new twocheckout_inline_helper(false);
$params = $_POST;
$hash = $helper->extractHashFromParams($params);
$secretKey = MODULE_PAYMENT_TWOCHECKOUT_INLINE_SECRET_KEY;

if (!isset($params['REFNOEXT']) || (!isset($params['REFNO']) || empty($params['REFNO']))) {
throw new Exception(sprintf('Cannot identify order: "%s".', $params['REFNOEXT']));
}
// ignore all other payment methods
if ($helper->is_tco_inline_order($params['REFNO'])) {

if (!$helper->isIpnResponseValid($params)) {
throw new Exception(sprintf('MD5 hash mismatch for 2Checkout IPN with date: "%s".', $params['IPN_DATE']));
if (!$helper->isIpnResponseValid($params, $secretKey, $hash)) {
exit(sprintf('Hash mismatch for 2Checkout IPN with date: "%s".', $params['IPN_DATE']));
}

$helper->processOrderStatus($params);

echo $helper->calculateIpnResponse($params);
echo $helper->calculateIpnResponse($params, $secretKey, $hash['algo']);
exit();
}

123 changes: 77 additions & 46 deletions includes/modules/payment/twocheckout/twocheckout_api_helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function is_2payjs_order($transaction_id)
if (!$transaction || !$transaction->fields['tco_transaction_id'] || !$transaction->fields['order_id']) {
return false;
}
$sql_order = "SELECT * FROM orders WHERE orders_id = " . $transaction->fields['order_id'];
$sql_order = "SELECT * FROM ".DB_PREFIX."orders WHERE orders_id = " . $transaction->fields['order_id'];
$order = $db->Execute($sql_order);

return $order && $order->fields['payment_module_code'] === 'twocheckout_api';
Expand All @@ -55,7 +55,7 @@ public function get_zen_order($transaction_id)
global $db;
$sql = "SELECT * FROM " . TABLE_2CHECKOUT_API . " WHERE tco_transaction_id = '" . trim($transaction_id)."'";
$transaction = $db->Execute($sql);
$sql_order = "SELECT * FROM orders WHERE orders_id = " . $transaction->fields['order_id'];
$sql_order = "SELECT * FROM ".DB_PREFIX."orders WHERE orders_id = " . $transaction->fields['order_id'];
return $db->Execute($sql_order)->fields;
}

Expand All @@ -69,11 +69,11 @@ private function getHeaders() {
}
$gmtDate = gmdate( 'Y-m-d H:i:s' );
$string = strlen( $this->sellerId ) . $this->sellerId . strlen( $gmtDate ) . $gmtDate;
$hash = hash_hmac( 'md5', $string, $this->secretKey );
$hash = hash_hmac( 'sha3-256', $string, $this->secretKey );

$headers[] = 'Content-Type: application/json';
$headers[] = 'Accept: application/json';
$headers[] = 'X-Avangate-Authentication: code="' . $this->sellerId . '" date="' . $gmtDate . '" hash="' . $hash . '"';
$headers[] = 'X-Avangate-Authentication: code="' . $this->sellerId . '" date="' . $gmtDate . '" hash="' . $hash . '" algo="sha3-256"';

return $headers;
}
Expand Down Expand Up @@ -279,18 +279,18 @@ public function getJsonResponseFromApi( $api_response, $success_url ) {
return $json_response;
}

/**
* @param $params
* @param $secretKey
* @return bool
*/
public function isIpnResponseValid($params)
/**
* @param $params
* @param $secretKey
* @param array $hash [algo, hash]
* @return bool
*/
public function isIpnResponseValid($params, $secretKey, $hash)
{
$result = '';
$secretKey = MODULE_PAYMENT_TWOCHECKOUT_API_SECRET_KEY;
$receivedHash = $params['HASH'];
$receivedHash = $hash['hash'];
foreach ($params as $key => $val) {
if ($key != "HASH") {
if (!in_array($key, ["HASH", "SIGNATURE_SHA2_256", "SIGNATURE_SHA3_256"])) {
if (is_array($val)) {
$result .= $this->arrayExpand($val);
} else {
Expand All @@ -301,7 +301,7 @@ public function isIpnResponseValid($params)
}

if (isset($params['REFNO']) && !empty($params['REFNO'])) {
$calcHash = $this->hmac($secretKey, $result);
$calcHash = $this->hmac($secretKey, $result, $hash['algo']);
if ($receivedHash === $calcHash) {
return true;
}
Expand All @@ -310,15 +310,15 @@ public function isIpnResponseValid($params)
return false;
}

/**
* @param $ipnParams
* @param $secret_key
* @return string
*/
public function calculateIpnResponse($ipnParams)
/**
* @param array $ipnParams
* @param string $secret_key
* @param string $algo
* @return string
*/
public function calculateIpnResponse($ipnParams, $secret_key, $algo='sha3-256')
{
$resultResponse = '';
$secret_key = MODULE_PAYMENT_TWOCHECKOUT_API_SECRET_KEY;
$ipnParamsResponse = [];
// we're assuming that these always exist, if they don't then the problem is on 2CO side
$ipnParamsResponse['IPN_PID'][0] = $ipnParams['IPN_PID'][0];
Expand All @@ -330,11 +330,19 @@ public function calculateIpnResponse($ipnParams)
$resultResponse .= $this->arrayExpand((array)$val);
}

return sprintf(
'<EPAYMENT>%s|%s</EPAYMENT>',
$ipnParamsResponse['DATE'],
$this->hmac($secret_key, $resultResponse)
);
if ('md5' === $algo)
return sprintf(
'<EPAYMENT>%s|%s</EPAYMENT>',
$ipnParamsResponse['DATE'],
$this->hmac($secret_key, $resultResponse, $algo)
);
else
return sprintf(
'<sig algo="%s" date="%s">%s</sig>',
$algo,
$ipnParamsResponse['DATE'],
$this->hmac($secret_key, $resultResponse, $algo)
);
}

/**
Expand All @@ -353,26 +361,31 @@ private function arrayExpand($array)
return $result;
}

/**
* @param $key
* @param $data
* @return string
*/
private function hmac($key, $data)
{
$b = 64; // byte length for md5
if (strlen($key) > $b) {
$key = pack("H*", md5($key));
}

$key = str_pad($key, $b, chr(0x00));
$ipad = str_pad('', $b, chr(0x36));
$opad = str_pad('', $b, chr(0x5c));
$k_ipad = $key ^ $ipad;
$k_opad = $key ^ $opad;

return md5($k_opad . pack("H*", md5($k_ipad . $data)));
}
/**
* @param $key
* @param $data
* @params $algo
* @return string
*/
private function hmac($key, $data, $algo='sha3-256')
{
if ('sha3-256' === $algo) {
return hash_hmac($algo, $data, $key);
}

$b = 64; // byte length for hash
if (strlen($key) > $b) {
$key = pack("H*", hash($algo, $key));
}

$key = str_pad($key, $b, chr(0x00));
$ipad = str_pad('', $b, chr(0x36));
$opad = str_pad('', $b, chr(0x5c));
$k_ipad = $key ^ $ipad;
$k_opad = $key ^ $opad;

return hash($algo, $k_opad . pack("H*", hash($algo, $k_ipad . $data)));
}

/**
* @param $params
Expand Down Expand Up @@ -449,4 +462,22 @@ private function isChargeBack($params, $order)
return false;
}

/**
* @params array $params
* @return array [hash, algo]
*/
public function extractHashFromParams($params): array {
if (!empty($params['SIGNATURE_SHA3_256'])) {
$receivedAlgo = 'sha3-256';
$receivedHash = $params['SIGNATURE_SHA3_256'];
} elseif (!empty($params['SIGNATURE_SHA2_256'])) {
$receivedAlgo = 'sha256';
$receivedHash = $params['SIGNATURE_SHA2_256'];
} else {
$receivedAlgo = 'md5';
$receivedHash = $params['HASH'];
}

return ['hash' => $receivedHash, 'algo' => $receivedAlgo];
}
}
Loading

0 comments on commit b6b991c

Please sign in to comment.