diff --git a/ajax_inscr_gestion.php b/ajax_inscr_gestion.php index a203322..92ef6f2 100644 --- a/ajax_inscr_gestion.php +++ b/ajax_inscr_gestion.php @@ -81,18 +81,24 @@ switch ($strAction) { break; } - // L'eve_id fait foi cote serveur (jamais le client) pour le controle d'acces a l'evenement. - $intEveId = intval($recCommandes['eve_id']); - if ($intEveId <= 0 || !fxInscrGestionCanDo($intComId, $intEveId, 'inscriptions_gestion.refund')) { + // L'eve_id de contexte = evenement de la fiche (participant), pas seulement celui du checkout. + require_once $_SERVER['DOCUMENT_ROOT'] . '/superadm/php/inc_fx_paypal.php'; + + $intEveContext = intval($_REQUEST['eve_id'] ?? 0); + if ($intEveContext <= 0) { + $intEveContext = intval($recCommandes['eve_id']); + } + if ($intEveContext <= 0 || !fxInscrGestionCanDo($intComId, $intEveContext, 'inscriptions_gestion.refund')) { $tabRetour = array('state' => 'error', 'message' => 'unauthorized'); break; } - - // Module mutualise (ne tire jamais inc_functions.php "U") : voir superadm/php/inc_fx_paypal.php - require_once $_SERVER['DOCUMENT_ROOT'] . '/superadm/php/inc_fx_paypal.php'; + if (!fxRefundPanierLinkedToEvent($recCommandes['no_panier'], $intEveContext, intval($recCommandes['eve_id']))) { + $tabRetour = array('state' => 'error', 'message' => 'panier_evenement_incompatible'); + break; + } $strRefundToken = fxRefundEnsureToken(); - $arrRefundCtx = fxRefundComputeContext($recCommandes); + $arrRefundCtx = fxRefundComputeContext($recCommandes, $intEveContext, false); // Auteur = promoteur connecte (alimente l'encart "Ajoute par" du formulaire). $strAuteur = trim((string)($_SESSION['com_info']['com_prenom'] ?? '') . ' ' diff --git a/inc_footer_scripts.php b/inc_footer_scripts.php index 6356f28..a4fe754 100644 --- a/inc_footer_scripts.php +++ b/inc_footer_scripts.php @@ -1401,6 +1401,7 @@ if ($strLangue == 'fr') { $.post('/ajax_inscr_gestion.php', { a: 'refund_form', no_panier: $btn.data('no_panier'), + eve_id: $btn.data('eve_id'), lang: '' }, function (result) { if (result && result.state === 'ok' && result.html) { diff --git a/php/inc_fx_inscriptions_gestion.php b/php/inc_fx_inscriptions_gestion.php index ef1de5f..e62f9b9 100644 --- a/php/inc_fx_inscriptions_gestion.php +++ b/php/inc_fx_inscriptions_gestion.php @@ -1420,6 +1420,7 @@ function fxInscrGestionRenderFiche($intEveId, $strLangue, $strBaseUrl, $arrReq, echo ''; } diff --git a/php/inc_settings.php b/php/inc_settings.php index e6cde5c..3c25153 100644 --- a/php/inc_settings.php +++ b/php/inc_settings.php @@ -7,7 +7,7 @@ * Constantes * * **************/ -define('_VERSION_CODE', '4.72.717'); +define('_VERSION_CODE', '4.72.718'); define('_DATE_CODE', '2026-06-30'); //MSIN-4290 define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe'); diff --git a/superadm/ajax_refund.php b/superadm/ajax_refund.php index 61abc3a..e646c96 100644 --- a/superadm/ajax_refund.php +++ b/superadm/ajax_refund.php @@ -76,30 +76,46 @@ try { } // --- 2bis) AUTORISATION du promoteur (le Super Admin est en bypass) --- + $recCommandesFull = null; 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" + $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."); } + $recCommandesFull = $rowOwner; // 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')) { + $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 à votre événement."); + } + + // inscriptions_gestion.refund sur l'evenement de contexte (fiche), pas seulement le checkout. + if (!fxEveAccesHasPermission($intComId, $intEveContext, '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; + $arrRefundCtx = fxRefundComputeContext($rowOwner, $intEveContext, false); + $fltMaxAllowed = (float) ($arrRefundCtx['maxblock'] ?? 0); + if ($fltMaxAllowed <= 0 || $amount > ($fltMaxAllowed + 0.009)) { + throw new Exception("Montant supérieur au plafond remboursable pour votre événement (" . number_format($fltMaxAllowed, 2, '.', '') . " $currencyUI)."); + } + + // Journalisation sur l'evenement de contexte (ventilation inter-evenements). + $eve_id = $intEveContext; } // --- 3) Appel PayPal --- diff --git a/superadm/commandes.php b/superadm/commandes.php index c523611..6644dd8 100644 --- a/superadm/commandes.php +++ b/superadm/commandes.php @@ -412,7 +412,7 @@ if (!isset($CAPTURE_ID) || !is_string($CAPTURE_ID) || $CAPTURE_ID === '') { } // Contexte de remboursement mutualisé (calculs + détails PayPal) -$refundCtx = fxRefundComputeContext($recCommandes); +$refundCtx = fxRefundComputeContext($recCommandes, 0, true); $paypaldetails = $refundCtx['paypaldetails']; // Auteur du remboursement (Super Admin connecté) : alimente l'encart "Ajouté par". diff --git a/superadm/php/inc_fx_paypal.php b/superadm/php/inc_fx_paypal.php index 160866f..0b1a190 100644 --- a/superadm/php/inc_fx_paypal.php +++ b/superadm/php/inc_fx_paypal.php @@ -969,17 +969,215 @@ function fxRefundEnsureToken(): string return $_SESSION['refund_token']; } +/** Acces BD depuis inc_fx_paypal (contexte promoteur ou Super Admin). */ +function fxRefundDb() { + if (isset($GLOBALS['objDatabase'])) { + return $GLOBALS['objDatabase']; + } + if (isset($GLOBALS['db'])) { + return $GLOBALS['db']; + } + return null; +} + +/** Somme des remboursements deja imputes a un evenement pour une commande. */ +function fxRefundSumEventRefunds(string $strNoCommande, int $intEveId): float +{ + $db = fxRefundDb(); + if (!$db || $strNoCommande === '' || $intEveId <= 0) { + return 0.0; + } + + $sql = "SELECT COALESCE(SUM(rem_montant), 0) AS total" + . " FROM remboursements" + . " WHERE rem_deleted = 0 AND tr_id = 2" + . " AND eve_id = " . intval($intEveId) + . " AND no_commande = '" . $db->fxEscape($strNoCommande) . "'"; + + return (float) $db->fxGetVar($sql); +} + +/** Ligne rapport_finances_membership pour un panier (transaction inter-evenements). */ +function fxRefundGetMembershipRow(string $strNoPanier): array +{ + $db = fxRefundDb(); + if (!$db || $strNoPanier === '') { + return array(); + } + + $sql = "SELECT * FROM rapport_finances_membership" + . " WHERE no_panier = '" . $db->fxEscape($strNoPanier) . "' LIMIT 1"; + $row = $db->fxGetRow($sql); + + return is_array($row) ? $row : array(); +} + +/** + * Part membership si la table jonction est absente (repli fxTotalPanierMembership). + */ +function fxRefundResolveMembershipTotal(string $strNoPanier, int $intOrderEveId): float +{ + $row = fxRefundGetMembershipRow($strNoPanier); + if (!empty($row['ach_total'])) { + return (float) $row['ach_total']; + } + + $db = fxRefundDb(); + if (!$db || $strNoPanier === '' || $intOrderEveId <= 0) { + return 0.0; + } + + $intMembershipEveId = (int) $db->fxGetVar( + "SELECT eve_id_membership FROM inscriptions_evenements" + . " WHERE eve_id = " . intval($intOrderEveId) . " LIMIT 1" + ); + if ($intMembershipEveId <= 0) { + return 0.0; + } + + $intHasSisterItems = (int) $db->fxGetVar( + "SELECT COUNT(*) FROM (" + . " SELECT no_panier FROM inscriptions_panier_epreuves_commandees" + . " WHERE no_panier = '" . $db->fxEscape($strNoPanier) . "' AND eve_id = " . intval($intMembershipEveId) + . " UNION" + . " SELECT no_panier FROM inscriptions_panier_produits_new" + . " WHERE no_panier = '" . $db->fxEscape($strNoPanier) . "' AND eve_id = " . intval($intMembershipEveId) + . ") AS sister_items" + ); + if ($intHasSisterItems <= 0) { + return 0.0; + } + + if (!function_exists('fxTotalPanierMembership')) { + require_once $_SERVER['DOCUMENT_ROOT'] . '/php/inc_fx_panier_js.php'; + } + if (!function_exists('fxTotalPanierMembership')) { + return 0.0; + } + + $arrTotals = fxTotalPanierMembership($intOrderEveId, $intMembershipEveId, $strNoPanier); + return (float) ($arrTotals['total'] ?? 0); +} + +/** Verifie qu'un panier a bien une part rattachable a l'evenement de contexte. */ +function fxRefundPanierLinkedToEvent(string $strNoPanier, int $intEveContext, int $intOrderEveId): bool +{ + if ($strNoPanier === '' || $intEveContext <= 0) { + return false; + } + if ($intEveContext === $intOrderEveId) { + return true; + } + + $row = fxRefundGetMembershipRow($strNoPanier); + if (!empty($row['eve_id_membership']) && intval($row['eve_id_membership']) === $intEveContext) { + return true; + } + + $db = fxRefundDb(); + if (!$db) { + return false; + } + + $intCount = (int) $db->fxGetVar( + "SELECT COUNT(*) FROM (" + . " SELECT no_panier FROM inscriptions_panier_epreuves_commandees" + . " WHERE no_panier = '" . $db->fxEscape($strNoPanier) . "' AND eve_id = " . intval($intEveContext) + . " UNION" + . " SELECT no_panier FROM inscriptions_panier_produits_new" + . " WHERE no_panier = '" . $db->fxEscape($strNoPanier) . "' AND eve_id = " . intval($intEveContext) + . ") AS ctx_items" + ); + + return $intCount > 0; +} + +/** + * Part remboursable pour un evenement (aligne rapport finances MSIN-4228). + * + * @return array{ + * role:string, event_share:float, sister_share:float, already_refunded:float, + * remaining_event:float, membership_eve_id:int, subtract_frais:bool + * } + */ +function fxRefundComputeEventShare(array $recCommandes, int $intEveContext): array +{ + $intOrderEveId = intval($recCommandes['eve_id'] ?? 0); + $strNoPanier = (string) ($recCommandes['no_panier'] ?? ''); + $strNoCommande = (string) ($recCommandes['no_commande'] ?? ''); + $fltAchTotal = (float) ($recCommandes['ach_total'] ?? 0); + $fltFraTotal = (float) ($recCommandes['fra_total'] ?? 0); + $intPaiId = intval($recCommandes['pai_id'] ?? 0); + + if ($intEveContext <= 0) { + $intEveContext = $intOrderEveId; + } + + $fltMemberTotal = fxRefundResolveMembershipTotal($strNoPanier, $intOrderEveId); + $rowMember = fxRefundGetMembershipRow($strNoPanier); + $intMembershipEveId = intval($rowMember['eve_id_membership'] ?? 0); + if ($intMembershipEveId <= 0 && $fltMemberTotal > 0) { + $db = fxRefundDb(); + if ($db && $intOrderEveId > 0) { + $intMembershipEveId = (int) $db->fxGetVar( + "SELECT eve_id_membership FROM inscriptions_evenements" + . " WHERE eve_id = " . intval($intOrderEveId) . " LIMIT 1" + ); + } + } + + $strRole = 'simple'; + $fltEventShare = 0.0; + $fltSisterShare = 0.0; + $blnSubtractFrais = false; + + if ($fltMemberTotal > 0.00001) { + $fltSisterShare = $fltMemberTotal; + if ($intEveContext === $intOrderEveId) { + $strRole = 'host'; + $fltEventShare = max(0.0, $fltAchTotal - $fltMemberTotal); + $blnSubtractFrais = ($intPaiId !== 6); + } elseif ($intMembershipEveId > 0 && $intEveContext === $intMembershipEveId) { + $strRole = 'sister'; + $fltEventShare = max(0.0, $fltMemberTotal); + $fltSisterShare = 0.0; + $blnSubtractFrais = false; + } + } elseif ($intEveContext === $intOrderEveId) { + $strRole = 'simple'; + $fltEventShare = max(0.0, $fltAchTotal); + $blnSubtractFrais = ($intPaiId !== 6); + } + + $fltAlreadyRefunded = fxRefundSumEventRefunds($strNoCommande, $intEveContext); + $fltRemainingEvent = max(0.0, $fltEventShare - $fltAlreadyRefunded); + + return array( + 'role' => $strRole, + 'event_share' => $fltEventShare, + 'sister_share' => $fltSisterShare, + 'already_refunded' => $fltAlreadyRefunded, + 'remaining_event' => $fltRemainingEvent, + 'membership_eve_id' => $intMembershipEveId, + 'subtract_frais' => $blnSubtractFrais, + 'fra_total' => $fltFraTotal, + ); +} + /** * Calcule le contexte de remboursement d'une commande (capture PayPal). * * @param array $recCommandes Ligne commande (TransactionID, eve_id, no_commande, fra_total...) + * @param int $intEveContext Evenement depuis lequel on rembourse (0 = eve_id commande). Ignore si Super Admin. + * @param bool $blnSuperAdmin true = capture PayPal complete (option A Super Admin). * @return array { * ok:bool, capture_id:string, currency:string, paypaldetails:array, * max_paypal:float, solde_dispo:float, frais_ms1:float, - * maxUI:float, maxblock:float, maxproposer:float + * maxUI:float, maxblock:float, maxproposer:float, + * split_active:bool, event_share:float, sister_share:float, eve_id_context:int * } */ -function fxRefundComputeContext(array $recCommandes): array +function fxRefundComputeContext(array $recCommandes, int $intEveContext = 0, bool $blnSuperAdmin = false): array { $out = [ 'ok' => false, @@ -992,6 +1190,11 @@ function fxRefundComputeContext(array $recCommandes): array 'maxUI' => 0.0, 'maxblock' => 0.0, 'maxproposer' => 0.0, + 'split_active' => false, + 'event_share' => 0.0, + 'sister_share' => 0.0, + 'eve_id_context'=> 0, + 'share_role' => 'simple', ]; $captureId = $recCommandes['TransactionID'] ?? null; @@ -1003,8 +1206,8 @@ function fxRefundComputeContext(array $recCommandes): array require_once $_SERVER['DOCUMENT_ROOT'] . '/paypal_advanced/PaypalCheckout.class.php'; $paypal = new PayPalCheckout(); - // Solde disponible promoteur (helper de ce fichier) - $out['solde_dispo'] = (float) statut_payement($recCommandes['eve_id'] ?? 0); + $intSoldeEveId = $blnSuperAdmin ? intval($recCommandes['eve_id'] ?? 0) : ($intEveContext > 0 ? $intEveContext : intval($recCommandes['eve_id'] ?? 0)); + $out['solde_dispo'] = (float) statut_payement($intSoldeEveId); // Details PayPal de la capture + reconciliation locale $paypaldetails = $paypal->paypaldetails($captureId); @@ -1031,16 +1234,35 @@ function fxRefundComputeContext(array $recCommandes): array $out['currency'] = (string) $paypaldetails['delails']['amount']['currency_code']; } - // Frais ms1 $out['frais_ms1'] = (float) ($recCommandes['fra_total'] ?? 0); - // Plafonds (fidele a l'ecran Super Admin actuel) : - // - maxUI affiche = max remboursable PayPal - // - maxblock (input max) = max_paypal si max_paypal < solde dispo, sinon 0 - // - valeur proposee = max(0, maxUI - frais ms1) - $out['maxUI'] = $maxPaypal; - $out['maxblock'] = ($maxPaypal < $out['solde_dispo']) ? $maxPaypal : 0.0; - $out['maxproposer'] = max(0.0, $out['maxUI'] - $out['frais_ms1']); + if ($blnSuperAdmin) { + // Option A — Super Admin : capture PayPal complete (comportement historique). + $out['maxUI'] = $maxPaypal; + $out['maxblock'] = ($maxPaypal < $out['solde_dispo']) ? $maxPaypal : 0.0; + $out['maxproposer'] = max(0.0, $out['maxUI'] - $out['frais_ms1']); + $out['ok'] = true; + return $out; + } + + if ($intEveContext <= 0) { + $intEveContext = intval($recCommandes['eve_id'] ?? 0); + } + $out['eve_id_context'] = $intEveContext; + + $arrShare = fxRefundComputeEventShare($recCommandes, $intEveContext); + $out['share_role'] = $arrShare['role']; + $out['event_share'] = (float) $arrShare['event_share']; + $out['sister_share'] = (float) $arrShare['sister_share']; + $out['split_active'] = ($arrShare['sister_share'] > 0.00001 || $arrShare['role'] === 'sister'); + + $fltCapEvent = (float) $arrShare['remaining_event']; + $fltMaxUi = min($maxPaypal, $fltCapEvent); + $fltFraisForProposal = !empty($arrShare['subtract_frais']) ? (float) $arrShare['fra_total'] : 0.0; + + $out['maxUI'] = $fltMaxUi; + $out['maxblock'] = $fltMaxUi; + $out['maxproposer'] = max(0.0, $fltMaxUi - $fltFraisForProposal); $out['ok'] = true; return $out; @@ -1081,6 +1303,10 @@ function fxRenderRefundForm(array $recCommandes, string $refundToken, array $ctx $soldeDispo = (float) ($ctx['solde_dispo'] ?? 0); $fraisMs1 = (float) ($ctx['frais_ms1'] ?? 0); $noCommande = (string) ($recCommandes['no_commande'] ?? ''); + $intEveContext = intval($ctx['eve_id_context'] ?? 0); + $blnSplitActive = !empty($ctx['split_active']); + $fltEventShare = (float) ($ctx['event_share'] ?? 0); + $fltSisterShare = (float) ($ctx['sister_share'] ?? 0); $blnHasDocAside = trim($strDocAsideHtml) !== ''; @@ -1105,6 +1331,12 @@ function fxRenderRefundForm(array $recCommandes, string $refundToken, array $ctx
Capture PayPal :
Solde disponible MS1 :
Frais MS1 :
+ +
Part de votre événement :
+ 0): ?> +
Part autre événement (non remboursable ici) :
+ +
Max remboursable :
@@ -1116,6 +1348,9 @@ function fxRenderRefundForm(array $recCommandes, string $refundToken, array $ctx + 0): ?> + +