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

+222
View File
@@ -0,0 +1,222 @@
<?php
// /plata/actions/create_checkout.php
require_once realpath(__DIR__ . '/../../public/includes/init_praticien.php');
require_once __DIR__ . '/../lib/stripe_client.php';
// $pdo
//require_once __DIR__ . '/../../public/includes/securite.php';
require_login_or_redirect(['praticien']);
$configAll = require __DIR__ . '/../config/stripe.php';
$cfg = $configAll[$configAll['mode']] ?? null;
if (!$cfg || empty($cfg['secret_key'])) {
http_response_code(500);
exit('Config Stripe invalide.');
}
$logFile = '/tmp/stripe_create_checkout.log';
@touch($logFile);
function log_line(string $msg): void {
global $logFile;
@file_put_contents($logFile, '['.date('c').'] '.$msg."\n", FILE_APPEND);
}
// ---- Données session ----
$praticien_id = $_SESSION['reference_id'] ?? null;
$abonnement_id = $_SESSION['paiement_abonnement_id'] ?? null;
$prix_raw = $_SESSION['paiement_prix'] ?? null;
// Nouveau (manual|auto)
$billing_mode = (string)($_SESSION['paiement_billing_mode'] ?? 'manual');
$billing_mode = in_array($billing_mode, ['manual','auto'], true) ? $billing_mode : 'manual';
// Nouveau : Stripe price_id récurrent (si auto)
$provider_plan_id = (string)($_SESSION['paiement_provider_plan_id'] ?? '');
if (!$praticien_id || !$abonnement_id || $prix_raw === null) {
http_response_code(400);
exit('Données paiement manquantes.');
}
// ---- Tentative de paiement (anti réutilisation de session Stripe) ----
$attemptId = (string)($_SESSION['checkout_attempt_id'] ?? '');
if ($attemptId === '') {
$attemptId = bin2hex(random_bytes(16));
$_SESSION['checkout_attempt_id'] = $attemptId;
}
// ---- Récupérer la clé de l’abonnement ----
$abonnement_key = '';
try {
if (!isset($pdo) || !($pdo instanceof PDO)) {
throw new RuntimeException('PDO indisponible');
}
$st = $pdo->prepare("SELECT nom FROM abonnement WHERE id = ? LIMIT 1");
$st->execute([(int)$abonnement_id]);
$row = $st->fetch(PDO::FETCH_ASSOC);
// nom = clé de traduction
$abonnement_key = trim((string)($row['nom'] ?? ''));
} catch (Throwable $e) {
log_line('WARN plan lookup failed: '.$e->getMessage());
$abonnement_key = '';
}
// Nom affiché : Abonnement #ID — traduction depuis fr.php / ru.php
$display_name = 'Abonnement #'.(int)$abonnement_id;
$abonnement_nom = ''; // nom affichable (traduit)
if ($abonnement_key !== '') {
$label = $translations[$abonnement_key] ?? ''; // traduction UNIQUEMENT
if ($label !== '') {
$abonnement_nom = $label;
if (function_exists('mb_strimwidth')) {
$display_name .= ' — '.mb_strimwidth($label, 0, 120, '…', 'UTF-8');
} else {
$display_name .= ' — '.$label;
}
}
}
// ---- Normalisation prix ----
$prix_norm = str_replace(["\u{00A0}", ' '], '', (string)$prix_raw);
$prix_norm = str_replace(',', '.', $prix_norm);
if (!is_numeric($prix_norm)) {
http_response_code(400);
exit('Montant invalide.');
}
$prix = (float)$prix_norm;
if ($prix < 0.5) {
http_response_code(400);
exit('Montant trop faible (Stripe min 0,50 €).');
}
$amount = (int) round($prix * 100); // centimes
// ---- Email du praticien ----
$userEmail = $_SESSION['user_email'] ?? null;
if (!$userEmail || !filter_var($userEmail, FILTER_VALIDATE_EMAIL)) {
try {
if (isset($pdo) && $pdo instanceof PDO) {
$st = $pdo->prepare("SELECT email FROM praticien WHERE id = ? LIMIT 1");
$st->execute([(int)$praticien_id]);
$row = $st->fetch(PDO::FETCH_ASSOC);
$candidate = $row['email'] ?? null;
if ($candidate && filter_var($candidate, FILTER_VALIDATE_EMAIL)) {
$userEmail = $candidate;
} else {
$userEmail = null;
}
}
} catch (Throwable $e) {
log_line('WARN email lookup failed: '.$e->getMessage());
$userEmail = null;
}
}
// ---- Construire la session Checkout ----
$successUrl = 'https://' . $_SERVER['HTTP_HOST'] . '/plata/views/success.php?session_id={CHECKOUT_SESSION_ID}';
$cancelUrl = 'https://' . $_SERVER['HTTP_HOST'] . '/plata/views/cancel.php';
$params = [
'success_url' => $successUrl,
'cancel_url' => $cancelUrl,
'payment_method_types' => ['card'],
'metadata' => [
'praticien_id' => (string)(int)$praticien_id,
'abonnement_id' => (string)(int)$abonnement_id,
'billing_mode' => $billing_mode,
'montant_cent' => (string)$amount,
'abonnement_key' => $abonnement_key !== '' ? $abonnement_key : 'N/A',
'abonnement_nom' => $abonnement_nom !== '' ? $abonnement_nom : 'N/A',
'provider_plan_id' => $provider_plan_id !== '' ? $provider_plan_id : 'N/A',
],
];
$params['client_reference_id'] = (string)(int)$praticien_id;
// Recommandé : éviter customer=null dans les events
if ($userEmail && filter_var($userEmail, FILTER_VALIDATE_EMAIL)) {
$params['customer_email'] = $userEmail;
}
// Email si dispo
if ($userEmail && filter_var($userEmail, FILTER_VALIDATE_EMAIL)) {
$params['customer_email'] = $userEmail;
}
// ---- Mode ponctuel vs abonnement auto ----
if ($billing_mode === 'auto') {
// Sécurité : il faut absolument un price_id récurrent
if ($provider_plan_id === '' || stripos($provider_plan_id, 'price_') !== 0) {
http_response_code(400);
exit('Plan récurrent manquant.');
}
$params['mode'] = 'subscription';
$params['line_items'] = [[
'price' => $provider_plan_id,
'quantity' => 1,
]];
// Important: metadata aussi sur la subscription (utile pour les webhooks invoice/subscription)
$params['subscription_data'] = [
'metadata' => $params['metadata'],
];
} else {
// Ponctuel (comportement actuel)
$params['mode'] = 'payment';
$params['line_items'] = [[
'price_data' => [
'currency' => $configAll['currency'] ?? 'eur',
'product_data' => ['name' => $display_name],
'unit_amount' => $amount,
],
'quantity' => 1,
]];
}
// ---- Génération d'une idempotency_key robuste ----
$idem_material = [
'praticien_id' => (int)$praticien_id,
'abonnement_id' => (int)$abonnement_id,
'billing_mode' => (string)$billing_mode,
'provider_plan' => (string)$provider_plan_id,
'amount' => (int)$amount,
'currency' => (string)($configAll['currency'] ?? 'eur'),
'name' => (string)$display_name,
'success_url' => (string)$successUrl,
'cancel_url' => (string)$cancelUrl,
'attempt' => (string)$attemptId,
];
$idem_hash = hash('sha256', json_encode($idem_material, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES));
$idempotency_key = 'chk_'.$idem_hash;
try {
/** @var \Stripe\StripeClient $stripe */
$session = $stripe->checkout->sessions->create($params, [
'idempotency_key' => $idempotency_key,
]);
header('Location: ' . $session->url, true, 303);
exit;
} catch (\Stripe\Exception\ApiErrorException $e) {
$httpStatus = method_exists($e, 'getHttpStatus') ? $e->getHttpStatus() : null;
$requestId = method_exists($e, 'getRequestId') ? $e->getRequestId() : null;
$stripeCode = method_exists($e, 'getStripeCode') ? $e->getStripeCode() : null;
$httpBody = method_exists($e, 'getHttpBody') ? $e->getHttpBody() : null;
$jsonBody = method_exists($e, 'getJsonBody') ? json_encode($e->getJsonBody()) : null;
log_line("STRIPE_API_ERROR status=$httpStatus req=$requestId code=$stripeCode msg=".$e->getMessage()." body=".$httpBody." json=".$jsonBody);
http_response_code(500);
exit('Erreur Stripe');
} catch (Throwable $e) {
log_line('PHP_ERROR '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine());
http_response_code(500);
exit('Erreur Stripe');
}