<?php
declare(strict_types=1);
namespace App\Controller\V1;
use App\Entity\Local\GiftCard;
use App\Entity\Local\Response\PaymentStartResponse;
use App\Entity\Local\Response\PaymentStatusResponse;
use App\Entity\Local\SessionBase;
use App\Entity\Local\PaymentRedirect;
use App\Entity\Vista\LoyaltyMember;
use App\Entity\Vista\Order;
use App\Entity\Vista\Session;
use App\Exceptions\PaymentException;
use App\Repository\BookingRepository as BookingHelper;
use App\Helper\MPay24Helper;
use App\Helper\PaymentHelper;
use App\Manager\SessionDetailsManager;
use App\Helper\UserHelper;
use App\Repository\MailingRepositoryInterface as MailingRepository;
use App\Repository\MPay24OrderPaymentTransactionRepository;
use App\Repository\OrderRepositoryInterface;
use App\Repository\RestrictionsRepository;
use App\Repository\SessionRepositoryInterface;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ORM\EntityManager;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\Controller\Annotations\Get;
use FOS\RestBundle\Controller\Annotations\Post;
use FOS\RestBundle\Controller\Annotations\Route;
use FOS\RestBundle\Controller\Annotations\View;
use Nelmio\ApiDocBundle\Annotation\Model;
use Nelmio\ApiDocBundle\Annotation\Security as NelmioSecurity;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Swagger\Annotations as SWG;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use App\Security\Security;
use Psr\Log\LoggerInterface;
use App\Entity\Vista\PayableInterface;
use App\Repository\GiftCardsRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
/**
* @Route("/orders/{orderId}/payment", requirements={"orderId"=".+"})
* @SWG\Tag(name="Order_v1")
*/
class OrderPaymentController extends AbstractController
{
/** @var MPay24Helper */
protected $mPay24Helper;
/** @var PaymentHelper */
protected $paymentHelper;
/** @var EntityManager */
protected $em;
/** @var OrderRepositoryInterface */
protected $orderRepository;
/** @var RestrictionsRepository */
protected $restrictionsRepository;
/** @var BookingHelper */
protected $bookingHelper;
/** @var SessionDetailsManager */
protected $sessionDetailsHelper;
/** @var UserHelper */
protected $userHelper;
/** @var MPay24OrderPaymentTransactionRepository */
protected $mPay24OrderPaymentTransactionRepository;
/** @var SessionRepositoryInterface */
private $sessionRepository;
/** @var GiftCardsRepository */
private $giftCardsRepository;
/** @var MailingRepository */
private $mailer;
/** @var LoggerInterface */
protected $logger;
public function __construct(
MPay24Helper $mPay24Helper,
PaymentHelper $paymentHelper,
OrderRepositoryInterface $orderRepository,
RestrictionsRepository $restrictionsRepository,
ManagerRegistry $managerRegistry,
BookingHelper $bookingHelper,
SessionDetailsManager $sessionDetailsHelper,
UserHelper $userHelper,
MPay24OrderPaymentTransactionRepository $mPay24OrderPaymentTransactionRepository,
SessionRepositoryInterface $sessionRepository,
MailingRepository $mailer,
GiftCardsRepository $giftCardsRepository,
LoggerInterface $logger
) {
$this->mPay24Helper = $mPay24Helper;
$this->paymentHelper = $paymentHelper;
$this->orderRepository = $orderRepository;
$this->restrictionsRepository = $restrictionsRepository;
$this->em = $managerRegistry->getManager();
$this->bookingHelper = $bookingHelper;
$this->sessionDetailsHelper = $sessionDetailsHelper;
$this->userHelper = $userHelper;
$this->mPay24OrderPaymentTransactionRepository = $mPay24OrderPaymentTransactionRepository;
$this->sessionRepository = $sessionRepository;
$this->mailer = $mailer;
$this->giftCardsRepository=$giftCardsRepository;
$this->logger = $logger;
}
/**
* @param string $orderId
* @return Order
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
*/
protected function renewOrder(string $orderId): Order
{
$order = $this->orderRepository->findOneBy([
'userSessionId' => $orderId,
'state' => Order::STATE_PAYMENT_STARTED,
]);
if (null === $order) {
return $this->orderRepository->findBySessionId($orderId, Order::STATE_NEW);
}
$order->setState(Order::STATE_NEW);
$this->orderRepository->save($order);
if ($transaction = $this->mPay24OrderPaymentTransactionRepository->find($order->getUserSessionId())) {
$transaction->setPaymentHasBeenStarted(false);
$this->mPay24OrderPaymentTransactionRepository->saveTransaction($transaction);
}
return $order;
}
/**
* @Post("/start")
* @Rest\QueryParam(name="email", allowBlank=true)
* @View()
*
* @SWG\Response(
* response=200,
* description="The payment has started",
* @Model(type=\App\Entity\Local\Response\PaymentStartResponse::class))
*
* @param $orderId
* @param Security $security
* @param string|null $email
*
* @return PaymentStartResponse
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \Throwable
*/
public function startPaymentAction($orderId, Security $security, string $email = null)
{
$time_start = microtime(true);
/** @var Order $order */
$order = $this->renewOrder($orderId);
$user = $security->getUser();
if( $order->getSession()){
$session = $this->sessionDetailsHelper->loadSession($order->getCinemaId(), $order->getSession()->getId());
if ($user) {
$this->checkTransactionHistory($order, $session, $user);
}
} else {
// concessions direct purchase
}
$restrictions = $this->restrictionsRepository->findOrCreate();
if( $order->getPriceInCents() < 1 ) {
$this->logger->info('Order value is zero, omitting payment');
$hash = $this->paymentHelper->startOrderPayment($order, $user ?? (string) $email);
$order->setState(Order::STATE_PAYMENT_FINISHED);
$succ=$this->paymentFinishZeroValue($order);
$return = (new PaymentStartResponse())
->setPaymentPageUrl($succ)
->setOrderId(null)
->setRestrictions($restrictions);
$execution_time = microtime(true) - $time_start; error_log(sprintf("%s %s(%s) orderId $orderId execution_time $execution_time ", date('Y-m-d H:i:s'), __METHOD__, __LINE__));
return $return;
} else {
if(getenv('RAIFFEISEN_ADDRESS')) { // Raiffeisen
$paymentPageUrl = (getenv('DEV_SIRMA') ? 'http://' . getenv('DEV_SIRMA') : getenv('SCHEME_AND_HTTP_HOST')) .
sprintf("/api/v1/orders/ord-%s/payment/raiffeisen-redirect/%s/%s/%s", $orderId, $this->paymentHelper->startOrderPayment($order, $user ?? (string) $email), $order->getOrderTotalValueInCents(), $order->getCinemaId());
} else if(getenv('CPAY_ADDRESS')) { // cPay
$paymentPageUrl = (getenv('DEV_SIRMA') ? 'http://' . getenv('DEV_SIRMA') : getenv('SCHEME_AND_HTTP_HOST')) .
sprintf("/api/v1/orders/%s/payment/cpay-redirect/%s/%s/%s", $orderId, $this->paymentHelper->startOrderPayment($order, $user ?? (string) $email), intval($order->getOrderTotalValueInCents()), $order->getCinemaId());
} else if(getenv('KOM_BANK_ADDRESS')) {
$paymentPageUrl = (getenv('DEV_SIRMA') ? 'http://' . getenv('DEV_SIRMA') : getenv('SCHEME_AND_HTTP_HOST')) .
sprintf("/api/v1/orders/%s/payment/kom-bank-redirect/%s/%s/%s", $orderId, $this->paymentHelper->startOrderPayment($order, $user ?? (string) $email), $order->getOrderTotalValueInCents(), $order->getCinemaId());
} else { // mpay24
$paymentPageUrl = $this->mPay24Helper->startOrderPayment($order, $user ?? (string) $email);
}
$response = (new PaymentStartResponse())
->setPaymentPageUrl($paymentPageUrl)
->setOrderId(null)
->setRestrictions($restrictions);
$order->setState(Order::STATE_PAYMENT_STARTED);
$this->em->flush();
$execution_time = microtime(true) - $time_start; error_log(sprintf("%s %s(%s) orderId $orderId execution_time $execution_time ", date('Y-m-d H:i:s'), __METHOD__, __LINE__));
return $response;
}
}
/**
* @Get("/finish/{hash}", name="payment_finish")
*
* @SWG\Response(
* response=200,
* description="The payment is finished",
* @Model(type=\App\Entity\Local\MPay24OrderPaymentTransaction::class))
*
* @param $orderId
* @param $hash
* @param Request $request
* @return Response
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function finishPaymentAction($orderId, $hash, Request $request)
{
try {
$order = $this->orderRepository->findBySessionId($orderId, Order::STATE_PAYMENT_STARTED);
} catch (\Exception $ex) {
$this->orderRepository->findBySessionId($orderId, Order::STATE_PAYMENT_FINISHED);
$response = new Response(PaymentHelper::PAYMENT_RESPONSE_OK);
return $response;
}
$result = $this->mPay24Helper->finishPayment($order, $hash, $request->query->all());
if ($result->getOrderToBeUpdated()) {
$order->setState(Order::STATE_PAYMENT_FINISHED);
$this->em->flush();
}
return new Response($result->getResponse());
}
/**
* @Get("/status")
* @View()
*
* @SWG\Response(
* response=200,
* description="Status of the payment",
* @Model(type=\App\Entity\Local\Response\PaymentStatusResponse::class))
*
* @param $orderId
* @return PaymentStatusResponse
* @throws \ReflectionException
* @throws \Symfony\Component\Serializer\Exception\ExceptionInterface
*/
public function getPaymentStatusAction($orderId)
{
[$status] = $this->mPay24Helper->requestStatus($orderId);
return (new PaymentStatusResponse())->setState($status);
}
/**
* @Get("/status-final")
* @View()
*
* @SWG\Response(
* response=200,
* description="Final status of the payment",
* @Model(type=\App\Entity\Local\Response\PaymentStatusResponse::class))
*
* @param $orderId
* @return PaymentStatusResponse
*/
public function getPaymentStatusFinalAction($orderId) {
$time_start = microtime(true);
if(getenv('KOM_BANK_ADDRESS') || getenv('RAIFFEISEN_ADDRESS') || getenv('CPAY_ADDRESS')) {
[$status, $mPay24State, $statusExtended] = $this->paymentHelper->getStatus($orderId);
} else {
[$status, $mPay24State, $statusExtended] = $this->mPay24Helper->getStatus($orderId);
}
$return = (new PaymentStatusResponse())
->setStatus($status)
->setMPay24State($mPay24State)
->setStatusExtended($statusExtended);
$execution_time = microtime(true) - $time_start; error_log(sprintf("%s %s(%s) orderId $orderId execution_time $execution_time ", date('Y-m-d H:i:s'), __METHOD__, __LINE__));
return $return;
}
/**
* Redirect endpoint for Raiffeisen
*
* @Get("/raiffeisen-redirect/{hash}/{amountCents}/{cinemaId}", name="raiffeisen_redirect")
*
* @SWG\Response(
* response=200,
* description="Stub empty result",
* @SWG\Schema(type="string"))
*
* @param $orderId
* @param $hash
* @param $amountCents
* @param $cinemaId
* @return Response
*/
public function handleRaiffeisenRedirect($orderId, $hash, $amountCents, $cinemaId) {
$paymentPageUrl = getenv('RAIFFEISEN_ADDRESS') ? getenv('RAIFFEISEN_ADDRESS') : 'https://ecg.test.upc.ua/rbko/enter'; // 'https://ecg.test.upc.ua/go/enter' https://ecg.test.upc.ua/rbko/enter
$certPath = getenv('RAIFFEISEN_CERT_PATH') ? getenv('RAIFFEISEN_CERT_PATH') : 'config/certificates/';
$certPass = getenv('RAIFFEISEN_CERT_PASS') ? getenv('RAIFFEISEN_CERT_PASS') : 'cineplexx';
$OrderID = rand();
$SD = $orderId;
$ids = $this->paymentHelper->getMerchant($cinemaId);
//error_log(sprintf("%s %s(%s) ", date('Y-m-d H:i:s'), __METHOD__, __LINE__) . " SD $SD cinemaId $cinemaId " . var_export($ids, true));
$merchantId = $ids && array_key_exists('id', $ids) ? $ids['id'] : '1755375'; //Merchant Id defined by bank to user
$terminalId = $ids && array_key_exists('pass', $ids) ? $ids['pass'] : 'E7883395'; //Store key value, defined by bank.
$env = getenv('APP_ENV');
if ($env == 'dev') {
$env = 'stage';
}
$country = getenv('APP_COUNTRY');;
if (!$cert_store = file_get_contents(__DIR__ . "/../../../{$certPath}Raiffeisen_{$country}_$env.p12")) {
echo "Error: Unable to read the cert file\n";
exit;
}
$cert_info = [];
if (openssl_pkcs12_read($cert_store, $cert_info, $certPass)) {
//error_log(sprintf("%s %s(%s) ", date('Y-m-d H:i:s'), __METHOD__, __LINE__) . $cert_info['pkey']);
} else {
error_log(sprintf("%s %s(%s) ", date('Y-m-d H:i:s'), __METHOD__, __LINE__) . "Error: Unable to read the cert store.\n");
}
$response = null;
if($cert_info && array_key_exists('pkey', $cert_info)) {
$privKey = $cert_info['pkey'];
//error_log("privKey $privKey");
$pkeyId = openssl_get_privatekey($privKey);
$purchaseTime = date("ymdHis") ;
$currencyId = $this->getRaiffeisenCurrency();
$data = "$merchantId;$terminalId;$purchaseTime;$OrderID;$currencyId;$amountCents;$SD;";
$signature = null;
openssl_sign($data , $signature, $pkeyId);
openssl_free_key($pkeyId);
$b64sign = base64_encode($signature);
//error_log(sprintf("%s %s(%s) ", date('Y-m-d H:i:s'), __METHOD__, __LINE__) . "data $data b64sign $b64sign");
$version = 1;
$locale = ($country == 'bih' || $country == 'bil') ? 'rs' : 'sq';
$paymentRedirect = (new PaymentRedirect())
->setRedirectUrl($paymentPageUrl)
->setFormTitle('Raiffeisen Bank')
->setPaymentData([
'Version' => $version,
'MerchantID' => $merchantId,
'TerminalID' => $terminalId,
'TotalAmount' => $amountCents,
'Currency' => $currencyId,
'locale' => $locale,
'PurchaseTime' => $purchaseTime,
'OrderID' => $OrderID,
'SD' => $SD,
'Signature' => $b64sign
]);
$response = $this->render('web/redirect.html.twig', ['paymentRedirect' => $paymentRedirect] );
//error_log(sprintf("%s %s(%s) payment_finish orderId $orderId", date('Y-m-d H:i:s'), __METHOD__, __LINE__));
} else {
$response = new Response();
}
$response->headers->set('Content-Type', 'text/html; charset=UTF-8'); // Content-Type: text/html; charset=UTF-8
return $response;
}
/**
* Redirect endpoint for KomBank
*
* @Get("/kom-bank-redirect/{hash}/{amount}/{cinemId}", name="kom_bank_redirect", defaults={"email"=""})
*
* @SWG\Response(
* response=200,
* description="Stub empty result",
* @SWG\Schema(type="string"))
*
* @param $orderId
* @param $hash
* @param $amount
* @param $cinemId
* @return Response
*/
public function handleKomBankRedirect(string $orderId, $hash, $amount, $cinemId) {
$response = $this->getKombankRedirectForm($orderId, sprintf('/api/v1/orders/%s/payment/handle-redirect-post/%s', $orderId, $hash), sprintf('%.2f', $amount / 100), $cinemId);
// $response = $this->getKombankRedirectForm($orderId, sprintf('%s/payment-return/%s/handle-redirect-post/%s', getenv('WEBSITE_SCHEME_AND_HTTP_HOST'), $orderId, $hash), sprintf('%.2f', $amount / 100), $cinemId);
//error_log(sprintf("%s %s(%s) payment_finish orderId $orderId", date('Y-m-d H:i:s'), __METHOD__, __LINE__));
return $response;
}
/**
* Redirect endpoint for Cpay
*
* @Get("/cpay-redirect/{hash}/{amount}/{cinemId}", name="cpay_redirect")
*
* @SWG\Response(
* response=200,
* description="Stub empty result",
* @SWG\Schema(type="string"))
*
* @param $orderId
* @param $hash
* @param $amount
* @param $cinemId
* @return Response
*/
public function handleCpayRedirectAction(string $orderId, $hash, $amount, $cinemId) {
$time_start = microtime(true);
$response = $this->getCpayRedirectForm($orderId, sprintf('/api/v1/orders/%s/payment/handle-redirect-post/%s', $orderId, $hash), $amount, $cinemId);
//error_log(sprintf("%s %s(%s) payment_finish orderId $orderId", date('Y-m-d H:i:s'), __METHOD__, __LINE__));
$execution_time = microtime(true) - $time_start; error_log(sprintf("%s %s(%s) orderId $orderId execution_time $execution_time ", date('Y-m-d H:i:s'), __METHOD__, __LINE__));
return $response;
}
/**
* Redirect endpoint for KomBank
*
* @Route("/kom-bank-booking/{hash}/{amount}/{cinemId}", methods={"GET"}, name="kom_bank_booking")
*
* @SWG\Response(
* response=200,
* description="Stub empty result",
* @SWG\Schema(type="string"))
*
* @param $orderId
* @param $hash
* @param $amount
* @param $cinemId
* @return Response
*/
public function getKombankBookingRedirect(string $orderId, $hash, $amount, $cinemId) {
$response = $this->getKombankRedirectForm($orderId, sprintf('/api/v1/users/bookings/%s/payment-finish-kombank/%s', $orderId, $hash), sprintf('%.2f', $amount / 100), $cinemId);
//error_log(sprintf("%s %s(%s) payment_finish orderId $orderId", date('Y-m-d H:i:s'), __METHOD__, __LINE__));
return $response;
}
/**
* Redirect endpoint for Cpay
*
* @Route("/cpay-booking/{hash}/{amount}/{cinemId}", methods={"GET"}, name="cpay_bank_booking")
*
* @SWG\Response(
* response=200,
* description="Stub empty result",
* @SWG\Schema(type="string"))
*
* @param $orderId
* @param $hash
* @param $amount
* @param $cinemId
* @return Response
*/
public function getCpayBookingRedirect(string $orderId, $hash, $amount, $cinemId) {
$time_start = microtime(true);
$response = $this->getCpayRedirectForm($orderId, sprintf('/api/v1/users/bookings/%s/payment-finish-cpay/%s', $orderId, $hash), $amount, $cinemId);
//error_log(sprintf("%s %s(%s) payment_finish orderId $orderId", date('Y-m-d H:i:s'), __METHOD__, __LINE__));
$execution_time = microtime(true) - $time_start; error_log(sprintf("%s %s(%s) orderId $orderId execution_time $execution_time ", date('Y-m-d H:i:s'), __METHOD__, __LINE__));
return $response;
}
public function getRaiffeisenCurrency() {
switch(getenv('APP_COUNTRY')){
case 'alb': return '008';
case 'bih': return '977';
case 'bil': return '977';
default: return '978';
}
}
/**
* Redirect endpoint for Raiffeisen
*
* @Post("/raiffeisen-response", name="raiffeisen_response")
*
* @SWG\Response(
* response=200,
* description="Stub empty result",
* @SWG\Schema(type="string"))
*
* @param $orderId
* @param Request $request
* @return Response
*/
public function handleRaiffeisenResponse($orderId, Request $request) { // bkn- ord- TranCode
$time_start = microtime(true);
//$queryParams = json_decode($request->getContent(), true);; // json_decode($request->getContent(), true);
$queryParams = $_POST;
//$request->request->all();
$result = $orderId;
if ($result == 'notify') {
$merchantId = $queryParams['MerchantID'];
$terminalId = $queryParams['TerminalID'];
$orderIdFake = $queryParams['OrderID'];
$currencyId = $this->getRaiffeisenCurrency();
$totalAmount = $queryParams['TotalAmount'];
$xid = $queryParams['XID'];
$purchaseTime = $queryParams['PurchaseTime'];
$responsetext = <<<TEXT
MerchantID="$merchantId"
TerminalID="$terminalId"
OrderID="$orderIdFake"
Currency="$currencyId"
TotalAmount="$totalAmount"
XID="$xid"
PurchaseTime="$purchaseTime"
Response.action=approve
TEXT;
$response = new Response($responsetext);
$response->headers->set('Content-Type', 'text/plain; charset=UTF-8');
return $response;
}
$orderIdComposed = $request->get('SD');
//error_log(sprintf("%s %s(%s) ", date('Y-m-d H:i:s'), __METHOD__, __LINE__) . " $orderIdComposed queryParams " . var_export($queryParams, true));
// http://93.123.103.157:8124/api/v1/orders/success/payment/raiffeisen-response success https://app.cineplexx-ks.eu http://93.123.103.157:8124 https://ial-stage.cineplexx-ks.eu
// http://93.123.103.157:8124/api/v1/orders/error/payment/raiffeisen-response error
// http://93.123.103.157:8124/api/v1/orders/notify/payment/raiffeisen-response notify
if (!preg_match('/^(ord-|bkn-)(.+$)/', $orderIdComposed, $matches) || count($matches) < 3) {
$return = new RedirectResponse(sprintf(
'%s/purchase/payment/%s',
getenv('WEBSITE_SCHEME_AND_HTTP_HOST'),
$orderIdComposed
));
return $return;
}
$paymentType = $matches[1];
$orderId = $matches[2];
if ($paymentType == 'bkn-') {
$this->logger->info('handleRaiffeisenResponse: paymentType = bkn');
$return = $this->paymentFinishRaiffeisen($orderId, $queryParams, $result);
return $return;
}
try {
$order = $this->orderRepository->findBySessionId($orderId, Order::STATE_PAYMENT_STARTED);
} catch (\Exception $ex) {
$this->orderRepository->findBySessionId($orderId, Order::STATE_PAYMENT_FINISHED);
//error_log(sprintf("%s %s(%s) ", date('Y-m-d H:i:s'), __METHOD__, __LINE__) . ' ex ' . var_export($ex, true));
$return = new RedirectResponse(sprintf(
'%s/purchase/payment/%s/%s',
getenv('WEBSITE_SCHEME_AND_HTTP_HOST'),
$result,
$orderId
));
return $return;
}
$this->logger->info('handleRaiffeisenResponse: result = ['.$result.']');
if ('success' === $result) {
$queryParams['STATUS'] = 'BILLED';
} else {
$queryParams['STATUS'] = 'FAILED';
}
$transaction = $this->mPay24OrderPaymentTransactionRepository->find($order->getPaymentId());
$payment = $this->paymentHelper->finishPayment($order, $transaction->getHash(), $queryParams);
if ($payment->getOrderToBeUpdated()) {
$order->setState(Order::STATE_PAYMENT_FINISHED);
$this->em->flush();
}
$return = new RedirectResponse(sprintf(
'%s/purchase/payment/%s/%s',
getenv('WEBSITE_SCHEME_AND_HTTP_HOST'),
$result,
$orderId
));
return $return;
}
// CIN311
/*
* @param Order $order
* @param Request $request
* @return string
*/
private function paymentFinishZeroValue($order)
{
/** @var Order $order */
$transaction = $this->paymentHelper->getTransaction($order->getPaymentId());
$queryParams=[];
$queryParams['STATUS'] = 'BILLED';
if ($transaction) {
$hash = $transaction->getHash();
$this->logger->info("finishing zero value payment");
$response = $this->paymentHelper->finishPayment($order, $hash, $queryParams);
return $response->getResponse()==PaymentHelper::PAYMENT_RESPONSE_OK ? 'success':'error';
}
return 'error';
}
//////
private function paymentFinishRaiffeisen($orderId, $queryParams, $result) {
$time_start = microtime(true);
$transaction = $this->paymentHelper->getTransaction($orderId);
$memberId = $transaction->getMemberId();
$member = (new LoyaltyMember())->setMemberId($memberId);
$user = $this->userHelper->validate($member);
$booking = $this->bookingHelper->findBooking((int) $transaction->getVistaBookingNumber(), $user);
if (null === $booking) {
throw new \InvalidArgumentException(sprintf(
'Invalid transaction, booking with number %s doesn\'t exist',
$transaction->getVistaBookingNumber()
));
}
if ($booking->isPaid() && 'success' === $result) {
$return = new RedirectResponse(sprintf(
'%s/purchase/payment/%s/%s',
getenv('WEBSITE_SCHEME_AND_HTTP_HOST'),
$result,
$orderId
));
return $return;
}
if ('success' === $result) {
$queryParams['STATUS'] = 'BILLED';
} else {
$queryParams['STATUS'] = 'FAILED';
}
if (!$booking->isPaid() && 'success' === $result) {
$hash = $transaction->getHash();
$this->paymentHelper->finishPayment($booking, $hash, $queryParams);
$return = new RedirectResponse(sprintf(
'%s/purchase/payment/%s/%s',
getenv('WEBSITE_SCHEME_AND_HTTP_HOST'),
$result,
$orderId
));
return $return;
}
$return = new RedirectResponse(sprintf(
'%s/purchase/payment/%s/%s',
getenv('WEBSITE_SCHEME_AND_HTTP_HOST'),
$result,
$orderId
));
return $return;
}
/**
* Redirect endpoint for KomBank
*
* @Post("/handle-redirect-post/{hash}/{result}", name="payment_handle_post_redirect")
*
* @SWG\Response(
* response=200,
* description="Stub empty result",
* @SWG\Schema(type="string"))
*
* @param $orderId
* @param $result
* @return Response
*/
public function handlePaymentPostRedirectAction($orderId, $hash, $result) {
$time_start = microtime(true);
$queryParams = $_POST;
//error_log(sprintf("%s %s(%s) orderId $orderId result $result hash $hash ", date('Y-m-d H:i:s'), __METHOD__, __LINE__));
$state = Order::STATE_PAYMENT_STARTED;
$order = null;
try {
$order = $this->orderRepository->findBySessionId($orderId, $state);
} catch (\Exception $ex) {
if (!getenv('CPAY_ADDRESS')) {
$this->orderRepository->findBySessionId($orderId, $state);
$return = new Response(PaymentHelper::PAYMENT_RESPONSE_OK);
$execution_time = microtime(true) - $time_start; error_log(sprintf("%s %s(%s) orderId $orderId execution_time $execution_time ", date('Y-m-d H:i:s'), __METHOD__, __LINE__));
return $return;
}
}
$redirectUrl = '%s/purchase/payment/success/%s';
if ('success' === $result) {
$queryParams['STATUS'] = 'BILLED';
} else {
$redirectUrl = '%s/purchase/payment/error/%s';
$queryParams['STATUS'] = 'FAILED';
}
if ($order) {
$payment = $this->paymentHelper->finishPayment($order, $hash, $queryParams);
if ($payment->getOrderToBeUpdated()) {
$order->setState(Order::STATE_PAYMENT_FINISHED);
$this->em->flush();
}
}
$redirectURL = sprintf(
$redirectUrl,
getenv('WEBSITE_SCHEME_AND_HTTP_HOST'),
$orderId
);
$response = $this->render('web/payment-redirect.html.twig', ['redirectUrl' => $redirectURL] );
$response->headers->set('Content-Type', 'text/html; charset=UTF-8');
return $response;
}
/**
* Redirect endpoint for MPay24
*
* @Get("/handle-redirect/{result}", name="payment_handle_redirect")
*
* @SWG\Response(
* response=200,
* description="Stub empty result",
* @SWG\Schema(type="string"))
*
* @param $orderId
* @param $result
* @return Response
*/
public function handlePaymentRedirectAction($orderId, $result) {
if ('success' === $result) {
return new RedirectResponse(sprintf( // missing /api/v1/orders/$orderId
'%s/purchase/payment-success/%s',
getenv('DEV_SIRMA') ? 'http://' . getenv('DEV_SIRMA') : getenv('WEBSITE_SCHEME_AND_HTTP_HOST'),
$orderId
));
} else {
return new RedirectResponse(sprintf(
'%s/purchase/payment-failure/%s',
getenv('DEV_SIRMA') ? 'http://' . getenv('DEV_SIRMA') : getenv('WEBSITE_SCHEME_AND_HTTP_HOST'),
$orderId
));
}
}
/**
* @Post("/reserveTickets")
* @NelmioSecurity(name="Bearer")
* @ParamConverter("order", options={"mapping": {"orderId": "userSessionId"}})
* @View()
*
* @SWG\Response(
* response=200,
* description="The payment has started",
* @Model(type=\App\Entity\Vista\Booking::class))
*
* @param $orderId
* @param Security $security
* @return mixed
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \GuzzleHttp\Exception\GuzzleException
* @throws \Throwable
*/
public function reserveTicketsAction($orderId, Security $security) {
$user = $security->getUser();
$order = $this->renewOrder($orderId);
$session = $this->sessionDetailsHelper->loadSession($order->getCinemaId(), $order->getSession()->getId());
$this->checkBookingHistory($order, $session, $user);
$user = $this->userHelper->validate($user);
$result = $this->paymentHelper->reserveTickets($user, $order);
if ($result) {
$order->setState(Order::STATE_RESERVED);
}
$this->em->flush();
return $result;
}
/**
* @Post("/with-bonus-card")
* @NelmioSecurity(name="Bearer")
* @View()
*
* @SWG\Response(
* response=200,
* description="The payment is finished",
* @Model(type=\App\Entity\Local\MPay24OrderPaymentTransaction::class))
*
* @param $orderId
* @param Security $security
* @return mixed
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \GuzzleHttp\Exception\GuzzleException
* @throws \Throwable
*/
public function vistaPaymentAction($orderId, Security $security) {
$order = $this->orderRepository->findBySessionId($orderId);
/** @var LoyaltyMember $loyaltyMember */
if (!($loyaltyMember = $security->getUser())) {
throw new AccessDeniedException('Only authorized users can make payments with bonus card');
}
if( $order->getSession()){
$session = $this->sessionDetailsHelper->loadSession($order->getCinemaId(), $order->getSession()->getId());
$this->checkTransactionHistory($order, $session, $loyaltyMember);
} else {
$this->logger->info("processing payment for concessions direct purchase");
}
$booking = $this->paymentHelper->makeVistaPayment($loyaltyMember, "", $order, []);
$order->setState(Order::STATE_PAYMENT_FINISHED);
$this->em->flush();
return $booking;
}
/**
* @Post("/with-gift-card")
* @NelmioSecurity(name="Bearer")
* @ParamConverter("giftcards", converter="fos_rest.request_body", class="array")
* @Rest\QueryParam(name="email", allowBlank=true)
* @View()
*
* @SWG\Response(
* response=200,
* description="The payment is finished",
* @Model(type=\App\Entity\Local\MPay24OrderPaymentTransaction::class))
*
* @param $orderId
* @param array $giftcards
* @param string $email
* @param Security $security
* @return mixed
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \GuzzleHttp\Exception\GuzzleException
* @throws \Throwable
*/
public function payWithGiftCardAction($orderId, $giftcards, $email, Security $security)
{
$this->logger->debug('paying with ' .count($giftcards). ' giftcard(s) for '.$email);
$order = $this->orderRepository->findBySessionId($orderId);
$usedgiftcards=[];
foreach($giftcards as $gcitem){
$gc=(new GiftCard)
->setNumber($gcitem['Number'])
->setValueInCents($this->giftCardsRepository->getGiftCardValue($gcitem['Number']));
$usedgiftcards[]=$gc;
}
$loyaltyMember = $security->getUser();
usort($usedgiftcards, function($a, $b){
return $a->getValueInCents() - $b->getValueInCents();
});
$this->logger->info('found '.count($usedgiftcards). ' giftcards and sorted them');
if( $order->getSession() and $loyaltyMember){
$session = $this->sessionDetailsHelper->loadSession($order->getCinemaId(), $order->getSession()->getId());
$this->checkTransactionHistory($order, $session, $loyaltyMember);
}
$booking = $this->paymentHelper->makeVistaPayment($loyaltyMember, $email, $order, $usedgiftcards);
$order->setState(Order::STATE_PAYMENT_FINISHED);
$this->em->flush();
return $booking;
}
/**
* @Post("/apply-gift-card")
* @NelmioSecurity(name="Bearer")
* @ParamConverter("giftcards", converter="fos_rest.request_body", class="array")
* @View()
*
* @SWG\Response(
* response=200,
* description="gift card applied",
* @Model(type=\App\Entity\Local\MPay24OrderPaymentTransaction::class))
*
* @param $orderId
* @param array $giftcards
* @param Security $security
* @return mixed
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \GuzzleHttp\Exception\GuzzleException
* @throws \Throwable
*/
public function applyGiftCardAction($orderId, $giftcards, Security $security)
{
try {
$order = $this->renewOrder($orderId);
} catch (\Throwable $ex) {
// must be a reservation :|
return;
}
if (count($giftcards) < 1) {
// remove all giftcards to let the user remove giftcards from the order
$this->paymentHelper->removeAllGiftCardsFromOrder($order);
$this->em->flush();
} else {
$this->logger->debug('applying ' . count($giftcards) . ' giftcard(s) to ' . $orderId);
$usedgiftcards = [];
foreach($giftcards as $gcitem){
$gc=(new GiftCard)
->setNumber($gcitem['Number'])
->setValueInCents($this->giftCardsRepository->getGiftCardValue($gcitem['Number']));
$usedgiftcards[]=$gc;
}
usort($usedgiftcards, function($a, $b){
return $a->getValueInCents() - $b->getValueInCents();
});
$this->logger->info('found '.count($usedgiftcards). ' giftcards and sorted them');
$result = $this->paymentHelper->applyGiftCardsToOrder($order, $usedgiftcards);
if ($result) {
$order->setOrderTotalValueInCents($result->getOrderValueRemaining());
$this->em->flush();
}
}
return $order;
}
/**
* @Get("/tickets")
* @View()
*
* @SWG\Response(
* response=200,
* description="Tickets of the payment",
* @Model(type=\App\Entity\Vista\Booking::class))
*
* @param $orderId
* @return mixed
*/
public function getTicketsAction($orderId) {
$transaction = $this->paymentHelper->getTransaction($orderId);
if (!$transaction->getVistaBookingId() || !$transaction->getCinemaId()) {
throw new \InvalidArgumentException(sprintf(
'Unpaid order, payment was not processed by MPay24, order id: %s',
$orderId
));
}
// TODO: check if vista booking exists
return $this->bookingHelper->getById($transaction->getVistaBookingId(), $transaction->getCinemaId());
}
/**
* @Route("/tickets/pdf", methods={"GET"})
* @Rest\View()
*
* @SWG\Response(
* response="200",
* description="Success")
*
* @param Security $security
* @param string $orderId
* @return Response
*/
public function pdfAction(Security $security, string $orderId) {
$transaction = $this->paymentHelper->getTransaction($orderId);
if (!$transaction->getVistaBookingId() || !$transaction->getCinemaId()) {
throw new \InvalidArgumentException(sprintf(
'Unpaid order, payment was not processed by MPay24, order id: %s',
$orderId
));
}
$booking = $this->bookingHelper->getById($transaction->getVistaBookingId(), $transaction->getCinemaId());
if (null === $booking) {
throw new NotFoundHttpException();
}
/** @var LoyaltyMember $user */
$user = $security->getUser();
$param = [];
$param['cinemaId'] = $booking->getCinemaId();
if ($booking->getTickets()) {
$param['sessionId'] = $booking->getTickets()[0]->getSessionId();
}
$session = $this->sessionRepository->findOneBy($param);
$pdf = $this->mailer->downloadTicketPdf($booking, $session, $user, $transaction->getEmail());
$response = new Response($pdf->getBody()->getContents());
$response->headers->set('Content-type', 'application/pdf');
return $response;
}
/**
* Check order restrictions
*
* @param Order $order
* @param Session $session
* @param LoyaltyMember $user
*
* @throws \Throwable
*/
protected function checkTransactionHistory(Order $order, Session $session, ?LoyaltyMember $user) {
$isBonus = false;
if (SessionBase::STATUS_RED === $session->getStatus()) {
throw PaymentException::create(
'You can only buy tickets at the desk for this session, hurry up!',
PaymentException::CODE_PAYMENT_SESSION_RED
);
}
foreach ($order->getTickets() as $ticket) {
if ($ticket->isLoyaltyTicket()) {
$isBonus = true;
}
}
$total = $order->getTicketsCount();
$bonusTicketsTotal = $order->getBonusTicketsCount();
if( ! is_null($user)){
$restrictions = $this->sessionDetailsHelper->calculateRestrictions(
$user,
(int) $session->getCinemaId(),
$session->getShowtime()
);
if (!$isBonus && ($total > $restrictions->getMaxTickets())) {
throw new \InvalidArgumentException('Max tickets per order limit reached');
}
if ($isBonus && ($bonusTicketsTotal > $restrictions->getMaxBonusCardTickets())) {
throw new \InvalidArgumentException('Max bonus card tickets per day limit reached');
}
}
}
/**
* Check user bookings
*
* @param Order $order
* @param Session $session
* @param LoyaltyMember $user
*
* @throws PaymentException
* @throws \Throwable
*/
protected function checkBookingHistory(Order $order, Session $session, LoyaltyMember $user) {
$total = $order->getTicketsCount();
$bonusTicketsTotal = $order->getBonusTicketsCount();
$restrictions = $this->sessionDetailsHelper->calculateRestrictions(
$user,
(int) $session->getCinemaId(),
$session->getShowtime()
);
if (SessionBase::STATUS_YELLOW === $session->getStatus()) {
throw PaymentException::create(
'You can only buy tickets for this session, reservation not possible!',
PaymentException::CODE_PAYMENT_SESSION_YELLOW
);
}
if (SessionBase::STATUS_RED === $session->getStatus()) {
throw PaymentException::create(
'You cannot reserve tickets for this session. Please, buy tickets at the desk.',
PaymentException::CODE_PAYMENT_SESSION_RED
);
}
$isBonus = false;
foreach ($order->getTickets() as $ticket) {
$ticket->isLoyaltyTicket() and $isBonus = true;
}
if (!$isBonus && ($total > $restrictions->getMaxReservedTickets())) {
throw new \InvalidArgumentException('Max reserved tickets per day limit reached');
}
if ($isBonus && ($bonusTicketsTotal > $restrictions->getMaxReservedBonusTickets())) {
throw new \InvalidArgumentException('Max reserved bonus card tickets per day limit reached');
}
}
private function getKombankRedirectForm($orderId, string $responseUrl, string $amount, $cinemaId): Response {
$paymentPageUrl = getenv('KOM_BANK_ADDRESS') ? getenv('KOM_BANK_ADDRESS') : "https://entegrasyon.asseco-see.com.tr/fim/est3Dgate";
$okUrl = (getenv('DEV_SIRMA') ? 'http://' . getenv('DEV_SIRMA') : getenv('SCHEME_AND_HTTP_HOST')) . "$responseUrl/success"; // "/api/v1/orders/$orderId/payment/handle-redirect-post/success"
$failUrl = (getenv('DEV_SIRMA') ? 'http://' . getenv('DEV_SIRMA') : getenv('SCHEME_AND_HTTP_HOST')) . "$responseUrl/error"; //URL which client be redirected if authentication is not successful
$callbackUrl = getenv('KOM_BANK_CALLBACKURL'); //URL which callback to client
$rnd = microtime(); //A random number, such as date/time
$currencyVal = "941"; //Currency code, 949 for TL, ISO_4217 standard RSD 941 2 Serbian dinar
$storetype = "3D_PAY_HOSTING"; //3D authentication model
$lang = "sr"; //Language parameter, "tr" for Turkish (default), "en" for English
$instalment = ""; //Instalment count, if there's no instalment should left blank
$transactionType = "Auth"; //transaction type
$refreshtime = 10;
$hashAlgorithm = 'ver3';
$ids = $this->paymentHelper->getMerchant($cinemaId);
//error_log(sprintf("%s %s(%s) ", date('Y-m-d H:i:s'), __METHOD__, __LINE__) . " orderId $orderId cinemaId $cinemaId " . var_export($ids, true));
$clientId = $ids && array_key_exists('id', $ids) ? $ids['id'] : '250300000'; //Merchant Id defined by bank to user
$storekey = $ids && array_key_exists('pass', $ids) ? $ids['pass'] : 'SKEY1234'; //Store key value, defined by bank.
$params = [
'amount' => $amount,
'callbackUrl' => $callbackUrl,
'clientid' => $clientId,
'currency' => $currencyVal,
'failUrl' => $failUrl,
'hashAlgorithm' => $hashAlgorithm,
'Instalment' => $instalment,
'lang' => $lang,
'oid' => $orderId,
'okUrl' => $okUrl,
'refreshtime' => $refreshtime,
'rnd' => $rnd,
'storetype' => $storetype,
'TranType' => $transactionType
];
$hash = $this->generateKomBankHash($params, $storekey);
$paymentRedirect = (new PaymentRedirect())
->setRedirectUrl($paymentPageUrl)
->setFormTitle('Komercijalna Bank')
->setPaymentData(
array_merge($params, ['hash' => $hash])
);
$response = $this->render('web/redirect.html.twig', ['paymentRedirect' => $paymentRedirect] );
//error_log(sprintf("%s %s(%s) payment_finish orderId $orderId", date('Y-m-d H:i:s'), __METHOD__, __LINE__));
$response->headers->set('Content-Type', 'text/html; charset=UTF-8'); // Content-Type: text/html; charset=UTF-8
return $response;
}
private function generateKomBankHash(array $params, string $storekey): string
{
$hashval = "";
foreach ($params as $param) {
$escapedParamValue = str_replace("|", "\\|", str_replace("\\", "\\\\", $param));
if ($param != "hash" && $param != "encoding") {
$hashval = $hashval . $escapedParamValue . "|";
}
}
$escapedStoreKey = str_replace("|", "\\|", str_replace("\\", "\\\\", $storekey));
$hashval = $hashval . $escapedStoreKey;
$calculatedHashValue = hash('sha512', $hashval);
return base64_encode (pack('H*',$calculatedHashValue));
}
private function getCpayRedirectForm($orderId, string $responseUrl, string $amount, $cinemaId): Response {
// inspired by https://github.com/c0nevski/CaSys-php-implementation
// https://www.cpay.com.mk/client/Page/default.aspx?xml_id=/mk-MK/.loginToPay/
$paymentPageUrl = getenv('CPAY_ADDRESS') ? getenv('CPAY_ADDRESS') : 'https://www.cpay.com.mk/client/Page/default.aspx?xml_id=/mk-MK/.loginToPay/.simple/';
$okUrl = (getenv('DEV_SIRMA') ? 'http://' . getenv('DEV_SIRMA') : getenv('SCHEME_AND_HTTP_HOST')) . "$responseUrl/success"; // "/api/v1/orders/$orderId/payment/handle-redirect-post/success"
$failUrl = (getenv('DEV_SIRMA') ? 'http://' . getenv('DEV_SIRMA') : getenv('SCHEME_AND_HTTP_HOST')) . "$responseUrl/error"; //URL which client be redirected if authentication is not successful
$ids = $this->paymentHelper->getMerchant($cinemaId);
//error_log(sprintf("%s %s(%s) ", date('Y-m-d H:i:s'), __METHOD__, __LINE__) . " SD $SD cinemaId $cinemaId " . var_export($ids, true));
$merchantId = $ids && array_key_exists('id', $ids) ? $ids['id'] : '1755375'; //Merchant Id defined by bank to user getenv('CPAY_MERCHANT_ID')
$md5password = $ids && array_key_exists('pass', $ids) ? $ids['pass'] : 'E7883395'; //Store key value, defined by bank. getenv('CPAY_PASS')
$paymentValues = [
'PaymentOKURL' => $okUrl, // strana kade formata kje prenasocuva koga plakjanjeto e uspeshno
'PaymentFailURL' => $failUrl, // strana kade formata kje prenasocuva koga plakjanjeto e neuspeshno
'AmountToPay' => $amount, // $mult; // cenata sto treba da se plati pomnozena so 100
'AmountCurrency' => 'MKD', // valuta vo koja se vrsi plakjanjeto
'PayToMerchant' => $merchantId, // "Tuka obicno imate dadeno 10 cifren broj - ova e fiksno od caSys"; // ova ne treba da se menuva, ova e dadeno od CaSys
'MerchantName' => 'Cineplexx Macedonia', // ova ne treba da se menuva, ova e dadeno od CaSys
'Details1' => 'CINEPLEXX', // Moze da se napise sto bilo - go pisuva vo casys formata za plakanje kako detali 1
'Details2' => $orderId, // unikatni redni broevi
'OriginalAmount' => sprintf('%.2f', floatval($amount) / 100)
];
$fieldNames = '';
$fieldLens = '';
$fieldValues = '';
$postForm = '';
foreach($paymentValues as $key => $value) {
$fieldNames .= "$key,";
$fieldLens .= sprintf("%03d", mb_strlen($value, 'UTF-8'));
$fieldValues .= "$value";
$postForm .= "<input id='$key' name='$key' value='$value' type='hidden' />\n";
}
$fieldValues .= $md5password;
$CheckSumHeader = sprintf("%02d", count($paymentValues)) . "$fieldNames$fieldLens";
$CheckSum = md5("$CheckSumHeader$fieldValues"); //ova generira md5 od checkshumheader
$paymentValues['CheckSumHeader'] = $CheckSumHeader;
$paymentValues['CheckSum'] = $CheckSum;
$paymentRedirect = (new PaymentRedirect())
->setRedirectUrl($paymentPageUrl)
->setFormTitle('CPay Bank')
->setPaymentData($paymentValues);
$response = $this->render('web/redirect.html.twig', ['paymentRedirect' => $paymentRedirect] );
//error_log(sprintf("%s %s(%s) payment_finish orderId $orderId", date('Y-m-d H:i:s'), __METHOD__, __LINE__));
$response->headers->set('Content-Type', 'text/html; charset=UTF-8'); // Content-Type: text/html; charset=UTF-8
return $response;
}
}