Refactor PayPal refund process by introducing a modular approach for refund context and rendering. This update consolidates refund logic into dedicated functions in inc_fx_paypal.php, enhancing maintainability and clarity. The commandes.php file is updated to utilize these new functions, improving the overall structure and readability of the refund handling code.

This commit is contained in:
2026-06-26 14:26:06 -04:00
parent f1e43c911b
commit 74ee34634e
2 changed files with 237 additions and 190 deletions

View File

@ -399,10 +399,7 @@ include('inc_header.php');
require_once $_SERVER["DOCUMENT_ROOT"] . "/superadm/php/inc_fx_paypal.php";
// Jeton anti double clic / re-soumission
if (empty($_SESSION['refund_token'])) {
$_SESSION['refund_token'] = bin2hex(random_bytes(16));
}
$refundToken = $_SESSION['refund_token'];
$refundToken = fxRefundEnsureToken();
// Récupération des infos commande (présentes dans ton contexte)
$CAPTURE_ID = $recCommandes["TransactionID"] ?? null;
@ -414,194 +411,24 @@ if (!isset($CAPTURE_ID) || !is_string($CAPTURE_ID) || $CAPTURE_ID === '') {
return;
}
// Charger ta classe PayPal
require_once '../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 nen 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);
//print_rsl($paypaldetails);
// Déterminer le max remboursable PayPal
$max_remboursement_paypal = 0.0;
//print_rsl($paypaldetails);
// 1) S'il existe au moins un refund → on prend remaining (même si cest 0)
if (isset($paypaldetails['refunds']['totals']['remaining'])) {
$max_remboursement_paypal = (float)$paypaldetails['refunds']['totals']['remaining'];
}
// 2) Sinon → fallback : montant total capturé
else {
if (!empty($paypaldetails['details']['amount']['value'])) {
$max_remboursement_paypal = (float)$paypaldetails['details']['amount']['value'];
} elseif (!empty($paypaldetails['delails']['amount']['value'])) {
$max_remboursement_paypal = (float)$paypaldetails['delails']['amount']['value'];
}
}
// Déterminer la devise daffichage 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 dinput 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 si on veux pas renbourcer les frais ms1
// $maxUI=$max_remboursement_paypal-$frais_ms1;
// $maxUI le max Remboursement
$maxUI=$max_remboursement_paypal;
echosl($maxUI);
$maxproposer = max(0, $maxUI - $frais_ms1);
// Helpers UI
// Contexte de remboursement mutualisé (calculs + détails PayPal)
$refundCtx = fxRefundComputeContext($recCommandes);
$paypaldetails = $refundCtx['paypaldetails'];
// Infos debug (Super Admin)
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($refundCtx['max_paypal'], 2) . " " . $refundCtx['currency']);
echo("solde dispo ms1 :");
echosl($refundCtx['solde_dispo']);
echo("frais ms1 :");
echosl($refundCtx['frais_ms1']);
echo("max Remboursement :");
echosl($refundCtx['maxUI']);
// Formulaire de remboursement (rendu mutualisé : voir inc_fx_paypal.php)
echo fxRenderRefundForm($recCommandes, $refundToken, $refundCtx, $vDomaine);
?>
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Remboursement PayPal</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body style="font-family:system-ui,Arial,Helvetica;">
<div style="max-width:560px;border:1px solid #ddd;border-radius:10px;padding:14px;">
<div style="font-weight:700;font-size:16px;margin-bottom:8px;">Remboursement PayPal (Checkout)</div>
<!-- Zone messages AJAX -->
<div id="refund-msg" style="margin-bottom:10px;display:none;padding:10px;border-radius:8px;"></div>
<?php if ($maxUI > 0): ?>
<div style="background:#f9fbff;border:1px solid #dbe7ff;padding:10px;border-radius:8px;margin-bottom:10px;">
<div><strong>Capture:</strong> <?= h($CAPTURE_ID) ?></div>
<div><strong>Max remboursable (affiché):</strong> <span id="pp_ref_max"><?= h(money2($maxUI)) ?></span> <?= h($currencyUI) ?></div>
</div>
<form id="refund-form" method="post" 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($CAPTURE_ID) ?>">
<input type="hidden" name="currency" value="<?= h($currencyUI) ?>">
<input type="hidden" name="eve_id" value="<?= h($recCommandes["eve_id"]) ?>">
<input type="hidden" name="no_commande" value="<?= h($recCommandes["no_commande"]) ?>">
<div style="grid-column:1/2;">
<label style="display:block;font-size:12px;color:#333;margin-bottom:4px;">
Montant à rembourser (<?= h($currencyUI) ?>)
</label>
<input id="pp_ref_amount" type="number" name="refund_amount" step="0.01" min="0.01"
max="<?= h(money2($maxblock)) ?>"
value="<?= h(money2($maxproposer)) ?>"
style="width:100%;padding:8px;border:1px solid #ccc;border-radius:8px;">
</div>
<div style="grid-column:2/3;">
<label style="display:block;font-size:12px;color:#333;margin-bottom:4px;">Note (optionnelle)</label>
<input type="text" name="refund_note" placeholder="Raison du remboursement"
style="width:100%;padding:8px;border:1px solid #ccc;border-radius:8px;">
</div>
<div style="grid-column:1/3;text-align:right;margin-top:6px;">
<button type="submit"
style="padding:10px 14px;border:1px solid #2d6cdf;border-radius:8px;background:#2d6cdf;color:#fff;cursor:pointer;">
Rembourser
</button>
</div>
</form>
<?php else: ?>
<div style="background:#fff8e6;border:1px solid #ffe1a6;padding:10px;border-radius:8px;">
Aucun montant remboursable pour cette capture.
</div>
<?php endif; ?>
</div>
<script>
// Petit helper visuel
function showMsg(text, ok) {
const box = document.getElementById('refund-msg');
box.style.display = 'block';
box.style.background = ok ? '#e9f8ef' : '#feecec';
box.style.border = ok ? '1px solid #b8e3c5' : '1px solid #f2b8b5';
box.innerText = text;
}
document.getElementById('refund-form')?.addEventListener('submit', async (e) => {
e.preventDefault();
showMsg('⏳ Traitement en cours...', false);
const data = new FormData(e.target);
const res = await fetch( '<?php echo $vDomaine; ?>/superadm/ajax_refund.php', { method: 'POST', body: data });
// Défensif
if (!res.ok) {
showMsg('⚠️ Erreur réseau pendant le remboursement.', false);
return;
}
let json;
try { json = await res.json(); } catch (_) {
showMsg('⚠️ Réponse invalide du serveur.', false);
return;
}
if (json.success) {
showMsg('✅ ' + (json.message || 'Remboursement effectué.'), true);
// 👉 Ici, tu peux déclencher des actions UI :
// - recharger une grille des remboursements
// - rafraîchir un solde
// - etc.
// refreshTableRemboursements();
} else {
showMsg('⚠️ ' + (json.message || 'Erreur remboursement.'), false);
}
});
</script>
</body>
</html>

View File

@ -785,3 +785,223 @@ $qryInsert = $GLOBALS['db']->fxQuery($sqlInsert); // $stmt->execute([$capture_
}
function h($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
/* ==================================================================
* Remboursement PayPal - briques mutualisees (Super Admin + fiche gestion)
*
* IMPORTANT : ces fonctions ne dependent QUE de ce fichier
* (statut_payement, reconcileRefundsWithLocal, money2, h) + de la classe
* PaypalCheckout. Elles n'incluent JAMAIS superadm/php/inc_functions.php
* (le « U »), afin d'eviter tout conflit "Cannot redeclare" avec
* php/inc_fonctions.php (le « O ») charge dans le contexte /compte.
* ================================================================== */
/**
* Garantit la presence d'un jeton anti double-clic en session.
* @return string le jeton
*/
function fxRefundEnsureToken(): string
{
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if (empty($_SESSION['refund_token'])) {
$_SESSION['refund_token'] = bin2hex(random_bytes(16));
}
return $_SESSION['refund_token'];
}
/**
* Calcule le contexte de remboursement d'une commande (capture PayPal).
*
* @param array $recCommandes Ligne commande (TransactionID, eve_id, no_commande, fra_total...)
* @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
* }
*/
function fxRefundComputeContext(array $recCommandes): array
{
$out = [
'ok' => false,
'capture_id' => '',
'currency' => 'CAD',
'paypaldetails' => [],
'max_paypal' => 0.0,
'solde_dispo' => 0.0,
'frais_ms1' => 0.0,
'maxUI' => 0.0,
'maxblock' => 0.0,
'maxproposer' => 0.0,
];
$captureId = $recCommandes['TransactionID'] ?? null;
if (!is_string($captureId) || $captureId === '') {
return $out; // pas de capture PayPal exploitable
}
$out['capture_id'] = $captureId;
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);
// Details PayPal de la capture + reconciliation locale
$paypaldetails = $paypal->paypaldetails($captureId);
$paypaldetails = reconcileRefundsWithLocal($paypaldetails);
$out['paypaldetails'] = $paypaldetails;
// Max remboursable PayPal : remaining si dispo, sinon montant capture (tolere "delails")
$maxPaypal = 0.0;
if (isset($paypaldetails['refunds']['totals']['remaining'])) {
$maxPaypal = (float) $paypaldetails['refunds']['totals']['remaining'];
} elseif (!empty($paypaldetails['details']['amount']['value'])) {
$maxPaypal = (float) $paypaldetails['details']['amount']['value'];
} elseif (!empty($paypaldetails['delails']['amount']['value'])) {
$maxPaypal = (float) $paypaldetails['delails']['amount']['value'];
}
$out['max_paypal'] = $maxPaypal;
// Devise d'affichage
if (!empty($paypaldetails['refunds']['totals']['currency'])) {
$out['currency'] = (string) $paypaldetails['refunds']['totals']['currency'];
} elseif (!empty($paypaldetails['details']['amount']['currency_code'])) {
$out['currency'] = (string) $paypaldetails['details']['amount']['currency_code'];
} elseif (!empty($paypaldetails['delails']['amount']['currency_code'])) {
$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']);
$out['ok'] = true;
return $out;
}
/**
* Rend le formulaire de remboursement PayPal (carte + script AJAX).
* Fragment HTML autonome (pas de <html>/<head>), reutilisable partout.
*
* @param array $recCommandes Ligne commande
* @param string $refundToken Jeton anti double-clic
* @param array $ctx Resultat de fxRefundComputeContext()
* @param string $vDomaine Domaine de base (pour l'URL AJAX par defaut)
* @param string|null $ajaxUrl URL de l'endpoint (def: $vDomaine.'/superadm/ajax_refund.php')
* @return string HTML
*/
function fxRenderRefundForm(array $recCommandes, string $refundToken, array $ctx, string $vDomaine, ?string $ajaxUrl = null): string
{
if ($ajaxUrl === null) {
$ajaxUrl = $vDomaine . '/superadm/ajax_refund.php';
}
$captureId = (string) ($ctx['capture_id'] ?? '');
$currencyUI = (string) ($ctx['currency'] ?? 'CAD');
$maxUI = (float) ($ctx['maxUI'] ?? 0);
$maxblock = (float) ($ctx['maxblock'] ?? 0);
$maxproposer = (float) ($ctx['maxproposer'] ?? 0);
ob_start();
?>
<div class="ms1-refund" style="max-width:560px;border:1px solid #ddd;border-radius:10px;padding:14px;">
<div style="font-weight:700;font-size:16px;margin-bottom:8px;">Remboursement PayPal (Checkout)</div>
<!-- Zone messages AJAX -->
<div id="refund-msg" style="margin-bottom:10px;display:none;padding:10px;border-radius:8px;"></div>
<?php if ($maxUI > 0): ?>
<div style="background:#f9fbff;border:1px solid #dbe7ff;padding:10px;border-radius:8px;margin-bottom:10px;">
<div><strong>Capture:</strong> <?= h($captureId) ?></div>
<div><strong>Max remboursable (affiché):</strong> <span id="pp_ref_max"><?= h(money2($maxUI)) ?></span> <?= h($currencyUI) ?></div>
</div>
<form id="refund-form" method="post" 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) ?>">
<input type="hidden" name="eve_id" value="<?= h($recCommandes['eve_id'] ?? '') ?>">
<input type="hidden" name="no_commande" value="<?= h($recCommandes['no_commande'] ?? '') ?>">
<div style="grid-column:1/2;">
<label style="display:block;font-size:12px;color:#333;margin-bottom:4px;">
Montant à rembourser (<?= h($currencyUI) ?>)
</label>
<input id="pp_ref_amount" type="number" name="refund_amount" step="0.01" min="0.01"
max="<?= h(money2($maxblock)) ?>"
value="<?= h(money2($maxproposer)) ?>"
style="width:100%;padding:8px;border:1px solid #ccc;border-radius:8px;">
</div>
<div style="grid-column:2/3;">
<label style="display:block;font-size:12px;color:#333;margin-bottom:4px;">Note (optionnelle)</label>
<input type="text" name="refund_note" placeholder="Raison du remboursement"
style="width:100%;padding:8px;border:1px solid #ccc;border-radius:8px;">
</div>
<div style="grid-column:1/3;text-align:right;margin-top:6px;">
<button type="submit"
style="padding:10px 14px;border:1px solid #2d6cdf;border-radius:8px;background:#2d6cdf;color:#fff;cursor:pointer;">
Rembourser
</button>
</div>
</form>
<?php else: ?>
<div style="background:#fff8e6;border:1px solid #ffe1a6;padding:10px;border-radius:8px;">
Aucun montant remboursable pour cette capture.
</div>
<?php endif; ?>
</div>
<script>
(function () {
function showMsg(text, ok) {
var box = document.getElementById('refund-msg');
if (!box) return;
box.style.display = 'block';
box.style.background = ok ? '#e9f8ef' : '#feecec';
box.style.border = ok ? '1px solid #b8e3c5' : '1px solid #f2b8b5';
box.innerText = text;
}
var form = document.getElementById('refund-form');
if (!form) return;
form.addEventListener('submit', async function (e) {
e.preventDefault();
showMsg('⏳ Traitement en cours...', false);
var data = new FormData(e.target);
var res;
try {
res = await fetch(<?= json_encode($ajaxUrl) ?>, { method: 'POST', body: data });
} catch (_) {
showMsg('⚠️ Erreur réseau pendant le remboursement.', false);
return;
}
if (!res.ok) {
showMsg('⚠️ Erreur réseau pendant le remboursement.', false);
return;
}
var json;
try { json = await res.json(); } catch (_) {
showMsg('⚠️ Réponse invalide du serveur.', false);
return;
}
if (json.success) {
showMsg('✅ ' + (json.message || 'Remboursement effectué.'), true);
} else {
showMsg('⚠️ ' + (json.message || 'Erreur remboursement.'), false);
}
});
})();
</script>
<?php
return ob_get_clean();
}