```
✨ feat(Stripe): Intègre Stripe pour la gestion des paiements et les webhooks
Ajoute Stripe pour la synchronisation des clients et la configuration des webhooks.
Crée une commande pour synchroniser les clients locaux avec Stripe.
Ajoute un champ customerId à l'entité Customer.
```
This commit is contained in:
9
.env
9
.env
@@ -79,11 +79,12 @@ NOTIFUSE_EMAIL=
|
||||
NOTIFUSE_ACCOUNT=
|
||||
NOTIFUSE_LIST=
|
||||
|
||||
STRIPE_PK=
|
||||
STRIPE_SK=
|
||||
STRIPE_PK=pk_test_51SUA22173W4aeFB1nO6oFfDZ12HOTffDKtCshhZ8rkUg6kUO2ZaQC0tK72rhE79Tr8treeHX9KMcZtvcQZ0X8VSm00Q6GQ365V
|
||||
STRIPE_SK=sk_test_51SUA22173W4aeFB16EB2LxGI0hNvNJzFshDI98zRImWBIhSfzqOGAz5TlPxSpUWbj3x4COm6kmSsaal9FpQR1A7M0022DvjbbR
|
||||
STRIPE_WEBHOOKS_SECRET=
|
||||
|
||||
SIGN_URL=
|
||||
STRIPE_BASEURL=https://e3358705e82c.ngrok-free.app
|
||||
|
||||
MINIO_S3_URL=
|
||||
MINIO_S3_CLIENT_ID=
|
||||
@@ -91,3 +92,7 @@ MINIO_S3_CLIENT_SECRET=
|
||||
MINIO_S3_CLIENT_BUCKET=
|
||||
|
||||
ESY_SEARCH_KEY=b09d9a708b427d495c39fe6e8fc5361fe33fee57a0435f3e1bf3ed8155f2a277
|
||||
|
||||
###> stripe/stripe-php ###
|
||||
STRIPE_SECRET_KEY=sk_test_***
|
||||
###< stripe/stripe-php ###
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
"spomky-labs/web-push-bundle": "^3.1",
|
||||
"stancer/stancer": ">=2.0.1",
|
||||
"stevenmaguire/oauth2-keycloak": "^5.1",
|
||||
"stripe/stripe-php": "^19.1",
|
||||
"symfony/amazon-mailer": "7.3.*",
|
||||
"symfony/asset": "7.3.*",
|
||||
"symfony/asset-mapper": "7.3.*",
|
||||
|
||||
61
composer.lock
generated
61
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "f476989731711ededed94397298a39d6",
|
||||
"content-hash": "bae39e4278669ceaf6f28005f1d75605",
|
||||
"packages": [
|
||||
{
|
||||
"name": "async-aws/core",
|
||||
@@ -9393,6 +9393,65 @@
|
||||
},
|
||||
"time": "2023-10-24T06:10:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "stripe/stripe-php",
|
||||
"version": "v19.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/stripe/stripe-php.git",
|
||||
"reference": "4e3de7211645699b1f5b5f1f1b45bd9faf369426"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/stripe/stripe-php/zipball/4e3de7211645699b1f5b5f1f1b45bd9faf369426",
|
||||
"reference": "4e3de7211645699b1f5b5f1f1b45bd9faf369426",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"php": ">=5.6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "3.72.0",
|
||||
"phpstan/phpstan": "^1.2",
|
||||
"phpunit/phpunit": "^5.7 || ^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Stripe\\": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Stripe and contributors",
|
||||
"homepage": "https://github.com/stripe/stripe-php/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Stripe PHP Library",
|
||||
"homepage": "https://stripe.com/",
|
||||
"keywords": [
|
||||
"api",
|
||||
"payment processing",
|
||||
"stripe"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/stripe/stripe-php/issues",
|
||||
"source": "https://github.com/stripe/stripe-php/tree/v19.1.0"
|
||||
},
|
||||
"time": "2025-12-16T19:48:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/amazon-mailer",
|
||||
"version": "v7.3.0",
|
||||
|
||||
7
config/packages/stripe.yaml
Normal file
7
config/packages/stripe.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
services:
|
||||
stripe.client:
|
||||
class: 'Stripe\StripeClient'
|
||||
arguments:
|
||||
- '%env(STRIPE_SECRET_KEY)%'
|
||||
|
||||
Stripe\StripeClient: '@stripe.client'
|
||||
32
migrations/Version20260116114457.php
Normal file
32
migrations/Version20260116114457.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260116114457 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE customer ADD customer_id VARCHAR(255) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE customer DROP customer_id');
|
||||
}
|
||||
}
|
||||
32
migrations/Version20260116121121.php
Normal file
32
migrations/Version20260116121121.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260116121121 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE stripe_config (id SERIAL NOT NULL, name VARCHAR(255) DEFAULT NULL, webhook_id INT NOT NULL, secret VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('DROP TABLE stripe_config');
|
||||
}
|
||||
}
|
||||
32
migrations/Version20260116121156.php
Normal file
32
migrations/Version20260116121156.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260116121156 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE stripe_config ALTER webhook_id TYPE VARCHAR(255)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE stripe_config ALTER webhook_id TYPE INT');
|
||||
}
|
||||
}
|
||||
67
src/Command/StripeCommand.php
Normal file
67
src/Command/StripeCommand.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Entity\Customer;
|
||||
use App\Service\Stripe\Client;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'app:stripe:sync',
|
||||
description: 'Synchronise les clients locaux vers Stripe et configure les Webhooks'
|
||||
)]
|
||||
class StripeCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly Client $client,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$io->title('Synchronisation Stripe : Clients & Webhooks');
|
||||
|
||||
// 1. Synchronisation des clients manquants
|
||||
$io->section('Synchronisation des clients');
|
||||
$customers = $this->entityManager->getRepository(Customer::class)->findBy(['customerId' => null]);
|
||||
|
||||
if (empty($customers)) {
|
||||
$io->success('Tous les clients sont déjà synchronisés.');
|
||||
} else {
|
||||
$io->progressStart(count($customers));
|
||||
|
||||
foreach ($customers as $customer) {
|
||||
$result = $this->client->createCustomer($customer);
|
||||
|
||||
if ($result['state']) {
|
||||
$this->entityManager->persist($customer);
|
||||
} else {
|
||||
$io->error(sprintf('Échec pour %s : %s', $customer->getEmail(), $result['message']));
|
||||
}
|
||||
|
||||
$io->progressAdvance();
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
$io->progressFinish();
|
||||
$io->success('Synchronisation des clients terminée.');
|
||||
}
|
||||
|
||||
// 2. Configuration des Webhooks
|
||||
$io->section('Configuration des Webhooks');
|
||||
$this->client->webhooks();
|
||||
$io->success('La configuration Stripe est à jour.');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,9 @@ class Customer
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $siret = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $customerId = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
@@ -122,4 +125,16 @@ class Customer
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCustomerId(): ?string
|
||||
{
|
||||
return $this->customerId;
|
||||
}
|
||||
|
||||
public function setCustomerId(?string $customerId): static
|
||||
{
|
||||
$this->customerId = $customerId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
65
src/Entity/StripeConfig.php
Normal file
65
src/Entity/StripeConfig.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\StripeConfigRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: StripeConfigRepository::class)]
|
||||
class StripeConfig
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $name = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $webhookId = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $secret = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(?string $name): static
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getWebhookId(): ?int
|
||||
{
|
||||
return $this->webhookId;
|
||||
}
|
||||
|
||||
public function setWebhookId(string $webhookId): static
|
||||
{
|
||||
$this->webhookId = $webhookId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSecret(): ?string
|
||||
{
|
||||
return $this->secret;
|
||||
}
|
||||
|
||||
public function setSecret(string $secret): static
|
||||
{
|
||||
$this->secret = $secret;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
43
src/Repository/StripeConfigRepository.php
Normal file
43
src/Repository/StripeConfigRepository.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\StripeConfig;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<StripeConfig>
|
||||
*/
|
||||
class StripeConfigRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, StripeConfig::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return StripeConfig[] Returns an array of StripeConfig objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('s')
|
||||
// ->andWhere('s.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('s.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?StripeConfig
|
||||
// {
|
||||
// return $this->createQueryBuilder('s')
|
||||
// ->andWhere('s.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
169
src/Service/Stripe/Client.php
Normal file
169
src/Service/Stripe/Client.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Stripe;
|
||||
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\StripeConfig;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Stripe\Exception\ApiConnectionException;
|
||||
use Stripe\Exception\ApiErrorException;
|
||||
use Stripe\Exception\AuthenticationException;
|
||||
use Stripe\StripeClient;
|
||||
|
||||
class Client
|
||||
{
|
||||
private StripeClient $client;
|
||||
|
||||
public function __construct(
|
||||
private EntityManagerInterface $em
|
||||
) {
|
||||
// Récupération de la clé secrète depuis le .env
|
||||
$stripeSk = $_ENV['STRIPE_SK'] ?? '';
|
||||
$this->client = new StripeClient($stripeSk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie la connexion avec l'API Stripe
|
||||
*/
|
||||
public function check(): array
|
||||
{
|
||||
try {
|
||||
// Appel léger pour valider la clé API
|
||||
$this->client->accounts->all(['limit' => 1]);
|
||||
|
||||
return [
|
||||
'state' => true,
|
||||
'message' => 'Connexion établie avec Stripe'
|
||||
];
|
||||
} catch (AuthenticationException $e) {
|
||||
return ['state' => false, 'message' => 'Clé API Stripe invalide ou expirée.'];
|
||||
} catch (ApiConnectionException $e) {
|
||||
return ['state' => false, 'message' => 'Problème de connexion réseau avec Stripe.'];
|
||||
} catch (\Exception $e) {
|
||||
return ['state' => false, 'message' => 'Erreur : ' . $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un client sur Stripe et met à jour l'entité locale avec l'ID Stripe
|
||||
*/
|
||||
public function createCustomer(Customer $customer): array
|
||||
{
|
||||
try {
|
||||
$stripeCustomer = $this->client->customers->create([
|
||||
'name' => sprintf('%s %s', $customer->getSurname(), $customer->getName()),
|
||||
'email' => $customer->getEmail(),
|
||||
'phone' => $customer->getPhone(),
|
||||
'metadata' => [
|
||||
'internal_id' => $customer->getId(),
|
||||
'type' => $customer->getType(),
|
||||
],
|
||||
'description' => 'Client synchronisé depuis Ludikevent Intranet',
|
||||
]);
|
||||
|
||||
$customer->setCustomerId($stripeCustomer->id);
|
||||
// Note: Le flush est à faire dans le contrôleur pour valider la transaction globale
|
||||
|
||||
return [
|
||||
'state' => true,
|
||||
'message' => 'Client synchronisé avec succès.',
|
||||
'id' => $stripeCustomer->id
|
||||
];
|
||||
} catch (ApiErrorException $e) {
|
||||
return ['state' => false, 'message' => 'Erreur Stripe : ' . $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure, met à jour et sauvegarde les secrets des Webhooks
|
||||
*/
|
||||
public function webhooks(): array
|
||||
{
|
||||
$baseUrl = $_ENV['STRIPE_BASEURL'] ?? 'https://votre-domaine.fr';
|
||||
|
||||
// Configuration des routes attendues
|
||||
$configs = [
|
||||
'refund' => [
|
||||
'url' => $baseUrl . '/webhooks/refund',
|
||||
'events' => ['refund.created', 'refund.failed', 'refund.updated']
|
||||
],
|
||||
'payment' => [
|
||||
'url' => $baseUrl . '/webhooks/payment-intent',
|
||||
'events' => [
|
||||
'payment_intent.created',
|
||||
'payment_intent.canceled',
|
||||
'payment_intent.succeeded',
|
||||
'payment_intent.amount_capturable_updated'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$report = [];
|
||||
|
||||
try {
|
||||
// Récupération des endpoints existants chez Stripe
|
||||
$existingEndpoints = $this->client->webhookEndpoints->all(['limit' => 100]);
|
||||
|
||||
foreach ($configs as $name => $config) {
|
||||
$stripeEndpoint = null;
|
||||
|
||||
// On cherche si l'URL est déjà enregistrée chez Stripe
|
||||
foreach ($existingEndpoints->data as $endpoint) {
|
||||
if ($endpoint->url === $config['url']) {
|
||||
$stripeEndpoint = $endpoint;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Recherche de la config correspondante en BDD
|
||||
$dbConfig = $this->em->getRepository(StripeConfig::class)->findOneBy(['name' => $name]);
|
||||
if (!$dbConfig) {
|
||||
$dbConfig = new StripeConfig();
|
||||
$dbConfig->setName($name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if ($stripeEndpoint) {
|
||||
// MISE À JOUR de l'endpoint chez Stripe
|
||||
$this->client->webhookEndpoints->update($stripeEndpoint->id, [
|
||||
'enabled_events' => $config['events']
|
||||
]);
|
||||
|
||||
$dbConfig->setWebhookId($stripeEndpoint->id);
|
||||
$report[$name] = ['status' => 'updated', 'url' => $config['url']];
|
||||
} else {
|
||||
// CRÉATION de l'endpoint chez Stripe
|
||||
$newEndpoint = $this->client->webhookEndpoints->create([
|
||||
'url' => $config['url'],
|
||||
'enabled_events' => $config['events'],
|
||||
'description' => 'Ludikevent Webhook - ' . $name
|
||||
]);
|
||||
|
||||
$dbConfig->setWebhookId($newEndpoint->id);
|
||||
$dbConfig->setSecret($newEndpoint->secret); // On sauve le secret whsec_...
|
||||
|
||||
$report[$name] = ['status' => 'created', 'url' => $config['url']];
|
||||
}
|
||||
|
||||
$this->em->persist($dbConfig);
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
return ['state' => true, 'data' => $report];
|
||||
|
||||
} catch (ApiErrorException $e) {
|
||||
return ['state' => false, 'message' => 'Erreur API Stripe : ' . $e->getMessage()];
|
||||
} catch (\Exception $e) {
|
||||
return ['state' => false, 'message' => 'Erreur système : ' . $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accès direct au client Stripe pour des besoins spécifiques
|
||||
*/
|
||||
public function getNativeClient(): StripeClient
|
||||
{
|
||||
return $this->client;
|
||||
}
|
||||
}
|
||||
26
src/Twig/StripeExtension.php
Normal file
26
src/Twig/StripeExtension.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Twig;
|
||||
|
||||
use App\Service\Stripe\Client;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
class StripeExtension extends AbstractExtension
|
||||
{
|
||||
public function __construct(private readonly Client $client)
|
||||
{
|
||||
}
|
||||
|
||||
public function getFunctions()
|
||||
{
|
||||
return [
|
||||
new TwigFunction('syncStripe', [$this, 'syncStripe']),
|
||||
];
|
||||
}
|
||||
|
||||
public function syncStripe(): array
|
||||
{
|
||||
return $this->client->check();
|
||||
}
|
||||
}
|
||||
12
symfony.lock
12
symfony.lock
@@ -194,6 +194,18 @@
|
||||
"spomky-labs/web-push-bundle": {
|
||||
"version": "3.1.2"
|
||||
},
|
||||
"stripe/stripe-php": {
|
||||
"version": "19.1",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "main",
|
||||
"version": "19.0",
|
||||
"ref": "d6829c693e3927a8972c7671d74a1a5c505712b0"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/stripe.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/amazon-mailer": {
|
||||
"version": "7.3",
|
||||
"recipe": {
|
||||
|
||||
@@ -128,6 +128,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# MESSAGE D'ERREUR STRIPE #}
|
||||
{% if syncStripe().state == false %}
|
||||
<div class="mb-8 flex items-center p-6 backdrop-blur-xl bg-rose-500/5 border border-rose-500/20 rounded-[2rem] shadow-xl shadow-rose-500/5 animate-in fade-in slide-in-from-top-4 duration-500">
|
||||
<div class="flex-shrink-0 w-12 h-12 rounded-2xl bg-rose-500/10 border border-rose-500/20 flex items-center justify-center text-rose-500 mr-5">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-[10px] font-black text-rose-500 uppercase tracking-[0.2em] mb-1">Erreur de synchronisation Stripe</h4>
|
||||
<p class="text-sm text-slate-400 font-medium leading-relaxed italic">
|
||||
"{{ syncStripe().message }}"
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="w-full">
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
|
||||
@@ -46,15 +46,39 @@
|
||||
{# 1. IDENTITÉ #}
|
||||
<td class="px-8 py-6 whitespace-nowrap">
|
||||
<div class="flex items-center">
|
||||
{# Avatar avec initiales #}
|
||||
<div class="h-10 w-10 rounded-xl bg-gradient-to-br from-slate-700 to-slate-800 flex flex-shrink-0 items-center justify-center text-white font-black text-xs border border-white/10 shadow-lg">
|
||||
{{ customer.surname|first|upper }}{{ customer.name|first|upper }}
|
||||
</div>
|
||||
|
||||
<div class="ml-4">
|
||||
{# Nom et Civilité #}
|
||||
<div class="text-sm font-bold text-white">
|
||||
<span class="text-slate-500 text-[10px] uppercase mr-1">{{ customer.civ }}</span>
|
||||
{{ customer.surname|upper }} {{ customer.name }}
|
||||
</div>
|
||||
<div class="text-[10px] text-blue-400 font-medium tracking-tight">Client ID: #{{ customer.id }}</div>
|
||||
|
||||
{# ID Interne et État Stripe #}
|
||||
<div class="flex items-center mt-1 space-x-2">
|
||||
{# Badge ID Interne #}
|
||||
<span class="text-[9px] font-bold text-slate-500 tracking-tighter">
|
||||
ID: #{{ customer.id }}
|
||||
</span>
|
||||
|
||||
{% if customer.customerId %}
|
||||
{# ÉTAT : SYNCHRONISÉ (VERT) #}
|
||||
<div class="flex items-center text-[8px] font-black text-emerald-400 uppercase tracking-[0.1em] bg-emerald-500/10 px-2 py-0.5 rounded-md border border-emerald-500/30 shadow-sm shadow-emerald-500/10">
|
||||
<span class="flex h-1.5 w-1.5 rounded-full bg-emerald-500 mr-2"></span>
|
||||
Stripe synchronisé
|
||||
</div>
|
||||
{% else %}
|
||||
{# ÉTAT : NON SYNCHRONISÉ (ROUGE) #}
|
||||
<div class="flex items-center text-[8px] font-black text-rose-500 uppercase tracking-[0.1em] bg-rose-500/10 px-2 py-0.5 rounded-md border border-rose-500/30 shadow-sm shadow-rose-500/10">
|
||||
<span class="flex h-1.5 w-1.5 rounded-full bg-rose-500 mr-2 animate-pulse"></span>
|
||||
Stripe non synchronisé
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
Reference in New Issue
Block a user