162 lines
6.8 KiB
PHP
162 lines
6.8 KiB
PHP
<?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 : s’assurer 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) -> bypass total (comportement historique, aucun droit granulaire)
|
||
// - Promoteur (com_info) -> soumis a inscriptions_gestion.refund + ownership de l'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) AUTORISATION du promoteur (le Super Admin est en bypass) ---
|
||
if (!$blnIsSuperAdmin) {
|
||
// L'evenement de reference est RELU en DB depuis la commande (jamais fourni par le client).
|
||
require_once $_SERVER["DOCUMENT_ROOT"] . "/php/inc_fx_eve_acces.php";
|
||
|
||
$sqlOwner = "SELECT eve_id, TransactionID FROM inscriptions_panier_acheteurs"
|
||
. " WHERE no_commande = '" . $db->fxEscape($no_commande) . "' LIMIT 1";
|
||
$rowOwner = $db->fxGetRow($sqlOwner);
|
||
if (!$rowOwner) {
|
||
throw new Exception("Commande introuvable.");
|
||
}
|
||
|
||
// Defense : la capture postee doit correspondre a la commande reclamee.
|
||
if ((string)$rowOwner['TransactionID'] !== (string)$CAPTURE_ID) {
|
||
throw new Exception("Incohérence commande / capture.");
|
||
}
|
||
|
||
$intEveIdReel = intval($rowOwner['eve_id']);
|
||
// inscriptions_gestion.refund implique aussi l'acces du promoteur a cet evenement.
|
||
if ($intEveIdReel <= 0 || !fxEveAccesHasPermission($intComId, $intEveIdReel, 'inscriptions_gestion.refund')) {
|
||
throw new Exception("Accès refusé à cet événement.");
|
||
}
|
||
|
||
// On force l'eve_id reel pour la suite (journalisation du remboursement).
|
||
$eve_id = $intEveIdReel;
|
||
}
|
||
|
||
// --- 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);
|
||
|
||
|
||
|