260 lines
8.8 KiB
PHP
260 lines
8.8 KiB
PHP
<?php
|
|
|
|
namespace App\Service;
|
|
|
|
use App\Entity\SZEPCardEntry;
|
|
use Doctrine\ORM\EntityManager;
|
|
use GuzzleHttp\Client;
|
|
|
|
class SZEPManagerService
|
|
{
|
|
const TEMPLATE_WORKFLOW = "data/soap-xmls/SZEP_startWorkflowSynch.xml";
|
|
const TEMPLATE_QUERY_CARD = "data/soap-xmls/SZEP_queryCard.xml";
|
|
|
|
const CERTIFICATE_WEB_PATH = "https://www.otpbankdirekt.hu/homebank/mobilalkalmazas/certificate";
|
|
const CERTIFICATE_CACHED_PATH = "data/cache/SZEP_cert.pem";
|
|
|
|
const SOAP_ENDPOINT = "https://www.otpbankdirekt.hu/mwaccesspublic/mwaccess";
|
|
|
|
const POCKET_FOOD = 'Vendéglátás';
|
|
const POCKET_SPORT = 'Szabadidő';
|
|
|
|
const TAG_POCKET_FOOD = 'SZÉP kártya';
|
|
const TAG_POCKET_SPORT = 'SZÉP kártya - szabadidő';
|
|
|
|
const AES_KEY = [
|
|
11, 67, -99,-119,-110, -76, 76, -86,
|
|
-105, -40, -7, 73,-113, 126, -53,-100,
|
|
29, 52, 43, 98,-101, 41, 121, -68,
|
|
122, 40, 67,-123, -85, -61, 79,-107
|
|
];
|
|
const IV_PARAM = [
|
|
45, 84, 55, 96,
|
|
22,-119, 113, -64,
|
|
116, -64, 106, 56,
|
|
-100, 114,-123, -81
|
|
];
|
|
|
|
/** @var array */
|
|
private $config;
|
|
|
|
/** @var Client */
|
|
private $httpClient;
|
|
|
|
/** @var EntityManager */
|
|
private $em;
|
|
|
|
/** @var KoinService */
|
|
private $koinService;
|
|
|
|
public function __construct(Client $httpClient,
|
|
array $config,
|
|
EntityManager $em,
|
|
KoinService $koinService)
|
|
{
|
|
$this->httpClient = $httpClient;
|
|
$this->config = $config;
|
|
$this->em = $em;
|
|
$this->koinService = $koinService;
|
|
}
|
|
|
|
/**
|
|
* @throws \Doctrine\ORM\ORMException
|
|
* @throws \Doctrine\ORM\OptimisticLockException
|
|
* @throws \Doctrine\ORM\TransactionRequiredException
|
|
* @throws \Exception
|
|
*/
|
|
public function pollRecent()
|
|
{
|
|
if (null !== ($pollResult = $this->getRecentXml())) {
|
|
$this->parseResult($pollResult);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
* @throws \Exception
|
|
*/
|
|
private function getRecentXml(): ?string
|
|
{
|
|
$certificate = $this->getCertificate();
|
|
openssl_public_encrypt($this->config['szep.card'], $cryptCardId, $certificate, OPENSSL_PKCS1_PADDING);
|
|
openssl_public_encrypt($this->config['szep.key'], $cryptCardKey, $certificate, OPENSSL_PKCS1_PADDING);
|
|
|
|
$endDate = new \DateTimeImmutable();
|
|
$startDate = $endDate->sub(new \DateInterval('P4D'));
|
|
|
|
$gueryTemplate = file_get_contents(self::TEMPLATE_QUERY_CARD);
|
|
$query = sprintf($gueryTemplate,
|
|
"X" . base64_encode($cryptCardId),
|
|
base64_encode($cryptCardKey),
|
|
$startDate->format("Y.m.d"),
|
|
$endDate->format("Y.m.d")
|
|
);
|
|
|
|
$soapXml = sprintf(file_get_contents(self::TEMPLATE_WORKFLOW), $this->encryptPayload($query));
|
|
try{
|
|
$soapResponseXml = $this->doSoapRequest($soapXml);
|
|
} catch (\Exception $e) {
|
|
return null;
|
|
}
|
|
|
|
$domDocument = new \DOMDocument();
|
|
$domDocument->loadXML($soapResponseXml);
|
|
|
|
$documentXpath = new \DOMXPath($domDocument);
|
|
/** @var \DOMElement $returnElement */
|
|
$returnElement = $documentXpath->query('//return/result')->item(0);
|
|
return base64_decode($returnElement->textContent);
|
|
}
|
|
|
|
/**
|
|
* Parse the decoded payload
|
|
* @param string $resultXml
|
|
* @throws \Doctrine\ORM\ORMException
|
|
* @throws \Doctrine\ORM\OptimisticLockException
|
|
* @throws \Doctrine\ORM\TransactionRequiredException
|
|
*/
|
|
private function parseResult(string $resultXml)
|
|
{
|
|
$domDocument = new \DOMDocument();
|
|
$domDocument->loadXML($resultXml);
|
|
|
|
$documentXpath = new \DOMXPath($domDocument);
|
|
/** @var \DOMElement[] $returnElements */
|
|
$recordElements = $documentXpath->query('/answer/resultset/record');
|
|
|
|
$newRecords = [];
|
|
|
|
/** @var \DOMElement $element */
|
|
foreach ($recordElements as $element) {
|
|
$date = trim($documentXpath->query('./datum', $element)->item(0)->textContent);
|
|
$amount = trim($documentXpath->query('./osszeg', $element)->item(0)->textContent);
|
|
$merchant = trim($documentXpath->query('./ellenoldali_nev', $element)->item(0)->textContent);
|
|
$pocket = trim($documentXpath->query('./alszamla', $element)->item(0)->textContent);
|
|
|
|
$hash = md5("$date#$amount#$merchant#$pocket");
|
|
if (null != $this->em->find(SZEPCardEntry::class, $hash)) {
|
|
continue;
|
|
}
|
|
|
|
$szepCardEntity = new SZEPCardEntry();
|
|
$szepCardEntity->setHash($hash)
|
|
->setAmount($amount)
|
|
->setMerchant($merchant)
|
|
->setPocket($pocket)
|
|
->setDate(new \DateTimeImmutable(str_replace(".", "-", $date)));
|
|
$newRecords[] = $szepCardEntity;
|
|
}
|
|
|
|
/** @var SZEPCardEntry $newRecord */
|
|
foreach ($newRecords as $newRecord) {
|
|
$resultCode = $this->koinService->saveTransaction(
|
|
abs($newRecord->getAmount()),
|
|
'HUF',
|
|
$newRecord->getDate()->format("Y-m-d"),
|
|
$this->getCategory($newRecord),
|
|
$this->getTags($newRecord)
|
|
);
|
|
if ($resultCode > 199 && $resultCode < 300) {
|
|
$this->em->persist($newRecord);
|
|
}
|
|
}
|
|
$this->em->flush();
|
|
}
|
|
|
|
private function getCategory(SZEPCardEntry $SZEPCardEntry): string
|
|
{
|
|
return ($SZEPCardEntry->getAmount() > 0)
|
|
? KoinService::CATEGORY_PAYMENT
|
|
: (
|
|
$SZEPCardEntry->getPocket() == self::POCKET_FOOD
|
|
? KoinService::CATEGORY_FOOD
|
|
: KoinService::DEFAULT_EXPENSE
|
|
);
|
|
}
|
|
|
|
private function getTags(SZEPCardEntry $SZEPCardEntry): array
|
|
{
|
|
$tags = [];
|
|
|
|
if (false !== strpos($SZEPCardEntry->getMerchant(), "Sigma Technology")) {
|
|
$tags[] = 'Sigma';
|
|
$tags[] = 'Cafeteria';
|
|
} elseif (false !== strpos($SZEPCardEntry->getMerchant(), "Ericsson Ház Étterem")) {
|
|
$tags[] = 'Ericsson Ház';
|
|
$tags[] = 'Menza';
|
|
} elseif (false !== strpos($SZEPCardEntry->getMerchant(), "Planet Sushi Alle")) {
|
|
$tags[] = 'Planet Sushi';
|
|
$tags[] = 'Sushi';
|
|
} elseif (false !== strpos($SZEPCardEntry->getMerchant(), "Stoczek utcai Étterem")) {
|
|
$tags[] = 'Stoczek';
|
|
$tags[] = 'Street Food';
|
|
} elseif (false !== strpos($SZEPCardEntry->getMerchant(), "Stoczek")) {
|
|
$tags[] = 'Stoczek';
|
|
} elseif (false !== strpos($SZEPCardEntry->getMerchant(), "Science Park")) {
|
|
$tags[] = 'Science Park';
|
|
$tags[] = 'Menza';
|
|
} elseif (false !== strpos($SZEPCardEntry->getMerchant(), "Cafe Park")) {
|
|
$tags[] = 'Cafe Park';
|
|
$tags[] = 'Menza';
|
|
} elseif (false !== strpos($SZEPCardEntry->getMerchant(), "Wikinger Gyorsétterem")) {
|
|
$tags[] = 'Wikinger';
|
|
} elseif (false !== strpos($SZEPCardEntry->getMerchant(), "Wasabi Étterem")) {
|
|
$tags[] = 'Wasabi';
|
|
$tags[] = 'Sushi';
|
|
} elseif (false !== strpos($SZEPCardEntry->getMerchant(), "Szentmihályi Uszoda")) {
|
|
$tags[] = 'Tope';
|
|
}
|
|
|
|
switch ($SZEPCardEntry->getPocket()) {
|
|
case self::POCKET_FOOD: $tags[] = self::TAG_POCKET_FOOD; break;
|
|
case self::POCKET_SPORT: $tags[] = self::TAG_POCKET_SPORT; break;
|
|
}
|
|
|
|
$tags[] = 'Auto';
|
|
return $tags;
|
|
}
|
|
|
|
/**
|
|
* Returns AES encrypted base64 encoded $payload
|
|
* @param string $payload
|
|
* @param array $key
|
|
* @param array $iv
|
|
* @return string
|
|
*/
|
|
private function encryptPayload(string $payload, $key = self::AES_KEY, $iv = self::IV_PARAM): string
|
|
{
|
|
$aesKeyString = call_user_func_array("pack", array_merge(array("c*"), $key));
|
|
$ivParamStr = call_user_func_array("pack", array_merge(array("c*"), $iv));
|
|
return openssl_encrypt($payload, "AES-256-CBC", $aesKeyString, 0, $ivParamStr);
|
|
}
|
|
|
|
/**
|
|
* @param string $soapXml
|
|
* @return string
|
|
*/
|
|
private function doSoapRequest(string $soapXml): string
|
|
{
|
|
$response = $this->httpClient->post(self::SOAP_ENDPOINT, [
|
|
'body' => $soapXml,
|
|
]);
|
|
return $response->getBody()->getContents();
|
|
}
|
|
|
|
/**
|
|
* Returns the cached public certificate
|
|
* @return string
|
|
*/
|
|
private function getCertificate(): string
|
|
{
|
|
if (file_exists(self::CERTIFICATE_CACHED_PATH)) {
|
|
return file_get_contents(self::CERTIFICATE_CACHED_PATH);
|
|
}
|
|
|
|
$cert = file_get_contents(self::CERTIFICATE_WEB_PATH);
|
|
file_put_contents(self::CERTIFICATE_CACHED_PATH, $cert);
|
|
return $cert;
|
|
}
|
|
}
|