ldc-legaulois/app/Lib/Mercanet/Mercanet.php
Jérémy Munsch bc7140f945 phpcs Fixes
2021-06-14 15:15:52 +02:00

759 lines
25 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Lib\Mercanet;
use BadMethodCallException;
use Illuminate\Support\Facades\Log;
use InvalidArgumentException;
use Normalizer;
class Mercanet
{
public const TEST = "https://payment-webinit-mercanet.test.sips-atos.com/rs-services/v2/paymentInit";
public const SIMULATION = "https://payment-webinit.simu.mercanet.bnpparibas.net/rs-services/v2/paymentInit";
public const PRODUCTION = "https://payment-webinit.mercanet.bnpparibas.net/rs-services/v2/paymentInit";
public const INTERFACE_VERSION = "IR_WS_2.20";
public const INSTALMENT = "INSTALMENT";
// BYPASS3DS
public const BYPASS3DS_ALL = "ALL";
public 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'
);
/** @var ShaComposer */
private $secretKey;
private $env;
private $returnUrl;
private $automaticUrl = false;
private $pspURL = self::TEST;
private $responseData;
private $parameters = array();
private $pspFields = array(
'amount', 'cardExpiryDate', 'cardNumber', 'cardCSCValue',
'currencyCode', 'merchantId', 'interfaceVersion', 'sealAlgorithm',
'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',
'customerEmail', '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', 'captureMode', 'merchantTransactionDateTime', 'fraudData.bypass3DS', 'seal',
'orderChannel', 'orderId', 'returnContext', 'transactionOrigin', 'merchantWalletId', 'paymentMeanId'
);
private $requiredFields = array(
'amount', 'currencyCode', 'interfaceVersion', 'keyVersion', 'merchantId', 'normalReturnUrl', 'orderChannel',
'transactionReference'
);
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, $MerchantId, $env, $returnUrl, $automaticUrl = false)
{
$this->secretKey = $secret;
$this->parameters['merchantId'] = $MerchantId;
$this->env = $env;
$this->returnUrl = $returnUrl;
if ($automaticUrl !== false) {
$this->automaticUrl = $automaticUrl;
}
switch ($this->env) {
case 'TEST':
$this->setUrl(Mercanet::TEST);
break;
case 'SIMULATION':
$this->setUrl(Mercanet::SIMULATION);
break;
case 'PRODUCTION':
$this->setUrl(Mercanet::PRODUCTION);
break;
}
}
public function shaCompose(array $parameters)
{
$shaString = '';
foreach ($parameters as $key => $value) {
if ($key != 'keyVersion') {
if (is_array($value)) {
$shaString .= implode('', $value);
} else {
$shaString .= $value;
}
}
}
return $shaString;
}
/** @return string */
public function getShaSign()
{
$this->validate();
return hash_hmac('sha256', utf8_encode($this->shaCompose($this->toArray())), $this->secretKey);
}
/** @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
*/
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 setCustomerEmail($email)
{
$this->parameters['customerEmail'] = $email;
}
public function setPaymentBrands($brands)
{
$this->parameters['paymentMeanBrandList'] = '';
foreach ($brands as $brand) {
if (!array_key_exists(strtoupper($brand), $this->brandsmap)) {
throw new InvalidArgumentException("Unknown Brand [$brand].");
}
}
$this->parameters['paymentMeanBrandList'] = $brands;
}
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)
{
// replace quotes
$this->parameters['billingContact.firstname'] = str_replace(
array("'", '"'),
'',
Normalizer::normalize($firstname)
);
}
public function setBillingContactLastname($lastname)
{
// replace quotes
$this->parameters['billingContact.lastname'] = str_replace(
array("'", '"'),
'',
Normalizer::normalize($lastname)
);
}
public function setCaptureDay($number)
{
if (strlen($number) > 2) {
throw new InvalidArgumentException("captureDay is too long");
}
$this->parameters['captureDay'] = $number;
}
public function setCaptureMode($value)
{
if (strlen($value) > 20) {
throw new InvalidArgumentException("captureMode is too long");
}
$this->parameters['captureMode'] = $value;
}
public function setMerchantTransactionDateTime($value)
{
if (strlen($value) > 25) {
throw new InvalidArgumentException("merchantTransactionDateTime is too long");
}
$this->parameters['merchantTransactionDateTime'] = $value;
}
public function setInterfaceVersion($value)
{
$this->parameters['interfaceVersion'] = $value;
}
public function setSealAlgorithm($value)
{
$this->parameters['sealAlgorithm'] = $value;
}
public function setOrderChannel($value)
{
if (strlen($value) > 20) {
throw new InvalidArgumentException("orderChannel is too long");
}
$this->parameters['orderChannel'] = $value;
}
public function setOrderId($value)
{
if (strlen($value) > 32) {
throw new InvalidArgumentException("orderId is too long");
}
$this->parameters['orderId'] = $value;
}
public function setReturnContext($value)
{
if (strlen($value) > 255) {
throw new InvalidArgumentException("returnContext is too long");
}
$this->parameters['returnContext'] = $value;
}
public function setTransactionOrigin($value)
{
if (strlen($value) > 20) {
throw new InvalidArgumentException("transactionOrigin is too long");
}
$this->parameters['transactionOrigin'] = $value;
}
// Methodes liees a la carte
public function setCardNumber($number)
{
if (strlen($number) > 19) {
throw new InvalidArgumentException("cardNumber is too long");
}
if (strlen($number) < 12) {
throw new InvalidArgumentException("cardNumber is too short");
}
$this->parameters['cardNumber'] = $number;
}
public function setCardExpiryDate($date)
{
if (strlen($date) != 6) {
throw new InvalidArgumentException("cardExpiryDate value is invalid");
}
$this->parameters['cardExpiryDate'] = $date;
}
public function setCardCscValue($value)
{
if (strlen($value) > 4) {
throw new InvalidArgumentException("cardCSCValue value is invalid");
}
$this->parameters['cardCSCValue'] = $value;
}
// 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;
}
public function setPaymentMeanId($value)
{
if (strlen($value) > 6) {
throw new InvalidArgumentException("paymentMeanId is too long");
}
$this->parameters['paymentMeanId'] = $value;
}
// 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));
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()
{
ksort($this->parameters);
return $this->parameters;
}
public function toParameterString()
{
ksort($this->parameters);
$seal = $this->getShaSign();
$tmp = array_merge($this->parameters, ['seal' => $seal]);
return json_encode($tmp, JSON_UNESCAPED_SLASHES);
}
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 */
public const SHASIGN_FIELD = "SEAL";
/** @var string */
public 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;
private $responseRequest;
private $responseStatus;
private $parameterArray;
/**
* Filter http request parameters
* @param array $requestParameters
*/
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();
$this->responseData = $httpRequest[self::DATA_FIELD];
$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()
{
$resultat = false;
$signature = $this->responseData;
$compute = hash('sha256', utf8_encode($signature . $this->secretKey));
if (strcmp($this->shaSign, $compute) == 0) {
// phpcs:ignore PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace
// if (
// (strcmp($this->parameters['responseCode'], "00") == 0) ||
// (strcmp($this->parameters['responseCode'], "60") == 0)
// ) {
// $resultat = true;
// }
// phpcs:ignore PSR12.ControlStructures.ControlStructureSpacing.FirstExpressionLine
if (in_array($this->parameters['responseCode'], [
'00', // Paiement accepté
'02', // Demande d’autorisation par téléphone à la banque à cause d’un dépassement du plafond d’autorisation sur la carte, si vous êtes autorisé à forcer les transactions
'03', // Contrat commerçant invalide
'05', // Autorisation refusée Refus 3D Secure, Refus bancaire acquéreur, Repli VADS, Fraude
'11', // Utilisé dans le cas d'un contrôle différé. Le PAN est en opposition
'12', // Transaction invalide, vérifier les paramètres transférés dans la requête
'14', // Coordonnées du moyen de paiement invalides (ex: n° de carte ou cryptogramme visuel de la carte) ou vérification AVS échouée
'17', // Annulation de l’acheteur
'30', // Erreur de format
'34', // Suspicion de fraude (seal erroné), Refus fraude acquéreur
'54', // Date de validité du moyen de paiement dépassée
'60', // ???
'75', // Nombre de tentatives de saisie des coordonnées du moyen de paiement sous Sips Paypage dépassé
'90', // Service temporairement indisponible
'94', // Transaction dupliquée : le transactionReference de la transaction est déjà utilisé
'97', // Délai expiré, transaction refusée
'97', // Délai expiré, transaction refusée
'99' // Problème temporaire du serveur de paiement.
])
) {
$resultat = true;
}
}
return $resultat;
}
public function getXmlValueByTag($inXmlset, $needle)
{
$resource = xml_parser_create(); //Create an XML parser
xml_parse_into_struct($resource, $inXmlset, $outArray); // Parse XML data into an array structure
xml_parser_free($resource); //Free an XML parser
$count = count($outArray);
for ($i = 0; $i < $count; $i++) {
if ($outArray[$i]['tag'] == strtoupper($needle)) {
$tagValue = $outArray[$i]['value'];
}
}
return $tagValue;
}
/**
* Retrieves a response parameter
* @param string $param
* @throws \InvalidArgumentException
*/
public function getParam($key)
{
return $this->parameterArray[$key];
}
public function getResponseRequest()
{
return $this->responseRequest;
}
public function executeRequest()
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->getUrl());
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->toParameterString());
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'Accept:application/json'));
curl_setopt($ch, CURLOPT_PORT, 443);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$result = curl_exec($ch);
$info = curl_getinfo($ch);
if (!$result) {
dd('toto', $this->parameters['transactionReference']);
Log::critical("MERCANET [$this->parameters['transactionReference']] : curl error: " . curl_error($ch));
curl_close($ch);
return (object)['status' => 'KO', 'code' => 500];
}
if ($info['http_code'] != 200) {
Log::critical(sprintf(
"MERCANET [%s] : service error: %s - Return : %s",
$this->parameters['transactionReference'],
$info['http_code'],
$result
));
curl_close($ch);
return (object)['status' => 'KO', 'code' => 500];
}
curl_close($ch);
if (strlen($result) == 0) {
Log::critical(sprintf(
"MERCANET [%s] : service did not sent back data",
$this->parameters['transactionReference']
));
return (object)['status' => 'KO', 'code' => 500];
}
$result_array = json_decode($result);
if (isset($result_array->redirectionStatusCode)) {
if ($result_array->redirectionStatusCode === '00') {
return (object)[
'status' => 'OK', 'code' => $result_array->redirectionStatusCode,
'url' => $result_array->redirectionUrl, 'version' => $result_array->redirectionVersion,
'data' => $result_array->redirectionData
];
} else {
return (object)[
'status' => 'KO',
'code' => $result_array->redirectionStatusCode,
'seal' => $result_array->seal
];
}
} else {
Log::critical(sprintf(
"MERCANET [%s] : redirectionStatusCode missing",
$this->parameters['transactionReference']
));
return (object)['status' => 'KO', 'code' => 500];
}
}
public function quickPayment($amount, $ref)
{
// Required fields
$this->setInterfaceVersion(Mercanet::INTERFACE_VERSION);
$this->setKeyVersion('2');
$this->setCurrency('EUR');
$this->setOrderChannel("INTERNET");
// Variables
$this->setAmount($amount);
$this->setTransactionReference($ref);
$this->setNormalReturnUrl($this->returnUrl);
if ($this->automaticUrl !== false) {
$this->setAutomaticResponseUrl($this->automaticUrl);
}
// Optional fields
$this->setPaymentBrands(['CB', 'VISA', 'MASTERCARD']);
$this->setCaptureMode('IMMEDIATE');
// Validate
$this->validate();
return $this->executeRequest();
}
}