223 lines
7.4 KiB
PHP
223 lines
7.4 KiB
PHP
<?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');
|
||
}
|
||
|