diff --git a/doc/dev-setup.md b/doc/dev-setup.md
index 70aaaeba0..90e58b843 100644
--- a/doc/dev-setup.md
+++ b/doc/dev-setup.md
@@ -208,14 +208,14 @@ Ensuite pour le paiement, il faut utiliser ces informations [de carte](https://w
#### Callbacks de paiement
-Après le paiement, paybox effectue un retour sur le serveur et c'est suite à ce retour que l'on effectue des actions
-comme l'ajout de la cotisation.
+Afin de simuler un appel Paybox, il y a une commande disponible (en environnement de `dev` uniquement).
-Afin d'en simplifier l'appel, il existe une commande dédiée qui s'appelle comme cela, où l'argument en exemple
-correspond à l'URL de la page de retour sur le site après paiement.
+Cette commande utilise une série de questions pour guider son utilisation.
+Il faut cependant récupérer le cmd qui est indiqué dans la fenêtre de Paybox.
+Ce paramètre est de la forme : `C2026-170120261126-0-1-ADMIN-84B` ou `F202601-1701-JDOE-11f8d`
```
-bin/console dev:callback-paybox-cotisation "https://localhost:9206/association/paybox-redirect?total=3000&cmd=C2020-150120201239-0-770-GALLO-E4F&autorisation=XXXXXX&transaction=588033888&status=00000"
+bin/console dev:paybox-callback-simulator
```
### GitHub
diff --git a/sources/AppBundle/Command/DevCallBackPayboxCotisationCommand.php b/sources/AppBundle/Command/DevCallBackPayboxCotisationCommand.php
deleted file mode 100644
index 1d48dbfc9..000000000
--- a/sources/AppBundle/Command/DevCallBackPayboxCotisationCommand.php
+++ /dev/null
@@ -1,55 +0,0 @@
-setName('dev:callback-paybox-cotisation')
- ->addArgument('url_paiement_effectue', InputArgument::REQUIRED)
- ->setHelp($help);
- }
-
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $parsedUrl = parse_url((string) $input->getArgument('url_paiement_effectue'));
-
- $query = $parsedUrl['query'];
-
- parse_str($query, $params);
-
- $callBackParameters = [
- 'total' => $params['total'],
- 'cmd' => $params['cmd'],
- 'autorisation' => $params['autorisation'],
- 'transaction' => $params['transaction'],
- 'status' => $params['status'],
- ];
-
- $url = 'https://apachephp:80/association/paybox-callback?' . http_build_query($callBackParameters);
-
- $curl = curl_init($url);
- curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
- curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
- curl_exec($curl);
-
- $output->writeln("Appel au callback de paiement de cotisation effectué");
-
- return Command::SUCCESS;
- }
-}
diff --git a/sources/AppBundle/Command/PayboxCallbackSimulatorCommand.php b/sources/AppBundle/Command/PayboxCallbackSimulatorCommand.php
new file mode 100644
index 000000000..a541402f5
--- /dev/null
+++ b/sources/AppBundle/Command/PayboxCallbackSimulatorCommand.php
@@ -0,0 +1,168 @@
+title("Commande pour la simulation d'appel de Paybox");
+ $style->info("Cette commande permet de simuler des appels de callback depuis Paybox.\nLaissez-vous guider par les questions.\n");
+
+ $question = new ChoiceQuestion('Quel type de paiement souhaitez-vous simuler ?', [
+ 'Cotisation',
+ 'Inscription',
+ ], 'Cotisation');
+ $payementType = $helper->ask($input, $output, $question);
+
+ if ($payementType === 'Cotisation') {
+ $example = 'C2026-170120261126-0-1-ADMIN-84B';
+ $regexp = '"^C\d{4}"';
+ } else {
+ $example = 'F202601-1701-JDOE-11f8d';
+ $regexp = '"^F\d{4}"';
+ }
+ $question = new Question(sprintf('Pour quel identifiant de paiement (cmd) ? (par exemple: %s)', $example));
+ $cmd = $helper->ask($input, $output, $question);
+ $question->setValidator(function ($cmd) use ($regexp): void {
+ if (!preg_match($regexp, $cmd)) {
+ throw new \RuntimeException(
+ sprintf('Le format du CMD n\'est pas valide. Il doit être de la forme : %s', $regexp),
+ );
+ }
+ });
+
+ $question = new ChoiceQuestion('Quel statut de paiement ?', [
+ 'Validé',
+ 'Déjà effectué',
+ 'Annulé',
+ 'Refusé',
+ ], 'Validé');
+ $status = $helper->ask($input, $output, $question);
+
+ if ($payementType === 'Cotisation') {
+ $url = $this->callCotisation($cmd, $status);
+ } else {
+ $url = $this->callInvoice($cmd, $status);
+ }
+
+ $style->title('Résumé');
+ $style->text([
+ "Vous êtes sur le point de simuler un appel Paybox :",
+ "Type de paiement: $payementType",
+ "Statut du paiement: $status",
+ "CMD: $cmd",
+ "URL: $url",
+ "",
+ ]);
+
+ $question = new ConfirmationQuestion('Êtes-vous sûr de vouloir faire cet appel Paybox (oui/non)?',false, '/^(y|o)/i');
+ if ($helper->ask($input, $output, $question)) {
+ $this->callCallback($url);
+ $style->success('Appel Paybox effectué');
+ return Command::SUCCESS;
+ }
+ $style->warning('Appel Paybox annulé');
+
+ return Command::SUCCESS;
+ }
+
+ private function callCotisation(string $cmd, string $status): string
+ {
+ $account = $this->cotisations->getAccountFromCmd($cmd);
+ $cotisation = $this->cotisations->obtenirDerniere(MemberType::from($account['type']), $account['id']);
+ if (!$cotisation) {
+ throw new \RuntimeException(
+ sprintf('Cotisation non trouvée avec ce CMD: %s', $cmd),
+ );
+ }
+ $url = $this->urlGenerator->generate('membership_payment');
+
+ return $this->buildUrl($url, (float) $cotisation['montant'], $cmd, $status);
+ }
+
+ private function callInvoice(string $cmd, string $status): string
+ {
+ $invoice = $this->invoiceRepository->getByReference($cmd);
+ if (!$invoice instanceof Invoice) {
+ throw new \RuntimeException(
+ sprintf('Facture non trouvée avec ce CMD: "%s"', $cmd),
+ );
+ }
+ $event = $this->eventRepository->get($invoice->getForumId());
+ if (!$event instanceof Event) {
+ throw new \RuntimeException(
+ sprintf('Inscription non trouvé avec ce CMD: "%s"', $cmd),
+ );
+ }
+
+ $url = $this->urlGenerator->generate('ticket_paybox_callback', ['eventSlug' => $event->getPath()]);
+
+ return $this->buildUrl($url, $invoice->getAmount(), $cmd, $status);
+ }
+
+ private function buildUrl(string $baseUrl, float $amount, string $cmd, string $state): string
+ {
+ $status = match ($state) {
+ 'Déjà effectué' => PayboxResponse::STATUS_DUPLICATE,
+ 'Annulé' => '00117',
+ 'Refusé' => '001XX',
+ default => PayboxResponse::STATUS_SUCCESS,
+ };
+
+ $callBackParameters = [
+ 'total' => $amount * 100,
+ 'cmd' => $cmd,
+ 'autorisation' => 'fake_' . bin2hex(random_bytes(5)),
+ 'transaction' => random_int(400000, 600000),
+ 'status' => $status,
+ ];
+
+ return 'https://apachephp:80' . $baseUrl . '?' . http_build_query($callBackParameters);
+ }
+
+ private function callCallback(string $url): string
+ {
+ $curl = curl_init($url);
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
+ curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
+ curl_exec($curl);
+
+ return $url;
+ }
+}