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,22 @@
{
"name": "twisto-payments/twisto",
"type": "library",
"license": "BSD-3-Clause",
"description": "Library providing support for Twisto.cz payments service",
"keywords": ["twisto", "payments"],
"homepage": "https://github.com/TwistoPayments/Twisto.php",
"authors": [
{
"name": "Twisto payments a.s.",
"email": "dotaz@twisto.cz"
}
],
"autoload": {
"psr-4": {
"Twisto\\": "src/Twisto/"
}
},
"require": {
"php": ">=5.3.0"
}
}

View File

@@ -0,0 +1,19 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "b9c17d1ab7878202721bd4669f5045f0",
"packages": [],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=5.3.0"
},
"platform-dev": []
}

View File

@@ -0,0 +1,177 @@
<?php
class cvIngCfg
{
public $config;
public $structure;
public $route;
public $culture;
public function __construct($configName, $structure, $route = null)
{
$this->structure = $structure;
$this->loadConfigOrCreate($configName);
$this->culture = $this->config->getCulture();
$this->route = $route;
}
public function loadConfigOrCreate($configName)
{
if(SF_APP == 'backend') {
$culture = sfContext::getInstance()->getRequest()->getParameter('culture', stLanguage::getOptLanguage());
} else {
$culture = sfContext::getInstance()->getUser()->getCulture();
}
$this->config = stConfig::getInstance($configName, array('culture' => $culture));
if($this->config->isEmpty()) {
$this->createConfig();
}
}
public function createConfig()
{
foreach($this->structure as $fieldsetName => $fieldset) {
foreach($fieldset as $cfgname => $labels) {
foreach($labels as $labelName => $label) {
$default = isset($label['default'])
? $label['default']
: '';
$this->config->set($labelName, $default);
}
}
}
$this->config->save();
}
public function getContent()
{
$html = '';
foreach($this->structure as $fieldsetName => $fieldset) {
$formFields = $this->getFormFields($fieldset);
$name = content_tag('h2', $fieldsetName);
$html .= content_tag('fieldset', $name . implode('', $formFields));
}
return $html;
}
public function getFormFields($fieldset)
{
$formFields = array();
foreach($fieldset as $cfgname => $labels) {
foreach($labels as $labelName => $label) {
$i18n = isset($label['i18n']) && $label['i18n']
? true
: false;
$value = $this->config->get($labelName, '', $i18n);
$params = $label['params'];
$this->modParams($params, $value);
switch($label['type']) {
case 'plain':
{
$value = $params['value'];
unset($params['value']);
$formFields[] = st_admin_get_form_field($cfgname . '[' . $labelName . ']', $label['name'], $value, $label['type'], $params);
}
break;
case 'checkbox_tag':
{
$params = array_merge($params, array('checked' => $value));
$formFields[] = st_admin_get_form_field($cfgname . '[' . $labelName . ']', $label['name'], 1, $label['type'], $params);
}
break;
default:
{
$formFields[] = st_admin_get_form_field($cfgname . '[' . $labelName . ']', $label['name'], $value, $label['type'], $params);
}
break;
}
}
}
return $formFields;
}
public function modParams(&$params, &$value)
{
if(isset($params['truncate'])) {
if(strlen($value) > $params['truncate']) {
$value = substr($value, 0, $params['truncate']) . '...';
}
unset($params['truncate']);
}
if(isset($params['warn_if_empty'])) {
if(empty($value)) {
$value = 'brak';
$params['style'] = 'color: red;';
}
unset($params['warn_if_empty']);
}
}
public function save()
{
if(sfContext::getInstance()->getRequest()->getMethod() == sfRequest::POST) {
foreach($this->structure as $fieldsetName => $fieldset) {
foreach($fieldset as $cfgname => $labels) {
$parameter = sfContext::getInstance()->getRequest()->getParameter($cfgname, array());
foreach($labels as $labelName => $label) {
if(isset($parameter[$labelName])) {
$value = $parameter[$labelName];
} else {
$value = '';
}
$i18n = isset($label['i18n']) && $label['i18n']
? true
: false;
$this->config->set($labelName, $value, $i18n);
}
}
}
if(!sfContext::getInstance()->getRequest()->hasErrors()) {
$this->config->save();
return true;
}
}
return false;
}
#PARAMS MOD
public function validate()
{
if(sfContext::getInstance()->getRequest()->getMethod() == sfRequest::POST) {
foreach($this->structure as $fieldsetName => $fieldset) {
foreach($fieldset as $cfgname => $labels) {
$config = sfContext::getInstance()->getRequest()->getParameter($cfgname);
foreach($labels as $labelName => $label) {
if(isset($label['validate']['method']) && isset($label['validate']['message'])) {
$result = $this->{$label['validate']['method']}($config[$labelName]);
if($result) {
sfContext::getInstance()->getRequest()->setError($cfgname . '{' . $labelName . '}', $label['validate']['message']);
}
}
}
}
}
}
return !sfContext::getInstance()->getRequest()->hasErrors();
}
#VALIDATE
public function isEmpty($v)
{
return empty($v);
}
}

View File

@@ -0,0 +1,7 @@
<?php
class cvIngImoje extends cvIngPlugin{
public function checkPaymentConfiguration($imojeOnly = 0) {
return parent::checkPaymentConfiguration(1);
}
}

View File

@@ -0,0 +1,72 @@
<?php
class cvIngListener
{
public static function smartySlotAppend(sfEvent $event, $components)
{
return $components;
}
public static function postExecuteOrderSave(sfEvent $event)
{
if(!cvIngTwisto::isActive()) {
return false;
}
$action = $event->getSubject();
$twistoData = $action->getRequestParameter('user_data_billing[twisto_data]', true);
if(!empty($twistoData) && $action->order instanceof Order) {
$payment = $action->order->getOrderPayment();
if(!$payment) {
return false;
}
$payment->setCvTwistoData($twistoData);
$payment->save();
}
}
public static function validateAddBasketUser(sfEvent $event, $ok)
{
if(PaymentTypePeer::isActive('cvIngImoje') || PaymentTypePeer::isActive('cvIngTwisto')) {
$action = $event->getSubject();
if($action->getRequest()->getMethod() == sfRequest::POST) {
$delivery = stDeliveryFrontend::getInstance($action->getUser()->getBasket())->getDefaultDelivery();
if($delivery) {
$paymentModuleName = $delivery->getDefaultPayment()->getPaymentType()->getModuleName();
}
if(isset($paymentModuleName) && in_array($paymentModuleName, array(
'cvIngImoje',
'cvIngTwisto',
))) {
$tl = new cvIngTranslationCfg();
$txt = $tl->getArray();
$validateError = array(
'full_name' => $txt['twisto_validate_full_name'],
);
$billing = $action->getRequestParameter('user_data_billing', array());
if(!$billing['full_name']) {
$i18n = $action->getContext()->getI18N();
$action->getRequest()->setError('user_data_billing{full_name}', $i18n->__('Brak imienia i nazwiska.'));
$ok = false;
} elseif(!cvIngService::validateName($billing['full_name'])) {
$action->getRequest()->setError('user_data_billing{full_name}', $validateError['full_name']);
$ok = false;
}
}
}
}
return $ok;
}
}

View File

@@ -0,0 +1,22 @@
<?php
class cvIngLog{
public static function getLogPath() {
return sfConfig::get('sf_log_dir') . DIRECTORY_SEPARATOR . "cvIngPlugin.log";
}
public static function add($sth) {
if(stConfig::getInstance('cvIngBackend')->get('log_enabled')){
$file = fopen(self::getLogPath(), "a");
$sth = is_array($sth) || is_object($sth) ? print_r($sth, 1) : $sth;
fwrite($file, PHP_EOL . date("Y:m:d H:i:s") . " ==> " . $sth);
fclose($file);
}
}
public static function clearLog() {
if (file_exists(self::getLogPath())) {
unlink($this->logFile);
}
}
}

View File

@@ -0,0 +1,46 @@
<?php
class cvIngPlugin {
public static $version = '1.1.0';
protected $config;
public function __construct() {
$this->config = stPaymentType::getConfiguration('cvIngBackend');
}
public function __call($method, $arguments)
{
return stPaymentType::call($method, $this->config);
}
public function checkPaymentConfiguration($imojeOnly = 0) {
if (!$this->hasClientId()){
return false;
}
if (!$this->hasShopId()){
return false;
}
if (!$this->hasShopKey()){
return false;
}
if($imojeOnly){
return true;
}
return true;
}
public static function getTwistoPaymentIds(){
$c=new Criteria();
$c->addSelectColumn(PaymentTypePeer::ID);
$c->add(PaymentTypePeer::MODULE_NAME, 'cvIngTwisto');
$rowset = PaymentTypePeer::doSelectRS($c);
$rowset->setFetchmode(ResultSet::FETCHMODE_ASSOC);
$result = array();
foreach($rowset as $row){
$result[] = $row['ID'];
}
return $result;
}
}

View File

@@ -0,0 +1,399 @@
<?php
require_once(dirname(__FILE__) . '/src/CartData.php');
require_once(dirname(__FILE__) . '/src/Util.php');
class cvIngService
{
public $errors;
public $testModeEnabled;
public $dataDecoded;
public $transactionData;
public $paymentUrl;
public $twistoOrderData;
protected $cfg;
protected $clientId;
protected $shopId;
protected $shopKey;
protected $hashMethod = 'sha256';
public function __construct()
{
$this->cfg = stConfig::getInstance('cvIngBackend');
$this->clientId = $this->cfg->get('client_id');
$this->shopId = $this->cfg->get('shop_id');
$this->shopKey = $this->cfg->get('shop_key');
$this->testModeEnabled = $this->cfg->get('test_mode_enabled');
if($this->testModeEnabled) {
error_reporting(E_ALL);
register_shutdown_function([
'cvIngService',
'handleShutdown',
]);
}
$this->setUrl();
}
protected function setUrl()
{
if($this->testModeEnabled) {
$this->paymentUrl = 'https://sandbox.paywall.imoje.pl/pl/payment';
} else {
$this->paymentUrl = 'https://paywall.imoje.pl/pl/payment';
}
}
public static function validateName($string)
{
return preg_match('/^[^0-9!<>,;?=+()@#"°{}_$%:]+\s[^0-9!<>,;?=+()@#"°{}_$%:]+$/', $string);
}
public static function validateAddress($string)
{
return preg_match('/^[^!<>?=+@{}_$%]+\s[\w\d\/\.\/\s-]+$/', $string);
}
public static function validateTown($string)
{
return preg_match('/^[^!<>;?=+@#"°{}_$%]+$/', $string);
}
public static function validateCode($string)
{
return preg_match('/^[0-9]{2}\-[0-9]{3}$/', $string);
}
public static function validatePhone($string)
{
return preg_match('/^[0-9]{9}$/', $string);
}
public static function validateCountry($string = null)
{
return is_int(intval($string));
}
public static function validateEmail($string)
{
return preg_match('/^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i', $string);
}
public static function handleShutdown()
{
$error = error_get_last();
if($error !== null) {
cvIngLog::add("[SHUTDOWN] file:" . $error['file'] . " | ln:" . $error['line'] . " | msg:" . $error['message']);
}
}
/**
* Przygotowanie danych transkcji dla płatności imoje
*
* @param type $order
* @param type $twisto
*
* @return string
*/
public function getTransactionData($order, $twisto = 0)
{
if(!$order) {
return false;
}
$userBilling = $order->getOrderUserDataBilling();
$controller = sfContext::getInstance()->getController();
$data['serviceId'] = $this->shopId;
$data['merchantId'] = $this->clientId;
$data['amount'] = (int) $this->getOrderAmount(stPayment::getUnpayedAmountByOrder($order));
$data['currency'] = $order->getOrderCurrency()->getShortcut();
$data['orderId'] = $order->getId();
$data['customerFirstName'] = $userBilling->getName();
$data['customerLastName'] = $userBilling->getSurname();
$data['customerEmail'] = $order->getGuardUser()->getUsername();
$data['urlReturn'] = $controller->genUrl('cvIngImojeFrontend/return', true);
$data['urlSuccess'] = $controller->genUrl('cvIngImojeFrontend/returnSuccess', true);
$data['urlFailure'] = $controller->genUrl('cvIngImojeFrontend/returnFail', true);
if($twisto) {
$payment = $order->getOrderPayment();
if(!$payment) {
return false;
}
$cartData = $this->getCartData($order);
foreach($this->getPreviousOrders($order->getGuardUser()->getUsername()) as $previousOrder) {
$cartData->addPrevious($previousOrder);
}
$data['cartData'] = $cartData->prepareCartData();
}
$data['signature'] = $this->createSignature($data, $this->shopKey, $this->hashMethod) . ';' . $this->hashMethod;
return $data;
}
protected function getOrderAmount($orderAmountBrutto)
{
return number_format($orderAmountBrutto, 2, '', '');
}
protected function getCartData($order)
{
$cartData = new \Imoje\Payment\CartData();
$cartData->setAmount((int) $this->getOrderAmount(stPayment::getUnpayedAmountByOrder($order)));
$cartData->setCreatedAt(strtotime($order->getCreatedAt()));
foreach($order->getOrderProducts() as $product) {
$quantity = $product->getQuantity();
$cartData->addItem(
$product->getId(),
(int) $product->getVat(),
$product->getName(),
\Imoje\Payment\Util::convertAmountToFractional($product->getTotalAmount(true) / $quantity),
(int) $quantity
);
}
$userBilling = $order->getOrderUserDataBilling();
$cartData->setAddressBilling(
$userBilling->getTown(),
$userBilling->getFullname(),
$userBilling->getPhone(),
$userBilling->getStreet()
. ' '
. $userBilling->getHouse()
. ' '
. $userBilling->getFlat(),
$userBilling->getCountry()->getIsoA2(),
$userBilling->getCode()
);
$userDelivery = $order->getOrderUserDataDelivery();
$cartData->setAddressDelivery(
$userDelivery->getTown(),
$userDelivery->getFullname(),
$userDelivery->getPhone(),
$userDelivery->getStreet()
. ' '
. $userDelivery->getHouse()
. ' '
. $userDelivery->getFlat(),
$userDelivery->getCountry()->getIsoA2(),
$userDelivery->getCode()
);
$shipping = $order->getOrderDelivery();
$cartData->setShipping(
(int) $shipping->getOptTax(),
$shipping->getName(),
\Imoje\Payment\Util::convertAmountToFractional($shipping->getCostBrutto())
);
return $cartData;
}
/**
* @param string $email
*
* @return array
* @throws PropelException
*/
protected function getPreviousOrders($email)
{
$orderPeerCriteria = new Criteria();
$orderPeerCriteria->add(OrderPeer::OPT_CLIENT_EMAIL, $email);
$prevOrders = OrderPeer::doSelect($orderPeerCriteria);
$orders = [];
$iteration = 0;
foreach($prevOrders as $order) {
if($iteration === \Imoje\Payment\CartData::MAX_PREVIOUS_ORDERS) {
break;
}
$orderUserDataBilling = $order->getOrderUserDataDelivery();
$orderUserDataDelivery = $order->getOrderUserDataDelivery();
$orderUserDataBillingFullName = $orderUserDataBilling->getFullName();
$orderUserDataDeliveryFullName = $orderUserDataDelivery->getFullName();
$orderUserDataBillingPhone = $orderUserDataBilling->getPhone();
$orderUserDataDeliveryPhone = $orderUserDataDelivery->getPhone();
if(!$orderUserDataBillingFullName
|| !$orderUserDataDeliveryFullName
|| !$orderUserDataBillingPhone
|| !is_numeric($orderUserDataBillingPhone)
|| !$orderUserDataDeliveryPhone
|| !is_numeric($orderUserDataDeliveryPhone)) {
continue;
}
$total = (int) $this->getOrderAmount(stPayment::getUnpayedAmountByOrder($order));
$calcTotal = 0;
foreach($order->getOrderProducts() as $product) {
$calcTotal += \Imoje\Payment\Util::convertAmountToFractional($product->getTotalAmount(true));
}
$calcTotal += \Imoje\Payment\Util::convertAmountToFractional($order->getOrderDelivery()->getCostBrutto());
if($total != $calcTotal) {
continue;
}
$orders[] = $this->getCartData($order);
$iteration += 1;
}
return $orders;
}
/**
* @param array $orderData
* @param string $serviceKey
* @param string $hashMethod
*
* @return string
*/
protected function createSignature($orderData, $serviceKey, $hashMethod)
{
return hash($hashMethod, $this->prepareData($orderData) . $serviceKey);
}
/**
* @param array $data * @param string $prefix
*
* @return string
*/
protected function prepareData($data, $prefix = '')
{
ksort($data);
$hashData = [];
foreach($data as $key => $value) {
if($prefix) {
$key = $prefix . '[' . $key . ']';
}
if(is_array($value)) {
$hashData[] = prepareData($value, $key);
} else {
$hashData[] = $key . '=' . $value;
}
}
return
implode('&', $hashData);
}
#VALIDATE METHODS
public function verifySignature()
{
$dataEncoded = file_get_contents('php://input');
$this->dataDecoded = json_decode($dataEncoded, 1);
$header = $this->getHeaders();
if(!isset($header['X-Imoje-Signature']) || substr($header['Content-Type'], 0, 16) != 'application/json') {
cvIngLog::add('Wrong headers');
return false;
}
cvIngLog::add($this->dataDecoded);
cvIngLog::add($header);
$headerSignature = $this->parseHeaderValues($header['X-Imoje-Signature']);
$signature = hash($this->hashMethod, $dataEncoded . $this->shopKey);
cvIngLog::add($headerSignature['signature']);
cvIngLog::add($signature);
if($signature === $headerSignature['signature']) {
cvIngLog::add('Correct signature');
return true;
} else {
cvIngLog::add('Wrong signature');
return false;
}
}
protected function getHeaders()
{
foreach($_SERVER as $name => $value) {
if(substr($name, 0, 5) == 'HTTP_') {
$name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
$headers[$name] = $value;
} elseif($name == "CONTENT_TYPE") {
$headers["Content-Type"] = $value;
} elseif($name == "CONTENT_LENGTH") {
$headers["Content-Length"] = $value;
}
}
return $headers;
}
protected function parseHeaderValues($headerValues)
{
$result = [];
$varsets = explode(';', $headerValues);
foreach($varsets as $varset) {
$chunk = explode('=', $varset);
$result[$chunk[0]] = $chunk[1];
}
return $result;
}
public function verifyTransactionData()
{
$result = true;
if(!isset($this->dataDecoded['transaction']) || empty($this->dataDecoded['transaction'])) {
$result = false;
}
if(!isset($this->dataDecoded['transaction']['orderId']) || empty($this->dataDecoded['transaction']['orderId'])) {
$result = false;
}
if(!isset($this->dataDecoded['transaction']['status']) || empty($this->dataDecoded['transaction']['status'])) {
$result = false;
}
if($result) {
$this->transactionData = $this->dataDecoded['transaction'];
cvIngLog::add('Correct transaction data');
return true;
} else {
cvIngLog::add('Wrong transaction data');
return false;
}
}
}

View File

@@ -0,0 +1,265 @@
<?php
class cvIngTranslationCfg{
public $cfg;
public function __construct(){
$this->cfg= new cvIngCfg('cvIngTranslation', $this->getStructure(), 'cvIngBackend/translations');
}
protected function getStructure(){
$i18n = sfContext::getInstance()->getI18n();
$params = array('cols' => 150, 'rows'=>2, 'required'=>true);
$validate = array('method' => 'isEmpty', 'message'=>$i18n->__('To pole nie może być puste'));
$configFields['Informacje'] = array(
'config' => array(
'header_info_imoje' => array(
'name' => $i18n->__('Nagłówek imoje na stronie płatności'),
'default'=> 'ING - Płacę online imoje',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'header_info_twisto' => array(
'name' => $i18n->__('Nagłówek Twisto na stronie płatności'),
'default'=> 'ING - Kup i zapłać za 21 dni z Twisto',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'payment_config_info' => array(
'name' => $i18n->__('Błąd konfiguracji imoje'),
'default'=>'Wystąpił problem z konfiguracją płatności ING.',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_popup_header' => array(
'name' => $i18n->__('Nagłówek popupa twisto'),
'default'=>'Weryfikacja płatności Twisto',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'return_success' => array(
'name' => $i18n->__('Tekst powrotu - akceptacja'),
'default'=>'Dziękujemy za dokonanie płatności.',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'return_fail' => array(
'name' => $i18n->__('Tekst powrotu - odrzucenie'),
'default'=>'Płatność nie została zrealizowana.',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
)
);
$configFields['Błędy i komunikaty'] = array(
'config' => array(
'twisto_accept' => array(
'name' => $i18n->__('Akceptacja Twisto'),
'default'=>'Płatność Twisto została zaakceptowana',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_reject' => array(
'name' => $i18n->__('Twisto odrzucenie'),
'default'=>'Płatność Twisto została odrzucona.',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_form_error' => array(
'name' => $i18n->__('Twisto błędy formularza'),
'default'=>'Dane w formularzu zamówienia zawierają błędy',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_cfg_error' => array(
'name' => $i18n->__('Twisto błąd konfiguracji'),
'default'=>'Płatność nie jest poprawnie skonfigurowana',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_response_0' => array(
'name' => $i18n->__('Odpowiedź Twisto 0'),
'default'=>'Bardzo nam przykro, ale ten zakup z Twisto jest niemożliwy. Prosimy wybrać inną formę płatności.',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_response_1' => array(
'name' => $i18n->__('Odpowiedź Twisto 1'),
'default'=>'Mimo naszych starań nie udało nam się odnaleźć podanego przez Ciebie adresu. Sprawdź czy na pewno jest poprawny, a jeśli to nie pomoże - spróbuj skorzystać z innego adresu, np. miejsca pracy.',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_response_2' => array(
'name' => $i18n->__('Odpowiedź Twisto 2'),
'default'=>'Limit Twisto na ten zakup został przekroczony. Nie martw się, jeśli zapłacisz za swoje poprzednie zamówienia, Twój zakup na pewno się uda!',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_response_3' => array(
'name' => $i18n->__('Odpowiedź Twisto 3'),
'default'=>'Limit Twisto na ten zakup został przekroczony. Nie martw się, jeśli kwota Twojego zamówienia wyniesie poniżej 450PLN, Twój zakup na pewno się uda!',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_response_5' => array(
'name' => $i18n->__('Odpowiedź Twisto 5'),
'default'=>'Prosimy wpisać swoje pełne imię i nazwisko w adresie zamówienia.',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_response_6' => array(
'name' => $i18n->__('Odpowiedź Twisto 6'),
'default'=>'Ten zakup jest dostępny tylko dla zarejestrowanych klientów Twisto. Załóż konto na www.Twisto.pl',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_response_100' => array(
'name' => $i18n->__('Odpowiedź Twisto 100'),
'default'=>'Ten zakup przekracza poziom dostępnego limitu na Twoim Koncie Twisto. Nie martw się, jeśli kwota Twojego zamówienia wyniesie poniżej 450 PLN, Twój zakup na pewno się uda!',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_response_102' => array(
'name' => $i18n->__('Odpowiedź Twisto 102'),
'default'=>'Okno logowania do konta Twisto zostało zamknięte. Z powodów bezpieczeństwa prosimy o ponownie zalogowanie się przed przejściem dalej',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
)
);
$configFields['Walidacja formularza zamówienia dla twisto'] = array(
'config' => array(
'twisto_validation_error' => array(
'name' => $i18n->__('Twisto walidacja'),
'default'=>'Popraw dane w formularzu zamówienia:',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_validate_empty' => array(
'name' => $i18n->__('Walidacja pustego pola'),
'default'=>'Puste pole',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_validate_full_name' => array(
'name' => $i18n->__('Walidacja imienia i nazwiska'),
'default'=>'Imię i nazwisko powinno się składać z minimum dwóch wyrazów, nie powinno zawierać cyfr i znaków specjalnych.',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_validate_address' => array(
'name' => $i18n->__('Walidacja adresu'),
'default'=>'Adres powinien zawierać numer budynku/lokalu, nie powinien zawierać znaków specjalnych.',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_validate_town' => array(
'name' => $i18n->__('Walidacja miasta'),
'default'=>'Miasto nie powinno zawierać znaków specjalnych.',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_validate_code' => array(
'name' => $i18n->__('Walidacja kodu pocztowego'),
'default'=>'Prawidłowy format kodu pocztowego to 00-000.',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_validate_country' => array(
'name' => $i18n->__('Walidacja karaju'),
'default'=>'Wybierz kraj.',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_validate_phone' => array(
'name' => $i18n->__('Walidacja telefonu'),
'default'=>'Telefon powinien zawierać 9 cyfr, bez dodatkowych znaków.',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
'twisto_validate_email' => array(
'name' => $i18n->__('Walidacja email'),
'default'=>'Email powinien mieć format nazwa@domena.pl',
'type' => 'textarea_tag',
'params' => $params,
'validate' => $validate,
'i18n' => true,
),
)
);
return $configFields;
}
public function getArray($culture=null){
if($culture == null){
$culture= sfContext::getInstance()->getUser()->getCulture();
}
$configArray=$this->cfg->config->getArray();
if(isset($configArray['_i18n'][$culture])){
return $configArray['_i18n'][$culture];
}else{
return $configArray;
}
}
}

View File

@@ -0,0 +1,14 @@
<?php
class cvIngTwisto extends cvIngPlugin
{
public static function isActive()
{
if(!PaymentTypePeer::isActive('cvIngTwisto')) {
return false;
}
return true;
}
}

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);
}
}

View File

@@ -0,0 +1,7 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInita5e0980552123c4eaa30bed812790eab::getLoader();

View File

@@ -0,0 +1,445 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
private $missingClasses = array();
private $apcuPrefix;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath.'\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}

View File

@@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,9 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@@ -0,0 +1,10 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Twisto\\' => array($baseDir . '/src/Twisto'),
);

View File

@@ -0,0 +1,52 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInita5e0980552123c4eaa30bed812790eab
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInita5e0980552123c4eaa30bed812790eab', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInita5e0980552123c4eaa30bed812790eab', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInita5e0980552123c4eaa30bed812790eab::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
return $loader;
}
}

View File

@@ -0,0 +1,31 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInita5e0980552123c4eaa30bed812790eab
{
public static $prefixLengthsPsr4 = array (
'T' =>
array (
'Twisto\\' => 7,
),
);
public static $prefixDirsPsr4 = array (
'Twisto\\' =>
array (
0 => __DIR__ . '/../..' . '/src/Twisto',
),
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInita5e0980552123c4eaa30bed812790eab::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInita5e0980552123c4eaa30bed812790eab::$prefixDirsPsr4;
}, null, ClassLoader::class);
}
}

View File

@@ -0,0 +1 @@
[]