You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

531 lines
15 KiB

<?php
namespace app\libraries;
class Axepta
{
const PAYSSL = "https://paymentpage.axepta.bnpparibas/payssl.aspx";
const DIRECT = "https://paymentpage.axepta.bnpparibas/direct.aspx";
const DIRECT3D = "https://paymentpage.axepta.bnpparibas/direct3d.aspx";
const CAPTURE = "https://paymentpage.axepta.bnpparibas/capture.aspx";
const CREDIT = "https://paymentpage.axepta.bnpparibas/credit.aspx";
const ABO = "https://paymentpage.axepta.bnpparibas/abo.aspx";
const INSTALMENT = "INSTALMENT";
/** @var ShaComposer */
private $secretKey;
/** @var ShaComposer */
private $cryptKey;
private $pspURL = self::PAYSSL;
private $parameters = array();
/** Axepta fields **/
private $pspFields = array(
'Debug',
'PayID',
'TransID',
'MerchantID',
'Amount',
'Currency',
'MAC',
'RefNr',
'Amount3D',
'URLSuccess',
'URLFailure',
'URLNotify',
'Response',
'UserData',
'Capture',
'OrderDesc',
'ReqID',
'Plain',
'Custom',
'expirationTime',
'AccVerify',
'RTF',
'ChDesc',
'Len',
'Data',
'Template',
'Language',
'Background',
'URLBack',
'MID',
'mid',
'refnr',
'XID',
'Status',
'Description',
'Code',
'PCNr',
'CCNr',
'CCBrand',
'CCExpiry',
'AboID',
'ACSXID',
'MaskedPan',
'CAVV',
'ECI',
'DDD',
'Type',
'Plain',
'Custom',
'MsgVer',
'CustomField1','CustomField2','CustomField3','CustomField4','CustomField5','CustomField6','CustomField7',
'CustomField8','CustomField9','CustomField10','CustomField11','CustomField12','CustomField13','CustomField14'
);
/** Axepta request hmac fields **/
private $QHMACFields = array(
'PayID', 'TransID', 'MerchantID', 'Amount','Currency'
);
/** Axepta response hmac fields **/
private $RHMACFields = array(
'PayID', 'TransID', 'MerchantID', 'Status','Code'
);
/** Axepta blowfish crypt fields **/
private $BfishFields = array(
'PayID','TransID','Amount','Currency','MAC',
'RefNr','Amount3D','URLSuccess','URLFailure','URLNotify','Response','UserData','Capture','OrderDesc','ReqID',
'Plain','Custom','expirationTime','AccVerify','RTF','ChDesc',
'MID','XID','Status','Description','Code','PCNr','CCNr','CCBrand','CCExpiry',
'AboID','ACSXID','MaskedPan','CAVV','ECI','DDD','Type','Plain','Custom', 'MsgVer'
// 'CustomField1','CustomField2','CustomField3','CustomField4','CustomField5','CustomField6','CustomField7',
// 'CustomField8','CustomField9','CustomField10','CustomField11','CustomField12','CustomField13','CustomField14'
);
/** Axepta request required fields **/
private $requiredFields = array(
// 'MerchantID', 'TransID', 'Amount', 'Currency','URLSuccess','URLFailure','URLNotify','OrderDesc'
'MerchantID', 'TransID', 'Amount', 'Currency','OrderDesc'
);
public $allowedlanguages = array(
'nl', 'fr', 'de', 'it', 'es', 'cy', 'en'
);
public function __construct($secret)
{
$this->secretKey = $secret; // HMAC key
}
public function setCryptKey($secret)
{
$this->cryptKey = $secret; // blowfish crypt key
}
/** hack to retrieve response field **/
public function setReponse($encrypt='encrypt')
{
$this->parameters['Response'] = $encrypt;
}
/** HMAC compute and store in MAC field**/
public function shaCompose(array $parameters)
{
// compose SHA string
$shaString = '';
foreach($parameters as $key) {
if(array_key_exists($key, $this->parameters) && !empty($this->parameters[$key])) {
$value = $this->parameters[$key];
$shaString .= $value;
}
$shaString .= (array_search($key, $parameters) != (count($parameters)-1)) ? '*' : '';
}
$this->parameters['MAC'] = hash_hmac('sha256', $shaString, $this->secretKey);
return $this->parameters['MAC'];
}
/** @return string */
public function getShaSign()
{
$this->validate();
return $this->shaCompose($this->QHMACFields);
}
public function BfishCompose(array $parameters)
{
// compose Blowfish hex string
$blowfishString = '';
foreach($parameters as $key) {
if(array_key_exists($key, $this->parameters) && !empty($this->parameters[$key])) {
$value = $this->parameters[$key];
$blowfishString .= $key.'='.$value.'&';
}
}
$blowfishString = rtrim($blowfishString,'&');
$this->parameters['Debug'] = $blowfishString;
$this->parameters['Len'] = strlen($blowfishString);
$this->parameters[self::DATA_FIELD] = bin2hex($this->encrypt($blowfishString,$this->cryptKey));
return $this->parameters[self::DATA_FIELD];
}
/** @return string */
public function getBfishCrypt()
{
$this->validate();
return $this->BFishCompose($this->BfishFields);
}
private function encrypt($data, $key)
{
$l = strlen($key);
if ($l < 16)
$key = str_repeat($key, ceil(16/$l));
if ($m = strlen($data)%8)
$data .= str_repeat("\x00", 8 - $m);
if (function_exists('mcrypt_encrypt'))
$val = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $data, MCRYPT_MODE_ECB);
else
$val = openssl_encrypt($data, 'BF-ECB', $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING);
return $val;
}
private function decrypt($data, $key)
{
$l = strlen($key);
if ($l < 16)
$key = str_repeat($key, ceil(16/$l));
if (function_exists('mcrypt_encrypt'))
$val = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $data, MCRYPT_MODE_ECB);
else
$val = openssl_decrypt($data, 'BF-ECB', $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING);
return rtrim($val, "\0");
}
/** @return string */
public function getUrl()
{
return $this->pspURL;
}
public function setUrl($pspUrl)
{
$this->validateUri($pspUrl);
$this->pspURL = $pspUrl;
}
public function setURLSuccess($url)
{
$this->validateUri($url);
$this->parameters['URLSuccess'] = $url;
}
public function setMsgVer($m)
{
$this->parameters['MsgVer'] = $m;
}
public function setUserData($data) {
$this->parameters['UserData'] = $data;
}
public function setURLFailure($url)
{
$this->validateUri($url);
$this->parameters['URLFailure'] = $url;
}
public function setURLNotify($url)
{
$this->validateUri($url);
$this->parameters['URLNotify'] = $url;
}
public function setTransID($transactionReference)
{
if(preg_match('/[^a-zA-Z0-9_-]/', $transactionReference)) {
throw new \InvalidArgumentException("TransactionReference cannot contain special characters");
}
$this->parameters['TransID'] = $transactionReference;
}
/**
* Set amount in cents, eg EUR 12.34 is written as 1234
*/
public function setAmount($amount)
{
if(!is_int($amount)) {
throw new \InvalidArgumentException("Integer expected. Amount is always in cents");
}
if($amount <= 0) {
throw new \InvalidArgumentException("Amount must be a positive number");
}
$this->parameters['Amount'] = $amount;
}
public function setCaptureDay($number)
{
if (strlen($number) > 2) {
throw new \InvalidArgumentException("captureDay is too long");
}
$this->parameters['captureDay'] = $number;
}
// Methodes liees a la lutte contre la fraude
public function setFraudDataBypass3DS($value)
{
if(strlen($value) > 128) {
throw new \InvalidArgumentException("fraudData.bypass3DS is too long");
}
$this->parameters['fraudData.bypass3DS'] = $value;
}
// Methodes liees au paiement one-click
public function setMerchantWalletId($wallet)
{
if(strlen($wallet) > 21) {
throw new \InvalidArgumentException("merchantWalletId is too long");
}
$this->parameters['merchantWalletId'] = $wallet;
}
// instalmentData.number instalmentData.datesList instalmentData.transactionReferencesList instalmentData.amountsList paymentPattern
// Methodes liees au paiement en n-fois
public function setInstalmentDataNumber($number)
{
if (strlen($number) > 2) {
throw new \InvalidArgumentException("instalmentData.number is too long");
}
if ( ($number < 2) || ($number > 50) ) {
throw new \InvalidArgumentException("instalmentData.number invalid value : value must be set between 2 and 50");
}
$this->parameters['instalmentData.number'] = $number;
}
public function setInstalmentDatesList($datesList)
{
$this->parameters['instalmentData.datesList'] = $datesList;
}
public function setInstalmentDataTransactionReferencesList($transactionReferencesList)
{
$this->parameters['instalmentData.transactionReferencesList'] = $transactionReferencesList;
}
public function setInstalmentDataAmountsList($amountsList)
{
$this->parameters['instalmentData.amountsList'] = $amountsList;
}
public function setPaymentPattern($paymentPattern)
{
$this->parameters['paymentPattern'] = $paymentPattern;
}
public function __call($method, $args)
{
if(substr($method, 0, 3) == 'set') {
// $field = lcfirst(substr($method, 3));
$field = substr($method, 3);
if(in_array($field, $this->pspFields)) {
$this->parameters[$field] = $args[0];
return;
}
}
if(substr($method, 0, 3) == 'get') {
// $field = lcfirst(substr($method, 3));
$field = substr($method, 3);
if(array_key_exists($field, $this->parameters)) {
return $this->parameters[$field];
}
}
throw new \BadMethodCallException("Unknown method $method");
}
public function toArray()
{
return $this->parameters;
}
public function toParameterString()
{
$parameterString = "";
foreach($this->parameters as $key => $value) {
$parameterString .= $key . '=' . $value;
$parameterString .= (array_search($key, array_keys($this->parameters)) != (count($this->parameters)-1)) ? '|' : '';
}
return $parameterString;
}
/** @return PaymentRequest */
public static function createFromArray(ShaComposer $shaComposer, array $parameters)
{
$instance = new static($shaComposer);
foreach($parameters as $key => $value)
{
$instance->{"set$key"}($value);
}
return $instance;
}
public function validate()
{
foreach($this->requiredFields as $field) {
if(empty($this->parameters[$field])) {
throw new \RuntimeException($field . " can not be empty");
}
}
}
protected function validateUri($uri)
{
if(!filter_var($uri, FILTER_VALIDATE_URL)) {
throw new \InvalidArgumentException("Uri is not valid");
}
if(strlen($uri) > 200) {
throw new \InvalidArgumentException("Uri is too long");
}
}
// Traitement des reponses d'Axepta
// -----------------------------------
/** @var string */
const SHASIGN_FIELD = "MAC";
/** @var string */
const DATA_FIELD = "Data";
public function setResponse(array $httpRequest)
{
// use lowercase internally
// $httpRequest = array_change_key_case($httpRequest, CASE_UPPER);
// set sha sign
// $this->shaSign = $this->extractShaSign($httpRequest);
// filter request for Sips parameters
$this->parameters = $this->filterRequestParameters($httpRequest);
}
/**
* @var string
*/
private $shaSign;
private $dataString;
/**
* Filter http request parameters
* @param array $requestParameters
*/
private function filterRequestParameters(array $httpRequest)
{
//filter request for Sips parameters
$parameters = $this->parameters;
if(!array_key_exists(self::DATA_FIELD, $httpRequest) || $httpRequest[self::DATA_FIELD] == '') {
// throw new InvalidArgumentException('Data parameter not present in parameters.');
$parameters['Debug'] = implode('&',$httpRequest);
foreach($httpRequest as $key=>$value) {
$key = ($key=='mid')? 'MerchantID':$key;
$parameters[$key]=$value;
}
} else {
$parameters[self::DATA_FIELD] = $httpRequest[self::DATA_FIELD];
$this->dataString = $this->decrypt(hex2bin($parameters[self::DATA_FIELD]),$this->cryptKey);
$parameters['Debug'] = $this->dataString;
$dataParams = explode('&', $this->dataString);
foreach($dataParams as $dataParamString) {
$dataKeyValue = explode('=',$dataParamString,2);
$key = ($dataKeyValue[0]=='mid')?'MerchantID':$dataKeyValue[0];
$parameters[$key] = $dataKeyValue[1];
}
}
return $parameters;
}
public function getSeal()
{
return $this->shaSign;
}
private function extractShaSign(array $parameters)
{
if(!array_key_exists(self::SHASIGN_FIELD, $parameters) || $parameters[self::SHASIGN_FIELD] == '') {
throw new \InvalidArgumentException('SHASIGN parameter not present in parameters.');
}
return $parameters[self::SHASIGN_FIELD];
}
/**
* Checks if the response is valid
* @param ShaComposer $shaComposer
* @return bool
*/
public function isValid()
{
// return $this->shaCompose($this->RHMACFields) == $this->shaSign;
return $this->shaCompose($this->RHMACFields) == $this->parameters['MAC'];
}
/**
* Retrieves a response parameter
* @param string $param
* @throws \InvalidArgumentException
*/
public function getParam($key)
{
if(method_exists($this, 'get'.$key)) {
return $this->{'get'.$key}();
}
// always use uppercase
// $key = strtoupper($key);
// $parameters = array_change_key_case($this->parameters,CASE_UPPER);
$parameters = $this->parameters;
if(!array_key_exists($key, $parameters)) {
throw new \InvalidArgumentException('Parameter ' . $key . ' does not exist.');
}
return $parameters[$key];
}
/**
* @return int Amount in cents
*/
public function getAmount()
{
$value = trim($this->parameters['Amount']);
return (int) ($value);
}
public function isSuccessful()
{
return in_array($this->getParam('Status'), array("OK", "AUTHORIZED"));
}
public function getDataString()
{
return $this->dataString;
}
}
?>