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

+241
View File
@@ -0,0 +1,241 @@
<?php
// /public/paiement_simule.php
require_once __DIR__ . '/includes/init_praticien.php';
if (isset($pdo) && $pdo instanceof PDO) {
$pdo->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]
);
}
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title><?= htmlspecialchars($translations['paiement_en_ligne_title'] ?? 'Paiement en ligne') ?></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/css/bootstrap.min.css" rel="stylesheet">
<style>
:root{ --brand-blue:#38378B; --brand-red:#E03C3F; --brand-blue-hover:#2f2e73; --page-blue:#8CA9FF; --text-dark:#111; --border:#e5e7eb; --radius:18px; --shadow:0 8px 20px rgba(0,0,0,.08); }
html,body{height:100%}
body{background:var(--page-blue); font-family:'Montserrat',system-ui,-apple-system,Segoe UI,Roboto,'Helvetica Neue',Arial; color:var(--text-dark);}
h3{color:var(--text-dark); font-weight:700;}
.card{border:1px solid var(--border); border-radius:var(--radius); box-shadow:var(--shadow); background:#fff;}
.form-label{font-weight:600; color:#0f172a;}
.form-select,.form-control{border-radius:12px; border-color:#d1d5db;}
.form-select:focus,.form-control:focus{border-color:var(--brand-blue); box-shadow:0 0 0 .25rem rgba(56,55,139,.15);}
.btn-primary{background:var(--brand-red); border-color:var(--brand-red); border-radius:999px; font-weight:600; box-shadow:0 8px 22px rgba(0,0,0,.18);}
.btn-primary:hover,.btn-primary:focus{background:var(--brand-red); border-color:var(--brand-red);}
</style>
</head>
<body class="d-flex justify-content-center align-items-center">
<div class="card p-4" style="min-width:360px; max-width:480px;">
<h3 class="mb-4 text-center"><?= htmlspecialchars($titrePaiement) ?></h3>
<?php if (!$err && $billing_mode === 'auto'): ?>
<div class="alert alert-light border" role="alert">
<?= htmlspecialchars($translations['info_abonnement_auto'] ?? "Le renouvellement automatique est activé. Vous pouvez résilier à tout moment depuis votre espace.") ?>
</div>
<?php endif; ?>
<?php if ($err): ?>
<div class="alert alert-danger" role="alert"><?= htmlspecialchars($err) ?><br>
<a href="/public/praticien/resiliation.php" class="text-decoration-none">
← <?= htmlspecialchars($translations['retour_resiliation'] ?? 'Retour à la résiliation') ?>
</a></div>
<?php endif; ?>
<?php if (!$err && $msg): ?>
<div class="alert alert-info" role="alert"><?= htmlspecialchars($msg) ?></div>
<?php endif; ?>
<form method="POST">
<input type="hidden" name="csrf" value="<?= htmlspecialchars($CSRF, ENT_QUOTES, 'UTF-8') ?>">
<div class="mb-3">
<label for="methode" class="form-label"><?= htmlspecialchars($translations['label_methode_paiement'] ?? 'Méthode de paiement') ?></label>
<select name="methode" id="methode" class="form-select" required>
<option value="carte"><?= htmlspecialchars($translations['methode_cb'] ?? 'Carte bancaire') ?></option>
<!--<option value="paypal"><?= htmlspecialchars($translations['methode_paypal'] ?? 'PayPal') ?></option>
<option value="virement"><?= htmlspecialchars($translations['methode_virement'] ?? 'Virement') ?></option>-->
</select>
</div>
<button type="submit" class="btn btn-primary w-100">
<?= htmlspecialchars($translations['btn_payer_maintenant'] ?? 'Payer maintenant') ?>
</button>
</form>
</div>
</body>
</html>