Add refund functionality to mobile event management

This commit introduces a new refund feature in the mobile event management system. It includes an AJAX-based refund form that allows users to request refunds directly from the event management interface. The refund button is conditionally displayed based on user permissions, and the form is dynamically loaded to enhance user experience. Additionally, the refund processing logic is integrated with existing PayPal functions to ensure seamless transaction handling. This update improves the overall usability and functionality of the event management system.
This commit is contained in:
2026-06-26 14:45:09 -04:00
parent bc26c37315
commit bc407a7c20
4 changed files with 144 additions and 4 deletions

View File

@ -39,6 +39,48 @@ switch ($strAction) {
$tabRetour = fxSetParStatutCourse($intParId, $strStatut, $strLangue);
break;
case 'refund_form':
// Charge le formulaire de remboursement mutualise pour la fiche de gestion (panneau repliable).
$strNoPanier = trim((string)($_REQUEST['no_panier'] ?? ''));
if ($strNoPanier === '') {
$tabRetour = array('state' => 'error', 'message' => 'no_panier_manquant');
break;
}
// Meme source que le Super Admin : la commande vient de inscriptions_panier_acheteurs.
$sqlCmd = "SELECT *, IF(a.com_id <> 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 = '" . $objDatabase->fxEscape($strNoPanier) . "'";
$recCommandes = $objDatabase->fxGetRow($sqlCmd);
if (!$recCommandes) {
$tabRetour = array('state' => 'error', 'message' => 'commande_introuvable');
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 || !fxInscrMobileCanDo($intComId, $intEveId, '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';
$strRefundToken = fxRefundEnsureToken();
$arrRefundCtx = fxRefundComputeContext($recCommandes);
// Auteur = promoteur connecte (alimente l'encart "Ajoute par" du formulaire).
$strAuteur = trim((string)($_SESSION['com_info']['com_prenom'] ?? '') . ' '
. (string)($_SESSION['com_info']['com_nom'] ?? ''));
// Markup seul (sans <script>, dernier param false) car injecte via innerHTML :
// le comportement (submit AJAX) est gere par le handler delegue dans inc_footer_scripts.php.
$strHtml = fxRenderRefundForm($recCommandes, $strRefundToken, $arrRefundCtx, $vDomaine, $strAuteur, null, false);
$tabRetour = array('state' => 'ok', 'html' => $strHtml);
break;
default:
$tabRetour = array('state' => 'error', 'message' => 'invalid_action');
break;

View File

@ -1252,6 +1252,80 @@ 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 () {
$(document).on('click', '.btn_refund_toggle', function () {
var $btn = $(this);
var $panel = $($btn.attr('data-refund-target') || '#inscr-mobile-refund-panel');
if (!$panel.length) { return; }
// Si deja ouvert -> on replie.
if (!$panel.prop('hidden')) {
$panel.prop('hidden', true);
$btn.attr('aria-expanded', 'false');
return;
}
// Sinon -> on deplie.
$panel.prop('hidden', false);
$btn.attr('aria-expanded', 'true');
// Deja charge ? on ne recharge pas (le serveur recalcule a chaque (re)chargement reel).
if ($panel.attr('data-loaded') === '1') { return; }
$panel.html('<div class="inscr-mobile-muted" style="padding:10px;">…</div>');
$.post('<?php echo $vDomaine; ?>/ajax_inscr_mobile.php', {
a: 'refund_form',
no_panier: $btn.data('no_panier'),
lang: '<?php echo $strLangue; ?>'
}, function (result) {
if (result && result.state === 'ok' && result.html) {
$panel.html(result.html);
$panel.attr('data-loaded', '1');
} else {
$panel.html('<div style="background:#feecec;border:1px solid #f2b8b5;padding:10px;border-radius:8px;">\u26a0\ufe0f ' + ((result && result.message) || 'Erreur de chargement.') + '</div>');
}
}, 'json').fail(function () {
$panel.html('<div style="background:#feecec;border:1px solid #f2b8b5;padding:10px;border-radius:8px;">\u26a0\ufe0f Erreur reseau.</div>');
});
});
// Handler delegue du formulaire injecte (le <script> inline est absent en mode AJAX, cf. fxRenderRefundForm).
$(document).on('submit', '#refund-form', function (e) {
e.preventDefault();
var form = this;
var url = form.getAttribute('data-refund-url');
var $msg = $('#refund-msg');
function showMsg(text, ok) {
$msg.css({
display: 'block',
background: ok ? '#e9f8ef' : '#feecec',
border: ok ? '1px solid #b8e3c5' : '1px solid #f2b8b5'
}).text(text);
}
showMsg('\u23f3 Traitement en cours...', false);
fetch(url, { method: 'POST', body: new FormData(form) })
.then(function (res) {
if (!res.ok) { throw new Error('network'); }
return res.json();
})
.then(function (json) {
if (json && json.success) {
showMsg('\u2705 ' + (json.message || 'Remboursement effectue.'), true);
} else {
showMsg('\u26a0\ufe0f ' + ((json && json.message) || 'Erreur remboursement.'), false);
}
})
.catch(function () {
showMsg('\u26a0\ufe0f Erreur reseau pendant le remboursement.', false);
});
});
})();
$('#modal_teammates').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget);
var modal = $(this);

View File

@ -1185,8 +1185,10 @@ function fxInscrMobileRenderFiche($intEveId, $strLangue, $strBaseUrl, $arrReq, $
$blnCanEdit = fxInscrMobileCanDo($intComId, $intRowEveId, 'inscriptions_gestion.edit');
$blnCanCancel = fxInscrMobileCanDo($intComId, $intRowEveId, 'inscriptions_gestion.cancel');
$blnCanRestore = fxInscrMobileCanDo($intComId, $intRowEveId, 'inscriptions_gestion.restore');
// Droit de remboursement PayPal (etape remboursement) : controle l'affichage du bouton + panneau.
$blnCanRefund = fxInscrMobileCanDo($intComId, $intRowEveId, 'inscriptions_gestion.refund');
$blnHasEditable = ($blnCanStatut || $blnCanBib || $blnCanRemis);
$blnHasGestion = ($blnCanEdit || $blnCanCancel || $blnCanRestore);
$blnHasGestion = ($blnCanEdit || $blnCanCancel || $blnCanRestore || $blnCanRefund);
$strListUrl = fxInscrMobileBuildUrl($strBaseUrl, $intEveId, $arrReq, array('pec_id' => null, 'pg' => $arrReq['pg']));
$strEveCode = fxGetEvenementsUrl($arrRow['eve_id']);
@ -1254,7 +1256,14 @@ function fxInscrMobileRenderFiche($intEveId, $strLangue, $strBaseUrl, $arrReq, $
. ' data-epr_id="' . (int)$arrRow['epr_id'] . '" data-eve_id="' . (int)$arrRow['eve_id'] . '"'
. ' value="' . (int)$arrRow['pec_id_original'] . '">' . fxInscrMobileEsc(fxInscrMobileT('btn_modifierinscriptions')) . '</button>';
}
echo '<button class="btn btn-warning btn-sm rounded-0" type="button" data-inscr-field="remboursement">' . fxInscrMobileEsc(fxInscrMobileT('inscr_mobile_remboursement')) . '</button>';
if ($blnCanRefund) {
// Bouton bascule : ouvre/ferme le panneau de remboursement et declenche le chargement AJAX du formulaire.
echo '<button class="btn btn-warning btn-sm rounded-0 btn_refund_toggle" type="button"'
. ' data-inscr-field="remboursement"'
. ' data-no_panier="' . fxInscrMobileEsc($arrRow['no_panier']) . '"'
. ' data-refund-target="#inscr-mobile-refund-panel"'
. ' aria-expanded="false">' . fxInscrMobileEsc(fxInscrMobileT('inscr_mobile_remboursement')) . '</button>';
}
if ($blnCanCancel) {
echo '<button class="btn btn-secondary btn-sm rounded-0 btn_cancel_event_promo' . $strStyleCancel . '" type="button"'
. ' id="annuler_fiche" data-eve_code="' . fxInscrMobileEsc($strEveCode) . '"'
@ -1268,6 +1277,13 @@ function fxInscrMobileRenderFiche($intEveId, $strLangue, $strBaseUrl, $arrReq, $
. ' value="' . (int)$arrRow['pec_id_original'] . '">' . fxInscrMobileEsc(fxInscrMobileT('btn_retablirinscriptions')) . '</button>';
}
echo '</div>';
if ($blnCanRefund) {
// Panneau de remboursement : vide au depart, rempli en AJAX a la 1ere ouverture (action=refund_form).
// data-loaded evite de recharger inutilement ; data-no_panier identifie la commande cote serveur.
echo '<div class="inscr-mobile-refund-panel" id="inscr-mobile-refund-panel" style="margin-top:10px;"'
. ' data-no_panier="' . fxInscrMobileEsc($arrRow['no_panier']) . '"'
. ' data-loaded="0" hidden></div>';
}
fxInscrMobileRenderFicheSectionEnd();
}

View File

@ -900,9 +900,14 @@ function fxRefundComputeContext(array $recCommandes): array
* (resolu par l'appelant : Super Admin -> usa_info,
* promoteur -> com_info). Affiche dans l'encart "Ajoute par".
* @param string|null $ajaxUrl URL de l'endpoint (def: $vDomaine.'/superadm/ajax_refund.php')
* @param bool $blnInlineScript true = inclut le <script> inline (page complete, ex. Super Admin).
* false = markup seul, sans <script> : a utiliser quand le formulaire
* est injecte via AJAX (innerHTML n'execute pas les <script>).
* Dans ce cas, le comportement est gere par un handler delegue
* (voir inc_footer_scripts.php) qui lit l'attribut data-refund-url.
* @return string HTML
*/
function fxRenderRefundForm(array $recCommandes, string $refundToken, array $ctx, string $vDomaine, string $auteur = '', ?string $ajaxUrl = null): string
function fxRenderRefundForm(array $recCommandes, string $refundToken, array $ctx, string $vDomaine, string $auteur = '', ?string $ajaxUrl = null, bool $blnInlineScript = true): string
{
if ($ajaxUrl === null) {
$ajaxUrl = $vDomaine . '/superadm/ajax_refund.php';
@ -941,7 +946,8 @@ function fxRenderRefundForm(array $recCommandes, string $refundToken, array $ctx
</div>
<?php if ($maxUI > 0): ?>
<form id="refund-form" method="post" style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
<!-- data-refund-url : lu par le handler delegue quand le <script> inline est absent (mode AJAX) -->
<form id="refund-form" method="post" data-refund-url="<?= h($ajaxUrl) ?>" style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
<input type="hidden" name="refund_token" value="<?= h($refundToken) ?>">
<input type="hidden" name="capture_id" value="<?= h($captureId) ?>">
<input type="hidden" name="currency" value="<?= h($currencyUI) ?>">
@ -978,6 +984,7 @@ function fxRenderRefundForm(array $recCommandes, string $refundToken, array $ctx
<?php endif; ?>
</div>
<?php if ($blnInlineScript): // script inline uniquement en page complete (ex. Super Admin) ?>
<script>
(function () {
function showMsg(text, ok) {
@ -1018,6 +1025,7 @@ function fxRenderRefundForm(array $recCommandes, string $refundToken, array $ctx
});
})();
</script>
<?php endif; ?>
<?php
return ob_get_clean();
}