first commit

This commit is contained in:
2025-03-12 17:06:23 +01:00
commit 2241f7131f
13185 changed files with 1692479 additions and 0 deletions

View File

@@ -0,0 +1,260 @@
<?php
namespace Imoje\Payment;
/**
* Class CartData
*
* @package Imoje\Payment
*/
class CartData
{
const TYPE_SCHEMA_ITEMS = 'cartDataItems';
const TYPE_SCHEMA_ADDRESS = 'cartDataAddress';
const TYPE_SCHEMA_DISCOUNT = 'cartDataDiscount';
const TYPE_SCHEMA_SHIPPING = 'cartDataShipping';
const MAX_PREVIOUS_ORDERS = 3;
/**
* @var array
*/
private $items;
/**
* @var array
*/
private $addressBilling;
/**
* @var int
*/
private $createdAt;
/**
* @var int
*/
private $amount;
/**
* @var array
*/
private $addressDelivery;
/**
* @var array
*/
private $shipping = [];
/**
* @var array
*/
private $discount = [];
/**
* @var CartData[]
*/
private $previous = [];
/**
* @param $id
* @param $vat
* @param $name
* @param $amount
* @param $quantity
*
* @return void
*/
public function addItem($id, $vat, $name, $amount, $quantity)
{
$this->items[] = [
'id' => $id,
'vat' => $vat,
'name' => $name,
'amount' => $amount,
'quantity' => $quantity,
];
}
/**
* @param int $vat
* @param string $name
* @param int $amount
*
* @return void
*/
public function setDiscount($vat, $name, $amount)
{
$this->discount = [
'vat' => $vat,
'name' => $name,
'amount' => $amount,
];
}
/**
* @param CartData $previousOrder
*
* @return void
*/
public function addPrevious($previousOrder)
{
if (count($this->previous) === self::MAX_PREVIOUS_ORDERS) {
return;
}
$this->previous[] = $previousOrder;
}
/**
* @param int $vat
* @param string $name
* @param int $amount
*
* @return void
*/
public function setShipping($vat, $name, $amount)
{
$this->shipping = [
'vat' => $vat,
'name' => $name,
'amount' => $amount,
];
}
/**
* @param int $amount
*
* @return void
*/
public function setAmount($amount)
{
$this->amount = $amount;
}
/**
* @param int $createdAt
*
* @return void
*/
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
}
/**
* @param string $city
* @param string $name
* @param string $phone
* @param string $street
* @param string $country
* @param string $postalCode
*
* @return void
*/
public function setAddressBilling($city, $name, $phone, $street, $country, $postalCode)
{
$this->addressBilling = [
'city' => $city,
'name' => $name,
'phone' => (string) $phone,
'street' => $street,
'country' => $country,
'postalCode' => (string) $postalCode,
];
}
/**
* @param string $city
* @param string $name
* @param string $phone
* @param string $street
* @param string $country
* @param string $postalCode
*
* @return void
*/
public function setAddressDelivery($city, $name, $phone, $street, $country, $postalCode)
{
$this->addressDelivery = [
'city' => $city,
'name' => $name,
'phone' => (string) $phone,
'street' => $street,
'country' => $country,
'postalCode' => (string) $postalCode,
];
}
/**
* @return string
*/
public function prepareCartData()
{
$cartData = $this->prepareCartDataArray();
if (!empty($this->previous)) {
$cartData['previous'] = $this->preparePrevious($this->previous);
}
return base64_encode(gzencode(json_encode($cartData), 5));
}
/**
* @return array
*/
public function prepareCartDataArray()
{
$data = [
'address' => [
'billing' => $this->addressBilling,
'delivery' => $this->addressDelivery,
],
'items' => $this->items,
];
if (!empty($this->discount)) {
$data['discount'] = $this->discount;
}
if (!empty($this->shipping)) {
$data['shipping'] = $this->shipping;
}
if (!empty($this->createdAt)) {
$data['createdAt'] = $this->createdAt;
}
if (!empty($this->amount)) {
$data['amount'] = $this->amount;
}
return $data;
}
/**
* @param CartData[] $cartDataList
*
* @return array
*/
private function preparePrevious($cartDataList)
{
$data = [];
foreach ($cartDataList as $cartData) {
$data[] = $cartData->prepareCartDataArray();
}
return $data;
}
}

View File

@@ -0,0 +1,761 @@
<?php
namespace Imoje\Payment;
use JsonSchema\Validator;
/**
* Class Util
*
* @package Imoje\Payment
*/
class Util
{
// region notification codes
const NC_OK = 0;
const NC_INVALID_SIGNATURE = 1;
const NC_SERVICE_ID_NOT_MATCH = 2;
const NC_ORDER_NOT_FOUND = 3;
const NC_INVALID_SIGNATURE_HEADERS = 4;
const NC_EMPTY_NOTIFICATION = 5;
const NC_NOTIFICATION_IS_NOT_JSON = 6;
const NC_INVALID_JSON_STRUCTURE = 7;
const NC_INVALID_ORDER_STATUS = 8;
const NC_AMOUNT_NOT_MATCH = 9;
const NC_UNHANDLED_STATUS = 10;
const NC_ORDER_STATUS_NOT_CHANGED = 11;
const NC_CART_NOT_FOUND = 12;
const NC_ORDER_STATUS_IS_NOT_SETTLED_ORDER_ARRANGEMENT_AFTER_IPN = 13;
const NC_ORDER_EXISTS_ORDER_ARRANGEMENT_AFTER_IPN = 14;
const NC_REQUEST_IS_NOT_POST = 15;
const NC_MISSING_TWISTO_RESPONSE_IN_POST = 16;
const NC_MISSING_ORDER_ID_IN_POST = 17;
const NC_CURL_IS_NOT_INSTALLED = 18;
const NC_MISSING_SIGNATURE_IN_POST = 19;
const NC_UNKNOWN = 100;
// endregion
// region notification status
const NS_OK = 'ok';
const NS_ERROR = 'error';
// endregion
const METHOD_REQUEST_POST = 'POST';
const METHOD_REQUEST_GET = 'GET';
const ENVIRONMENT_PRODUCTION = 'production';
const ENVIRONMENT_SANDBOX = 'sandbox';
private static $cdnUrl = 'https://data.imoje.pl';
/**
* @var array
*/
private static $supportedCurrencies = [
'pln' => 'PLN',
'eur' => 'EUR',
'czk' => 'CZK',
'gbp' => 'GBP',
'usd' => 'USD',
'uah' => 'UAH',
'hrk' => 'HRK',
'huf' => 'HUF',
'sek' => 'SEK',
'ron' => 'RON',
'chf' => 'CHF',
'bgn' => 'BGN',
];
/**
* @var array
*/
private static $paymentMethods = [
'blik' => 'blik',
'twisto' => 'twisto',
'pbl' => 'pbl',
'card' => 'card',
'ing' => 'ing',
];
/**
* @var array
*/
private static $paymentMethodCodeList = [
'blik' => 'blik',
];
/**
* @var array
*/
private static $paymentMethodCodeLogoExt = [
'alior' => 'alior.svg',
'bnpparibas' => 'bnpparibas.png',
'bos' => 'bos.png',
'bs' => 'bs.png',
'bspb' => 'bspb.png',
'bzwbk' => 'bzwbk.png',
'citi' => 'citi.png',
'creditagricole' => 'creditagricole.svg',
'envelo' => 'envelo.png',
'getin' => 'getin.svg',
'ideabank' => 'ideabank.png',
'ing' => 'ing.png',
'inteligo' => 'inteligo.png',
'ipko' => 'ipko.png',
'millennium' => 'millennium.svg',
'mtransfer' => 'mtransfer.png',
'nest' => 'nest.svg',
'noble' => 'noble.png',
'pbs' => 'pbs.png',
'pekao24' => 'pekao24.svg',
'plusbank' => 'plusbank.png',
'pocztowy' => 'pocztowy.svg',
'tmobile' => 'tmobile.svg',
];
/**
* @var array
*/
private static $hashMethods = [
'sha224' => 'sha224',
'sha256' => 'sha256',
'sha384' => 'sha384',
'sha512' => 'sha512',
];
/**
* @var array
*/
private static $transactionStatuses = [
'new' => 'new',
'authorized' => 'authorized',
'pending' => 'pending',
'submitted_for_settlement'
=> 'submitted_for_settlement',
'rejected' => 'rejected',
'settled' => 'settled',
'error' => 'error',
'cancelled' => 'cancelled',
];
/**
* Functions that return true when passed currency is on supported currencies list.
*
* @param string $currencyCode ISO4217
*
* @return bool
*/
public static function canUseForCurrency($currencyCode)
{
return isset(self::$supportedCurrencies[strtolower($currencyCode)]);
}
/**
* @param array $order
* @param string $submitValue
* @param string $url
*
* @return string
*/
public static function createOrderForm($order, $submitValue = '', $url = '', $method = '')
{
if (!$submitValue) {
$submitValue = 'Continue';
}
if (!$url) {
$url = self::getServiceUrl();
}
if (!$method) {
$method = 'POST';
}
$form = '<form method="' . $method . '" action="' . $url . '">';
if (is_array($order)) {
foreach ($order as $key => $value) {
$form .= '<input type="hidden" value="' . htmlentities($value) . '" name="' . $key . '" id="imoje_' . $key . '">';
}
}
$form .= '<button type="submit" id="submit-payment-form">' . $submitValue . '</button>';
$form .= '</form>';
return $form;
}
/**
* @return string
*/
public static function getServiceUrl()
{
$url = Configuration::getServiceUrl(Configuration::getEnvironment());
if ($url === false) {
return '';
}
return $url . Configuration::ROUTE_PAY;
}
/**
* @param int $amount
* @param string $currency
* @param string $orderId
* @param string $orderDescription
* @param string $customerFirstName
* @param string $customerLastName
* @param null|string $customerEmail
* @param null|string $customerPhone
* @param null|string $urlSuccess
* @param null|string $urlFailure
* @param null|string $urlReturn
* @param null|string $version
* @param null|string $cartData
*
* @return array
*/
public static function prepareData(
$amount, $currency, $orderId, $orderDescription = null, $customerFirstName, $customerLastName,
$customerEmail = null, $customerPhone = null, $urlSuccess = null,
$urlFailure = null, $urlReturn = null, $version = null, $cartData = null, $visibleMethod = null
) {
$data = [];
$data['amount'] = $amount;
$data['currency'] = $currency;
$data['orderId'] = $orderId;
if ($orderDescription) {
$data['orderDescription'] = $orderDescription;
}
$data['customerFirstName'] = $customerFirstName;
$data['customerLastName'] = $customerLastName;
if ($customerPhone) {
$data['customerPhone'] = $customerPhone;
}
if ($customerEmail) {
$data['customerEmail'] = $customerEmail;
}
if ($urlSuccess) {
$data['urlSuccess'] = $urlSuccess;
}
if ($urlFailure) {
$data['urlFailure'] = $urlFailure;
}
if ($urlReturn) {
$data['urlReturn'] = $urlReturn;
}
if ($version) {
$data['version'] = $version;
}
if ($cartData) {
$data['cartData'] = $cartData;
}
if ($visibleMethod) {
$data['visibleMethod'] = $visibleMethod;
}
return $data;
}
/**
* @param string $serviceId
* @param int $amount
* @param string $currency
* @param string $orderId
* @param string $paymentMethod
* @param string $paymentMethodCode
* @param string $successReturnUrl
* @param string $failureReturnUrl
* @param string $customerFirstName
* @param string $customerLastName
* @param string $customerEmail
* @param string $clientIp
* @param string $type
*
* @return string
*/
public static function prepareDataApi(
$serviceId,
$amount,
$currency,
$orderId,
$paymentMethod,
$paymentMethodCode,
$successReturnUrl,
$failureReturnUrl,
$customerFirstName,
$customerLastName,
$customerEmail,
$type = 'sale',
$clientIp = ''
) {
if (!$clientIp) {
$clientIp = $_SERVER['REMOTE_ADDR'];
}
return json_encode([
'type' => $type,
'serviceId' => $serviceId,
'amount' => $amount,
'currency' => $currency,
'orderId' => (string) $orderId,
'paymentMethod' => $paymentMethod,
'paymentMethodCode' => $paymentMethodCode,
'successReturnUrl' => $successReturnUrl,
'failureReturnUrl' => $failureReturnUrl,
'clientIp' => $clientIp,
'customer' => [
'firstName' => $customerFirstName,
'lastName' => $customerLastName,
'email' => $customerEmail,
],
]);
}
/**
* @param string $url
* @param string $methodRequest
* @param string $body
* @param string $authorizationToken
* @param bool $decode
*
* @return bool|string|array
*/
public static function callApi($url, $methodRequest, $body, $authorizationToken, $decode = false)
{
$curlInit = curl_init($url);
curl_setopt($curlInit, CURLOPT_CUSTOMREQUEST, $methodRequest);
curl_setopt($curlInit, CURLOPT_POSTFIELDS, $body);
curl_setopt($curlInit, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlInit, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($curlInit, CURLOPT_TIMEOUT, 10);
curl_setopt($curlInit, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $authorizationToken,
]);
$resultCurl = curl_exec($curlInit);
if ((curl_getinfo($curlInit, CURLINFO_HTTP_CODE) !== 200) || !$resultCurl) {
return false;
}
if ($decode) {
return json_decode($resultCurl, true);
}
return $resultCurl;
}
/**
* @param string $string
*
* @return array
*/
public static function parseStringToArray($string)
{
$array = [];
parse_str($string, $array);
return $array;
}
/**
* Simple functions that adds signature and service_id to order array depends on Configuration::$apiMode.
*
* @param array $order
* @param string $hashMethod
*
* @return array
*/
public static function prepareOrderData($order, $hashMethod = 'sha256')
{
if (Configuration::getApiMode()) {
return $order;
}
$order['serviceId'] = Configuration::getServiceId();
$order['merchantId'] = Configuration::getMerchantId();
$order['signature'] = self::createSignature($order, Configuration::getServiceKey(), $hashMethod);
return $order;
}
/**
* @param array $orderData
* @param string $serviceKey
* @param string $hashMethod
*
* @return string|bool
*/
private static function createSignature($orderData, $serviceKey, $hashMethod = 'sha256')
{
if (!isset(self::$hashMethods[$hashMethod])
|| !is_array($orderData)) {
return false;
}
ksort($orderData);
$data = [];
foreach ($orderData as $key => $value) {
$data[] = $key . '=' . $value;
}
return self::hashSignature($hashMethod, implode('&', $data), $serviceKey) . ';' . $hashMethod;
}
/**
* @param string $hashMethod
* @param string $data
* @param string $serviceKey
*
* @return string
*/
public static function hashSignature($hashMethod, $data, $serviceKey)
{
return hash($hashMethod, $data . $serviceKey);
}
/**
* @return array
*/
public static function getSupportedCurrencies()
{
return self::$supportedCurrencies;
}
/**
* @param string $paymentMethod
*
* @return string
*/
public static function getPaymentMethod($paymentMethod)
{
if (isset(self::$paymentMethods[$paymentMethod])) {
return self::$paymentMethods[$paymentMethod];
}
return '';
}
/**
* @param string $paymentMethodCode
*
* @return string
*/
public static function getPaymentMethodCodeLogo($paymentMethodCode)
{
if (isset(self::$paymentMethodCodeLogoExt[$paymentMethodCode])) {
return self::$cdnUrl . '/img/pay/' . self::$paymentMethodCodeLogoExt[$paymentMethodCode];
}
return '';
}
/**
* @param string $paymentMethodCode
*
* @return string
*/
public static function getPaymentMethodCode($paymentMethodCode)
{
if (isset(self::$paymentMethodCodeList[$paymentMethodCode])) {
return self::$paymentMethodCodeList[$paymentMethodCode];
}
return '';
}
/**
* @param string $status
* @param string $code
* @param bool|string $debugMode
* @param string|int|null $statusBefore
* @param string|int|null $statusAfter
* @param bool|string $arrangementCreateOrder
**
*
* @return string
*/
public static function notificationResponse($status, $code = '', $debugMode = false, $statusBefore = null, $statusAfter = null, $arrangementCreateOrder = false)
{
$response = [
'status' => $status,
];
if ($debugMode && $code) {
$response['data'] = [
'code' => $code,
];
}
if ($arrangementCreateOrder) {
$response['data']['creatingOrderMode'] = $arrangementCreateOrder;
}
if ($statusBefore) {
$response['data']['statusBefore'] = $statusBefore;
}
if ($statusAfter) {
$response['data']['statusAfter'] = $statusAfter;
}
// region add additional data for some cases and set proper header
switch ($status) {
case self::NS_OK:
header('HTTP/1.1 200 OK');
break;
case self::NS_ERROR:
header('HTTP/1.1 404 Not Found');
break;
default:
break;
}
// endregion
header('Content-Type: application/json');
return json_encode($response);
}
/**
* Verify body and signature of notification
*
* @param string $serviceKey
* @param string $serviceId
*
* @return bool|array
*/
public static function checkRequestNotification($serviceKey, $serviceId)
{
if (!isset($_SERVER['CONTENT_TYPE'], $_SERVER[Configuration::getHeaderSignatureName()]) || strpos($_SERVER['CONTENT_TYPE'], 'application/json') !== 0) {
return self::NC_INVALID_SIGNATURE_HEADERS;
}
$payload = file_get_contents('php://input', true);
if (!$payload) {
return self::NC_EMPTY_NOTIFICATION;
}
if (!self::isJson($payload)) {
return self::NC_NOTIFICATION_IS_NOT_JSON;
}
$header = $_SERVER[Configuration::getHeaderSignatureName()];
$header = (explode(';', $header));
$algoFromNotification = explode('=', $header[3]);
$algoFromNotification = $algoFromNotification[1];
$headerSignature = explode('=', $header[2]);
if ($headerSignature[1] !== self::hashSignature($algoFromNotification, $payload, $serviceKey)) {
return self::NC_INVALID_SIGNATURE;
}
if (!self::validateNotificationJson($payload)['success']) {
return self::NC_INVALID_JSON_STRUCTURE;
}
$payloadDecoded = json_decode($payload, true);
if ($payloadDecoded['transaction']['serviceId'] !== $serviceId) {
return self::NC_SERVICE_ID_NOT_MATCH;
}
return $payloadDecoded;
}
/**
* @param string $variable
*
* @return bool
*/
public static function isJson($variable)
{
json_decode($variable);
return (json_last_error() === JSON_ERROR_NONE);
}
/**
* @param string $notification
*
* @return array
*/
private static function validateNotificationJson($notification)
{
$notification = json_decode($notification);
$schema = [
'title' => 'order',
'type' => 'object',
'properties' => [
'transaction' => [
'type' => 'object',
'properties' => [
'amount' => [
'type' => 'integer',
'minimum' => 0,
'exclusiveMinimum' => true,
],
'currency' => [
'type' => 'string',
'enum' => array_values(self::$supportedCurrencies),
],
'status' => [
'type' => 'string',
'enum' => array_values(self::getTransactionStatuses()),
],
'orderId' => [
'type' => 'string',
],
'serviceId' => [
'type' => 'string',
],
'type' => [
'type' => 'string',
'enum' => [
'sale',
'refund',
],
],
],
'required' => [
'amount',
'currency',
'status',
'orderId',
'serviceId',
'type',
],
],
],
'required' => [
'transaction',
],
];
$validator = new Validator();
$validator->validate($notification, json_decode(json_encode($schema)));
if ($validator->isValid()) {
return [
'success' => true,
'errors' => [],
];
}
$errors = [];
foreach ($validator->getErrors() as $error) {
$errors[$error['property']] = $error['message'];
}
return [
'success' => false,
'errors' => $errors,
];
}
/**
* @return array
*/
public static function getTransactionStatuses()
{
return self::$transactionStatuses;
}
/**
* @param array $payloadDecoded
* @param int $amount
* @param string $currency
*
* @return bool
*/
public static function checkRequestAmount(array $payloadDecoded, $amount, $currency)
{
return $payloadDecoded['transaction']['amount'] === $amount && $payloadDecoded['transaction']['currency'] === $currency;
}
/**
* @param float $amount
*
* @return int
*/
public static function convertAmountToFractional($amount)
{
return (int) self::multiplyValues(round($amount, 2), 100, 0);
}
/**
* @param number $firstValue
* @param number $secondValue
* @param number $precision
*
* @return float
*/
public static function multiplyValues($firstValue, $secondValue, $precision)
{
return round($firstValue * $secondValue, $precision);
}
/**
* @param string $name
* @param string $street
* @param string $city
* @param string $postalCode
* @param string $countryCode
* @param string $phone
* @param string $region
*
* @return array
*/
public static function formatAddress($name, $street, $city, $postalCode, $countryCode, $phone, $region = '')
{
return [
'name' => $name,
'street' => $street,
'city' => $city,
'postalCode' => $postalCode,
'countryCode' => $countryCode,
'phone' => $phone,
'region' => $region,
];
}
/**
* @param int $amount
*
* @return float
*/
public static function convertAmountToMain($amount)
{
return (float) round($amount / 100, 2);
}
}