feat: request replay command for bpn api
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"php": ">=8.1",
|
"php": ">=8.1",
|
||||||
"ext-ctype": "*",
|
"ext-ctype": "*",
|
||||||
|
"ext-dom": "*",
|
||||||
"ext-iconv": "*",
|
"ext-iconv": "*",
|
||||||
"ext-simplexml": "*",
|
"ext-simplexml": "*",
|
||||||
"ext-sockets": "*",
|
"ext-sockets": "*",
|
||||||
|
|||||||
@@ -468,6 +468,82 @@ class ApiClient
|
|||||||
return $this->sendRequest(static::TYPE_PROMO_VOUCHER, $data);
|
return $this->sendRequest(static::TYPE_PROMO_VOUCHER, $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends raw XML to the BPN API with key regeneration.
|
||||||
|
*
|
||||||
|
* Parses the XML to extract the request type, regenerates the authentication key
|
||||||
|
* with the current date, and sends the request. Returns the raw XML response.
|
||||||
|
*
|
||||||
|
* @param string $xml The raw XML request body
|
||||||
|
* @param bool $debug Enable debug mode (XML dumps)
|
||||||
|
*
|
||||||
|
* @return string The raw XML response
|
||||||
|
*
|
||||||
|
* @throws ApiClientException If the request fails or XML is invalid
|
||||||
|
*/
|
||||||
|
public function sendRawXml(string $xml, bool $debug = false): string
|
||||||
|
{
|
||||||
|
$requestId = date(DATE_ATOM).uniqid();
|
||||||
|
|
||||||
|
$doc = new \DOMDocument();
|
||||||
|
if (false === @$doc->loadXML($xml)) {
|
||||||
|
throw new ApiClientException('Invalid XML provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
$satzNode = $doc->getElementsByTagName('satz')->item(0);
|
||||||
|
if (null === $satzNode) {
|
||||||
|
throw new ApiClientException('Missing <satz> element in XML');
|
||||||
|
}
|
||||||
|
|
||||||
|
$type = $satzNode->getAttribute('typ');
|
||||||
|
if ('' === $type) {
|
||||||
|
throw new ApiClientException('Missing typ attribute on <satz> element');
|
||||||
|
}
|
||||||
|
|
||||||
|
$keyNode = $doc->getElementsByTagName('key')->item(0);
|
||||||
|
if (null === $keyNode) {
|
||||||
|
throw new ApiClientException('Missing <key> element in XML');
|
||||||
|
}
|
||||||
|
|
||||||
|
$newKey = $this->createKey(
|
||||||
|
$this->config['bpn_username'],
|
||||||
|
$this->config['bpn_password'],
|
||||||
|
$type
|
||||||
|
);
|
||||||
|
$keyNode->nodeValue = $newKey;
|
||||||
|
|
||||||
|
$body = $doc->saveXML();
|
||||||
|
|
||||||
|
$this->logger->info('Sending raw XML request to BPN API', [
|
||||||
|
'requestId' => $requestId,
|
||||||
|
'type' => $type,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (true === $debug || true === $this->config['debug']) {
|
||||||
|
$this->dumpXmlToFile('request', $requestId, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
$socket = $this->connect(
|
||||||
|
$this->config['bpn_api_ip'],
|
||||||
|
$this->config['bpn_api_port'],
|
||||||
|
$this->config['max_retries'],
|
||||||
|
$this->config['connection_timeout'],
|
||||||
|
$this->config['stream_timeout'],
|
||||||
|
$this->config['total_timeout']
|
||||||
|
);
|
||||||
|
$this->send($socket, $body, $this->config['total_timeout']);
|
||||||
|
$response = $this->receive($socket, $this->config['total_timeout']);
|
||||||
|
$this->disconnect($socket);
|
||||||
|
|
||||||
|
$responseXml = substr($response, 10);
|
||||||
|
|
||||||
|
if (true === $debug || true === $this->config['debug']) {
|
||||||
|
$this->dumpXmlToFile('response', $requestId, $responseXml);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $responseXml;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws ApiClientException
|
* @throws ApiClientException
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use App\BusProNet\ApiClient;
|
||||||
|
use App\BusProNet\Exception\ApiClientException;
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'bpn:replay',
|
||||||
|
description: 'Replay a BPN API request from an XML file',
|
||||||
|
)]
|
||||||
|
class BpnReplayCommand extends Command
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ApiClient $apiClient,
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->addArgument('file', InputArgument::REQUIRED, 'Path to the XML request file')
|
||||||
|
->addOption('dry-run', 'd', InputOption::VALUE_NONE, 'Show regenerated XML without sending')
|
||||||
|
->addOption('output', 'o', InputOption::VALUE_REQUIRED, 'Save response to file')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
|
||||||
|
$filePath = $input->getArgument('file');
|
||||||
|
if (false === file_exists($filePath)) {
|
||||||
|
$io->error(sprintf('File not found: %s', $filePath));
|
||||||
|
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$xml = file_get_contents($filePath);
|
||||||
|
if (false === $xml) {
|
||||||
|
$io->error(sprintf('Unable to read file: %s', $filePath));
|
||||||
|
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->info(sprintf('Loaded XML from: %s', $filePath));
|
||||||
|
|
||||||
|
$type = $this->extractRequestType($xml);
|
||||||
|
if (null !== $type) {
|
||||||
|
$io->info(sprintf('Request type: %s', $type));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true === $input->getOption('dry-run')) {
|
||||||
|
$regeneratedXml = $this->regenerateKey($xml);
|
||||||
|
if (null === $regeneratedXml) {
|
||||||
|
$io->error('Failed to regenerate key in XML');
|
||||||
|
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->section('Regenerated XML (dry-run)');
|
||||||
|
$io->writeln($this->formatXml($regeneratedXml));
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->info('Sending request to BPN API...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $this->apiClient->sendRawXml($xml);
|
||||||
|
} catch (ApiClientException $e) {
|
||||||
|
$io->error(sprintf('API request failed: %s', $e->getMessage()));
|
||||||
|
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$outputFile = $input->getOption('output');
|
||||||
|
if (null !== $outputFile) {
|
||||||
|
if (false === file_put_contents($outputFile, $response)) {
|
||||||
|
$io->error(sprintf('Failed to write response to: %s', $outputFile));
|
||||||
|
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
$io->success(sprintf('Response saved to: %s', $outputFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->section('Response');
|
||||||
|
$io->writeln($this->formatXml($response));
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractRequestType(string $xml): ?string
|
||||||
|
{
|
||||||
|
$doc = new \DOMDocument();
|
||||||
|
if (false === @$doc->loadXML($xml)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$satzNode = $doc->getElementsByTagName('satz')->item(0);
|
||||||
|
|
||||||
|
return $satzNode?->getAttribute('typ');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function regenerateKey(string $xml): ?string
|
||||||
|
{
|
||||||
|
$doc = new \DOMDocument();
|
||||||
|
if (false === @$doc->loadXML($xml)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$keyNode = $doc->getElementsByTagName('key')->item(0);
|
||||||
|
if (null === $keyNode) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$keyNode->nodeValue = '[KEY_WOULD_BE_REGENERATED]';
|
||||||
|
|
||||||
|
return $doc->saveXML();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatXml(string $xml): string
|
||||||
|
{
|
||||||
|
$doc = new \DOMDocument();
|
||||||
|
$doc->preserveWhiteSpace = false;
|
||||||
|
$doc->formatOutput = true;
|
||||||
|
|
||||||
|
if (false === @$doc->loadXML($xml)) {
|
||||||
|
return $xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $doc->saveXML() ?: $xml;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user