538 lines
17 KiB
PHP
538 lines
17 KiB
PHP
<?php
|
|
/*************************************************************************************/
|
|
/* Copyright (c) Franck Allimant, CQFDev */
|
|
/* email : thelia@cqfdev.fr */
|
|
/* web : http://www.cqfdev.fr */
|
|
/* */
|
|
/* For the full copyright and license information, please view the LICENSE */
|
|
/* file that was distributed with this source code. */
|
|
/*************************************************************************************/
|
|
|
|
/**
|
|
* Created by Franck Allimant, CQFDev <franck@cqfdev.fr>
|
|
* Date: 11/06/2018 22:13
|
|
*/
|
|
|
|
namespace Mercanet\Api;
|
|
|
|
/**
|
|
* Class MercanetApi
|
|
* @package Mercanet\Api
|
|
* @method setMerchantId($string)
|
|
* @method setKeyVersion($string)
|
|
*/
|
|
class MercanetApi
|
|
{
|
|
const TEST = "https://payment-webinit-mercanet.test.sips-atos.com/paymentInit";
|
|
const PRODUCTION = "https://payment-webinit.mercanet.bnpparibas.net/paymentInit";
|
|
|
|
const INTERFACE_VERSION = "HP_2.20";
|
|
const INSTALMENT = "INSTALMENT";
|
|
|
|
// BYPASS3DS
|
|
const BYPASS3DS_ALL = "ALL";
|
|
const BYPASS3DS_MERCHANTWALLET = "MERCHANTWALLET";
|
|
|
|
private $brandsmap = array(
|
|
'ACCEPTGIRO' => 'CREDIT_TRANSFER',
|
|
'AMEX' => 'CARD',
|
|
'BCMC' => 'CARD',
|
|
'BUYSTER' => 'CARD',
|
|
'BANK CARD' => 'CARD',
|
|
'CB' => 'CARD',
|
|
'IDEAL' => 'CREDIT_TRANSFER',
|
|
'INCASSO' => 'DIRECT_DEBIT',
|
|
'MAESTRO' => 'CARD',
|
|
'MASTERCARD' => 'CARD',
|
|
'MASTERPASS' => 'CARD',
|
|
'MINITIX' => 'OTHER',
|
|
'NETBANKING' => 'CREDIT_TRANSFER',
|
|
'PAYPAL' => 'CARD',
|
|
'PAYLIB' => 'CARD',
|
|
'REFUND' => 'OTHER',
|
|
'SDD' => 'DIRECT_DEBIT',
|
|
'SOFORT' => 'CREDIT_TRANSFER',
|
|
'VISA' => 'CARD',
|
|
'VPAY' => 'CARD',
|
|
'VISA ELECTRON' => 'CARD',
|
|
'CBCONLINE' => 'CREDIT_TRANSFER',
|
|
'KBCONLINE' => 'CREDIT_TRANSFER'
|
|
);
|
|
|
|
private $secretKey;
|
|
|
|
private $pspURL = self::TEST;
|
|
|
|
private $parameters = array();
|
|
|
|
private $pspFields = array(
|
|
'amount', 'currencyCode', 'merchantId', 'normalReturnUrl',
|
|
'transactionReference', 'keyVersion', 'paymentMeanBrand', 'customerLanguage',
|
|
'billingAddress.city', 'billingAddress.company', 'billingAddress.country',
|
|
'billingAddress', 'billingAddress.postBox', 'billingAddress.state',
|
|
'billingAddress.street', 'billingAddress.streetNumber', 'billingAddress.zipCode',
|
|
'billingContact.email', 'billingContact.firstname', 'billingContact.gender',
|
|
'billingContact.lastname', 'billingContact.mobile', 'billingContact.phone',
|
|
'customerAddress', 'customerAddress.city', 'customerAddress.company',
|
|
'customerAddress.country', 'customerAddress.postBox', 'customerAddress.state',
|
|
'customerAddress.street', 'customerAddress.streetNumber', 'customerAddress.zipCode',
|
|
'customerContact', 'customerContact.email', 'customerContact.firstname',
|
|
'customerContact.gender', 'customerContact.lastname', 'customerContact.mobile',
|
|
'customerContact.phone', 'customerContact.title', 'expirationDate', 'automaticResponseUrl',
|
|
'templateName','paymentMeanBrandList', 'instalmentData.number', 'instalmentData.datesList',
|
|
'instalmentData.transactionReferencesList', 'instalmentData.amountsList', 'paymentPattern',
|
|
'captureDay', 'fraudData.bypass3DS',
|
|
's10TransactionReference.s10TransactionId', 'orderId',
|
|
);
|
|
|
|
private $requiredFields = array(
|
|
'amount', 'currencyCode', 'merchantId', 'normalReturnUrl', 'keyVersion'
|
|
);
|
|
|
|
public $allowedlanguages = array(
|
|
'nl', 'fr', 'de', 'it', 'es', 'cy', 'en'
|
|
);
|
|
|
|
private static $currencies = array(
|
|
'EUR' => '978', 'USD' => '840', 'CHF' => '756', 'GBP' => '826',
|
|
'CAD' => '124', 'JPY' => '392', 'MXP' => '484', 'TRY' => '949',
|
|
'AUD' => '036', 'NZD' => '554', 'NOK' => '578', 'BRC' => '986',
|
|
'ARP' => '032', 'KHR' => '116', 'TWD' => '901', 'SEK' => '752',
|
|
'DKK' => '208', 'KRW' => '410', 'SGD' => '702', 'XPF' => '953',
|
|
'XOF' => '952'
|
|
);
|
|
|
|
public static function convertCurrencyToCurrencyCode($currency)
|
|
{
|
|
if(!in_array($currency, array_keys(self::$currencies)))
|
|
throw new \InvalidArgumentException("Unknown currencyCode $currency.");
|
|
return self::$currencies[$currency];
|
|
}
|
|
|
|
public static function convertCurrencyCodeToCurrency($code)
|
|
{
|
|
if(!in_array($code, array_values(self::$currencies)))
|
|
throw new \InvalidArgumentException("Unknown Code $code.");
|
|
return array_search($code, self::$currencies);
|
|
}
|
|
|
|
public static function getCurrencies()
|
|
{
|
|
return self::$currencies;
|
|
}
|
|
|
|
public function __construct($secret)
|
|
{
|
|
$this->secretKey = $secret;
|
|
}
|
|
|
|
public function shaCompose(array $parameters)
|
|
{
|
|
// compose SHA string
|
|
$shaString = '';
|
|
foreach($parameters as $key => $value) {
|
|
$shaString .= $key . '=' . $value;
|
|
$shaString .= (array_search($key, array_keys($parameters)) != (count($parameters)-1)) ? '|' : $this->secretKey;
|
|
}
|
|
|
|
return hash('sha256', $shaString);
|
|
}
|
|
|
|
/** @return string */
|
|
public function getShaSign()
|
|
{
|
|
$this->validate();
|
|
return $this->shaCompose($this->toArray());
|
|
}
|
|
|
|
/** @return string */
|
|
public function getUrl()
|
|
{
|
|
return $this->pspURL;
|
|
}
|
|
|
|
public function setUrl($pspUrl)
|
|
{
|
|
$this->validateUri($pspUrl);
|
|
$this->pspURL = $pspUrl;
|
|
}
|
|
|
|
public function setNormalReturnUrl($url)
|
|
{
|
|
$this->validateUri($url);
|
|
$this->parameters['normalReturnUrl'] = $url;
|
|
}
|
|
|
|
public function setAutomaticResponseUrl($url)
|
|
{
|
|
$this->validateUri($url);
|
|
$this->parameters['automaticResponseUrl'] = $url;
|
|
}
|
|
|
|
public function setTransactionReference($transactionReference)
|
|
{
|
|
if(preg_match('/[^a-zA-Z0-9_-]/', $transactionReference)) {
|
|
throw new \InvalidArgumentException("TransactionReference cannot contain special characters");
|
|
}
|
|
$this->parameters['transactionReference'] = $transactionReference;
|
|
}
|
|
|
|
/**
|
|
* Set amount in cents, eg EUR 12.34 is written as 1234
|
|
* @param $amount
|
|
*/
|
|
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 setCurrency($currency)
|
|
{
|
|
if(!array_key_exists(strtoupper($currency), self::getCurrencies())) {
|
|
throw new \InvalidArgumentException("Unknown currency");
|
|
}
|
|
$this->parameters['currencyCode'] = self::convertCurrencyToCurrencyCode($currency);
|
|
}
|
|
|
|
public function setLanguage($language)
|
|
{
|
|
if(!in_array($language, $this->allowedlanguages)) {
|
|
throw new \InvalidArgumentException("Invalid language locale");
|
|
}
|
|
$this->parameters['customerLanguage'] = $language;
|
|
}
|
|
|
|
public function setPaymentBrand($brand)
|
|
{
|
|
$this->parameters['paymentMeanBrandList'] = '';
|
|
if(!array_key_exists(strtoupper($brand), $this->brandsmap)) {
|
|
throw new \InvalidArgumentException("Unknown Brand [$brand].");
|
|
}
|
|
$this->parameters['paymentMeanBrandList'] = strtoupper($brand);
|
|
}
|
|
|
|
public function setCustomerContactEmail($email)
|
|
{
|
|
if(strlen($email) > 50) {
|
|
throw new \InvalidArgumentException("Email is too long");
|
|
}
|
|
if(!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
throw new \InvalidArgumentException("Email is invalid");
|
|
}
|
|
$this->parameters['customerContact.email'] = $email;
|
|
}
|
|
|
|
public function setBillingContactEmail($email)
|
|
{
|
|
if(strlen($email) > 50) {
|
|
throw new \InvalidArgumentException("Email is too long");
|
|
}
|
|
if(!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
throw new \InvalidArgumentException("Email is invalid");
|
|
}
|
|
$this->parameters['billingContact.email'] = $email;
|
|
}
|
|
|
|
public function setBillingAddressStreet($street)
|
|
{
|
|
if(strlen($street) > 35) {
|
|
throw new \InvalidArgumentException("street is too long");
|
|
}
|
|
$this->parameters['billingAddress.street'] = \Normalizer::normalize($street);
|
|
}
|
|
|
|
public function setBillingAddressStreetNumber($nr)
|
|
{
|
|
if(strlen($nr) > 10) {
|
|
throw new \InvalidArgumentException("streetNumber is too long");
|
|
}
|
|
$this->parameters['billingAddress.streetNumber'] = \Normalizer::normalize($nr);
|
|
}
|
|
|
|
public function setBillingAddressZipCode($zipCode)
|
|
{
|
|
if(strlen($zipCode) > 10) {
|
|
throw new \InvalidArgumentException("zipCode is too long");
|
|
}
|
|
$this->parameters['billingAddress.zipCode'] = \Normalizer::normalize($zipCode);
|
|
}
|
|
|
|
public function setBillingAddressCity($city)
|
|
{
|
|
if(strlen($city) > 25) {
|
|
throw new \InvalidArgumentException("city is too long");
|
|
}
|
|
$this->parameters['billingAddress.city'] = \Normalizer::normalize($city);
|
|
}
|
|
|
|
public function setBillingContactPhone($phone)
|
|
{
|
|
if(strlen($phone) > 30) {
|
|
throw new \InvalidArgumentException("phone is too long");
|
|
}
|
|
$this->parameters['billingContact.phone'] = $phone;
|
|
}
|
|
|
|
public function setBillingContactFirstname($firstname)
|
|
{
|
|
$this->parameters['billingContact.firstname'] = str_replace(array("'", '"'), '', \Normalizer::normalize($firstname)); // replace quotes
|
|
}
|
|
|
|
public function setBillingContactLastname($lastname)
|
|
{
|
|
$this->parameters['billingContact.lastname'] = str_replace(array("'", '"'), '', \Normalizer::normalize($lastname)); // replace quotes
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* @param $transactionId 0 to 999999
|
|
*/
|
|
public function setS10TransactionId($transactionId)
|
|
{
|
|
$this->parameters['s10TransactionReference.s10TransactionId'] = $transactionId;
|
|
}
|
|
|
|
public function setOrderId(int $orderId)
|
|
{
|
|
$this->parameters['orderId'] = $orderId;
|
|
}
|
|
|
|
public function __call($method, $args)
|
|
{
|
|
if(substr($method, 0, 3) == 'set') {
|
|
$field = lcfirst(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));
|
|
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;
|
|
}
|
|
|
|
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 de Mercanet
|
|
// -----------------------------------
|
|
|
|
/** @var string */
|
|
const SHASIGN_FIELD = "SEAL";
|
|
|
|
/** @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 $httpRequest
|
|
* @return array
|
|
*/
|
|
private function filterRequestParameters(array $httpRequest)
|
|
{
|
|
//filter request for Sips parameters
|
|
if(!array_key_exists(self::DATA_FIELD, $httpRequest) || $httpRequest[self::DATA_FIELD] == '') {
|
|
throw new \InvalidArgumentException('Data parameter not present in parameters.');
|
|
}
|
|
$parameters = array();
|
|
$dataString = $httpRequest[self::DATA_FIELD];
|
|
$this->dataString = $dataString;
|
|
$dataParams = explode('|', $dataString);
|
|
foreach($dataParams as $dataParamString) {
|
|
$dataKeyValue = explode('=',$dataParamString,2);
|
|
$parameters[$dataKeyValue[0]] = $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
|
|
* @return bool
|
|
*/
|
|
public function isValid()
|
|
{
|
|
return $this->shaCompose($this->parameters) == $this->shaSign;
|
|
}
|
|
|
|
/**
|
|
* Retrieves a response parameter
|
|
* @param string $key
|
|
* @return mixed
|
|
* @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);
|
|
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('RESPONSECODE'), array("00", "60"));
|
|
}
|
|
|
|
public function getDataString()
|
|
{
|
|
return $this->dataString;
|
|
}
|
|
}
|