<?php
namespace App\Service\Subscription;
use Psr\Log\LoggerInterface;
use Stripe\Event;
use App\Entity\Payment\Subscription;
use App\Entity\Factures;
use App\Entity\FailedPaymentNotification;
use App\Entity\Centre;
use App\Entity\Audio;
use Doctrine\ORM\EntityManagerInterface;
use App\Service\PublicFunction;
use App\Service\Subscription\StripeService;
use App\Entity\SpecificSubscription;
use App\Service\Billing\BillingService;
use App\Service\Notification\FailedPaymentNotificationService;
use App\Service\Billing\BillingAtolService;
use Doctrine\Persistence\ManagerRegistry;
use DateTime;
use App\Service\Notification\EmailNotificationService;
use DateTimeZone;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class StripeWebhookService
{
private $logger;
private $entityManager;
private $invoice;
private $invoiceAtol;
private $publicFunction;
private $doctrine;
private $stripe;
private $emailNotificationService;
private $failedNotificationService;
private $parameterBag;
public function __construct(EntityManagerInterface $entityManager,ParameterBagInterface $parameterBag,EmailNotificationService $emailNotificationService,FailedPaymentNotificationService $failedNotificationService,ManagerRegistry $doctrine, LoggerInterface $logger, PublicFunction $publicFunction, BillingService $invoice, BillingAtolService $invoiceAtol, StripeService $stripe)
{
$this->entityManager = $entityManager;
$this->logger = $logger;
$this->publicFunction = $publicFunction;
$this->invoice = $invoice;
$this->invoiceAtol = $invoiceAtol;
$this->doctrine = $doctrine;
$this->stripe = $stripe;
$this->emailNotificationService = $emailNotificationService;
$this->failedNotificationService = $failedNotificationService;
$this->parameterBag = $parameterBag;
}
public function constructEventFromPayload($payload, $sig_header)
{
try {
$event = \Stripe\Event::constructFrom(json_decode($payload, true));
$this->logger->info("Stripe webhook received", ['event' => $event->type]);
return $event;
} catch (\Exception $e) {
$this->logger->error("Error in Stripe webhook", ['message' => $e->getMessage()]);
throw $e;
}
}
public function handleEvent(Event $event)
{
try {
switch ($event->type) {
case 'invoice.paid':
$this->handlePaymentSuccess($event);
break;
case 'invoice.payment_failed':
$this->handlePaymentFailed($event);
break;
/*case 'invoice.payment_succeeded':
$this->handlePaymentSuccess($event);
break;*/
case 'customer.subscription.trial_will_end':
$this->handleTrialWillEnd($event);
break;
case 'customer.subscription.deleted':
$this->handleSubscriptionCancellation($event);
break;
case 'payment_method.attached':
$this->retryInvoiceWithNewPaymentMethod($event);
break;
/*case 'customer.source.created':
$this->retryInvoiceWithNewPaymentMethod($event);
break;*/
}
} catch (\Exception $e) {
$this->logger->error("Error handling Stripe event", [
'event' => $event->type,
'message' => $e->getMessage()
]);
}
}
private function handleTrialWillEnd(Event $event)
{
$subscriptionId = $event->data->object->id;
$customerId = $event->data->object->customer;
$invoiceId = $event->data->object->id;
$subscription = $this->doctrine
->getRepository(Subscription::class)
->findOneBy(['stripeCustomerId' => $customerId]);
// we could place it later in dry function private and also move the subcsription here not in controller center
$centre = $this->doctrine
->getRepository(Centre::class)
->find($subscription->getCentre()->getId());
$audio = $this->doctrine
->getRepository(Audio::class)
->find($subscription->getAudio()->getId());
$user = [];
$user['nomAudioprothesiste'] = $audio->getLastName();
$user['prenomAudioprothesiste'] = $audio->getName();
$user['emailAudioprothesiste'] = $audio->getMail();
$user['telephoneAudioprothesiste'] = $audio->getPhone();
$user['adresseAudioprothesiste'] = $centre->getAddress();
$user['dateInscription'] = $subscription->getCreated()->format('d-m-Y H:i');
$user['dateDebutEssai'] = $subscription->getPlanPeriodStart()->format('d-m-Y H:i');
$user['dateFinEssai'] = $subscription->getPlanPeriodEnd()->format('d-m-Y H:i');
$user['formule'] = $subscription->getPlan()->getSlug();
$user['signDate'] = $centre->getSignupDate()->format('d-m-Y H:i');
$user['typeAbonnement'] = $subscription->getPlanInterval() == "year" ? "Annuel" : "Mensuel";
$user['nbrLicence'] = $subscription->getQuantity();
$user['montant'] = $subscription->getPaidAmount();
$user['urlProfil'] = $centre->getSlug();
$this->logger->info("Stripe webhook trial user", ['event' => $user]);
// we send parametre for the trait methid
$params = $this->emailNotificationService->prepareEmailParams($user);
// send email notifcation
$this->emailNotificationService->sendEmail($params,"myaudio.fr@gmail.com", 'Fin de la période d\'essai gratuit sur My Audio Pro', 140);
$this->emailNotificationService->sendEmail($params,"contact@myaudio.fr", 'Fin de la période d\'essai gratuit sur My Audio Pro', 140);
$this->logger->info("Stripe webhook trial", ['event' => "success"]);
}
private function handlePaymentSuccess(Event $event)
{
$subscriptionId = $event->data->object->id;
$customerId = $event->data->object->customer;
$invoiceId = $event->data->object->id;
$lines = $event->data['object']['lines']['data'];
foreach ($lines as $line) {
if (isset($line['period']['end'])) {
$currentPeriodEnd = (new DateTime())->setTimestamp($line['period']['end']);
}
if (isset($line['period']['start'])) {
$currentPeriodStart = (new DateTime())->setTimestamp($line['period']['start']);
}
}
// Convert timestamps to DateTime objects
// we will create invoice also need to update the facture->paiment to sripe invoiceid
/*invoice.finalized
invoice.created
charge.failed
invoice.paid */
sleep(2);
$subscription = $this->doctrine
->getRepository(Subscription::class)
->findOneBy(['stripeCustomerId' => $customerId]);
$existing = $this->doctrine->getRepository(Factures::class)->findOneBy(['paiement' => $invoiceId]);
if ($existing) {
return;
}
if($subscription && $event->data->object->amount_due !== 0){
$center = $this->doctrine
->getRepository(Centre::class)
->find($subscription->getCentre()->getId());
$specificSubscription = $this->doctrine
->getRepository(SpecificSubscription::class)
->findOneBy(['center' => $center]);
$data = ["quantity" => $subscription->getQuantity(), "period" => $subscription->getPlanInterval(), "plan" => $subscription->getPlan()->getSlug()];
$today = new DateTime();
$planPeriodStart = $subscription->getPlanPeriodStart();
if ($planPeriodStart->format('Y-m-d') !== $today->format('Y-m-d') && $subscription->getPlanInterval() === 'year') {
$this->invoice->createClientUpdatedYearly($center,$data);
$this->logger->info('The plan is yearly, and the start date is different from today.');
}
else {
if($specificSubscription->getContractCategory()->getId() == 7){
$data = ["quantity" => $subscription->getQuantity(), "period" => "atol", "plan" => $subscription->getPlan()->getSlug()];
$this->invoiceAtol->createClient($center,$data);
} else{
$this->invoice->createClient($center,$data);
}
}
$subscription->setPlanPeriodStart($currentPeriodStart);
$subscription->setPlanPeriodEnd($currentPeriodEnd);
$this->entityManager->persist($subscription);
$invoiceBilling = $this->doctrine
->getRepository(Factures::class)
->findOneBy(
['centre' => $subscription->getCentre()->getId()],
['id' => 'DESC']
);
$invoiceBilling->setPaiement($invoiceId);
$this->entityManager->persist($invoiceBilling);
$center = $this->doctrine
->getRepository(Centre::class)
->find($subscription->getCentre()->getId());
$center->setIsBlocked(false);
$this->entityManager->persist($center);
$this->entityManager->flush();
$planInterval = $subscription->getPlanInterval();
if ($planInterval === 'year') {
$subscriptionStripe = \Stripe\Subscription::retrieve($subscription->getStripeSubscriptionId());
$isDev = $_ENV['APP_ENV'] === 'dev';
$isCategory7 = $specificSubscription && $specificSubscription->getContractCategory()->getId() == 7;
if ($isDev) {
$newPriceId = $isCategory7
? 'price_1RH6HNG7kx5a2kgk6fRFPNvj'
: 'price_1QY6ZDG7kx5a2kgkwymPYd6H';
} else {
$newPriceId = $isCategory7
? 'price_1RH6LfG7kx5a2kgkecTIQe2W'
: 'price_1QY6dxG7kx5a2kgkC4C0mKbE';
}
$updatedSubscription = \Stripe\Subscription::update(
$subscriptionStripe->id,
[
'items' => [
[
'id' => $subscriptionStripe->items->data[0]->id,
'price' => $newPriceId,
]
],
'proration_behavior' => 'none',
]
);
}
// need here to check if has notification inside failure for the facturation
// if yes : send special email id (231)+udate the table failur and arcihved
$this->sendCenterBillingMail($center);
}
$this->logger->info("Handled payment success", ['event' => $invoiceId]);
}
private function handleSubscriptionCancellation(Event $event)
{
$subscriptionId = $event->data->object->id;
$customerId = $event->data->object->customer;
//customer.subscription.trial_will_end
$this->logger->info("Handled subscription cancellation", [
'subscriptionId' => $subscriptionId,
'customerId' => $customerId,
'event' => $event->jsonSerialize()
]);
}
private function retryInvoiceWithNewPaymentMethod(Event $event) {
$subscriptionId = $event->data->object->id;
$customerId = $event->data->object->customer;
$outstandingInvoices = $this->stripe->getOutstandingInvoices($customerId);
foreach ($outstandingInvoices as $invoice) {
if ($this->stripe->isInvoiceRetryable($invoice)) {
$this->stripe->retryBillingInvoice($invoice->id);
$subscription = $this->doctrine
->getRepository(Subscription::class)
->findOneBy(['stripeCustomerId' => $customerId]);
$center = $this->doctrine
->getRepository(Centre::class)
->find($subscription->getCentre()->getId());
$center->setIsBlocked(false);
$this->entityManager->persist($center);
$this->entityManager->flush();
}
}
//customer.subscription.trial_will_end
$this->logger->info("Handled payment method new created", [
'subscriptionId' => $subscriptionId,
'customerId' => $customerId,
'event' => $event->jsonSerialize()
]);
}
private function handlePaymentFailed(Event $event)
{
$subscriptionId = $event->data->object->id;
$customerId = $event->data->object->customer;
$this->handlePaymentFailureBlocking($event);
$this->logger->info("Handled payment failed", [
'subscriptionId' => $subscriptionId,
'customerId' => $customerId,
'event' => $event->jsonSerialize()
]);
}
public function handlePaymentFailureBlocking($event)
{
$subscriptionId = $event->data->object->id;
$customerId = $event->data->object->customer;
$subscription = $this->doctrine
->getRepository(SpecificSubscription::class)
->findOneBy(['stripeCustomer' => $customerId]);
$center = $this->doctrine
->getRepository(Centre::class)
->find($subscription->getCenter()->getId());
$invoice = $event->data->object;
$paymentIntentId = $invoice->payment_intent;
if ($paymentIntentId) {
$paymentIntent = \Stripe\PaymentIntent::retrieve($paymentIntentId);
if ($paymentIntent->last_payment_error) {
$comment = $paymentIntent->last_payment_error->message;
}
}
if (!$center->getIsBlocked()) {
$this->failedNotificationService->create($center, $comment);
}
}
public function getCustomerById(Event $event)
{
$subscriptionId = $event->data->object->id;
$customerId = $event->data->object->customer;
$subscription = $this->doctrine
->getRepository(Subscription::class)
->findOneBy(['stripeCustomerId' => $customerId]);
return $subscription;
}
private function payInvoice($invoiceId) {
\Stripe\Stripe::setApiKey('your_stripe_secret_key');
try {
$invoice = \Stripe\Invoice::retrieve($invoiceId);
if ($invoice->status === 'draft') {
$invoice->finalizeInvoice();
}
if ($invoice->status === 'past_due') {
$paidInvoice = $invoice->pay(['expand' => ['payment_intent']]);
} else if ($invoice->status === 'open') {
$paidInvoice = $invoice->pay(['expand' => ['payment_intent']]);
} else {
//..
}
return $paidInvoice;
} catch (\Exception $e) {
// Handle exceptions, possibly log them
$this->logger->error("Error paying invoice", ['message' => $e->getMessage()]);
return null;
}
}
private function sendCenterBillingMail(Centre $center) : void
{
$invoice = $this->entityManager->getRepository(Factures::class)->findOneBy(['centre' => $center]);
$projectDir = $this->parameterBag->get('kernel.project_dir');
$pdfFilePath = $projectDir . '/public/assets/partner/facture' . $invoice->getToken() . '.pdf';
$this->failedNotificationService->unblockCenterAndArchive($center);
$params = [
"nom" => "{$center->getIdGerant()->getLastName()}",
"lien" =>"{$_ENV['BASE_logiciel']}",
];
$this->publicFunction->sendEmailWithAttachment($params,$center->getIdGerant()->getMail(),$center->getIdGerant()->getLastName(), "Vos factures sont disponibles", 191,$pdfFilePath);
$logger->info('Centre found', ['centre' => $center->getId()]);
}
}