sms-store/src/App/Service/SZEPManagerService.php
Danyi Dávid a7b2bd4e50 * payload is no AES encrypted as in the mobile app
* async+poll query changed to sync+parse
2018-03-18 15:57:29 +01:00

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