src/UI/Frontend/Controller/PaymentController.php line 139

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\UI\Frontend\Controller;
  4. use App\Domain\Payment\Model\Payment;
  5. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  6. use Symfony\Component\HttpFoundation\Request;
  7. use Symfony\Component\HttpFoundation\Response;
  8. use Symfony\Component\Routing\Annotation\Route;
  9. use Doctrine\ORM\EntityManagerInterface;
  10. use Symfony\Component\HttpFoundation\JsonResponse;
  11. use Psr\Log\LoggerAwareInterface;
  12. use Psr\Log\LoggerAwareTrait;
  13. use GuzzleHttp\Client;
  14. use GuzzleHttp\ClientInterface;
  15. use GuzzleHttp\Exception\GuzzleException;
  16. use Psr\Http\Message\ResponseInterface;
  17. use App\Application\Common\CommonServices;
  18. class PaymentController extends AbstractController implements LoggerAwareInterface
  19. {
  20.     private EntityManagerInterface $em;
  21.     private ClientInterface $client;
  22.     private CommonServices $commonServices;
  23.     private string $appEnv;
  24.     private string $ibanxsAuthTokenUrl;
  25.     private string $ibanxsApiBaseUrl;
  26.     public function __construct(EntityManagerInterface $emCommonServices $commonServicesstring $appEnvstring $ibanxsAuthTokenUrlstring $ibanxsApiBaseUrl)
  27.     {
  28.         $this->em $em;
  29.         $this->client = new Client();
  30.         $this->commonServices $commonServices;
  31.         $this->appEnv $appEnv;
  32.         $this->ibanxsAuthTokenUrl $ibanxsAuthTokenUrl;
  33.         $this->ibanxsApiBaseUrl $ibanxsApiBaseUrl;
  34.     }
  35.     use LoggerAwareTrait;
  36.     #[Route(path'/payments/{id}'name'frontend_payments_proxy')]
  37.     public function proxy(Request $requestPayment $payment): Response
  38.     {
  39.         if ($payment->getStatus()->isCompleted()) {
  40.             $redirectUrl $this->getQueryUrl($payment->getSuccessUrl(), $payment);
  41.             return $this->redirect($redirectUrl);
  42.         } elseif ($payment->getStatus()->isFailed()) {
  43.             return $this->redirect($payment->getFailureUrl());
  44.         } else {
  45.             $redirectUrl $this->getQueryUrl($payment->getPendingUrl(), $payment);
  46.             return $this->redirect($redirectUrl);
  47.         }
  48.     }
  49.     public function getQueryUrl($url$payment)
  50.     {
  51.         $amount $payment->getFloatAmount();
  52.         // Parse the URL into components
  53.         $urlComponents parse_url($url);
  54.         // Start building the URL
  55.         $updatedUrl $urlComponents['scheme'] . '://' $urlComponents['host'];
  56.         // Add the port if it exists
  57.         if (isset($urlComponents['port'])) {
  58.             $updatedUrl .= ':' $urlComponents['port'];
  59.         }
  60.         // Add the path if it exists
  61.         if (isset($urlComponents['path'])) {
  62.             $updatedUrl .= $urlComponents['path'];
  63.         }
  64.         // Handle query parameters
  65.         $queryParams = [];
  66.         if (isset($urlComponents['query'])) {
  67.             parse_str($urlComponents['query'], $queryParams);
  68.         }
  69.         $queryParams['amount'] = number_format($amount2'.''');
  70.         // Append the query string
  71.         $updatedUrl .= '?' http_build_query($queryParams);
  72.         return $updatedUrl;
  73.     }
  74.     #[Route(path'/genome/payment/notify/{id}'name'genome_payment_webhook'methods: ['POST'])]
  75.     public function genomeWebhook(string $idRequest $request): Response
  76.     {
  77.         try {
  78.             // You can log, validate, or queue with the ID directly
  79.             $payload json_decode($request->getContent(), true);
  80.             // Add the request URL into the payload
  81.             $payload['request_url'] = $request->getUri();
  82.             // Example: log and return immediately
  83.             $this->logger?->info('Received webhook for ID: ' $id, ['payload' => $payload]);
  84.             $this->validateAndUpdatePayment($payload'genome');
  85.             return new JsonResponse(['status' => 'Webhook received']);
  86.         } catch (\Throwable $e) {
  87.             $this->logger?->error('Webhook error: ' $e->getMessage());
  88.             return new JsonResponse(['error' => 'Internal error'], Response::HTTP_INTERNAL_SERVER_ERROR);
  89.         }
  90.     }
  91.     #[Route(path'/isx/payment/notify'name'isx_payment_webhook'methods: ['POST'])]
  92.     public function isxWebhook(Request $request): Response
  93.     {
  94.         try {
  95.             // You can log, validate, or queue with the ID directly
  96.             $payload json_decode($request->getContent(), true);
  97.             // Add the request URL into the payload
  98.             $payload['request_url'] = $request->getUri();
  99.             // Example: log and return immediately
  100.             $this->logger?->info('Received isxWebhook: ', ['payload' => $payload]);
  101.             $this->validateAndUpdatePayment($payload'isx');
  102.             return new JsonResponse(['status' => 'Webhook received']);
  103.         } catch (\Throwable $e) {
  104.             $this->logger?->error('Webhook error: ' $e->getMessage());
  105.             return new JsonResponse(['error' => 'Internal error'], Response::HTTP_INTERNAL_SERVER_ERROR);
  106.         }
  107.     }
  108.     #[Route(path'/dimoco/payment/notify/{id}'name'dimoco_payment_webhook'methods: ['POST'])]
  109.     public function dimocoWebhook(string $idRequest $request): Response
  110.     {
  111.         try {
  112.         $this->logger?->info('Received dimoco webhook', [
  113.             'id' => $id,
  114.             'method' => $request->getMethod(),
  115.             'url' => $request->getUri(),
  116.             'headers' => $request->headers->all(),
  117.             'query' => $request->query->all(),
  118.             'request' => $request->request->all(), // Form data
  119.             'body' => $request->getContent(),       // Raw JSON/XML body
  120.         ]);
  121.         return new JsonResponse([
  122.             'status' => 'Webhook received'
  123.         ]);
  124.     } catch (\Throwable $e) {
  125.         $this->logger?->error('Webhook error: ' $e->getMessage());
  126.         return new JsonResponse([
  127.             'error' => 'Internal error'
  128.         ], Response::HTTP_INTERNAL_SERVER_ERROR);
  129.     }
  130.     }
  131.     private function validateAndUpdatePayment(array $payloadstring $type): void
  132.     {
  133.         try {
  134.             if ($type === 'genome') {
  135.                 $description $payload['description'] ?? null;
  136.                 $mappingId $this->extractMappingId($description);
  137.                 $currency $payload['amount']['currency'] ?? null;
  138.                 $rawAmount $payload['amount']['amount'] ?? 0.0;
  139.                 $amount = (int) round((float) $rawAmount 100); // Convert to cents
  140.                 $senderName $payload['sender']['name'] ?? null;
  141.             } elseif ($type === 'isx') {
  142.                 $description $payload['payment_provider_responses'][0]['details'] ?? null;
  143.                 $mappingId $this->extractMappingId($description);
  144.                 $currency $payload['payment_amount']['currency'] ?? null;
  145.                 $amount $payload['payment_amount']['amount'] ?? 0.0;
  146.                 $senderName $payload['original_sender_name'] ?? null;
  147.             } else {
  148.                 $this->logger?->warning('Unsupported webhook type: ' $type);
  149.                 return;
  150.             }
  151.             if (!$description || !$amount || !$currency) {
  152.                 $this->logger?->info('Invalid payload: missing fields', [
  153.                     'description' => $description,
  154.                     'mappiingId' => $mappingId,
  155.                     'amount' => $amount,
  156.                     'currency' => $currency,
  157.                     'payload' => $payload,
  158.                 ]);
  159.                 return;
  160.             }
  161.             $conn $this->em->getConnection();
  162.             // Step 1: Check for matching payment
  163.             $sql "SELECT * FROM payments WHERE mapping_id = :mapping_id AND amount = :amount AND currency = :currency LIMIT 1";
  164.             $result $conn->fetchAssociative($sql, [
  165.                 'mapping_id' => $mappingId,
  166.                 'amount' => $amount,
  167.                 'currency' => $currency,
  168.             ]);
  169.             // Step 2: Update if found
  170.             if ($result) {
  171.                 if ($result['status'] === 'completed') {
  172.                     $this->logger?->info('Payment already completed', [
  173.                         'description' => $description,
  174.                         'mappingId' => $mappingId,
  175.                         'amount' => $amount,
  176.                         'currency' => $currency,
  177.                     ]);
  178.                     return; // Payment already completed
  179.                 }
  180.                 $sql "SELECT secret, billing_information_is_bank_webhook FROM sites WHERE id = :id  LIMIT 1";
  181.                 $site $conn->fetchAssociative($sql, [
  182.                     'id' => $result['site_id']
  183.                 ]);
  184.                 if (!$site['billing_information_is_bank_webhook']) {
  185.                     $this->logger?->info('Bank Webhook is not enabled');
  186.                     return; // Bank Webhook is not enabled
  187.                 }
  188.                 $logs = [
  189.                     'title' => 'Received new webhook call from Bank',
  190.                     'details' => $payload
  191.                 ];
  192.                 $updateSql "UPDATE payments SET  status = 'completed', payment_received = True, receiving_response = :response, bank_status = 'completed', updated_at = NOW() WHERE mapping_id = :mapping_id";
  193.                 $conn->executeStatement($updateSql, [
  194.                     'response' => json_encode($payload),
  195.                     'mapping_id' => $mappingId,
  196.                 ]);
  197.                 $this->commonServices->savePaymentLogs($result['id'], 200$logs);
  198.                 $this->commonServices->saveUserPaymentInfo(payment_id$result['id'], name$senderName);
  199.                 $this->sendMerchantNotification($result$site['secret']);
  200.                 $this->logger?->info("Payment updated successfully for ID: {$description}");
  201.             } else {
  202.                 $this->logger?->warning("No matching payment found", [
  203.                     'description' => $description,
  204.                     'mappiingId' => $mappingId,
  205.                     'amount' => $amount,
  206.                     'currency' => $currency,
  207.                 ]);
  208.             }
  209.         } catch (\Throwable $e) {
  210.             $this->logger?->error('Error in validateAndUpdatePayment: ' $e->getMessage(), [
  211.                 'exception' => $e
  212.             ]);
  213.         }
  214.     }
  215.     // private function extractMappingId(string $description)
  216.     // {
  217.     //     $conn = $this->em->getConnection();
  218.     //     $sql = "SELECT mapping_id FROM payments WHERE created_at >= NOW() - INTERVAL '80 hours'  AND bank_status = 'initiated' ORDER BY created_at DESC";
  219.     //     $results = $conn->fetchAllAssociative($sql);
  220.     //     foreach ($results as $row) {
  221.     //         if (isset($row['mapping_id']) && stripos($description, (string)$row['mapping_id']) !== false) {
  222.     //             return $row['mapping_id']; // Found mapping_id inside description
  223.     //         }
  224.     //     }
  225.     // }
  226.     private function extractMappingId(string $description)
  227.     {
  228.         $conn $this->em->getConnection();
  229.         $sql "
  230.             SELECT mapping_id
  231.             FROM payments
  232.             WHERE bank_status = 'initiated'
  233.             AND pisp_status NOT IN ('rejected', 'initiated')
  234.             AND :description ILIKE '%' || mapping_id || '%'
  235.             ORDER BY LENGTH(mapping_id) DESC, created_at DESC
  236.             LIMIT 1
  237.         ";
  238.         $mappingId $conn->fetchOne($sql, [
  239.             'description' => $description,
  240.         ]);
  241.         if ($mappingId === false || $mappingId === null) {
  242.             $this->logger?->warning('No mapping_id found in description', [
  243.                 'description' => $description,
  244.             ]);
  245.             return null;
  246.         }
  247.         return $mappingId;
  248.     }
  249.     public function sendMerchantNotification(array $paymentNotificationstring $secret): bool
  250.     {
  251.         $payment $paymentNotification;
  252.         $payload = [
  253.             'id' => $payment['id'],
  254.             'paymentId' => $payment['external_id'],
  255.             'pisp_status' => $payment['pisp_status'],
  256.             'fin_status' => 'completed',
  257.             'currency' => $paymentNotification['currency'],
  258.             'amount' => $payment['amount'] / 100,
  259.             'userId' => $payment['customer_id']
  260.         ];
  261.         try {
  262.             $response $this->sendRequest('POST'$paymentNotification['notification_url'], $payload$secret);
  263.         } catch (GuzzleException $exception) {
  264.             $this->logger->error('Merchant notification failed', [
  265.                 'id' => $payment['id'],
  266.                 'paymentId' => $payment['external_id'],
  267.                 'error' => $exception->getMessage()
  268.             ]);
  269.             $saveFailedNotification $this->commonServices->saveFailedNotifications(
  270.                 $payment['id'],
  271.                 $payload,
  272.                 $paymentNotification['notification_url'],
  273.                 $secret
  274.             );
  275.             $logs = [
  276.                 'title' => 'Failed to send Bank status update to merchant',
  277.                 'details' => $payload
  278.             ];
  279.             $this->commonServices->savePaymentLogs($payment['id'], $response->getStatusCode(), $logs);
  280.             if ($saveFailedNotification) {
  281.                 $this->logger->warning('Merchant failed notification saved to DB', [
  282.                     'id' => $payment['id'],
  283.                     'paymentId' => $payment['external_id'],
  284.                 ]);
  285.             } else {
  286.                 $this->logger->warning('Merchant failed notification failed to save to DB', [
  287.                     'id' => $payment['id'],
  288.                     'paymentId' => $payment['external_id'],
  289.                 ]);
  290.             }
  291.             return false;
  292.         }
  293.         if ($response->getStatusCode() >= 200 && $response->getStatusCode() < 300) {
  294.             $this->logger->info('Merchant notification sent successfully', [
  295.                 'id' => $payment['id'],
  296.                 'paymentId' => $payment['external_id']
  297.             ]);
  298.             $logs = [
  299.                 'title' => 'The Bank status update was successfully sent to the merchant',
  300.                 'details' => $payload
  301.             ];
  302.             $this->commonServices->savePaymentLogs($payment['id'], $response->getStatusCode(), $logs);
  303.             return true;
  304.         }
  305.         $logs = [
  306.             'title' => 'Failed to send Bank status update to merchant',
  307.             'details' => $payload
  308.         ];
  309.         $this->commonServices->savePaymentLogs($payment['id'], $response->getStatusCode(), $logs);
  310.         $this->logger->error('Merchant notification failed', [
  311.             'id' => $payment['id'],
  312.             'paymentId' => $payment['external_id'],
  313.             'statusCode' => $response->getStatusCode(),
  314.             'error' => $response->getBody()->getContents()
  315.         ]);
  316.         $saveFailedNotification $this->commonServices->saveFailedNotifications(
  317.             $payment['id'],
  318.             $payload,
  319.             $paymentNotification['notification_url'],
  320.             $secret
  321.         );
  322.         if ($saveFailedNotification) {
  323.             $this->logger->warning('Merchant failed notification saved to DB', [
  324.                 'id' => $payment['id'],
  325.                 'paymentId' => $payment['external_id'],
  326.             ]);
  327.         } else {
  328.             $this->logger->warning('Merchant failed notification failed to save to DB', [
  329.                 'id' => $payment['id'],
  330.                 'paymentId' => $payment['external_id'],
  331.             ]);
  332.         }
  333.         return false;
  334.     }
  335.     /**
  336.      * @throws GuzzleException
  337.      */
  338.     private function sendRequest(string $methodstring $uri, array $paramsstring $secretKey): ResponseInterface
  339.     {
  340.         $jsonBase64 base64_encode(json_encode($params));
  341.         return $this->client->request($method$uri, [
  342.             'headers' => [
  343.                 'X-Signature' => $this->generateSignature($jsonBase64$secretKey),
  344.             ],
  345.             'body' => $jsonBase64,
  346.             'http_errors' => false,
  347.             'timeout' => 10,
  348.         ]);
  349.     }
  350.     private function generateSignature(string $jsonBase64string $secretKey): string
  351.     {
  352.         return hash_hmac('sha256'$jsonBase64$secretKey);
  353.     }
  354.     #[Route(path'/countries/list'name'countries_list'methods: ['GET'])]
  355.     public function getCountries(Request $request): JsonResponse
  356.     {
  357.         $authorization $request->headers->get('Authorization');
  358.         $factoryNameConfigDtls $this->getFactoryNameAndConfig($authorization);
  359.         $factoryName $factoryNameConfigDtls['factoryName'];
  360.         $configDtls $factoryNameConfigDtls['config'];
  361.         switch ($factoryName) {
  362.             case 'yapily':
  363.                 return $this->json([
  364.                     'success' => false,
  365.                     'message' => 'Yapily does not support this feature',
  366.                 ], 415);
  367.                 break;
  368.             case 'iban_xs':
  369.                 try {
  370.                     $token $this->getIbanXsToken($configDtls);
  371.                     $client = new Client();
  372.                     $response $client->get($this->ibanxsApiBaseUrl 'v1/countries', [
  373.                         'headers' => [
  374.                             'Accept' => 'application/json',
  375.                             'Authorization' => 'Bearer ' $token,
  376.                         ],
  377.                     ]);
  378.                     return $this->json([
  379.                         'success' => true,
  380.                         'data' => json_decode($response->getBody()->getContents(), true),
  381.                     ]);
  382.                 } catch (\Throwable $e) {
  383.                     return $this->json([
  384.                         'success' => false,
  385.                         'message' => $e->getMessage(),
  386.                     ], 500);
  387.                 }
  388.                 break;
  389.             default:
  390.                 return $this->json([
  391.                     'success' => false,
  392.                     'message' => 'Invalid Payment Factory',
  393.                 ], 400);
  394.                 break;
  395.         }
  396.     }
  397.     #[Route(path'/countries/banks/list'name'countries_banks_list'methods: ['GET'])]
  398.     public function getBanksForCountry(Request $request): JsonResponse
  399.     {
  400.         $countryCode $request->query->get('countryCode');
  401.         if (!$countryCode) {
  402.             return $this->json([
  403.                 'success' => false,
  404.                 'message' => 'countryCode query parameter is required.',
  405.             ], 400);
  406.         }
  407.         $authorization $request->headers->get('Authorization');
  408.         $factoryNameConfigDtls $this->getFactoryNameAndConfig($authorization);
  409.         $factoryName $factoryNameConfigDtls['factoryName'];
  410.         $configDtls $factoryNameConfigDtls['config'];
  411.         switch ($factoryName) {
  412.             case 'yapily':
  413.                 return $this->json([
  414.                     'success' => false,
  415.                     'message' => 'Yapily does not support this feature',
  416.                 ], 415);
  417.                 break;
  418.             case 'iban_xs':
  419.                 try {
  420.                     $token $this->getIbanXsToken($configDtls);
  421.                     $client = new Client();
  422.                     $response $client->get($this->ibanxsApiBaseUrl 'v1/countries/' $countryCode '/banks', [
  423.                         'headers' => [
  424.                             'Accept' => 'application/json',
  425.                             'Authorization' => 'Bearer ' $token,
  426.                         ],
  427.                     ]);
  428.                     $data json_decode($response->getBody()->getContents(), true);
  429.                     $banks array_map(static function (array $bank) {
  430.                         return [
  431.                             'bankId' => $bank['bankId'],
  432.                             'name' => $bank['name'],
  433.                         ];
  434.                     }, $data);
  435.                     return $this->json([
  436.                         'success' => true,
  437.                         'data' => $banks,
  438.                     ]);
  439.                 } catch (\Throwable $e) {
  440.                     return $this->json([
  441.                         'success' => false,
  442.                         'message' => $e->getMessage(),
  443.                     ], 500);
  444.                 }
  445.                 break;
  446.             default:
  447.                 return $this->json([
  448.                     'success' => false,
  449.                     'message' => 'Invalid Payment Factory',
  450.                 ], 400);
  451.                 break;
  452.         }
  453.     }
  454.     #[Route(path'/banks/parameters'name'banks_parameters'methods: ['GET'])]
  455.     public function getBanksParameters(Request $request): JsonResponse
  456.     {
  457.         $bankId $request->query->get('bankId');
  458.         if (!$bankId) {
  459.             return $this->json([
  460.                 'success' => false,
  461.                 'message' => 'bankId query parameter is required.',
  462.             ], 400);
  463.         }
  464.         $authorization $request->headers->get('Authorization');
  465.         $factoryNameConfigDtls $this->getFactoryNameAndConfig($authorization);
  466.         $factoryName $factoryNameConfigDtls['factoryName'];
  467.         $configDtls $factoryNameConfigDtls['config'];
  468.         switch ($factoryName) {
  469.             case 'yapily':
  470.                 return $this->json([
  471.                     'success' => false,
  472.                     'message' => 'Yapily does not support this feature',
  473.                 ], 415);
  474.                 break;
  475.             case 'iban_xs':
  476.                 try {
  477.                     $token $this->getIbanXsToken($configDtls);
  478.                     $client = new Client();
  479.                     $response $client->get($this->ibanxsApiBaseUrl 'v1/banks/' $bankId .'/specification/PIS', [
  480.                         'headers' => [
  481.                             'Accept' => 'application/json',
  482.                             'Authorization' => 'Bearer ' $token,
  483.                         ],
  484.                     ]);
  485.                     $data json_decode($response->getBody()->getContents(), true);
  486.                     return $this->json([
  487.                         'success' => true,
  488.                         'data' => $data,
  489.                     ]);
  490.                 } catch (\Throwable $e) {
  491.                     return $this->json([
  492.                         'success' => false,
  493.                         'message' => $e->getMessage(),
  494.                     ], 500);
  495.                 }
  496.                 break;
  497.             default:
  498.                 return $this->json([
  499.                     'success' => false,
  500.                     'message' => 'Invalid Payment Factory',
  501.                 ], 400);
  502.                 break;
  503.         }
  504.     }
  505.     // HELPER FUNCTIONS
  506.     private function getFactoryNameAndConfig(string $authorization)
  507.     {
  508.         if (!$authorization || !str_starts_with($authorization'Basic ')) {
  509.             return $this->json([
  510.                 'success' => false,
  511.                 'message' => 'Missing or invalid Authorization header',
  512.             ], 401);
  513.         }
  514.         $base64 substr($authorization6);
  515.         $decoded base64_decode($base64true);
  516.         if ($decoded === false) {
  517.             return $this->json([
  518.                 'success' => false,
  519.                 'message' => 'Invalid Basic token',
  520.             ], 401);
  521.         }
  522.         // Format: username:password (username is expected to be site UUID, password is site password)
  523.         $parts explode(':'$decoded2);
  524.         $username $parts[0] ?? null;
  525.         $password $parts[1] ?? null;
  526.         if (!$username || $password === null) {
  527.             return $this->json([
  528.                 'success' => false,
  529.                 'message' => 'Invalid Basic token format',
  530.             ], 401);
  531.         }
  532.         // Validate against DB (matches SiteAuthenticator/SiteLoginProvider contract)
  533.         try {
  534.             $sites $this->em
  535.                 ->getRepository(\App\Domain\Site\Model\Site::class)
  536.                 ->findById(\Symfony\Component\Uid\Uuid::fromString((string) $username));
  537.             $site $sites[0] ?? null;
  538.             if (!$site) {
  539.                 return $this->json([
  540.                     'success' => false,
  541.                     'message' => 'Invalid credentials',
  542.                 ], 401);
  543.             }
  544.         } catch (\Throwable $e) {
  545.             return $this->json([
  546.                 'success' => false,
  547.                 'message' => 'Invalid credentials',
  548.             ], 401);
  549.         }
  550.         $hash $site->getPassword();
  551.         if (!is_string($hash) || $hash === '') {
  552.             return $this->json([
  553.                 'success' => false,
  554.                 'message' => 'Invalid credentials',
  555.             ], 401);
  556.         }
  557.         // Password hash check
  558.         if ($password !== $hash) {
  559.             return $this->json([
  560.                 'success' => false,
  561.                 'message' => 'Invalid credentials',
  562.             ], 401);
  563.         }
  564.         $factoryName $site->getPaymentGatewayConfig()->getFactoryName();
  565.         $config $site->getPaymentGatewayConfig()->getConfig();
  566.         return [
  567.             'factoryName' => $factoryName,
  568.             'config' => $config
  569.         ];
  570.     }
  571.     private function getIbanXsToken($configDtls): string
  572.     {
  573.         $client = new Client();
  574.         $response $client->post($this->ibanxsAuthTokenUrl, [
  575.             'headers' => [
  576.                 'Content-Type' => 'application/x-www-form-urlencoded',
  577.             ],
  578.             'form_params' => [
  579.                 'client_id' => $configDtls['client_id'],
  580.                 'client_secret' => $configDtls['client_secret'],
  581.                 'grant_type' => 'client_credentials',
  582.             ],
  583.         ]);
  584.         $body json_decode($response->getBody()->getContents(), true);
  585.         return $body['access_token'];
  586.     }
  587. }