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:
-
Max remboursable (affiché):
-
- -
- - - - "> - "> - -
- - -
- -
- - -
- -
- -
-
- -
- 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'); }