Cette intégration permet aux amicales de configurer leurs comptes Stripe Express pour accepter les paiements par carte bancaire avec 0% de commission plateforme. ## 🎯 Fonctionnalités implémentées ### API PHP (Backend) - **POST /api/stripe/accounts**: Création comptes Stripe Express - **GET /api/stripe/accounts/:id/status**: Vérification statut compte - **POST /api/stripe/accounts/:id/onboarding-link**: Liens onboarding - **POST /api/stripe/locations**: Création locations Terminal - **POST /api/stripe/terminal/connection-token**: Tokens connexion - **POST /api/stripe/webhook**: Réception événements Stripe ### Interface Flutter (Frontend) - Widget configuration Stripe dans amicale_form.dart - Service StripeConnectService pour communication API - États visuels dynamiques avec codes couleur - Messages utilisateur "100% des paiements pour votre amicale" ## 🔧 Corrections techniques ### StripeController.php - Fix Database::getInstance() → $this->db - Fix $db->prepare() → $this->db->prepare() - Suppression colonne details_submitted inexistante - Ajout exit après réponses JSON (évite 502) ### StripeService.php - Ajout imports Stripe SDK (use Stripe\Account) - Fix Account::retrieve() → $this->stripe->accounts->retrieve() - **CRUCIAL**: Déchiffrement données encrypted_email/encrypted_name - Suppression calcul commission (0% plateforme) ### Router.php - Suppression logs debug excessifs (fix nginx 502 "header too big") ### AppConfig.php - application_fee_percent: 0 (était 2.5) - application_fee_minimum: 0 (était 50) - **POLITIQUE**: 100% des paiements vers amicales ## ✅ Tests validés - Compte pilote créé: acct_1S2YfNP63A07c33Y - Location Terminal: tml_GLJ21w7KCYX4Wj - Onboarding Stripe complété avec succès - Toutes les APIs retournent 200 OK ## 📚 Documentation - Plannings mis à jour avec accomplissements - Architecture technique documentée - Erreurs résolues listées avec solutions ## 🚀 Prêt pour production V1 Stripe Connect opérationnelle - Prochaine étape: Terminal Payments V2 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
435 lines
14 KiB
PHP
Executable File
435 lines
14 KiB
PHP
Executable File
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Configuration de l'application Geosector
|
|
*
|
|
* Ce fichier contient la configuration de l'application Geosector pour les trois environnements :
|
|
* - Production (app.geosector.fr)
|
|
* - Recette (rapp.geosector.fr)
|
|
* - Développement (dapp.geosector.fr)
|
|
*
|
|
* Il inclut les paramètres de base de données, les informations SMTP,
|
|
* les clés de chiffrement et les configurations des services externes (Mapbox, Stripe, SMS OVH).
|
|
*/
|
|
class AppConfig {
|
|
private static ?self $instance = null;
|
|
private array $headers;
|
|
private array $config;
|
|
private string $currentHost;
|
|
private string $clientIp;
|
|
|
|
private function __construct() {
|
|
// Récupération du host directement depuis SERVER_NAME ou HTTP_HOST
|
|
$this->currentHost = $_SERVER['SERVER_NAME'] ?? $_SERVER['HTTP_HOST'] ?? '';
|
|
|
|
// Récupérer les autres en-têtes pour une utilisation ultérieure si nécessaire
|
|
// getallheaders() n'existe pas en CLI, donc on vérifie
|
|
$this->headers = function_exists('getallheaders') ? getallheaders() : [];
|
|
|
|
// Déterminer l'adresse IP du client
|
|
$this->clientIp = $this->getClientIpAddress();
|
|
|
|
$this->initConfig();
|
|
$this->validateApp();
|
|
}
|
|
|
|
public static function getInstance(): self {
|
|
if (self::$instance === null) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
private function initConfig(): void {
|
|
// Configuration de base commune à tous les environnements
|
|
$baseConfig = [
|
|
'name' => 'geosector',
|
|
'encryption_key' => 'Qga2M8Ov6tyx2fIQRWHQ1U6oMK/bAFdTL7A8VRtiDhk=',
|
|
'smtp' => [
|
|
'host' => 'barbotte.o2switch.net',
|
|
'auth' => true,
|
|
'user' => 'noreply@geosector.fr',
|
|
'pass' => '@G83^[OMSo^Q',
|
|
'secure' => 'ssl',
|
|
'port' => 465,
|
|
],
|
|
'email' => [
|
|
'from' => 'noreply@geosector.fr',
|
|
'contact' => 'contact@geosector.fr',
|
|
'hourly_limit' => 1500, // Limite de 1500 emails/heure comme mentionné dans le cahier des charges
|
|
],
|
|
'mapbox' => [
|
|
'api_key' => '', // À remplir avec la clé API Mapbox
|
|
],
|
|
'stripe' => [
|
|
'public_key_test' => 'pk_test_51QwoVN00pblGEgsXkf8qlXmLGEpxDQcG0KLRpjrGLjJHd7AVZ4Iwd6ChgdjO0w0n3vRqwNCEW8KnHUe5eh3uIlkV00k07kCBmd', // À remplacer par votre clé publique TEST
|
|
'secret_key_test' => 'sk_test_51QwoVN00pblGEgsXnvqi8qfYpzHtesWWclvK3lzQjPNoHY0dIyOpJmxIkoLqsbmRMEUZpKS5MQ7iFDRlSqVyTo9c006yWetbsd', // À remplacer par votre clé secrète TEST
|
|
'public_key_live' => 'pk_live_XXXXXXXXXXXX', // À remplacer par votre clé publique LIVE
|
|
'secret_key_live' => 'sk_live_XXXXXXXXXXXX', // À remplacer par votre clé secrète LIVE
|
|
'webhook_secret_test' => 'whsec_test_XXXXXXXXXXXX', // À remplacer après création webhook TEST
|
|
'webhook_secret_live' => 'whsec_live_XXXXXXXXXXXX', // À remplacer après création webhook LIVE
|
|
'api_version' => '2024-06-20',
|
|
'application_fee_percent' => 0, // Pas de commission plateforme
|
|
'application_fee_minimum' => 0, // Pas de commission minimum
|
|
'mode' => 'test', // 'test' ou 'live'
|
|
],
|
|
'sms' => [
|
|
'provider' => 'ovh', // Comme mentionné dans le cahier des charges
|
|
'api_key' => '', // À remplir avec la clé API SMS OVH
|
|
'api_secret' => '', // À remplir avec le secret API SMS OVH
|
|
],
|
|
'backup' => [
|
|
'encryption_key' => 'K8mN2pQ5rT9wX3zA6bE1fH4jL7oS0vY2', // Clé de 32 caractères pour AES-256
|
|
'compression' => true,
|
|
'compression_level' => 6,
|
|
'cipher' => 'AES-256-CBC'
|
|
],
|
|
];
|
|
|
|
// Configuration PRODUCTION
|
|
$this->config['app.geosector.fr'] = array_merge($baseConfig, [
|
|
'env' => 'production',
|
|
'database' => [
|
|
'host' => 'localhost',
|
|
'name' => 'geo_app',
|
|
'username' => 'geo_app_user_prod',
|
|
'password' => 'QO:96-SrHJ6k7-df*?k{4W6m',
|
|
],
|
|
'addresses_database' => [
|
|
'host' => '13.23.33.26',
|
|
'name' => 'adresses',
|
|
'username' => 'adr_geo_user',
|
|
'password' => 'd66,AdrGeo.User',
|
|
],
|
|
]);
|
|
|
|
// Configuration RECETTE
|
|
$this->config['rapp.geosector.fr'] = array_merge($baseConfig, [
|
|
'env' => 'recette',
|
|
'database' => [
|
|
'host' => 'localhost',
|
|
'name' => 'geo_app',
|
|
'username' => 'geo_app_user_rec',
|
|
'password' => 'QO:96df*?k-dS3KiO-{4W6m',
|
|
],
|
|
'addresses_database' => [
|
|
'host' => '13.23.33.36',
|
|
'name' => 'adresses',
|
|
'username' => 'adr_geo_user',
|
|
'password' => 'd66,AdrGeoRec.User',
|
|
],
|
|
// Vous pouvez remplacer d'autres paramètres spécifiques à l'environnement de recette ici
|
|
]);
|
|
|
|
// Configuration DÉVELOPPEMENT
|
|
$this->config['dapp.geosector.fr'] = array_merge($baseConfig, [
|
|
'env' => 'development',
|
|
'database' => [
|
|
'host' => 'localhost',
|
|
'name' => 'geo_app',
|
|
'username' => 'geo_app_user_dev',
|
|
'password' => '34GOz-X5gJu-oH@Fa3$#Z',
|
|
],
|
|
'addresses_database' => [
|
|
'host' => '13.23.33.46',
|
|
'name' => 'adresses',
|
|
'username' => 'adr_geo_user',
|
|
'password' => 'd66,AdrGeoDev.User',
|
|
],
|
|
// Vous pouvez activer des fonctionnalités de débogage en développement
|
|
'debug' => true,
|
|
]);
|
|
}
|
|
|
|
private function validateApp(): void {
|
|
// Si l'hôte est vide, utiliser une solution de secours (développement par défaut)
|
|
if (empty($this->currentHost)) {
|
|
// Journaliser cette situation anormale
|
|
error_log("WARNING: No host detected, falling back to development environment");
|
|
$this->currentHost = 'dapp.geosector.fr';
|
|
}
|
|
|
|
// Si l'hôte n'existe pas dans la configuration, tenter une correction
|
|
if (!isset($this->config[$this->currentHost])) {
|
|
// Essayer de faire correspondre avec l'un des hôtes connus
|
|
$knownHosts = array_keys($this->config);
|
|
foreach ($knownHosts as $host) {
|
|
if (strpos($this->currentHost, str_replace(['app.', 'rapp.', 'dapp.'], '', $host)) !== false) {
|
|
// Correspondance trouvée, utiliser cette configuration
|
|
$this->currentHost = $host;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Si toujours pas de correspondance, utiliser l'environnement de développement par défaut
|
|
if (!isset($this->config[$this->currentHost])) {
|
|
error_log("WARNING: Unknown host '{$this->currentHost}', falling back to development environment");
|
|
$this->currentHost = 'dapp.geosector.fr';
|
|
}
|
|
}
|
|
|
|
// Journaliser l'environnement détecté
|
|
$environment = $this->config[$this->currentHost]['env'] ?? 'unknown';
|
|
// error_log("INFO: Environment detected: {$environment} (Host: {$this->currentHost}, IP: {$this->clientIp})");
|
|
}
|
|
|
|
/**
|
|
* Retourne le type de client (web, mobile, etc.)
|
|
*
|
|
* @return string Le type de client ou 'unknown' si non défini
|
|
*/
|
|
public function getClientType(): string {
|
|
return $this->headers['X-Client-Type'] ?? $_SERVER['HTTP_X_CLIENT_TYPE'] ?? 'unknown';
|
|
}
|
|
|
|
/**
|
|
* Retourne l'identifiant de l'application basé sur l'hôte
|
|
*
|
|
* @return string L'identifiant de l'application (app.geosector.fr, rapp.geosector.fr, dapp.geosector.fr)
|
|
*/
|
|
public function getAppIdentifier(): string {
|
|
return $this->currentHost;
|
|
}
|
|
|
|
/**
|
|
* Retourne l'environnement actuel (production, recette, development)
|
|
*
|
|
* @return string L'environnement actuel
|
|
*/
|
|
public function getEnvironment(): string {
|
|
return $this->getCurrentConfig()['env'] ?? 'production';
|
|
}
|
|
|
|
/**
|
|
* Vérifie si l'application est en mode développement
|
|
*
|
|
* @return bool True si l'application est en mode développement
|
|
*/
|
|
public function isDevelopment(): bool {
|
|
return $this->getEnvironment() === 'development';
|
|
}
|
|
|
|
/**
|
|
* Vérifie si l'application est en mode recette
|
|
*
|
|
* @return bool True si l'application est en mode recette
|
|
*/
|
|
public function isRecette(): bool {
|
|
return $this->getEnvironment() === 'recette';
|
|
}
|
|
|
|
/**
|
|
* Vérifie si l'application est en mode production
|
|
*
|
|
* @return bool True si l'application est en mode production
|
|
*/
|
|
public function isProduction(): bool {
|
|
return $this->getEnvironment() === 'production';
|
|
}
|
|
|
|
/**
|
|
* Retourne la configuration complète de l'environnement actuel
|
|
*
|
|
* @return array Configuration de l'environnement
|
|
*/
|
|
public function getCurrentConfig(): array {
|
|
return $this->config[$this->currentHost];
|
|
}
|
|
|
|
/**
|
|
* Retourne le nom de l'application
|
|
*
|
|
* @return string Nom de l'application (geosector)
|
|
*/
|
|
public function getName(): string {
|
|
return $this->getCurrentConfig()['name'];
|
|
}
|
|
|
|
/**
|
|
* Retourne la configuration de la base de données
|
|
*
|
|
* @return array Configuration de la base de données
|
|
*/
|
|
public function getDatabaseConfig(): array {
|
|
return $this->getCurrentConfig()['database'];
|
|
}
|
|
|
|
/**
|
|
* Retourne la configuration de la base de données des adresses
|
|
*
|
|
* @return array Configuration de la base de données des adresses
|
|
*/
|
|
public function getAddressesDatabaseConfig(): array {
|
|
return $this->getCurrentConfig()['addresses_database'];
|
|
}
|
|
|
|
/**
|
|
* Retourne la clé de chiffrement
|
|
*
|
|
* @return string Clé de chiffrement
|
|
*/
|
|
public function getEncryptionKey(): string {
|
|
return $this->getCurrentConfig()['encryption_key'];
|
|
}
|
|
|
|
/**
|
|
* Retourne la configuration SMTP
|
|
*
|
|
* @return array Configuration SMTP
|
|
*/
|
|
public function getSmtpConfig(): array {
|
|
return $this->getCurrentConfig()['smtp'];
|
|
}
|
|
|
|
/**
|
|
* Retourne la configuration email
|
|
*
|
|
* @return array Configuration email
|
|
*/
|
|
public function getEmailConfig(): array {
|
|
return $this->getCurrentConfig()['email'];
|
|
}
|
|
|
|
/**
|
|
* Retourne la configuration Mapbox
|
|
*
|
|
* @return array Configuration Mapbox
|
|
*/
|
|
public function getMapboxConfig(): array {
|
|
return $this->getCurrentConfig()['mapbox'];
|
|
}
|
|
|
|
/**
|
|
* Retourne la configuration Stripe
|
|
*
|
|
* @return array Configuration Stripe
|
|
*/
|
|
public function getStripeConfig(): array {
|
|
return $this->getCurrentConfig()['stripe'];
|
|
}
|
|
|
|
/**
|
|
* Retourne la configuration SMS
|
|
*
|
|
* @return array Configuration SMS
|
|
*/
|
|
public function getSmsConfig(): array {
|
|
return $this->getCurrentConfig()['sms'];
|
|
}
|
|
|
|
/**
|
|
* Retourne si le mode debug est activé
|
|
*
|
|
* @return bool True si le mode debug est activé
|
|
*/
|
|
public function isDebugEnabled(): bool {
|
|
return $this->getCurrentConfig()['debug'] ?? false;
|
|
}
|
|
|
|
/**
|
|
* Retourne la liste des origines autorisées (domaines)
|
|
*
|
|
* @return array Liste des origines autorisées
|
|
*/
|
|
public function getAllowedOrigins(): array {
|
|
return array_keys($this->config);
|
|
}
|
|
|
|
/**
|
|
* Vérifie si le client est d'un type spécifique
|
|
*
|
|
* @param string $type Type de client à vérifier
|
|
* @return bool True si le client est du type spécifié
|
|
*/
|
|
public function isClientType(string $type): bool {
|
|
return $this->getClientType() === $type;
|
|
}
|
|
|
|
/**
|
|
* Retourne la configuration complète pour l'utilisation externe
|
|
*
|
|
* @return array Configuration complète
|
|
*/
|
|
public function getFullConfig(): array {
|
|
return [
|
|
'environment' => $this->getEnvironment(),
|
|
'database' => $this->getDatabaseConfig(),
|
|
'api' => [
|
|
'allowed_origins' => $this->getAllowedOrigins(),
|
|
'current_site' => $this->getName(),
|
|
],
|
|
'debug' => $this->isDebugEnabled()
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Retourne l'adresse IP du client
|
|
*
|
|
* @return string L'adresse IP du client
|
|
*/
|
|
public function getClientIp(): string {
|
|
return $this->clientIp;
|
|
}
|
|
|
|
/**
|
|
* Retourne la configuration des backups
|
|
*
|
|
* @return array Configuration des backups
|
|
*/
|
|
public function getBackupConfig(): array {
|
|
return $this->getCurrentConfig()['backup'];
|
|
}
|
|
|
|
/**
|
|
* Retourne la clé de chiffrement des backups
|
|
*
|
|
* @return string Clé de chiffrement des backups
|
|
*/
|
|
public function getBackupEncryptionKey(): string {
|
|
return $this->getCurrentConfig()['backup']['encryption_key'];
|
|
}
|
|
|
|
/**
|
|
* Détermine l'adresse IP du client en tenant compte des proxys et load balancers
|
|
*
|
|
* @return string L'adresse IP du client
|
|
*/
|
|
private function getClientIpAddress(): string {
|
|
// Vérifier les en-têtes courants pour l'IP client
|
|
$ipSources = [
|
|
'HTTP_X_REAL_IP', // Nginx proxy
|
|
'HTTP_CLIENT_IP', // Proxy partagé
|
|
'HTTP_X_FORWARDED_FOR', // Proxy ou load balancer courant
|
|
'HTTP_X_FORWARDED', // Proxy générique
|
|
'HTTP_X_CLUSTER_CLIENT_IP', // Reverse proxy
|
|
'HTTP_FORWARDED_FOR', // Proxies précédents
|
|
'HTTP_FORWARDED', // Format standardisé (RFC 7239)
|
|
'REMOTE_ADDR', // Fallback direct
|
|
];
|
|
|
|
foreach ($ipSources as $source) {
|
|
if (!empty($_SERVER[$source])) {
|
|
// Pour des en-têtes comme X-Forwarded-For qui peuvent contenir plusieurs IPs séparées par des virgules
|
|
// (format: "client, proxy1, proxy2")
|
|
if ($source === 'HTTP_X_FORWARDED_FOR' || $source === 'HTTP_FORWARDED_FOR') {
|
|
$ips = explode(',', $_SERVER[$source]);
|
|
$clientIp = trim($ips[0]); // Prendre la première adresse (client original)
|
|
} else {
|
|
$clientIp = $_SERVER[$source];
|
|
}
|
|
|
|
// Valider l'IP pour éviter les injections
|
|
$filteredIp = filter_var($clientIp, FILTER_VALIDATE_IP);
|
|
if ($filteredIp !== false) {
|
|
return $filteredIp;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Si aucune adresse IP valide n'est trouvée, retourner une valeur par défaut
|
|
return '0.0.0.0';
|
|
}
|
|
}
|