setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); } function logErr(Throwable $e): void { $info = ($e instanceof PDOException && isset($e->errorInfo)) ? json_encode($e->errorInfo, JSON_UNESCAPED_UNICODE) : ''; @file_put_contents('/tmp/pay_errors.log', '['.date('Y-m-d H:i:s')."] ".$e->getMessage()." ".$info."\n", FILE_APPEND ); } function colExists(PDO $pdo, string $table, string $col): bool { $sql = "SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ? LIMIT 1"; $st = $pdo->prepare($sql); $st->execute([$table,$col]); return (bool)$st->fetchColumn(); } /** * Retourne la date de fin d’accès si un abonnement est encore actif (manual/auto), * en priorité via billing_subscriptions, sinon via praticien.date_expiration. * * @return array{until:string|null, mode:string|null, status:string|null, sub_id:string|null} */ function activeAccessInfo(PDO $pdo, int $praticien_id): array { $out = ['until'=>null, 'mode'=>null, 'status'=>null, 'sub_id'=>null]; // 1) via billing_subscriptions si dispo if (colExists($pdo, 'billing_subscriptions', 'praticien_id') && colExists($pdo, 'billing_subscriptions', 'current_period_end')) { $sql = "SELECT billing_mode, status, provider_subscription_id, current_period_end FROM billing_subscriptions WHERE praticien_id = ? AND (provider IS NULL OR provider = 'stripe') ORDER BY id DESC LIMIT 1"; $st = $pdo->prepare($sql); $st->execute([$praticien_id]); $row = $st->fetch(PDO::FETCH_ASSOC); if ($row) { $cpe = trim((string)($row['current_period_end'] ?? '')); if ($cpe !== '') { $ts = strtotime($cpe); if ($ts !== false && $ts > time()) { $out['until'] = $cpe; $out['mode'] = (string)($row['billing_mode'] ?? ''); $out['status'] = (string)($row['status'] ?? ''); $out['sub_id'] = (string)($row['provider_subscription_id'] ?? ''); return $out; } } } } // 2) fallback via praticien.date_expiration if (colExists($pdo, 'praticien', 'date_expiration')) { $st = $pdo->prepare("SELECT date_expiration FROM praticien WHERE id = ? LIMIT 1"); $st->execute([$praticien_id]); $de = (string)($st->fetchColumn() ?: ''); if ($de !== '') { $ts = strtotime($de); if ($ts !== false && $ts > time()) { $out['until'] = $de; return $out; } } } return $out; } $err = ''; $msg = ''; // Données nécessaires déjà posées en session avant d’arriver ici $praticien_id = $_SESSION['reference_id'] ?? null; $abonnement_id = $_SESSION['paiement_abonnement_id'] ?? null; $prix = $_SESSION['paiement_prix'] ?? null; // Nouveau: mode de facturation (manual|auto) + provider_plan_id (Stripe price_id pour auto) $billing_mode = (string)($_SESSION['paiement_billing_mode'] ?? 'manual'); $billing_mode = in_array($billing_mode, ['manual','auto'], true) ? $billing_mode : 'manual'; $provider = (string)($_SESSION['paiement_provider'] ?? 'stripe'); $provider_plan = (string)($_SESSION['paiement_provider_plan_id'] ?? ''); // price_id si auto if (!$praticien_id || !$abonnement_id || $prix === null) { $err = $translations['erreur_donnees_paiement'] ?? 'Données de paiement manquantes.'; } // Sécurité: si mode auto mais pas de plan provider → erreur if (!$err && $billing_mode === 'auto' && $provider === 'stripe' && $provider_plan === '') { $err = $translations['erreur_abonnement_auto'] ?? "Configuration manquante pour le renouvellement automatique."; } /* CSRF */ if (empty($_SESSION['csrf_pay'])) { $_SESSION['csrf_pay'] = bin2hex(random_bytes(32)); } $CSRF = $_SESSION['csrf_pay'] ?? ''; if ($_SERVER['REQUEST_METHOD'] === 'POST' && !$err) { try { // Vérif CSRF if (!hash_equals($CSRF, $_POST['csrf'] ?? '')) { throw new RuntimeException('CSRF'); } // Méthode choisie $allowed = ['carte','paypal','virement']; $methode = (string)($_POST['methode'] ?? ''); if (!in_array($methode, $allowed, true)) { throw new InvalidArgumentException('BAD_METHOD'); } // Carte bancaire → AVANT Stripe : bloquer si accès déjà actif if ($methode === 'carte') { $pid = (int)$praticien_id; // Si un accès est encore actif, on bloque toute nouvelle tentative (manual/auto) $active = activeAccessInfo($pdo, $pid); if (!empty($active['until'])) { // Message clair avec date (tu peux remplacer par une clé $translations si tu veux) $until = $active['until']; $modeTxt = ($active['mode'] === 'auto') ? 'auto' : (($active['mode'] === 'manual') ? 'manual' : ''); $extra = $modeTxt ? " (mode: ".$modeTxt.")" : ''; $err = ($translations['abonnement_deja_actif'] ?? "Un abonnement est déjà actif jusqu'au {date}. Vous ne pouvez pas en souscrire un autre avant expiration.") ; $err = strtr($err, ['{date}' => $until]) . $extra; } if ($err) { // on ne redirige pas, on affiche l’erreur dans la page } else { // OK -> redirection vers l’endpoint Stripe (création session Checkout) // L’endpoint utilisera les variables de session (praticien_id, abonnement_id, prix, billing_mode, provider_plan_id) $_SESSION['checkout_attempt_id'] = bin2hex(random_bytes(16)); header('Location: /plata/actions/create_checkout.php', true, 303); exit; } } // PayPal / Virement → à implémenter (on ne met pas à jour la BD ici) if ($methode === 'paypal') { $msg = $translations['paypal_pas_dispo'] ?? "PayPal n'est pas encore disponible. Merci de choisir Carte bancaire."; } elseif ($methode === 'virement') { $msg = $translations['virement_pas_dispo'] ?? "Virement non disponible en ligne. Contacte le support pour un règlement par virement."; } } catch (Throwable $e) { logErr($e); $code = $e->getMessage(); if ($code === 'CSRF') { $err = $translations['session_expiree'] ?? 'Session expirée. Recharge la page.'; } elseif ($code === 'BAD_METHOD') { $err = $translations['methode_invalide'] ?? 'Méthode de paiement invalide.'; } else { $err = $translations['erreur_traitement'] ?? 'Erreur serveur pendant le paiement.'; } } } // Affichage $montantAff = number_format((float)$prix, 2, ',', ' ') . ' €'; // Titre différent si auto if ($billing_mode === 'auto') { $titrePaiement = strtr( $translations['paiement_abonnement_auto_de_montant'] ?? 'Abonnement automatique de {montant}', ['{montant}' => $montantAff] ); } else { $titrePaiement = strtr( $translations['paiement_de_montant'] ?? 'Paiement de {montant}', ['{montant}' => $montantAff] ); } ?>