<?php
declare(strict_types = 1);
namespace App\Controller;
use App\Form\Model\ApplicationFilterModel;
use App\Form\Model\Instance\InstanceFilterModel;
use App\Form\Model\Instance\InstanceModel;
use App\Form\Model\Merchant\MerchantFilterModel;
use App\Form\Model\OperationFilterModel;
use App\Form\Model\Store\StoreFilterModel;
use App\Form\Model\TransactionFiltersModel;
use App\Form\Type\Instance\HashCalculatorType;
use App\Form\Type\Instance\InstanceType;
use App\Form\Type\InstanceFilterType;
use App\Form\Type\Operation\OperationAddType;
use App\Form\Type\Operation\OperationFilterType;
use App\Manager\S3UploaderManager;
use App\RequestManager\Account\ProgramRequestManager;
use App\RequestManager\Application\ApplicationRequestManager;
use App\RequestManager\InstanceRequestManager;
use App\RequestManager\MerchantRequestManager;
use App\RequestManager\OperationRequestManager;
use App\RequestManager\TerminalRequestManager;
use App\RequestManager\Transaction\TransactionV2RequestManager;
use GuzzleHttp\Exception\GuzzleException;
use Paynetics\Exception\ApiException;
use Paynetics\Exception\RequestException;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
/**
* Class InstanceController.
*/
class InstanceController extends BaseController
{
/**
* @required
*/
public InstanceRequestManager $instanceRequestManager;
/**
* @required
*/
public MerchantRequestManager $merchantRequestManager;
/**
* @required
*/
public ApplicationRequestManager $applicationRequestManager;
/**
* @required
*/
public TransactionV2RequestManager $transcationRequestManager;
/**
* @required
*/
public TerminalRequestManager $terminalRequestManager;
/**
* @required
*/
public ProgramRequestManager $programRequestManager;
/**
* @required
*/
public OperationRequestManager $operationRequestManager;
/**
* @required
*/
public S3UploaderManager $s3UploaderManager;
/**
* @throws ExceptionInterface
* @throws GuzzleException
* @throws \Exception
*/
public function index(Request $request, int $page = 1, int $limit = 20): Response
{
// $this->isGranted(['ROLE_SUPER_ADMIN', 'ROLE_CARD_OPS', 'ROLE_SUPPORT', 'ROLE_PM', 'ROLE_DISPUTE', 'ROLE_TRANSFERS', 'ROLE_RISK_MANAGER', 'ROLE_KY_MANAGER', 'ROLE_KYB_MANAGER', 'ROLE_KYC_MANAGER']);
$filter = $this->createForm(InstanceFilterType::class, new InstanceFilterModel(), ['action' => $this->generateUrl('instances')]);
$filter->handleRequest($request);
// For AJAX requests, also check for direct 'name' parameter
$filterData = $filter->getData();
if ($request->isXmlHttpRequest() && $request->query->has('name')) {
$filterData->setName($request->query->get('name'));
}
try {
$instances = $this->instanceRequestManager->getInstances($page, $limit, $filterData);
} catch (\Exception $e) {
throw new \Exception('Something went wrong');
}
if ('json' === $request->getContentType() || $request->isXmlHttpRequest()) {
return new JsonResponse($instances);
}
$instances = $this->paginate($instances, $page, $limit);
return $this->render('instance/index.html.twig', [
'instances' => $instances,
'filter' => $filter->createView(),
'toolbarPage' => 'Instances',
]);
}
/**
* @throws ApiException
* @throws GuzzleException
* @throws \JsonException
*/
public function merchants(int $limit, int $page, $token): Response
{
$this->isGranted(['ROLE_SUPER_ADMIN', 'ROLE_CARD_OPS', 'ROLE_SUPPORT', 'ROLE_PM', 'ROLE_DISPUTE', 'ROLE_TRANSFERS', 'ROLE_RISK_MANAGER', 'ROLE_KY_MANAGER', 'ROLE_KYB_MANAGER', 'ROLE_KYC_MANAGER']);
$instance = $this->instanceRequestManager->getInstancesByToken($token);
$merchantFilter = new MerchantFilterModel();
$merchantFilter->setInstance($token);
$merchants = $this->merchantRequestManager->getMerchants($page, $limit, $merchantFilter);
$merchants = $this->paginate($merchants, $page, $limit);
$toolbarPage = 'Instance';
return $this->render('instance/merchants.html.twig', compact('instance', 'merchants', 'toolbarPage'));
}
/**
* @throws ApiException
* @throws ExceptionInterface
* @throws GuzzleException
* @throws \JsonException
*/
public function view(string $token): Response
{
$this->isGranted(['ROLE_SUPER_ADMIN', 'ROLE_CARD_OPS', 'ROLE_SUPPORT', 'ROLE_PM', 'ROLE_DISPUTE', 'ROLE_TRANSFERS', 'ROLE_RISK_MANAGER', 'ROLE_KY_MANAGER', 'ROLE_KYB_MANAGER', 'ROLE_KYC_MANAGER']);
$instance = $this->instanceRequestManager->getInstancesByToken($token);
$merchantFilter = new MerchantFilterModel();
$merchantFilter->setInstance($token);
$merchants = $this->merchantRequestManager->getMerchants(1, 1, $merchantFilter);
$applicationFilter = new ApplicationFilterModel();
$applicationFilter->setInstance($token);
$applications = $this->applicationRequestManager->getApplications(1, 1, $applicationFilter);
$transactionFilter = new TransactionFiltersModel();
$transactionFilter->setInstance($token);
$transactions = $this->transcationRequestManager->getTransactions(1, 1, $transactionFilter);
$storeFilter = new StoreFilterModel();
$storeFilter->setInstance($token);
$stores = $this->terminalRequestManager->getStores(1, 1, $storeFilter);
return $this->render('instance/show.html.twig', [
'instance' => $instance,
'merchants' => $merchants,
'stores' => $stores,
'transactions' => $transactions,
'applications' => $applications,
'toolbarPage' => 'Instance',
]);
}
/**
* @throws ApiException
* @throws GuzzleException
*/
public function create(Request $request): Response
{
$this->isGranted(['ROLE_SUPER_ADMIN', 'ROLE_SUPPORT_MANAGER']);
$instanceForm = $this->createForm(InstanceType::class, new InstanceModel());
$instanceForm->handleRequest($request);
$isAjax = $request->isXmlHttpRequest();
if ($instanceForm->isSubmitted() && $instanceForm->isValid()) {
try {
$instance = $this->instanceRequestManager->create($instanceForm->getData());
if ($isAjax) {
return new JsonResponse(['success' => true, 'message' => 'Instance created successfully', 'token' => $instance['token']]);
}
return $this->redirectToRoute('instance_view', ['token' => $instance['token']]);
} catch (RequestException $exception) {
foreach ($exception->getErrors() as $error) {
$error = new FormError($error);
$instanceForm->addError($error);
}
if (0 === count($exception->getErrors())) {
$message = json_decode($exception->getMessage(), true);
$error = new FormError($message['message'] ?? null);
$instanceForm->addError($error);
}
}
}
if ($isAjax) {
$formAction = $this->generateUrl('instance_create');
return $this->render('instance/_partials/form-modal.html.twig', [
'form' => $instanceForm->createView(),
'formAction' => $formAction
]);
}
return $this->render('instance/create.html.twig', [
'form' => $instanceForm->createView(),
'toolbarPage' => 'Instance',
]);
}
/**
* @throws ApiException
* @throws ExceptionInterface
* @throws GuzzleException
* @throws \JsonException
*/
public function update(Request $request, string $token): Response
{
$this->isGranted(['ROLE_SUPER_ADMIN', 'ROLE_SUPPORT_MANAGER']);
$instance = $this->instanceRequestManager->getInstancesByToken($token);
$instanceModel = new InstanceModel();
$instanceModel->setApiKey($instance['api_key'] ?? null);
$instanceModel->setApiSecret($instance['api_secret'] ?? null);
$instanceModel->setAcquiringInstitutionIdentificationCode($instance['acquiring_institution_identification_code'] ?? null);
$this->denormalize($instance, InstanceModel::class, ['object_to_populate' => $instanceModel]);
$instanceForm = $this->createForm(InstanceType::class, $instanceModel);
$instanceForm->handleRequest($request);
if ($instanceForm->isSubmitted() && $instanceForm->isValid()) {
try {
$this->instanceRequestManager->update($instanceModel, $token);
return $this->redirectToRoute('instance_view', compact('token'));
} catch (RequestException $exception) {
foreach ($exception->getErrors() as $error) {
$error = new FormError($error);
$instanceForm->addError($error);
}
if (0 === count($exception->getErrors())) {
$message = json_decode($exception->getMessage(), true);
$error = new FormError($message['message'] ?? null);
$instanceForm->addError($error);
}
}
}
return $this->render('instance/update.html.twig', [
'instance' => $instance,
'form' => $instanceForm->createView(),
'toolbarPage' => 'Instance',
]);
}
/**
* @throws ApiException
* @throws GuzzleException
* @throws \JsonException
*/
public function stores(string $token, int $page = 1, int $limit = 5): Response
{
$instance = $this->instanceRequestManager->getInstancesByToken($token);
$storeFilter = new StoreFilterModel();
$storeFilter->setInstance($token);
$stores = $this->terminalRequestManager->getStores($page, $limit, $storeFilter);
$stores = $this->paginate($stores, $page, $limit);
$toolbarPage = 'Instance';
return $this->render('instance/stores.html.twig', compact('instance', 'stores', 'toolbarPage'));
}
/**
* @throws ApiException
* @throws GuzzleException
* @throws \JsonException
*/
public function transactions(string $token, int $page = 1, int $limit = 5): Response
{
$instance = $this->instanceRequestManager->getInstancesByToken($token);
$transactionFilter = new TransactionFiltersModel();
$transactionFilter->setInstance($token);
$transactions = $this->transcationRequestManager->getTransactions($page, $limit, $transactionFilter);
$transactions = $this->paginate($transactions, $page, $limit);
$toolbarPage = 'Instance';
return $this->render('instance/transactions.html.twig', compact('instance', 'transactions', 'toolbarPage'));
}
/**
* @throws ApiException
* @throws GuzzleException
* @throws \JsonException
*/
public function applications(string $token, int $page = 1, int $limit = 5): Response
{
$instance = $this->instanceRequestManager->getInstancesByToken($token);
$applicationFilter = new ApplicationFilterModel();
$applicationFilter->setInstance($token);
$applications = $this->applicationRequestManager->getApplications($page, $limit, $applicationFilter);
$applications = $this->paginate($applications, $page, $limit);
$toolbarPage = 'Instance';
return $this->render('instance/applications.html.twig', compact('instance', 'applications', 'toolbarPage'));
}
/**
* @throws ApiException
* @throws GuzzleException
* @throws \JsonException
*/
public function programs(string $token): Response
{
$this->isGranted(['ROLE_SUPER_ADMIN', 'ROLE_SUPPORT_MANAGER', 'ROLE_CARD_OPS']);
$instance = $this->instanceRequestManager->getInstancesByToken($token);
$programs = $this->programRequestManager->listPrograms(0, 0, ['instance' => $token]);
$toolbarPage = 'Instance';
return $this->render('instance/programs.html.twig', compact('instance', 'programs', 'toolbarPage'));
}
/**
* @throws ApiException
* @throws GuzzleException
* @throws \JsonException
*/
public function hashCalculator(Request $request, string $token)
{
$this->isGranted(['ROLE_SUPPORT_MANAGER']);
$instance = $this->instanceRequestManager->getInstancesByToken($token);
$form = $this->createForm(HashCalculatorType::class);
$form->handleRequest($request);
$headers = [];
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
if ('payoo' === $data['api']) {
$headers = [
'payoo-api-key' => $instance['api_key'],
'payoo-api-request-date' => $data['timestamp'],
'payoo-api-hash' => hash_hmac('sha256', $instance['api_key'] . $data['timestamp'] . $data['operation'], $instance['api_secret']),
];
} elseif ('webhook' === $data['api']) {
$payload = !empty($data['payload']) ? json_encode(json_decode($data['payload'], true, 512, JSON_THROW_ON_ERROR), JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : '';
$headers = [
'x-api-key' => $instance['api_key'],
'x-timestamp' => $data['timestamp'],
'x-hash' => hash_hmac('sha256', $payload, $instance['api_secret']),
];
} else {
$payload = !empty($data['payload']) ? json_encode(json_decode($data['payload'], true, 512, JSON_THROW_ON_ERROR), JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : '';
$headers = [
'x-api-key' => $instance['api_key'],
'x-timestamp' => $data['timestamp'],
'x-hash' => hash_hmac('sha256', $instance['api_key'] . ($data['timestamp'] ?? '') . ($data['operation'] ?? '') . $payload, $instance['api_secret']),
];
}
}
return $this->renderForm('instance/_partials/hash-calculator.html.twig', compact('instance', 'form', 'headers'));
}
/**
* @throws ApiException
* @throws GuzzleException
* @throws \JsonException
*/
public function showApiSecret(string $token): JsonResponse
{
$this->isGranted(['ROLE_SUPER_ADMIN', 'ROLE_SUPPORT_MANAGER']);
$instance = $this->instanceRequestManager->getInstancesByToken($token);
return new JsonResponse(['key' => $instance['api_secret']]);
}
/**
* @throws ApiException
* @throws GuzzleException
*/
public function generateSharedKey(string $token): RedirectResponse
{
$this->isGranted(['ROLE_SUPER_ADMIN', 'ROLE_SUPPORT_MANAGER']);
$this->instanceRequestManager->generateSharedKey($token);
return $this->redirectToRoute('instance_view', ['token' => $token]);
}
public function showSharedKey(string $token): JsonResponse
{
$this->isGranted(['ROLE_SUPER_ADMIN', 'ROLE_SUPPORT_MANAGER']);
$key = $this->instanceRequestManager->getSharedKey($token);
return new JsonResponse($key);
}
/**
* @throws ExceptionInterface
* @throws GuzzleException
*/
public function statusChange(string $token): RedirectResponse
{
$this->isGranted(['ROLE_SUPER_ADMIN', 'ROLE_SUPPORT_MANAGER', 'ROLE_CARD_OPS', 'ROLE_CARD_OPS_MANAGER']);
try {
$instance = $this->instanceRequestManager->getInstancesByToken($token);
/**
* @var InstanceModel $instanceModel
*/
$instanceModel = $this->denormalize($instance, InstanceModel::class);
$instanceModel->setIsEnabled(!$instanceModel->getIsEnabled());
$this->instanceRequestManager->update($instanceModel, $token);
} catch (\Exception $exception) {
return $this->redirectToRoute('instances');
}
return $this->redirectToRoute('instance_view', compact('token'));
}
/**
* @throws ApiException
* @throws ExceptionInterface
* @throws GuzzleException
* @throws \JsonException
*/
public function operations(Request $request, string $token, int $page = 1, int $limit = 15): Response
{
$this->isGranted(['ROLE_SUPER_ADMIN', 'ROLE_SUPPORT', 'ROLE_SUPPORT_MANAGER']);
/** @var OperationFilterModel $filterModel */
$filterModel = $this->denormalize($request->query->all(), OperationFilterModel::class, ['action' => $this->generateUrl('instance_operations', ['token' => $token])]);
$filterModel->setInstance($token);
$filters = $this->createForm(OperationFilterType::class, $filterModel);
$filters->handleRequest($request);
// Get all operations for the instance (OperationMap)
$operationsByInstance = $this->operationRequestManager->getOperationsByInstance($filterModel, 0, 0);
$allOperationsByInstance = $operationsByInstance;
if (null !== $filterModel->getOperation()) {
$filterModel->setOperation(null);
$allOperationsByInstance = $this->operationRequestManager->getOperationsByInstance($filterModel, 0, 0);
}
// Get all operations (Operation)
$operations = $this->operationRequestManager->getOperations(0, 0);
// Combine operation names for add select dropdown
$operationChoices = array_combine(array_column($operations['items'], 'name'), array_column($operations['items'], 'name'));
// Extract operation names from $operationsByInstance
$existingOperations = array_column(array_column($allOperationsByInstance['items'], 'operation'), 'name');
// Filter $operationChoices to remove existing operations
$operationChoices = array_filter($operationChoices, function ($operation) use ($existingOperations) {
return !in_array($operation, $existingOperations);
});
$addOperationForm = $this->createForm(OperationAddType::class, ['operationChoices' => $operationChoices, 'instance' => $token]);
$addOperationForm->handleRequest($request);
if ($addOperationForm->isSubmitted()) {
$newOperationsFromExisting = $addOperationForm->get('newOperationsFromExisting')->getData();
if (!empty($newOperationsFromExisting)) {
foreach ($newOperationsFromExisting as $newOperation) {
$filterModel->setOperation($newOperation);
$filterModel->setValue(['enabled' => true]);
$this->operationRequestManager->createOperationMap($filterModel);
}
$filterModel->setOperation(null);
$filterModel->setValue(null);
}
return $this->redirectToRoute('instance_operations', ['token' => $token, 'page' => $page, 'limit' => $limit]);
}
// Slice the array to get the subset of operations for the current page
$maxPage = ceil($operationsByInstance['total_items'] / $limit);
if ($page > $maxPage) {
$page = $maxPage;
}
if ($page < 1) {
$page = 1;
}
$operationsByInstance['items'] = array_slice($operationsByInstance['items'], ($page - 1) * $limit, $limit);
$operationsByInstance = $this->paginate($operationsByInstance, $page, $limit);
$instance = $this->instanceRequestManager->getInstancesByToken($token);
return $this->render('instance/operations.html.twig', [
'instance' => $instance,
'operations' => $operationsByInstance,
'addOperationForm' => $addOperationForm->createView(),
'toolbarPage' => 'Instance',
'filters' => $filters->createView(),
]);
}
/**
* @throws \JsonException
*/
public function handleOperationUpdateOrDelete(Request $request, string $token): Response
{
$this->isGranted(['ROLE_SUPER_ADMIN', 'ROLE_SUPPORT', 'ROLE_SUPPORT_MANAGER']);
$content = $request->getContent();
$data = json_decode($content, true);
switch ($data['action']) {
case 'delete':
$this->operationRequestManager->deleteOperationMap($data['operationToken']);
return new JsonResponse(['message' => 'Operation deleted']);
case 'reason':
$filterModel = new OperationFilterModel();
$filterModel->setInstance($token);
$filterModel->setOperation($data['operation']);
$filterModel->setOperationToken($data['operationToken']);
$filterModel->setReason($data['value']);
$this->operationRequestManager->updateOperationMap($filterModel, $data['operationToken']);
return new JsonResponse(['message' => 'Reason updated']);
default:
$json = json_decode($data['value'], true, 512, JSON_THROW_ON_ERROR);
if (JSON_ERROR_NONE !== json_last_error()) {
return new JsonResponse(['message' => 'Invalid JSON format'], Response::HTTP_BAD_REQUEST);
}
$filterModel = new OperationFilterModel();
$filterModel->setInstance($token);
$filterModel->setOperation($data['operation']);
$filterModel->setOperationToken($data['operationToken']);
$filterModel->setReason($data['reason']);
$filterModel->setValue($json);
$this->operationRequestManager->updateOperationMap($filterModel, $filterModel->getOperationToken());
return new JsonResponse($json);
}
}
}