feat: sync clients vers Stripe dans /admin/sync

SyncController::syncStripeCustomers :
- Route POST /admin/sync/stripe/customers
- Pour chaque client en BDD : si stripeCustomerId existe → update,
  sinon → create dans Stripe
- Envoie : email, nom (raison sociale ou fullName), phone, metadata
  (crm_user_id, siret, code_comptable)
- Stocke le stripeCustomerId apr��s création
- Compteurs : créés, mis à jour, erreurs avec flash détaillé

Template admin/sync/index.html.twig :
- Bloc "Clients - Stripe" avec compteurs sync/non sync/total
- Bouton "Synchroniser Stripe" avec data-confirm

Index enrichi avec customersSynced / customersNotSynced

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-04-04 11:26:53 +02:00
parent b498096af1
commit ac9f7a0314
2 changed files with 93 additions and 1 deletions

View File

@@ -39,12 +39,25 @@ class SyncController extends AbstractController
$webhookSecrets = $secretRepository->findAll();
$customers = $customerRepository->findAll();
$customersSynced = 0;
$customersNotSynced = 0;
foreach ($customers as $c) {
if (null !== $c->getStripeCustomerId() && '' !== $c->getStripeCustomerId()) {
++$customersSynced;
} else {
++$customersNotSynced;
}
}
return $this->render('admin/sync/index.html.twig', [
'totalCustomers' => $customerRepository->count([]),
'totalCustomers' => \count($customers),
'totalRevendeurs' => $revendeurRepository->count([]),
'totalPrices' => \count($prices),
'stripeSynced' => $stripeSynced,
'stripeNotSynced' => $stripeNotSynced,
'customersSynced' => $customersSynced,
'customersNotSynced' => $customersNotSynced,
'webhookSecrets' => $webhookSecrets,
]);
}
@@ -169,6 +182,58 @@ class SyncController extends AbstractController
return $this->redirectToRoute('app_admin_sync_index');
}
/** @codeCoverageIgnore */
#[Route('/stripe/customers', name: 'stripe_customers', methods: ['POST'])]
public function syncStripeCustomers(
CustomerRepository $customerRepository,
EntityManagerInterface $em,
#[Autowire(env: 'STRIPE_SK')] string $stripeSecretKey,
): Response {
if ('' === $stripeSecretKey || 'sk_test_***' === $stripeSecretKey) {
$this->addFlash('error', 'STRIPE_SK non configuree.');
return $this->redirectToRoute('app_admin_sync_index');
}
\Stripe\Stripe::setApiKey($stripeSecretKey);
$customers = $customerRepository->findAll();
$created = 0;
$updated = 0;
$errors = 0;
foreach ($customers as $customer) {
try {
$params = [
'email' => $customer->getEmail(),
'name' => $customer->getRaisonSociale() ?? $customer->getFullName(),
'phone' => $customer->getPhone(),
'metadata' => [
'crm_user_id' => (string) $customer->getUser()->getId(),
'siret' => $customer->getSiret() ?? '',
'code_comptable' => $customer->getCodeComptable() ?? '',
],
];
if (null !== $customer->getStripeCustomerId() && '' !== $customer->getStripeCustomerId()) {
\Stripe\Customer::update($customer->getStripeCustomerId(), $params);
++$updated;
} else {
$stripeCustomer = \Stripe\Customer::create($params);
$customer->setStripeCustomerId($stripeCustomer->id);
++$created;
}
} catch (\Throwable $e) {
$this->addFlash('error', 'Stripe client '.$customer->getEmail().' : '.$e->getMessage());
++$errors;
}
}
$em->flush();
$this->addFlash('success', $created.' client(s) cree(s), '.$updated.' mis a jour dans Stripe'.(0 < $errors ? ', '.$errors.' erreur(s)' : '').'.');
return $this->redirectToRoute('app_admin_sync_index');
}
#[Route('/all', name: 'all', methods: ['POST'])]
public function syncAll(CustomerRepository $customerRepository, RevendeurRepository $revendeurRepository, PriceAutomaticRepository $priceRepository, MeilisearchService $meilisearch): Response
{

View File

@@ -125,6 +125,33 @@
</div>
</div>
{# Sync Stripe Clients #}
<div class="glass p-6">
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<div class="w-12 h-12 bg-cyan-500/20 rounded-lg flex items-center justify-center">
<svg class="w-6 h-6 text-cyan-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
</div>
<div>
<h2 class="font-bold uppercase text-sm">Clients - Stripe</h2>
<p class="text-xs text-gray-500">Cree ou met a jour les clients dans Stripe (email, nom, adresse, SIRET, TVA)</p>
<p class="text-xs mt-0.5">
<span class="text-green-600 font-bold">{{ customersSynced }} sync</span>
{% if customersNotSynced > 0 %}
<span class="text-red-600 font-bold ml-2">{{ customersNotSynced }} non sync</span>
{% endif %}
<span class="text-gray-400 ml-1">/ {{ totalCustomers }} total</span>
</p>
</div>
</div>
<form method="post" action="{{ path('app_admin_sync_stripe_customers') }}" data-confirm="Synchroniser tous les clients avec Stripe ?">
<button type="submit" class="px-4 py-2 btn-glass text-cyan-600 font-bold uppercase text-[10px] tracking-wider">
Synchroniser Stripe
</button>
</form>
</div>
</div>
{# Sync Stripe Webhooks #}
<div class="glass p-6">
<div class="flex items-center justify-between flex-wrap gap-4">