Fix: Corriger le type PDO dans StripeService et retirer getConnection()
This commit is contained in:
446
api/src/Services/StripeService.php
Normal file
446
api/src/Services/StripeService.php
Normal file
@@ -0,0 +1,446 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Stripe\Stripe;
|
||||
use Stripe\StripeClient;
|
||||
use Stripe\Exception\ApiErrorException;
|
||||
use AppConfig;
|
||||
use Database;
|
||||
use PDO;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Service principal pour gérer l'intégration Stripe
|
||||
* Gère Stripe Connect, Terminal et les paiements
|
||||
*/
|
||||
class StripeService {
|
||||
private static ?self $instance = null;
|
||||
private StripeClient $stripe;
|
||||
private AppConfig $config;
|
||||
private PDO $db;
|
||||
private bool $testMode;
|
||||
|
||||
private function __construct() {
|
||||
$this->config = AppConfig::getInstance();
|
||||
$this->db = Database::getInstance();
|
||||
|
||||
// Déterminer le mode (test ou live)
|
||||
$stripeConfig = $this->config->get('stripe');
|
||||
$this->testMode = ($stripeConfig['mode'] ?? 'test') === 'test';
|
||||
|
||||
// Initialiser Stripe avec la bonne clé
|
||||
$secretKey = $this->testMode
|
||||
? $stripeConfig['secret_key_test']
|
||||
: $stripeConfig['secret_key_live'];
|
||||
|
||||
if (empty($secretKey) || strpos($secretKey, 'XXXX') !== false) {
|
||||
throw new Exception('Clé Stripe non configurée. Veuillez configurer vos clés dans AppConfig.php');
|
||||
}
|
||||
|
||||
$this->stripe = new StripeClient([
|
||||
'api_key' => $secretKey,
|
||||
'stripe_version' => $stripeConfig['api_version']
|
||||
]);
|
||||
|
||||
// Définir la clé API globalement aussi (pour certaines opérations)
|
||||
Stripe::setApiKey($secretKey);
|
||||
Stripe::setApiVersion($stripeConfig['api_version']);
|
||||
}
|
||||
|
||||
public static function getInstance(): self {
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Créer un compte Stripe Connect Express pour une amicale
|
||||
*/
|
||||
public function createConnectAccount(int $entiteId): array {
|
||||
try {
|
||||
// Récupérer les infos de l'entité
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT * FROM entites WHERE id = :id"
|
||||
);
|
||||
$stmt->execute(['id' => $entiteId]);
|
||||
$entite = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$entite) {
|
||||
throw new Exception("Entité non trouvée");
|
||||
}
|
||||
|
||||
// Vérifier si un compte existe déjà
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT * FROM stripe_accounts WHERE fk_entite = :fk_entite"
|
||||
);
|
||||
$stmt->execute(['fk_entite' => $entiteId]);
|
||||
$existingAccount = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($existingAccount) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Un compte Stripe existe déjà pour cette entité',
|
||||
'account_id' => $existingAccount['stripe_account_id']
|
||||
];
|
||||
}
|
||||
|
||||
// Créer le compte Stripe Connect Express
|
||||
$account = $this->stripe->accounts->create([
|
||||
'type' => 'express',
|
||||
'country' => 'FR',
|
||||
'email' => $entite['email'] ?? null,
|
||||
'capabilities' => [
|
||||
'card_payments' => ['requested' => true],
|
||||
'transfers' => ['requested' => true],
|
||||
],
|
||||
'business_type' => 'non_profit', // Association
|
||||
'business_profile' => [
|
||||
'name' => $entite['nom'],
|
||||
'product_description' => 'Vente de calendriers des pompiers',
|
||||
'support_email' => $entite['email'] ?? null,
|
||||
'url' => $entite['site_web'] ?? null,
|
||||
],
|
||||
'metadata' => [
|
||||
'entite_id' => $entiteId,
|
||||
'entite_name' => $entite['nom']
|
||||
]
|
||||
]);
|
||||
|
||||
// Sauvegarder en base de données
|
||||
$stmt = $this->db->prepare(
|
||||
"INSERT INTO stripe_accounts (fk_entite, stripe_account_id, created_at)
|
||||
VALUES (:fk_entite, :stripe_account_id, NOW())"
|
||||
);
|
||||
$stmt->execute([
|
||||
'fk_entite' => $entiteId,
|
||||
'stripe_account_id' => $account->id
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'account_id' => $account->id,
|
||||
'message' => 'Compte Stripe créé avec succès'
|
||||
];
|
||||
|
||||
} catch (ApiErrorException $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Erreur Stripe: ' . $e->getMessage()
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Erreur: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer les informations d'un compte Stripe Connect
|
||||
*/
|
||||
public function retrieveAccount(string $accountId) {
|
||||
try {
|
||||
return Account::retrieve($accountId);
|
||||
} catch (Exception $e) {
|
||||
Logger::getInstance()->error('Erreur récupération compte Stripe', [
|
||||
'account_id' => $accountId,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Générer un lien d'onboarding pour finaliser la configuration du compte
|
||||
*/
|
||||
public function createOnboardingLink(string $accountId, string $returnUrl, string $refreshUrl): array {
|
||||
try {
|
||||
$accountLink = $this->stripe->accountLinks->create([
|
||||
'account' => $accountId,
|
||||
'refresh_url' => $refreshUrl,
|
||||
'return_url' => $returnUrl,
|
||||
'type' => 'account_onboarding',
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'url' => $accountLink->url
|
||||
];
|
||||
|
||||
} catch (ApiErrorException $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Erreur Stripe: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Créer une Location pour Terminal/Tap to Pay
|
||||
*/
|
||||
public function createLocation(int $entiteId): array {
|
||||
try {
|
||||
// Récupérer le compte Stripe et l'entité
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT sa.*, e.*
|
||||
FROM stripe_accounts sa
|
||||
JOIN entites e ON sa.fk_entite = e.id
|
||||
WHERE sa.fk_entite = :fk_entite"
|
||||
);
|
||||
$stmt->execute(['fk_entite' => $entiteId]);
|
||||
$data = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$data) {
|
||||
throw new Exception("Compte Stripe non trouvé pour cette entité");
|
||||
}
|
||||
|
||||
// Créer la location
|
||||
$location = $this->stripe->terminal->locations->create([
|
||||
'display_name' => $data['nom'],
|
||||
'address' => [
|
||||
'line1' => $data['adresse'] ?? 'Adresse non renseignée',
|
||||
'city' => $data['ville'] ?? 'Ville',
|
||||
'postal_code' => $data['code_postal'] ?? '00000',
|
||||
'country' => 'FR',
|
||||
],
|
||||
'metadata' => [
|
||||
'entite_id' => $entiteId,
|
||||
'type' => 'tap_to_pay'
|
||||
]
|
||||
], [
|
||||
'stripe_account' => $data['stripe_account_id']
|
||||
]);
|
||||
|
||||
// Mettre à jour en base
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE stripe_accounts
|
||||
SET stripe_location_id = :location_id, updated_at = NOW()
|
||||
WHERE fk_entite = :fk_entite"
|
||||
);
|
||||
$stmt->execute([
|
||||
'location_id' => $location->id,
|
||||
'fk_entite' => $entiteId
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'location_id' => $location->id,
|
||||
'message' => 'Location créée avec succès'
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Erreur: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Créer un Connection Token pour Terminal/Tap to Pay
|
||||
*/
|
||||
public function createConnectionToken(int $entiteId): array {
|
||||
try {
|
||||
// Récupérer le compte et la location
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT * FROM stripe_accounts WHERE fk_entite = :fk_entite"
|
||||
);
|
||||
$stmt->execute(['fk_entite' => $entiteId]);
|
||||
$account = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$account || !$account['stripe_location_id']) {
|
||||
throw new Exception("Location Stripe non configurée pour cette entité");
|
||||
}
|
||||
|
||||
// Créer le token
|
||||
$connectionToken = $this->stripe->terminal->connectionTokens->create([
|
||||
'location' => $account['stripe_location_id']
|
||||
], [
|
||||
'stripe_account' => $account['stripe_account_id']
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'secret' => $connectionToken->secret
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Erreur: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Créer une intention de paiement
|
||||
*/
|
||||
public function createPaymentIntent(array $params): array {
|
||||
try {
|
||||
$amount = $params['amount'] ?? 0;
|
||||
$entiteId = $params['fk_entite'] ?? 0;
|
||||
$userId = $params['fk_user'] ?? 0;
|
||||
$metadata = $params['metadata'] ?? [];
|
||||
|
||||
if ($amount < 100) {
|
||||
throw new Exception("Le montant minimum est de 1€");
|
||||
}
|
||||
|
||||
// Récupérer le compte Stripe
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT * FROM stripe_accounts WHERE fk_entite = :fk_entite"
|
||||
);
|
||||
$stmt->execute(['fk_entite' => $entiteId]);
|
||||
$account = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$account) {
|
||||
throw new Exception("Compte Stripe non trouvé");
|
||||
}
|
||||
|
||||
// Calculer la commission (2.5% ou 50 centimes minimum)
|
||||
$stripeConfig = $this->config->get('stripe');
|
||||
$applicationFee = max(
|
||||
$stripeConfig['application_fee_minimum'],
|
||||
round($amount * $stripeConfig['application_fee_percent'] / 100)
|
||||
);
|
||||
|
||||
// Créer le PaymentIntent
|
||||
$paymentIntent = $this->stripe->paymentIntents->create([
|
||||
'amount' => $amount,
|
||||
'currency' => 'eur',
|
||||
'payment_method_types' => ['card_present'],
|
||||
'capture_method' => 'automatic',
|
||||
'application_fee_amount' => $applicationFee,
|
||||
'transfer_data' => [
|
||||
'destination' => $account['stripe_account_id'],
|
||||
],
|
||||
'metadata' => array_merge($metadata, [
|
||||
'entite_id' => $entiteId,
|
||||
'user_id' => $userId,
|
||||
'calendrier_annee' => date('Y'),
|
||||
]),
|
||||
]);
|
||||
|
||||
// Sauvegarder en base
|
||||
$stmt = $this->db->prepare(
|
||||
"INSERT INTO stripe_payment_intents
|
||||
(stripe_payment_intent_id, fk_entite, fk_user, amount, currency, status, application_fee, metadata, created_at)
|
||||
VALUES (:pi_id, :fk_entite, :fk_user, :amount, :currency, :status, :app_fee, :metadata, NOW())"
|
||||
);
|
||||
$stmt->execute([
|
||||
'pi_id' => $paymentIntent->id,
|
||||
'fk_entite' => $entiteId,
|
||||
'fk_user' => $userId,
|
||||
'amount' => $amount,
|
||||
'currency' => 'eur',
|
||||
'status' => $paymentIntent->status,
|
||||
'app_fee' => $applicationFee,
|
||||
'metadata' => json_encode($metadata)
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'client_secret' => $paymentIntent->client_secret,
|
||||
'payment_intent_id' => $paymentIntent->id,
|
||||
'amount' => $amount,
|
||||
'application_fee' => $applicationFee
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Erreur: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier la compatibilité Tap to Pay d'un appareil Android
|
||||
*/
|
||||
public function checkAndroidTapToPayCompatibility(string $manufacturer, string $model): array {
|
||||
try {
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT * FROM stripe_android_certified_devices
|
||||
WHERE manufacturer = :manufacturer
|
||||
AND model = :model
|
||||
AND tap_to_pay_certified = 1
|
||||
AND country = 'FR'"
|
||||
);
|
||||
$stmt->execute([
|
||||
'manufacturer' => $manufacturer,
|
||||
'model' => $model
|
||||
]);
|
||||
|
||||
$device = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($device) {
|
||||
return [
|
||||
'success' => true,
|
||||
'tap_to_pay_supported' => true,
|
||||
'message' => 'Tap to Pay disponible sur cet appareil',
|
||||
'min_android_version' => $device['min_android_version']
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'tap_to_pay_supported' => false,
|
||||
'message' => 'Appareil non certifié pour Tap to Pay en France',
|
||||
'alternative' => 'Utilisez un iPhone XS ou plus récent avec iOS 15.4+'
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Erreur: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer les appareils Android certifiés
|
||||
*/
|
||||
public function getCertifiedAndroidDevices(): array {
|
||||
try {
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT manufacturer, model, model_identifier, min_android_version
|
||||
FROM stripe_android_certified_devices
|
||||
WHERE tap_to_pay_certified = 1 AND country = 'FR'
|
||||
ORDER BY manufacturer, model"
|
||||
);
|
||||
$stmt->execute();
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'devices' => $stmt->fetchAll(PDO::FETCH_ASSOC)
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Erreur: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir le mode actuel (test ou live)
|
||||
*/
|
||||
public function isTestMode(): bool {
|
||||
return $this->testMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir la clé publique pour le frontend
|
||||
*/
|
||||
public function getPublicKey(): string {
|
||||
$stripeConfig = $this->config->get('stripe');
|
||||
return $this->testMode
|
||||
? $stripeConfig['public_key_test']
|
||||
: $stripeConfig['public_key_live'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user