* SZEP card poller implementation added
* new tags for sms parsing
This commit is contained in:
255
src/App/Service/SZEPManagerService.php
Normal file
255
src/App/Service/SZEPManagerService.php
Normal file
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\SZEPCardEntry;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
class SZEPManagerService
|
||||
{
|
||||
const TEMPLATE_WORKFLOW_START = "data/soap-xmls/SZEP_startWorkflow.xml";
|
||||
const TEMPLATE_WORKFLOW_STATE = "data/soap-xmls/SZEP_getWorkflowState.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ő';
|
||||
|
||||
/** @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()
|
||||
{
|
||||
$workflowResult = $this->startWorkflow();
|
||||
$pollResult = $this->pollResult($workflowResult);
|
||||
$this->parseResult($pollResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the soap workflow
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function startWorkflow(): 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_START), $query);
|
||||
$soapResponseXml = $this->doSoapRequest($soapXml);
|
||||
|
||||
$domDocument = new \DOMDocument();
|
||||
$domDocument->loadXML($soapResponseXml);
|
||||
|
||||
$documentXpath = new \DOMXPath($domDocument);
|
||||
/** @var \DOMElement $returnElement */
|
||||
$returnElement = $documentXpath->query('//return')->item(0);
|
||||
return $returnElement->textContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll until the result is ready
|
||||
* @param string $workflowResult
|
||||
* @return string
|
||||
*/
|
||||
private function pollResult(string $workflowResult): string
|
||||
{
|
||||
$soapXml = sprintf(file_get_contents(self::TEMPLATE_WORKFLOW_STATE), $workflowResult);
|
||||
do {
|
||||
sleep(1);
|
||||
$soapResponseXml = $this->doSoapRequest($soapXml);
|
||||
|
||||
$domDocument = new \DOMDocument();
|
||||
$domDocument->loadXML($soapResponseXml);
|
||||
|
||||
$documentXpath = new \DOMXPath($domDocument);
|
||||
/** @var \DOMElement $completedElement */
|
||||
$completedElement = $documentXpath->query('//completed')->item(0);
|
||||
} while ($completedElement->textContent != "true");
|
||||
|
||||
/** @var \DOMElement $resultElement */
|
||||
$resultElement = $documentXpath->query('//result')->item(0);
|
||||
return base64_decode($resultElement->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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user