Init Git Prod Mars 2026
This commit is contained in:
commit
d81efe93b1
362 files changed
+86627
No files matched your search
@@ -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>
|
||||
Reference in new issue
Block a user