src/Services/StripePaymentService.php line 273

  1. <?php
  2. namespace App\Services;
  3. use Stripe\Stripe;
  4. use Stripe\PaymentIntent;
  5. use Stripe\Customer;
  6. use Stripe\Webhook;
  7. use Stripe\Exception\SignatureVerificationException;
  8. use Psr\Log\LoggerInterface;
  9. use Symfony\Component\HttpFoundation\Request;
  10. use Stripe\Checkout\Session as StripeSession;
  11. class StripePaymentService
  12. {
  13.     private LoggerInterface $logger;
  14.     private string $stripeSecretKey;
  15.     private string $webhookSecret;
  16.     public function __construct(
  17.         LoggerInterface $logger,
  18.         string $stripeSecretKey,
  19.         string $webhookSecret
  20.     ) {
  21.         $this->logger = $logger;
  22.         $this->stripeSecretKey = $stripeSecretKey;
  23.         $this->webhookSecret = $webhookSecret;
  24.         
  25.         Stripe::setApiKey($this->stripeSecretKey);
  26.     }
  27.     /**
  28.      * Create a payment intent
  29.      */
  30.     public function createPaymentIntent(
  31.         int $amount,
  32.         string $currency = 'usd',
  33.         ?string $customerId = null,
  34.         array $metadata = []
  35.     ): PaymentIntent {
  36.         try {
  37.             $params = [
  38.                 'amount' => $amount, // Amount in cents
  39.                 'currency' => $currency,
  40.                 'automatic_payment_methods' => [
  41.                     'enabled' => true,
  42.                 ],
  43.                 'metadata' => $metadata,
  44.             ];
  45.             if ($customerId) {
  46.                 $params['customer'] = $customerId;
  47.             }
  48.             $paymentIntent = PaymentIntent::create($params);
  49.             
  50.             $this->logger->info('Payment intent created', [
  51.                 'payment_intent_id' => $paymentIntent->id,
  52.                 'amount' => $amount,
  53.                 'currency' => $currency,
  54.                 'customer_id' => $customerId
  55.             ]);
  56.             return $paymentIntent;
  57.         } catch (\Exception $e) {
  58.             $this->logger->error('Failed to create payment intent', [
  59.                 'error' => $e->getMessage(),
  60.                 'amount' => $amount,
  61.                 'currency' => $currency
  62.             ]);
  63.             throw $e;
  64.         }
  65.     }
  66.     /**
  67.      * Confirm a payment intent
  68.      */
  69.     public function confirmPaymentIntent(string $paymentIntentId, string $paymentMethodId): PaymentIntent
  70.     {
  71.         try {
  72.             $paymentIntent = PaymentIntent::retrieve($paymentIntentId);
  73.             $paymentIntent = $paymentIntent->confirm([
  74.                 'payment_method' => $paymentMethodId,
  75.             ]);
  76.             $this->logger->info('Payment intent confirmed', [
  77.                 'payment_intent_id' => $paymentIntentId,
  78.                 'status' => $paymentIntent->status
  79.             ]);
  80.             return $paymentIntent;
  81.         } catch (\Exception $e) {
  82.             $this->logger->error('Failed to confirm payment intent', [
  83.                 'payment_intent_id' => $paymentIntentId,
  84.                 'error' => $e->getMessage()
  85.             ]);
  86.             throw $e;
  87.         }
  88.     }
  89.     /**
  90.      * Create or retrieve a Stripe customer
  91.      */
  92.     public function createCustomer(string $email, string $name = '', array $metadata = []): Customer
  93.     {
  94.         try {
  95.             $customer = Customer::create([
  96.                 'email' => $email,
  97.                 'name' => $name,
  98.                 'metadata' => $metadata,
  99.             ]);
  100.             $this->logger->info('Customer created', [
  101.                 'customer_id' => $customer->id,
  102.                 'email' => $email
  103.             ]);
  104.             return $customer;
  105.         } catch (\Exception $e) {
  106.             $this->logger->error('Failed to create customer', [
  107.                 'email' => $email,
  108.                 'error' => $e->getMessage()
  109.             ]);
  110.             throw $e;
  111.         }
  112.     }
  113.     /**
  114.      * Retrieve a payment intent
  115.      */
  116.     public function getPaymentIntent(string $paymentIntentId): PaymentIntent
  117.     {
  118.         try {
  119.             return PaymentIntent::retrieve($paymentIntentId);
  120.         } catch (\Exception $e) {
  121.             $this->logger->error('Failed to retrieve payment intent', [
  122.                 'payment_intent_id' => $paymentIntentId,
  123.                 'error' => $e->getMessage()
  124.             ]);
  125.             throw $e;
  126.         }
  127.     }
  128.     /**
  129.      * Cancel a payment intent
  130.      */
  131.     public function cancelPaymentIntent(string $paymentIntentId): PaymentIntent
  132.     {
  133.         try {
  134.             $paymentIntent = PaymentIntent::retrieve($paymentIntentId);
  135.             $paymentIntent = $paymentIntent->cancel();
  136.             $this->logger->info('Payment intent cancelled', [
  137.                 'payment_intent_id' => $paymentIntentId
  138.             ]);
  139.             return $paymentIntent;
  140.         } catch (\Exception $e) {
  141.             $this->logger->error('Failed to cancel payment intent', [
  142.                 'payment_intent_id' => $paymentIntentId,
  143.                 'error' => $e->getMessage()
  144.             ]);
  145.             throw $e;
  146.         }
  147.     }
  148.     /**
  149.      * Handle Stripe webhooks
  150.      */
  151.     public function handleWebhook(Request $request): array
  152.     {
  153.         $payload = $request->getContent();
  154.         $sigHeader = $request->headers->get('stripe-signature');
  155.         try {
  156.             $event = Webhook::constructEvent($payload, $sigHeader, $this->webhookSecret);
  157.         } catch (\UnexpectedValueException $e) {
  158.             $this->logger->error('Invalid webhook payload', ['error' => $e->getMessage()]);
  159.             throw $e;
  160.         } catch (SignatureVerificationException $e) {
  161.             $this->logger->error('Invalid webhook signature', ['error' => $e->getMessage()]);
  162.             throw $e;
  163.         }
  164.         $this->logger->info('Webhook received', [
  165.             'event_type' => $event['type'],
  166.             'event_id' => $event['id']
  167.         ]);
  168.         // Handle the event
  169.         switch ($event['type']) {
  170.             case 'payment_intent.succeeded':
  171.                 $this->handlePaymentSucceeded($event['data']['object']);
  172.                 break;
  173.             case 'payment_intent.payment_failed':
  174.                 $this->handlePaymentFailed($event['data']['object']);
  175.                 break;
  176.             case 'payment_intent.canceled':
  177.                 $this->handlePaymentCanceled($event['data']['object']);
  178.                 break;
  179.             default:
  180.                 $this->logger->info('Unhandled webhook event type', ['event_type' => $event['type']]);
  181.         }
  182.         return [
  183.             'event_type' => $event['type'],
  184.             'event_id' => $event['id'],
  185.             'processed' => true
  186.         ];
  187.     }
  188.     /**
  189.      * Handle successful payment
  190.      */
  191.     private function handlePaymentSucceeded($paymentIntent): void
  192.     {
  193.         $this->logger->info('Payment succeeded', [
  194.             'payment_intent_id' => $paymentIntent['id'],
  195.             'amount' => $paymentIntent['amount'],
  196.             'currency' => $paymentIntent['currency']
  197.         ]);
  198.         // Add your business logic here
  199.         // For example: update order status, send confirmation email, etc.
  200.     }
  201.     /**
  202.      * Handle failed payment
  203.      */
  204.     private function handlePaymentFailed($paymentIntent): void
  205.     {
  206.         $this->logger->warning('Payment failed', [
  207.             'payment_intent_id' => $paymentIntent['id'],
  208.             'amount' => $paymentIntent['amount'],
  209.             'currency' => $paymentIntent['currency'],
  210.             'last_payment_error' => $paymentIntent['last_payment_error'] ?? null
  211.         ]);
  212.         // Add your business logic here
  213.         // For example: notify user, update order status, etc.
  214.     }
  215.     /**
  216.      * Handle canceled payment
  217.      */
  218.     private function handlePaymentCanceled($paymentIntent): void
  219.     {
  220.         $this->logger->info('Payment canceled', [
  221.             'payment_intent_id' => $paymentIntent['id'],
  222.             'amount' => $paymentIntent['amount'],
  223.             'currency' => $paymentIntent['currency']
  224.         ]);
  225.         // Add your business logic here
  226.     }
  227.     /**
  228.      * Convert amount to cents (Stripe expects amounts in cents)
  229.      */
  230.     public function convertToCents(float $amount): int
  231.     {
  232.         return (int) round($amount * 100);
  233.     }
  234.     /**
  235.      * Convert amount from cents to dollars
  236.      */
  237.     public function convertFromCents(int $amount): float
  238.     {
  239.         return $amount / 100;
  240.     }
  241.     public function createCheckoutSession(
  242.         int $amount,
  243.         string $currency = 'usd',
  244.         string $successUrl,
  245.         string $cancelUrl,
  246.         array $metadata = []
  247.     ): array {
  248.         try {
  249.             $session = StripeSession::create([
  250.                 'payment_method_types' => ['card'],
  251.                 'line_items' => [[
  252.                     'price_data' => [
  253.                         'currency' => $currency,
  254.                         'product_data' => [
  255.                             'name' => 'Paiement',
  256.                         ],
  257.                         'unit_amount' => $amount,
  258.                     ],
  259.                     'quantity' => 1,
  260.                 ]],
  261.                 'mode' => 'payment',
  262.                 'success_url' => $successUrl,
  263.                 'cancel_url' => $cancelUrl,
  264.                 'metadata' => $metadata,
  265.             ]);
  266.             $this->logger->info('Checkout session créée', [
  267.                 'session_id' => $session->id,
  268.                 'url' => $session->url,
  269.             ]);
  270.         return [  'url' =>  $session->url,
  271.             'session_id' => $session->id,
  272.         ];
  273.         } catch (\Exception $e) {
  274.             $this->logger->error('Erreur création session checkout', [
  275.                 'error' => $e->getMessage(),
  276.             ]);
  277.             throw $e;
  278.         }
  279.     }
  280. }