152 lines
4.4 KiB
PHP
152 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\PaymentTransaction;
|
|
use App\Models\User;
|
|
use Stripe\Exception\SignatureVerificationException;
|
|
use Stripe\PaymentIntent;
|
|
use Stripe\Stripe;
|
|
use Stripe\Webhook;
|
|
use UnexpectedValueException;
|
|
|
|
class StripePaymentService
|
|
{
|
|
private bool $configured = false;
|
|
|
|
private function ensureConfigured(): void
|
|
{
|
|
if ($this->configured) {
|
|
return;
|
|
}
|
|
|
|
if (! class_exists(Stripe::class)) {
|
|
throw new \RuntimeException(
|
|
'SDK Stripe manquant. Exécutez : composer require stripe/stripe-php',
|
|
);
|
|
}
|
|
|
|
Stripe::setApiKey(config('services.stripe.secret'));
|
|
$this->configured = true;
|
|
}
|
|
|
|
public function isEnabled(): bool
|
|
{
|
|
return class_exists(Stripe::class)
|
|
&& filled(config('services.stripe.secret'))
|
|
&& filled(config('services.stripe.key'));
|
|
}
|
|
|
|
public function publishableKey(): string
|
|
{
|
|
return (string) config('services.stripe.key');
|
|
}
|
|
|
|
public function createPaymentIntent(
|
|
User $user,
|
|
PaymentTransaction $payment,
|
|
float $amount,
|
|
): PaymentIntent {
|
|
$this->ensureConfigured();
|
|
|
|
return PaymentIntent::create([
|
|
'amount' => (int) round($amount * 100),
|
|
'currency' => strtolower($payment->currency),
|
|
'metadata' => [
|
|
'payment_uuid' => $payment->uuid,
|
|
'user_id' => (string) $user->id,
|
|
'idempotency_key' => $payment->idempotency_key,
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function retrievePaymentIntent(string $paymentIntentId): PaymentIntent
|
|
{
|
|
$this->ensureConfigured();
|
|
|
|
return PaymentIntent::retrieve($paymentIntentId, [
|
|
'expand' => ['payment_method'],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function summarizePaymentIntent(PaymentIntent $intent): array
|
|
{
|
|
$summary = [
|
|
'payment_intent_id' => $intent->id,
|
|
'status' => $intent->status,
|
|
'amount' => $intent->amount / 100,
|
|
'currency' => strtoupper($intent->currency),
|
|
];
|
|
|
|
$paymentMethod = $intent->payment_method;
|
|
if (is_object($paymentMethod)) {
|
|
$summary['payment_method_type'] = $paymentMethod->type ?? null;
|
|
|
|
if (isset($paymentMethod->card)) {
|
|
$summary['card_brand'] = $paymentMethod->card->brand ?? null;
|
|
$summary['card_last4'] = $paymentMethod->card->last4 ?? null;
|
|
$summary['card_exp_month'] = $paymentMethod->card->exp_month ?? null;
|
|
$summary['card_exp_year'] = $paymentMethod->card->exp_year ?? null;
|
|
}
|
|
}
|
|
|
|
return $summary;
|
|
}
|
|
|
|
public function isPaymentSucceeded(PaymentIntent $paymentIntent): bool
|
|
{
|
|
return $paymentIntent->status === 'succeeded';
|
|
}
|
|
|
|
/**
|
|
* @return array{provider_payment_id: string, status: string, payload: array<string, mixed>}
|
|
*/
|
|
public function parseWebhook(string $payload, string $signature): array
|
|
{
|
|
$this->ensureConfigured();
|
|
|
|
$secret = config('services.stripe.webhook_secret');
|
|
|
|
if (blank($secret)) {
|
|
throw new \RuntimeException('Webhook Stripe non configuré.');
|
|
}
|
|
|
|
try {
|
|
$event = Webhook::constructEvent($payload, $signature, $secret);
|
|
} catch (UnexpectedValueException|SignatureVerificationException $e) {
|
|
throw new \InvalidArgumentException('Signature webhook Stripe invalide.', 0, $e);
|
|
}
|
|
|
|
$type = $event->type;
|
|
$object = $event->data->object;
|
|
|
|
if (! in_array($type, [
|
|
'payment_intent.succeeded',
|
|
'payment_intent.payment_failed',
|
|
'payment_intent.canceled',
|
|
], true)) {
|
|
return [
|
|
'provider_payment_id' => $object->id ?? '',
|
|
'status' => 'ignored',
|
|
'payload' => $event->toArray(),
|
|
];
|
|
}
|
|
|
|
$status = match ($type) {
|
|
'payment_intent.succeeded' => 'succeeded',
|
|
'payment_intent.payment_failed' => 'failed',
|
|
'payment_intent.canceled' => 'cancelled',
|
|
default => 'ignored',
|
|
};
|
|
|
|
return [
|
|
'provider_payment_id' => $object->id,
|
|
'status' => $status,
|
|
'payload' => $event->toArray(),
|
|
];
|
|
}
|
|
}
|