Files
ms1inscription-v5/superadm/ajax_refund.php
stephan 8933753fe0 MSIN-4417 Update refund context handling in registration management
This commit refines the refund context handling by removing the unnecessary boolean parameter for Super Admin in the `fxRefundComputeContext` function, simplifying the logic. It ensures that refunds are consistently linked to the correct event context across various functions, enhancing authorization checks and calculations. The version code is incremented to 4.72.719 to reflect these changes.
2026-06-30 15:33:40 -04:00

171 lines
7.1 KiB
PHP
Raw 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
/****************************************************
* Fichier: AJAX_refund.php
* Rôle : Endpoint AJAX (JSON) pour exécuter un remboursement.
* - Valide le token de session (anti double clic)
* - Valide les inputs
* - Appelle PayPal
* - Retourne un JSON propre (success/message)
****************************************************/
session_start();
require_once $_SERVER["DOCUMENT_ROOT"] . "/superadm/php/inc_functions.php" ;
require_once $_SERVER["DOCUMENT_ROOT"] . "/superadm/php/inc_fx_paypal.php";
header('Content-Type: application/json; charset=utf-8');
// IMPORTANT : sassurer que la session est démarrée ici aussi
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
$response = ['success' => false, 'message' => 'Erreur inconnue'];
// ============================================================
// AUTHENTIFICATION : qui appelle cet endpoint ?
// - Super Admin (usa_info) -> plafond par evenement (meme ventilation que promoteur)
// - Promoteur (com_info) -> inscriptions_gestion.refund + plafond par evenement
// - Personne -> refus
// L'autorisation fine du promoteur (ownership) se fait plus bas, une fois la commande relue en DB.
// ============================================================
$blnIsSuperAdmin = !empty($_SESSION['usa_info']);
$intComId = intval($_SESSION['com_info']['com_id'] ?? 0);
if ($blnIsSuperAdmin) {
$strAjoutePar = trim(($_SESSION['usa_info']['com_prenom'] ?? '') . ' ' . ($_SESSION['usa_info']['com_nom'] ?? ''));
} else {
$strAjoutePar = trim(($_SESSION['com_info']['com_prenom'] ?? '') . ' ' . ($_SESSION['com_info']['com_nom'] ?? ''));
}
if (!$blnIsSuperAdmin && $intComId <= 0) {
// Aucun acteur authentifie : on refuse avant tout traitement.
echo json_encode(
['success' => false, 'message' => 'Session expirée ou accès refusé.'],
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
exit;
}
try {
// --- 1) Anti double clic / re-soumission ---
$postedToken = $_POST['refund_token'] ?? '';
if (!hash_equals($_SESSION['refund_token'] ?? '', $postedToken)) {
throw new Exception("Requête invalide ou déjà traitée.");
}
// On consomme le jeton : pas de re-soumission possible
unset($_SESSION['refund_token']);
// (Le prochain jeton sera régénéré lors du prochain affichage de la page)
// --- 2) Inputs ---
$amountStr = $_POST['refund_amount'] ?? '';
$note = $_POST['refund_note'] ?? '';
$CAPTURE_ID = $_POST['capture_id'] ?? '';
$currencyUI = $_POST['currency'] ?? 'CAD';
$eve_id = $_POST['eve_id'] ?? '';
$no_commande = $_POST['no_commande'] ?? '';
if ($CAPTURE_ID === '' || !is_string($CAPTURE_ID)) {
throw new Exception("CAPTURE_ID manquant.");
}
if ($amountStr === '' || !is_numeric($amountStr)) {
throw new Exception("Montant invalide.");
}
$amount = (float)$amountStr;
if ($amount <= 0) {
throw new Exception("Le montant doit être > 0.");
}
// --- 2bis) Commande relue en BD + plafond par evenement (Super Admin et promoteur) ---
$sqlOwner = "SELECT * FROM inscriptions_panier_acheteurs"
. " WHERE no_commande = '" . $db->fxEscape($no_commande) . "' LIMIT 1";
$rowOwner = $db->fxGetRow($sqlOwner);
if (!$rowOwner) {
throw new Exception("Commande introuvable.");
}
if ((string)$rowOwner['TransactionID'] !== (string)$CAPTURE_ID) {
throw new Exception("Incohérence commande / capture.");
}
$intEveContext = intval($_POST['refund_eve_context'] ?? 0);
if ($intEveContext <= 0) {
$intEveContext = intval($rowOwner['eve_id']);
}
if ($intEveContext <= 0 || !fxRefundPanierLinkedToEvent($rowOwner['no_panier'], $intEveContext, intval($rowOwner['eve_id']))) {
throw new Exception("Cette commande n'est pas rattachée à l'événement sélectionné.");
}
if (!$blnIsSuperAdmin) {
require_once $_SERVER["DOCUMENT_ROOT"] . "/php/inc_fx_eve_acces.php";
if (!fxEveAccesHasPermission($intComId, $intEveContext, 'inscriptions_gestion.refund')) {
throw new Exception("Accès refusé à cet événement.");
}
}
$arrRefundCtx = fxRefundComputeContext($rowOwner, $intEveContext);
$fltMaxAllowed = (float) ($arrRefundCtx['maxblock'] ?? 0);
if ($fltMaxAllowed <= 0 || $amount > ($fltMaxAllowed + 0.009)) {
throw new Exception("Montant supérieur au plafond remboursable pour cet événement (" . number_format($fltMaxAllowed, 2, '.', '') . " $currencyUI).");
}
$eve_id = $intEveContext;
// --- 3) Appel PayPal ---
require_once $_SERVER["DOCUMENT_ROOT"] .'/paypal_advanced/PaypalCheckout.class.php';
$paypal = new PayPalCheckout(); // adapte si nécessaire
// Idempotence possible ici si ton SDK le supporte
// $idemKey = 'refund_' . hash('sha256', $CAPTURE_ID.'|'.number_format($amount,2,'.','').'|'.$currencyUI);
// $res = $paypal->refundCapture($CAPTURE_ID, number_format($amount,2,'.',''), $currencyUI, $note, $idemKey);
$res = $paypal->refundCapture($CAPTURE_ID, number_format($amount,2,'.',''), $currencyUI, $note);
$status = strtoupper((string)($res['status'] ?? ''));
if (in_array($status, ['COMPLETED','PENDING','SUCCESS'], true)) {
// --- 4) Post-traitement maison (tables locales, logs, etc.) ---
// Implémente ici ta logique (mise à jour des tables, etc.)
try {
postRefundSuccess($CAPTURE_ID, $amount, $currencyUI, $note, $res, $eve_id, $no_commande, $strAjoutePar);
} catch (Throwable $inner) {
// On néchoue pas le remboursement pour autant : on renvoie un warning dans le message si tu veux.
// $response['post_warning'] = $inner->getMessage();
}
$response = [
'success' => true,
'message' => "Remboursement effectué: " . number_format($amount, 2, '.', '') . " $currencyUI (capture: " . htmlspecialchars($CAPTURE_ID, ENT_QUOTES, 'UTF-8') . ").",
'status' => $status,
];
} else {
throw new Exception("Remboursement non confirmé (statut: " . ($res['status'] ?? 'inconnu') . ").");
}
} catch (Throwable $e) {
// Nettoyage du message pour afficher une erreur claire (ex: REFUND_AMOUNT_EXCEEDED)
$msg = $e->getMessage();
if (preg_match('/\{.*\}/s', $msg, $m)) {
$json = json_decode($m[0], true);
if (!empty($json['details'][0]['description'])) {
$msg = $json['details'][0]['description'];
} elseif (!empty($json['message'])) {
$msg = $json['message'];
}
}
$response['message'] = "Erreur remboursement: " . $msg;
}
// Re-emission d'un jeton anti double-clic frais (l'ancien a ete consomme plus haut).
// Permet un 2e remboursement partiel ou une nouvelle tentative apres erreur, sans recharger.
// Le front (handler delegue) met a jour le champ cache refund_token avec cette valeur.
$_SESSION['refund_token'] = bin2hex(random_bytes(16));
$response['new_token'] = $_SESSION['refund_token'];
echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);