diff --git a/compte.php b/compte.php
index 30a30e4..1923305 100644
--- a/compte.php
+++ b/compte.php
@@ -716,14 +716,6 @@ require_once("inc_header.php");
$footer_script = true;
include("inc_tableau_rabais.php");
break;
-// MSIN-3679
- case 'compte_inc_tableau_remboursement':
- $blnBoutonRetour = false;
- $footer_script = true;
-
- include("inc_tableau_remboursement.php");
- break;
-
case 'compte_inc_tableau_inscriptions':
$blnBoutonRetour = false;
$footer_script = true;
diff --git a/inc_footer_scripts.php b/inc_footer_scripts.php
index d4eb34c..9a90ef3 100644
--- a/inc_footer_scripts.php
+++ b/inc_footer_scripts.php
@@ -1255,6 +1255,50 @@ if ($strLangue == 'fr') {
// ===== Remboursement PayPal (fiche de gestion) =====
// Bouton bascule : ouvre/ferme le panneau repliable et charge le formulaire en AJAX a la 1ere ouverture.
(function () {
+ // Texte explicatif affiche apres un remboursement reussi (oriente vers le bouton Annuler).
+ var refundCancelHint = ;
+
+ // Comportement post-succes : recalcul du restant, re-emission du jeton, mise en evidence du bouton Annuler.
+ function onRefundSuccess(form, json) {
+ var $form = $(form);
+
+ // 1) Re-emission du jeton anti double-clic (le serveur l'a consomme) -> autorise un 2e remboursement partiel.
+ if (json && json.new_token) {
+ $form.find('input[name="refund_token"]').val(json.new_token);
+ }
+
+ // 2) Recalcul du montant restant (remboursement partiel) cote client : ancien max - montant rembourse.
+ var amount = parseFloat($form.find('input[name="refund_amount"]').val()) || 0;
+ var $max = $('#pp_ref_max');
+ var oldMax = parseFloat(String($max.text() || '0').replace(',', '.')) || 0;
+ var newMax = Math.max(0, oldMax - amount);
+ $max.text(newMax.toFixed(2));
+
+ var $amountInput = $('#pp_ref_amount');
+ $amountInput.attr('max', newMax.toFixed(2));
+ if (newMax <= 0) {
+ // Plus rien a rembourser : on verrouille le formulaire (le solde affiche reste a 0).
+ $amountInput.val('0.00').prop('disabled', true);
+ $form.find('button[type="submit"]').prop('disabled', true);
+ } else {
+ $amountInput.val(newMax.toFixed(2));
+ }
+
+ // 3) Mise en evidence du bouton Annuler + texte explicatif (uniquement si le bouton est visible).
+ var $cancelBtn = $('#annuler_fiche');
+ if ($cancelBtn.length && $cancelBtn.is(':visible')) {
+ $cancelBtn.removeClass('btn-secondary').addClass('btn-danger')
+ .css('box-shadow', '0 0 0 3px rgba(220,53,69,.35)');
+ if (!$('#inscr-mobile-refund-cancel-hint').length) {
+ $('
')
+ .text(refundCancelHint)
+ .insertAfter($('#inscr-mobile-refund-panel'));
+ }
+ }
+ }
+
$(document).on('click', '.btn_refund_toggle', function () {
var $btn = $(this);
var $panel = $($btn.attr('data-refund-target') || '#inscr-mobile-refund-panel');
@@ -1316,6 +1360,7 @@ if ($strLangue == 'fr') {
.then(function (json) {
if (json && json.success) {
showMsg('\u2705 ' + (json.message || 'Remboursement effectue.'), true);
+ onRefundSuccess(form, json);
} else {
showMsg('\u26a0\ufe0f ' + ((json && json.message) || 'Erreur remboursement.'), false);
}
diff --git a/inc_tableau_remboursement.php b/inc_tableau_remboursement.php
deleted file mode 100644
index 43d9b7c..0000000
--- a/inc_tableau_remboursement.php
+++ /dev/null
@@ -1,294 +0,0 @@
-
- 0, (SELECT c.com_login FROM inscriptions_comptes c WHERE a.com_id = c.com_id), '') AS usager FROM inscriptions_panier_acheteurs a, inscriptions_panier_statuts s WHERE s.sta_id = a.sta_id AND a.no_panier = '" . $_GET['no_panier']."'";
-$recCommandes = $objDatabase->fxGetRow($sqlCommandes);
-
-
-
-?>
-">
-
-
-
-
-
-
Détails de la commande
-
-
-
-
-
-
-
PAYPAL
-
-
- // ⚠️ CAPTURE_ID non fourni côté serveur.
- // ';
- return;
-}
-
-// Charger ta classe PayPal
-require_once $_SERVER["DOCUMENT_ROOT"] . "/paypal_advanced/PaypalCheckout.class.php";
-$paypal = new PayPalCheckout(); // adapte si nécessaire
-
-// Préparation
-$errors = [];
-$successMsg = null;
-
-// Devise par défaut si jamais les détails PayPal n’en donnent pas
-$currencyUI = 'CAD';
-
-// ====== Calculs initiaux côté serveur ======
-
-// Solde dispo promoteur (ton helper existant)
-$solde_dispo_ms1 = statut_payement($recCommandes["eve_id"]);
-
-// Récupérer les détails PayPal de la capture
-$paypaldetails = $paypal->paypaldetails($CAPTURE_ID);
-
-// Harmoniser avec tes remboursements locaux si besoin
-$paypaldetails = reconcileRefundsWithLocal($paypaldetails);
-
-// Déterminer le max remboursable PayPal
-$max_remboursement_paypal = 0.0;
-
-// Cas habituel : clé remaining
-if (!empty($paypaldetails['refunds']['totals']['remaining'])) {
- $max_remboursement_paypal = (float)$paypaldetails['refunds']['totals']['remaining'];
-}
-// Fallback : montant total capturé (certains retours utilisent "details", d’autres ont eu une coquille "delails")
- elseif (!empty($paypaldetails['details']['amount']['value'])) {
- $max_remboursement_paypal = (float)$paypaldetails['details']['amount']['value'];
- } elseif (!empty($paypaldetails['delails']['amount']['value'])) { // compat. si ton code historique renvoie "delails"
- $max_remboursement_paypal = (float)$paypaldetails['delails']['amount']['value'];
- }
-
-// Déterminer la devise d’affichage si fournie par PayPal
-if (!empty($paypaldetails['refunds']['totals']['currency'])) {
- $currencyUI = (string)$paypaldetails['refunds']['totals']['currency'];
-} elseif (!empty($paypaldetails['details']['amount']['currency_code'])) {
- $currencyUI = (string)$paypaldetails['details']['amount']['currency_code'];
-} elseif (!empty($paypaldetails['delails']['amount']['currency_code'])) {
- $currencyUI = (string)$paypaldetails['delails']['amount']['currency_code'];
-}
-
-// Frais MS1 (selon ta structure)
-$frais_ms1 = (float)($recCommandes['fra_total'] ?? 0);
-
-// Max remboursable à l’écran (ta logique actuelle)
-$maxUI = $max_remboursement_paypal - $frais_ms1;
-
-// maxblock = limite stricte d’input côté front, selon ton choix actuel
-if ($max_remboursement_paypal < $solde_dispo_ms1) {
- $maxblock = $max_remboursement_paypal;
-} else {
- $maxblock = 0;
- $maxUI = 0;
-}
-// ******************** variable info
-//print_rsl($recCommandes);
- echosl( "no_commande : ".$recCommandes["no_commande"]);
- // echosl( "rem_ajoute_par : ".$_SESSION["usa_info"]["com_prenom"]." ".$_SESSION["usa_info"]["com_nom"]);
- echosl( "eve_id : ".$recCommandes["eve_id"]) ;
- echosl( "Montant remboursable : " . number_format($max_remboursement_paypal, 2) . " " . ($result['refunds']['totals']['currency'] ?? $result['delails']['amount']['currency_code'] ?? ''));
-
-
-
- echo("solde dispo ms1 :");
- echosl($solde_dispo_ms1);
- echo("frais ms1 :");
- $frais_ms1=$recCommandes['fra_total'];
- echosl($frais_ms1);
- echo("max Remboursement :");
-
- // $maxUI le max Remboursement
- $maxUI=$max_remboursement_paypal-$frais_ms1;
- echosl($maxUI);
- ?>
-
-
-
-
- Remboursement PayPal
-
-
-
-
-
-
Remboursement PayPal (Checkout)
-
-
-
-
- 0): ?>
-
-
Capture: = h($CAPTURE_ID) ?>
-
Max remboursable (affiché): = h(money2($maxUI)) ?> = h($currencyUI) ?>
-
-
-
-
-
- Aucun montant remboursable pour cette capture.
-
-
-
-
-
-
-
-
-
-
-
-
-
Détails PAYPAL
-
-
- ">
-
-
-
- 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'] ?? '';
@@ -50,6 +75,33 @@ try {
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
@@ -66,7 +118,7 @@ try {
// --- 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);
+ 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();
@@ -97,6 +149,12 @@ try {
$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);
diff --git a/superadm/php/inc_fx_paypal.php b/superadm/php/inc_fx_paypal.php
index 524b773..bd484f2 100644
--- a/superadm/php/inc_fx_paypal.php
+++ b/superadm/php/inc_fx_paypal.php
@@ -778,11 +778,23 @@ $out['refunds'] = $sectionOut;
return $out;
}
-function postRefundSuccess(string $capture_id, float $amount, string $currency, string $note, array $paypalResponse = [],int $eve_id,string $no_commande): void
+function postRefundSuccess(string $capture_id, float $amount, string $currency, string $note, array $paypalResponse = [],int $eve_id = 0,string $no_commande = '', string $strAjoutePar = ''): void
{
-$sqlInsert = "INSERT INTO remboursements SET no_commande='".$no_commande."', paypal_capture='".$capture_id."',rem_date=now(),rem_date_pour=now(),rem_details = '" . $note . "',eve_id=".$eve_id.",mr_id=6,tr_id=2,rem_montant=".$amount.",rem_ajoute_par='".$_SESSION["usa_info"]["com_prenom"]." ".$_SESSION["usa_info"]["com_nom"]."'" ;
-$qryInsert = $GLOBALS['db']->fxQuery($sqlInsert); // $stmt->execute([$capture_id, json_encode($paypalResponse, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)]);
+// Auteur du remboursement : fourni par l'appelant (Super Admin OU promoteur).
+// Fallback legacy sur la session Super Admin si l'appelant ne fournit rien.
+if ($strAjoutePar === '') {
+ $strAjoutePar = trim(($_SESSION["usa_info"]["com_prenom"] ?? '') . ' ' . ($_SESSION["usa_info"]["com_nom"] ?? ''));
+}
+$db = $GLOBALS['db'];
+// Echappement defensif (les valeurs peuvent venir d'un POST promoteur).
+$strNoCmdSql = $db->fxEscape($no_commande);
+$strCaptureSql = $db->fxEscape($capture_id);
+$strNoteSql = $db->fxEscape($note);
+$strAuteurSql = $db->fxEscape($strAjoutePar);
+
+$sqlInsert = "INSERT INTO remboursements SET no_commande='".$strNoCmdSql."', paypal_capture='".$strCaptureSql."',rem_date=now(),rem_date_pour=now(),rem_details = '".$strNoteSql."',eve_id=".intval($eve_id).",mr_id=6,tr_id=2,rem_montant=".(float)$amount.",rem_ajoute_par='".$strAuteurSql."'" ;
+$db->fxQuery($sqlInsert);
}
function h($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }