Files
drmaterac.pl/modules/przelewy24/classes/Przelewy24Class.php
2025-01-06 20:47:25 +01:00

682 lines
20 KiB
PHP

<?php
/**
* Przelewy24 comunication class
* Communication protol version
*
* @author Przelewy24
* @copyright Przelewy24
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html
*/
define('P24_VERSION', '3.2');
if (!class_exists('Przelewy24Class', false)) {
class Przelewy24Class implements
Przelewy24ClassInterface,
Przelewy24ClassBlikInterface,
Przelewy24ClassStaticInterface
{
/**
* Live system URL address.
*
* @var string
*/
private static $hostLive = 'https://secure.przelewy24.pl/';
/**
* Sandbox system URL address.
*
* @var string
*/
private static $hostSandbox = 'https://sandbox.przelewy24.pl/';
/**
* Use Live (false) or Sandbox (true) environment.
*
* @var bool
*/
private $testMode = false;
/**
* Merchant Id.
*
* @var int
*/
private $merchantId = 0;
/**
* Merchant posId.
*
* @var int
*/
private $posId = 0;
/**
* Salt to create a control sum (from P24 panel).
*
* @var string
*/
private $salt = '';
/**
* Array of POST data.
*
* @var array
*/
private $postData = array();
/**
* Minimal amount for single installment.
*
* @var int
*/
private static $minInstallmentValue = 300;
/**
* Obcject constructor. Set initial parameters.
*
* @param int $merchantId
* @param int $posId
* @param string $salt
* @param bool $testMode
*/
public function __construct($merchantId, $posId, $salt, $testMode = false)
{
$this->posId = (int)trim($posId);
$this->merchantId = (int)trim($merchantId);
if (0 === $this->merchantId) {
$this->merchantId = $this->posId;
}
$this->salt = trim($salt);
$this->testMode = $testMode;
$this->addValue('p24_merchant_id', $this->merchantId);
$this->addValue('p24_pos_id', $this->posId);
$this->addValue('p24_api_version', P24_VERSION);
}
/**
* Returns host URL.
*
* @return string
*/
public function getHost()
{
return self::getHostForEnvironment($this->testMode);
}
/**
* Returns host URL For Environmen
*
* @param bool $isTestMode
* @return string
*/
public static function getHostForEnvironment($isTestMode = false)
{
return $isTestMode ? self::$hostSandbox : self::$hostLive;
}
/**
* Get min installment value
*
* @return int
*/
public static function getMinInstallmentValue()
{
return self::$minInstallmentValue;
}
/**
* Returns URL for direct request (trnDirect).
*
* @return string
*/
public function trnDirectUrl()
{
return $this->getHost() . 'trnDirect';
}
/**
* Adds value do post request.
*
* @param string $name Argument name.
* @param int|string|bool $value Argument value.
*/
public function addValue($name, $value)
{
if ($this->validateField($name, $value)) {
$this->postData[$name] = $value;
}
}
/**
* Method is testing a connection with P24 server.
*
* @return array Array(INT Error, Array Data), where data.
*
* @throws Exception
*/
public function testConnection()
{
$arg = array();
$crc = md5($this->posId . "|" . $this->salt);
$arg["p24_pos_id"] = $this->posId;
$arg["p24_sign"] = $crc;
$arg["p24_merchant_id"] = $this->merchantId;
$res = $this->callUrl("testConnection", $arg);
return $res;
}
/**
* Prepare a transaction request.
*
* @param bool $redirect Set true to redirect to Przelewy24 after transaction registration.
*
* @return array array(INT Error code, STRING Token).
*
* @throws Exception
*/
public function trnRegister($redirect = false)
{
$crc = md5(
$this->postData["p24_session_id"] . "|"
. $this->posId . "|" . $this->postData["p24_amount"] . "|"
. $this->postData["p24_currency"] . "|"
. $this->salt
);
$this->addValue("p24_sign", $crc);
$res = $this->callUrl("trnRegister", $this->postData);
if ('0' === (string)$res["error"]) {
$token = $res["token"];
} else {
return $res;
}
if ($redirect) {
$this->trnRequest($token);
}
return array("error" => 0, "token" => $token);
}
/**
* Redirects or returns URL to a P24 payment screen.
*
* @param string $token Token
* @param bool $redirect If set to true redirects to P24 payment screen.
* If set to false function returns URL to redirect to P24 payment screen.
*
* @return string URL to P24 payment screen
*/
public function trnRequest($token, $redirect = true)
{
$token = Tools::substr($token, 0, 100);
$url = $this->getHost() . 'trnRequest/' . $token;
if ($redirect) {
Tools::redirect($url);
return '';
}
return $url;
}
/**
* Function verify received from P24 system transaction's result.
*
* @return array
*
* @throws Exception
*/
public function trnVerify()
{
$crc = md5(
$this->postData["p24_session_id"] . "|"
. $this->postData["p24_order_id"] . "|"
. $this->postData["p24_amount"] . "|"
. $this->postData["p24_currency"] . "|"
. $this->salt
);
$this->addValue('p24_sign', $crc);
$res = $this->callUrl('trnVerify', $this->postData);
return $res;
}
/**
* Method is used to communicate with P24 system.
*
* @param string $function Method name.
* @param array $ARG POST parameters.
*
* @return array array(INT Error code, ARRAY Result).
*
* @throws Exception
*/
private function callUrl($function, $ARG)
{
if (!in_array($function, array('trnRegister', 'trnRequest', 'trnVerify', 'testConnection'))) {
return array('error' => 201, 'errorMessage' => 'class:Method not exists');
}
if ('testConnection' !== $function) {
$this->checkMandatoryFieldsForAction($ARG, $function);
}
$REQ = array();
foreach ($ARG as $k => $v) {
$REQ[] = $k . "=" . urlencode($v);
}
$url = $this->getHost() . $function;
$user_agent = 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)';
if (($ch = curl_init())) {
if (count($REQ)) {
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, join("&", $REQ));
}
curl_setopt($ch, CURLOPT_URL, $url);
if ($this->testMode) {
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
} else {
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
}
curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
if (($result = curl_exec($ch))) {
$info = curl_getinfo($ch);
curl_close($ch);
if (200 !== (int)$info['http_code']) {
return array(
'error' => 200,
'errorMessage' => 'call:Page load error (' . $info['http_code'] . ')',
);
} else {
$res = array();
$X = explode('&', $result);
foreach ($X as $val) {
$Y = explode('=', $val);
$res[trim($Y[0])] = urldecode(trim($Y[1]));
}
return $res;
}
} else {
curl_close($ch);
return array('error' => 203, 'errorMessage' => 'call:Curl exec error');
}
} else {
return array('error' => 202, 'errorMessage' => 'call:Curl init error');
}
}
/**
* Validate api version.
*
* @param string $version
*
* @return bool
*/
private function validateVersion(&$version)
{
if (preg_match('/^[0-9]+(?:\.[0-9]+)*(?:[\.\-][0-9a-z]+)?$/', $version)) {
return true;
}
$version = '';
return false;
}
/**
* Validate email.
*
* @param string $email
* @return bool
*/
private function validateEmail(&$email)
{
if (($email = filter_var($email, FILTER_VALIDATE_EMAIL))) {
return true;
}
$email = '';
return false;
}
/**
* Validate number.
*
* @param string|float|int $value
* @param bool $min
* @param bool $max
*
* @return bool
*/
private function validateNumber(&$value, $min = false, $max = false)
{
if (is_numeric($value)) {
$value = (int)$value;
if ((false !== $min && $value < $min) || (false !== $max && $value > $max)) {
return false;
}
return true;
}
$value = (false !== $min ? $min : 0);
return false;
}
/**
* Validate string.
*
* @param string $value
* @param int $len
*
* @return bool
*/
private function validateString(&$value, $len = 0)
{
$len = (int)$len;
if (preg_match("/<[^<]+>/", $value, $m) > 0) {
return false;
}
if (0 === $len ^ Tools::strlen($value) <= $len) {
return true;
}
$value = '';
return false;
}
private function validateUrl(&$url, $len = 0)
{
$len = (int)$len;
if (0 === $len ^ Tools::strlen($url) <= $len) {
if (preg_match('@^https?://[^\s/$.?#].[^\s]*$@iS', $url)) {
return true;
}
}
$url = '';
return false;
}
/**
* Validate enum.
*
* @param string $value Provided value.
* @param string[] $haystack Array of valid values.
*
* @return bool
*/
private function validateEnum(&$value, $haystack)
{
if (in_array(Tools::strtolower($value), $haystack)) {
return true;
}
$value = $haystack[0];
return false;
}
/**
* Validate field.
*
* @param string $field
* @param mixed &$value
*
* @return boolean
*/
public function validateField($field, &$value)
{
$ret = false;
switch ($field) {
case 'p24_session_id':
$ret = $this->validateString($value, 100);
break;
case 'p24_description':
$ret = $this->validateString($value, 1024);
break;
case 'p24_address':
$ret = $this->validateString($value, 80);
break;
case 'p24_country':
case 'p24_language':
$ret = $this->validateString($value, 2);
break;
case 'p24_client':
case 'p24_city':
$ret = $this->validateString($value, 50);
break;
case 'p24_merchant_id':
case 'p24_pos_id':
case 'p24_order_id':
case 'p24_amount':
case 'p24_method':
case 'p24_time_limit':
case 'p24_channel':
case 'p24_shipping':
$ret = $this->validateNumber($value);
break;
case 'p24_wait_for_result':
$ret = $this->validateNumber($value, 0, 1);
break;
case 'p24_api_version':
$ret = $this->validateVersion($value);
break;
case 'p24_sign':
if ((32 === Tools::strlen($value)) && ctype_xdigit($value)) {
$ret = true;
} else {
$value = '';
}
break;
case 'p24_url_return':
case 'p24_url_status':
$ret = $this->validateUrl($value, 250);
break;
case 'p24_currency':
$ret = preg_match('/^[A-Z]{3}$/', $value);
if (!$ret) {
$value = '';
}
break;
case 'p24_email':
$ret = $this->validateEmail($value);
break;
case 'p24_encoding':
$ret = $this->validateEnum($value, array('iso-8859-2', 'windows-1250', 'urf-8', 'utf8'));
break;
case 'p24_transfer_label':
$ret = $this->validateString($value, 20);
break;
case 'p24_phone':
$ret = $this->validateString($value, 12);
break;
case 'p24_zip':
$ret = $this->validateString($value, 10);
break;
default:
if ((0 === strpos($field, 'p24_quantity_')) ||
(0 === strpos($field, 'p24_price_')) ||
(0 === strpos($field, 'p24_number_'))
) {
$ret = $this->validateNumber($value);
} elseif ((0 === strpos($field, 'p24_name_'))
|| (0 === strpos($field, 'p24_description_'))) {
$ret = $this->validateString($value, 127);
} else {
$value = '';
}
break;
}
return $ret;
}
/**
* Filter value.
*
* @param string $field
* @param string $value
*
* @return bool|string
*/
private function filterValue($field, $value)
{
return $this->validateField($field, $value) ? addslashes($value) : false;
}
/**
* Check mandatory fields for action.
*
* @param array $fieldsArray
* @param string $action
*
* @return bool
*
* @throws Exception
*/
public function checkMandatoryFieldsForAction($fieldsArray, $action)
{
$keys = array_keys($fieldsArray);
$verification = ('trnVerify' === $action);
static $mandatory = array(
'p24_order_id',//verify
'p24_sign',
'p24_merchant_id',
'p24_pos_id',
'p24_api_version',
'p24_session_id',
'p24_amount',//all
'p24_currency',
'p24_description',
'p24_country',
'p24_url_return',
'p24_currency',
'p24_email',
);
for ($i = ($verification ? 0 : 1); $i < ($verification ? 4 : count($mandatory)); $i++) {
if (!in_array($mandatory[$i], $keys)) {
throw new Exception('Field ' . $mandatory[$i] . ' is required for ' . $action . ' request!');
}
}
return true;
}
/**
* Parse and validate POST response data from Przelewy24.
*
* @return array - valid response | false - invalid crc | null - not a Przelewy24 response
*/
public function parseStatusResponse()
{
if (Tools::getIsset(
'p24_session_id',
'p24_order_id',
'p24_merchant_id',
'p24_pos_id',
'p24_amount',
'p24_currency',
'p24_method',
'p24_sign'
)) {
$session_id = $this->filterValue('p24_session_id', Tools::getValue('p24_session_id'));
$merchant_id = $this->filterValue('p24_merchant_id', Tools::getValue('p24_merchant_id'));
$pos_id = $this->filterValue('p24_pos_id', Tools::getValue('p24_pos_id'));
$order_id = $this->filterValue('p24_order_id', Tools::getValue('p24_order_id'));
$amount = $this->filterValue('p24_amount', Tools::getValue('p24_amount'));
$currency = $this->filterValue('p24_currency', Tools::getValue('p24_currency'));
$method = $this->filterValue('p24_method', Tools::getValue('p24_method'));
$sign = $this->filterValue('p24_sign', Tools::getValue('p24_sign'));
if (((int)$merchant_id !== (int)$this->merchantId) ||
((int)$pos_id !== (int)$this->posId) ||
(md5(
$session_id .
'|' .
$order_id .
'|' .
$amount .
'|' .
$currency .
'|' .
$this->salt
) !== $sign)) {
return false;
}
return array(
'p24_session_id' => $session_id,
'p24_order_id' => $order_id,
'p24_amount' => $amount,
'p24_currency' => $currency,
'p24_method' => $method,
);
}
return null;
}
/**
* Verifies data received in ping from P24.
*
* @param array|null $data
*
* @return bool|null null - status could not be parsed, true - data valid, false - data invalid.
*
* @throws Exception
*/
public function trnVerifyEx($data = null)
{
$response = $this->parseStatusResponse();
if (null === $response) {
return null;
} elseif ($response) {
if (is_array($data)) {
foreach ($data as $field => $value) {
if ((string)$response[$field] !== (string)$value) {
return false;
}
}
}
$this->postData = array_merge($this->postData, $response);
$result = $this->trnVerify();
return ('0' === (string)$result['error']);
}
return false;
}
/**
* Return direct sign.
*
* @param array $data
*
* @return string
*/
public function trnDirectSign($data)
{
return md5(
$data['p24_session_id'] . '|'
. $this->posId . '|'
. $data['p24_amount'] . '|'
. $data['p24_currency'] . '|'
. $this->salt
);
}
}
}