Files
2026-03-05 10:25:39 +01:00

223 lines
7.4 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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');
}