Init Git Prod Mars 2026

This commit is contained in:
Oleg Barykin committed 2026-03-05 10:25:39 +01:00
commit d81efe93b1
362 files changed
+86627

No files matched your search

+265
View File
@@ -0,0 +1,265 @@
<?php
// /public/souscrire_abonnement.php
require_once __DIR__ . '/includes/init_praticien.php';
// --- Helpers DB sûrs ---
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();
}
function enumOptions(PDO $pdo, string $table, string $col): array
{
$sql = "SELECT COLUMN_TYPE FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ? LIMIT 1";
$st = $pdo->prepare($sql);
$st->execute([$table, $col]);
$type = (string) $st->fetchColumn();
if (stripos($type, 'enum(') !== 0) {
return [];
}
preg_match_all("/'([^']*)'/", $type, $m);
return $m[1] ?? [];
}
function logErr(Throwable $e): void
{
$info = ($e instanceof PDOException && isset($e->errorInfo)) ? json_encode($e->errorInfo, JSON_UNESCAPED_UNICODE) : '';
@file_put_contents('/tmp/souscrire_abonnement.log',
'[' . date('c') . "] " . $e->getMessage() . " " . $info . "\n",
FILE_APPEND
);
}
function flash(string $type, string $msg): void
{
$_SESSION['flash_' . $type] = $msg;
}
// --- Sécurité PDO ---
if (isset($pdo) && $pdo instanceof PDO) {
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
}
$praticien_id = (int) ($_SESSION['reference_id'] ?? 0);
if (! $praticien_id) {
header('Location: /public/login.php');exit;
}
// --- CSRF depuis choisir_abonnement.php ---
$method = $_SERVER['REQUEST_METHOD'] ?? '';
$sessionCSRF = (string) ($_SESSION['csrf_abon'] ?? '');
$postedCSRF = (string) ($_POST['csrf'] ?? ($_POST['csrf_abon'] ?? ''));
if ($method !== 'POST' || $sessionCSRF === '' || $postedCSRF === '' || ! hash_equals($sessionCSRF, $postedCSRF)) {
flash('error', $translations['erreur_csrf'] ?? 'Session expirée. Merci de réessayer.');
header('Location: /public/choisir_abonnement.php');exit;
}
// --- Billing mode (manual|auto) ---
$billing_mode = (string) ($_POST['billing_mode'] ?? 'manual');
$billing_mode = in_array($billing_mode, ['manual', 'auto'], true) ? $billing_mode : 'manual';
// --- Mapping Stripe (TEST / staging) pour les abonnements auto ---
// NOTE: on gardera ça ici pour l’instant (staging). Plus tard on déplacera vers /plata/config/stripe.php
// Mensuel (id=3) -> price_... (25€/mois)
// Annuel (id=4) -> price_... (275€/an)
// Pro (id=5) -> price_... (575€/an)
/*$AUTO_PRICE_MAP = [
3 => 'price_1SoksWFEDARPqDCE0Ynql83K',
4 => 'price_1Sokt8FEDARPqDCEecbxJCZU',
5 => 'price_1Sol4VFEDARPqDCEdDpjrfUE',
];
Live;*/
$AUTO_PRICE_MAP = [
3 => 'price_1SpFEMFEDARPqDCEfGsL0SIN',
4 => 'price_1SpFFLFEDARPqDCE1fLBFWbq',
5 => 'price_1SpFGMFEDARPqDCEYW7FGU0P',
];
// --- Lecture de l’abonnement choisi ---
$abonnement_id = (int) ($_POST['abonnement_id'] ?? 0);
if ($abonnement_id <= 0) {
flash('error', $translations['abonnement_invalide'] ?? 'Abonnement invalide.');
header('Location: /public/choisir_abonnement.php');exit;
}
$st = $pdo->prepare("SELECT id, nom, prix, duree_jours, actif FROM abonnement WHERE id = ? LIMIT 1");
$st->execute([$abonnement_id]);
$ab = $st->fetch(PDO::FETCH_ASSOC);
if (! $ab || ((int) $ab['actif'] !== 1 && (int) $ab['id'] !== 2)) {
flash('error', $translations['abonnement_introuvable'] ?? 'Abonnement introuvable ou inactif.');
header('Location: /public/choisir_abonnement.php');exit;
}
$prix = (float) $ab['prix']; // 0.00 si gratuit
$duree_jours = (int) $ab['duree_jours'];
$nomKey = trim((string) ($ab['nom'] ?? ''));
$nomAb = $translations[$nomKey] ?? $nomKey;
// --- Payant → flux Stripe (ponctuel ou auto) ---
if ($prix >= 0.5) {
// Auto autorisé uniquement sur les abonnements configurés (3,4,5)
if ($billing_mode === 'auto') {
if (! isset($AUTO_PRICE_MAP[$abonnement_id])) {
flash('error', $translations['auto_indispo_text'] ?? "Le renouvellement automatique n'est disponible que pour les offres mensuelle/annuelle.");
header('Location: /public/choisir_abonnement.php');exit;
}
// Sécurité : si on arrive ici avec l'ID promo (2), on force manuel (pas de price récurrent mappé)
// (Normalement ton choisir_abonnement bloque déjà, mais on double-sécurise côté serveur.)
if ($abonnement_id === 2) {
$billing_mode = 'manual';
}
}
// On garde tes sessions actuelles (compatibles avec paiement_simule.php et create_checkout.php)
$_SESSION['paiement_abonnement_id'] = $abonnement_id;
$_SESSION['paiement_prix'] = $prix;
$_SESSION['paiement_abonnement_nom'] = $nomAb;
$_SESSION['paiement_abonnement_key'] = $nomKey;
// On ajoute les sessions pour le nouveau mode
$_SESSION['paiement_billing_mode'] = $billing_mode; // manual|auto
$_SESSION['paiement_provider'] = 'stripe';
if ($billing_mode === 'auto') {
$_SESSION['paiement_provider_plan_id'] = $AUTO_PRICE_MAP[$abonnement_id]; // Stripe price_id (recurring)
} else {
unset($_SESSION['paiement_provider_plan_id']);
}
header('Location: /public/paiement_simule.php');exit;
}
// --- Gratuit → activation locale (une seule fois) ---
$FREE_PLAN_ID = 1; // adapte si besoin
// Anti double-usage (cohérent avec ton affichage)
$st = $pdo->prepare("
SELECT 1
FROM paiement
WHERE praticien_id = ?
AND abonnement_id = ?
AND (statut IS NULL OR statut <> 'échoué')
LIMIT 1
");
$st->execute([$praticien_id, $FREE_PLAN_ID]);
$hasUsedFree = (bool) $st->fetchColumn();
if ($abonnement_id === $FREE_PLAN_ID && $hasUsedFree) {
flash('error', $translations['essai_deja_utilise'] ?? "L'offre gratuite a déjà été utilisée.");
header('Location: /public/choisir_abonnement.php');exit;
}
try {
// Choisir un statut compatible avec l’ENUM de la table paiement
$statut = null;
if (colExists($pdo, 'paiement', 'statut')) {
$opts = enumOptions($pdo, 'paiement', 'statut'); // ex: ['payé','en_attente','annulé']
if ($opts) {
$prefs = ['payé', 'paye', 'paid', 'validé', 'valide', 'completed', 'ok'];
foreach ($prefs as $p) {
if (in_array($p, $opts, true)) {$statut = $p;
break;}
}
if (! $statut) {
$statut = $opts[0];
}
}
}
// Calculer l’expiration
$date_expiration = (new DateTime('now'))->modify("+$duree_jours days")->format('Y-m-d H:i:s');
$pdo->beginTransaction();
// INSERT dans paiement (montant 0, méthode 'gratuit'), uniquement les colonnes existantes
$fields = [];
if (colExists($pdo, 'paiement', 'praticien_id')) {
$fields['praticien_id'] = $praticien_id;
}
if (colExists($pdo, 'paiement', 'abonnement_id')) {
$fields['abonnement_id'] = $abonnement_id;
}
if (colExists($pdo, 'paiement', 'montant')) {
$fields['montant'] = 0.0;
}
if (colExists($pdo, 'paiement', 'methode')) {
$fields['methode'] = 'gratuit';
}
if (colExists($pdo, 'paiement', 'statut') && $statut) {
$fields['statut'] = $statut;
}
if (colExists($pdo, 'paiement', 'currency')) {
$fields['currency'] = 'eur';
}
if (colExists($pdo, 'paiement', 'created_at')) {
$fields['created_at'] = ['expr' => 'NOW()'];
}
if (colExists($pdo, 'paiement', 'date_paiement')) {
$fields['date_paiement'] = ['expr' => 'NOW()'];
}
if (colExists($pdo, 'paiement', 'abonnement_nom') && $nomAb !== '') {
$fields['abonnement_nom'] = $nomAb;
}
if (! empty($fields)) {
$cols = [];
$holders = [];
$vals = [];
foreach ($fields as $col => $val) {
$cols[] = $col;
if (is_array($val) && isset($val['expr'])) {
$holders[] = $val['expr'];
} else {
$holders[] = '?';
$vals[] = $val;
}
}
$sql = "INSERT INTO paiement (" . implode(',', $cols) . ") VALUES (" . implode(',', $holders) . ")";
$pdo->prepare($sql)->execute($vals);
}
// UPDATE du praticien (abonnement + expiration)
if (colExists($pdo, 'praticien', 'date_expiration')) {
$pdo->prepare("UPDATE praticien SET abonnement_id = ?, date_expiration = ? WHERE id = ?")
->execute([$abonnement_id, $date_expiration, $praticien_id]);
} elseif (colExists($pdo, 'praticien', 'abonnement_expire_le')) {
$pdo->prepare("UPDATE praticien SET abonnement_id = ?, abonnement_expire_le = ? WHERE id = ?")
->execute([$abonnement_id, $date_expiration, $praticien_id]);
} // sinon, on ne bloque pas (pas de colonne), mais rien à faire.
$pdo->commit();
// Nettoyage session paiement (au cas où)
unset($_SESSION['paiement_abonnement_id'], $_SESSION['paiement_prix'], $_SESSION['paiement_billing_mode'], $_SESSION['paiement_provider'], $_SESSION['paiement_provider_plan_id']);
// Redirection finale
flash('success', $translations['essai_active'] ?? "L'essai gratuit a été activé.");
header('Location: /public/praticien/factures.php'); // ou ton tableau de bord
exit;
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
logErr($e);
http_response_code(500);
exit(($translations['erreur_traitement'] ?? 'Erreur serveur') . ' (FREE)');
}