<?php
namespace App\Payment;
use App\Entity\Sale;
use App\Entity\Transaction;
use App\Entity\Vehicle;
use App\Payment\Dto\Payment;
use App\Payment\Exception\AlreadyPaidException;
use App\Repository\SaleRepository;
use App\Repository\TransactionRepository;
use App\Repository\UserRepository;
use App\Repository\VehicleRepository;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* This Class gives informations about current payment context.
*/
class PaymentContext
{
private const SESSION_PAYMENT_KEY = 'payment';
private $defaultAmount;
public function __construct(
private readonly SessionInterface $session,
private readonly SaleRepository $saleRepository,
private readonly VehicleRepository $vehicleRepository,
private readonly UserRepository $userRepository,
private readonly TransactionRepository $transactionRepository,
private readonly EntityManagerInterface $entityManager,
private readonly LoggerInterface $paymentLogger,
int $paymentDefaultAmount
) {
$this->defaultAmount = $paymentDefaultAmount;
}
public function forget(): void
{
$this->log('forgeting payment');
$this->session->remove(self::SESSION_PAYMENT_KEY);
}
public function getCurrentPayment(): ?Payment
{
$payment = $this->session->get(self::SESSION_PAYMENT_KEY);
if (!$payment instanceof Payment) {
return null;
}
return $payment;
}
public function getCurrentVehicle(): ?Vehicle
{
$payment = $this->getCurrentPayment();
$vehicleId = $payment->getVehicleId();
if (null === $vehicleId) {
return null;
}
$vehicle = $this->vehicleRepository->find($vehicleId);
if (!$vehicle) {
throw new NotFoundHttpException(sprintf('vehicle with id %d couldn\'t be found', $vehicleId));
}
return $vehicle;
}
public function getCurrentSale(): ?Sale
{
$payment = $this->getCurrentPayment();
if (!$payment) {
return null;
}
$saleId = $payment->getSaleId();
if (null === $saleId) {
return null;
}
$sale = $this->saleRepository->find($saleId);
if (!$sale) {
throw new NotFoundHttpException(sprintf('sale with id %d couldn\'t be found', $saleId));
}
return $sale;
}
public function getCurrentUser()
{
$payment = $this->getCurrentPayment();
if (!$payment) {
return null;
}
$userId = $payment->getUserId();
if (null === $userId) {
return null;
}
$user = $this->userRepository->find($userId);
if (!$user) {
throw new NotFoundHttpException(sprintf('sale with id %d couldn\'t be found', $userId));
}
return $user;
}
/**
* @throws AlreadyPaidException
*/
public function checkAndStore(Payment $payment): void
{
if (!$payment->getBlockedAmount()) {
$payment->setBlockedAmount($this->defaultAmount);
}
if (!$payment->getFinalAmount()) {
$payment->setFinalAmount($payment->getBlockedAmount());
}
$this->checkIfItDoesNotExistsYet($payment);
$this->store($payment);
}
public function store(Payment $payment): void
{
$payment->validate();
$this->session->remove(self::SESSION_PAYMENT_KEY);
$this->session->set(self::SESSION_PAYMENT_KEY, $payment);
$this->log('store payment in session');
}
public function findOrCreateTransaction(): Transaction
{
$payment = $this->getCurrentPayment();
if (!$payment) {
throw new \Exception('Cannot get transaction because payment is not set');
}
$transaction = null;
if ($transactionId = $payment->getTransactionId()) {
$transaction = $this->transactionRepository->find($transactionId);
}
if ($transaction) {
if ($transaction->getUser() !== $this->getCurrentUser()) {
throw new \Exception('Wrong user for transaction');
}
if ($transaction->getSale() !== $this->getCurrentSale()) {
throw new \Exception('Wrong sale for transaction');
}
} else {
$transaction = new Transaction();
$transaction->setUser($this->getCurrentUser());
$transaction->setSale($this->getCurrentSale());
$transaction->setAmount($payment->getBlockedAmount() * 100);
$transaction->setCountry($payment->getCountry());
$this->entityManager->persist($transaction);
$this->entityManager->flush();
$payment->setTransaction($transaction);
$this->store($payment);
}
return $transaction;
}
public function log(string $message, ?array $data = [], string $level = 'info'): void
{
if (!method_exists($this->paymentLogger, $level)) {
throw new \LogicException(sprintf('Unknown log level %s for message «%s»', $level, $message));
}
if (!$data && $this->getCurrentPayment()) {
$data = get_object_vars($this->getCurrentPayment());
}
$this->paymentLogger->$level($message.' '.print_r($data, true));
}
/**
* @throws AlreadyPaidException
*/
private function checkIfItDoesNotExistsYet(Payment $payment): void
{
$transaction = $this->entityManager->getRepository(Transaction::class)
->findValidTransactionByUserAndSale(
$this->userRepository->find($payment->getUserId()),
$this->saleRepository->find($payment->getSaleId())
);
if ($transaction) {
throw new AlreadyPaidException($this, $transaction);
}
}
}