Files
ms1inscription-v5/superadm/php/inc_fx_paypal.php
2026-05-13 09:43:32 -04:00

788 lines
30 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
function paypaldetails($capId){
error_reporting(E_ALL);
ini_set('display_errors', '1');
require_once '../paypal_advanced/config.php';
include_once '../paypal_advanced/dbConnect.php';
$dbpaypal = new mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME);
if ($dbpaypal->connect_errno) {
printf("sl Connect failed: %s\n", $dbpaypal->connect_error);
exit();
}
require_once '../paypal_advanced/PaypalCheckout.class.php';
$paypal = new PaypalCheckout;
// Helpers pour un retour propre si PayPal ne répond pas
$makeUnavailableCapture = function(string $capId, string $rawMsg='') {
// essaie dextraire debug_id du message dexception PayPal
$debugId = '';
if ($rawMsg && preg_match('/"debug_id"\s*:\s*"([^"]+)"/', $rawMsg, $m)) {
$debugId = $m[1];
}
return [
'id' => $capId,
'status' => 'NON_DISPONIBLE',
'_error' => 'Pas disponible chez PayPal',
'_paypal_debug_id' => $debugId,
'_raw_error' => $rawMsg,
];
};
$makeEmptyRefunds = function(string $capId, string $msg='Pas disponible chez PayPal') {
return [
'ok' => false,
'capture_id' => $capId,
'invoice_id' => '',
'order_id' => '',
'refunds' => [],
'totals' => [
'currency' => '',
'captured_total' => 0.00,
'refunded_total' => 0.00,
'remaining' => 0.00,
],
'errors' => [$msg],
];
};
// 1) Détails de la capture
try {
$delails = $paypal->getCaptureDetails($capId);
} catch (Throwable $e) {
// Retour propre si la ressource nexiste pas (404/INVALID_RESOURCE_ID)
$delails = $makeUnavailableCapture($capId, $e->getMessage());
// On renvoie directement le bundle, avec refunds vides
return [
'delails' => $delails,
'refunds' => $makeEmptyRefunds($capId),
];
}
// 2) Refunds (si on a des détails valides)
try {
$Refunds = $paypal->getRefundsForCapture($delails);
} catch (Throwable $e) {
// Si la recherche refunds échoue, on nexplose pas; on renvoie un bloc vide + erreur
$Refunds = $makeEmptyRefunds($capId, 'Remboursements non disponibles ('.$e->getMessage().')');
}
return [
'delails' => $delails,
'refunds' => $Refunds,
];
}
/**
* Rendu HTML simple et lisible pour une capture PayPal v2 + ses remboursements.
* - $cap : tableau de la capture (/v2/payments/captures/{id})
* - $refunds : tableaux de refunds (réponses de refundCapture() ou getRefundDetails())
* - $tz : fuseau pour afficher les dates
*/
function renderPayPalCaptureHtml($capOrBundle, array $refundsParam = [], string $tz = 'America/Toronto'): string
{
// --- Helpers ---
$h = static function ($v): string {
return htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8');
};
$get = static function ($a, array $path, $default = '') {
if (!is_array($a)) return $default;
foreach ($path as $k) {
if (!is_array($a) || !array_key_exists($k, $a)) return $default;
$a = $a[$k];
}
return $a;
};
$moneyStr = static function ($amt): string {
if (is_array($amt)) {
$v = isset($amt['value']) ? (string)$amt['value'] : '';
$c = isset($amt['currency_code']) ? (string)$amt['currency_code'] : '';
if ($v !== '' && $c !== '') return number_format((float)$v, 2, '.', '') . ' ' . $c;
if ($v !== '') return number_format((float)$v, 2, '.', '');
if ($c !== '') return $c;
} elseif (is_numeric($amt)) {
return number_format((float)$amt, 2, '.', '');
}
return '';
};
$moneyVal = static function ($amt): float {
if (is_array($amt) && isset($amt['value'])) return (float)$amt['value'];
if (is_numeric($amt)) return (float)$amt;
return 0.0;
};
$yn = static function ($b): string {
return ($b === true || $b === 1 || $b === '1') ? 'Oui' : (($b === false || $b === 0 || $b === '0') ? 'Non' : '');
};
$fmtDate = static function ($iso, string $tz): string {
if (!$iso) return '';
try {
$dt = new DateTime($iso);
$dt->setTimezone(new DateTimeZone($tz));
return $dt->format('Y-m-d H:i:s T');
} catch (Throwable $e) {
return (string)$iso;
}
};
// --- Détecte le "nouveau bundle" vs ancien appel ---
$isBundle = is_array($capOrBundle) && (array_key_exists('delails', $capOrBundle) || array_key_exists('details', $capOrBundle));
if ($isBundle) {
$cap = $capOrBundle['delails'] ?? $capOrBundle['details']; // tolère "details" si tu corriges plus tard
$refBloc = $capOrBundle['refunds'] ?? [];
// Items de remboursements
$refundItems = $refBloc['refunds'] ?? [];
// Totaux fournis par lAPI de recherche (sinon on recalcule)
$totals = $refBloc['totals'] ?? [];
} else {
$cap = $capOrBundle;
$refundItems = $refundsParam;
$totals = [];
}
// ------------------ TABLE CAPTURE ------------------
$rows = [];
$rows['ID de capture'] = $get($cap, ['id']);
$rows['Statut'] = $get($cap, ['status']);
$rows['Montant capturé'] = $moneyStr($get($cap, ['amount'], []));
$rows['Frais PayPal'] = $moneyStr($get($cap, ['seller_receivable_breakdown','paypal_fee'], []));
$rows['Montant net'] = $moneyStr($get($cap, ['seller_receivable_breakdown','net_amount'], []));
$rows['Montant brut'] = $moneyStr($get($cap, ['seller_receivable_breakdown','gross_amount'], []));
$rows['Capture finale'] = $yn($get($cap, ['final_capture'], ''));
$rows['Protection vendeur'] = $get($cap, ['seller_protection','status']);
$rows['Invoice ID (invoice_id)'] = $get($cap, ['invoice_id']);
$rows['ID personnalisé (custom_id)'] = $get($cap, ['custom_id']);
$rows['ID commande (order_id)'] = $get($cap, ['supplementary_data','related_ids','order_id']);
$rows['Payee (email)'] = $get($cap, ['payee','email_address']);
$rows['Compte marchand'] = $get($cap, ['payee','merchant_id']);
$rows['Réseau (carte)'] = $get($cap, ['network_transaction_reference','network']);
$rows['Référence réseau'] = $get($cap, ['network_transaction_reference','id']);
$rows['Créé le'] = $fmtDate($get($cap, ['create_time']), $tz);
$rows['Mis à jour le'] = $fmtDate($get($cap, ['refundCapture(update_time']), $tz);
// Lien refund API (utile debug)
$refundLink = '';
foreach ((array)$get($cap, ['links'], []) as $lk) {
if (($lk['rel'] ?? '') === 'refund' && ($lk['method'] ?? '') === 'POST' && !empty($lk['href'])) {
$refundLink = $lk['href']; break;
}
}
if ($refundLink !== '') $rows['Lien remboursement (API)'] = $refundLink;
$html = '<table class="pp-capture" style="border-collapse:collapse;width:100%;max-width:900px;font-family:sans-serif;font-size:14px;margin-bottom:16px;">';
foreach ($rows as $label => $val) {
if ($val === '' || $val === null) continue;
$html .= '<tr>';
$html .= '<th style="text-align:left;vertical-align:top;border:1px solid #ddd;padding:8px;background:#f7f7f7;width:35%;">'.$h($label).'</th>';
if (is_string($val) && strpos($val, 'http') === 0) {
$html .= '<td style="border:1px solid #ddd;padding:8px;"><a href="'.$h($val).'" target="_blank" rel="noopener">'.$h($val).'</a></td>';
} else {
$html .= '<td style="border:1px solid #ddd;padding:8px;">'.$h($val).'</td>';
}
$html .= '</tr>';
}
$html .= '</table>';
// ------------------ SECTION REMBOURSEMENTS ------------------
// Normalise les items (acceptons 'id' ou 'refund_id', 'create_time' ou 'time')
// Normalise les items (acceptons 'id' ou 'refund_id', 'create_time' ou 'time')
// Normalise les items (acceptons 'id' ou 'refund_id', 'create_time' ou 'time')
$normRefunds = [];
foreach ((array)$refundItems as $rf) {
$rid = $get($rf, ['refund_id']) ?: $get($rf, ['id']);
$amtA = ['value' => $get($rf, ['amount'], 0), 'currency_code' => $get($rf, ['currency'],'')];
$stat = $get($rf, ['status']);
$time = $get($rf, ['create_time']) ?: $get($rf, ['time']);
// Notes
$notePending = $get($rf, ['note']); // ex.: "En attente de confirmation PayPal"
$noteToPayerHtml = $rf['note_to_payer_ui'] ?? ''; // ex.: "Note <u>TO</u> <u>PAYER</u> : ..."
$normRefunds[] = [
'id' => $rid,
'amountArr' => $amtA,
'amountVal' => $moneyVal($amtA),
'status' => $stat,
'time' => $time,
'note' => $notePending,
'note_to_payer_ui' => $noteToPayerHtml,
];
}
// Totaux (prend ceux fournis, sinon calcule)
$capCurrency = $get($cap, ['amount','currency_code'], '');
$capTotal = $moneyVal($get($cap, ['amount'], []));
$refTotal = isset($totals['refunded_total']) ? (float)$totals['refunded_total'] : 0.0;
if ($refTotal <= 0 && !empty($normRefunds)) {
foreach ($normRefunds as $nrf) $refTotal += (float)$nrf['amountVal'];
}
$currency = $totals['currency'] ?? $capCurrency;
$remaining = isset($totals['remaining'])
? (float)$totals['remaining']
: max(0, $capTotal - $refTotal);
// Affichage section
$html .= '<h3 style="font-family:sans-serif;margin:12px 0 6px;">Remboursements</h3>';
// Résumé des totaux
$html .= '<table class="pp-refunds-summary" style="border-collapse:collapse;width:100%;max-width:900px;font-family:sans-serif;font-size:14px;margin-bottom:10px;">';
$html .= '<tr><th style="text-align:left;border:1px solid #ddd;padding:8px;background:#f7f7f7;width:35%;">Total capturé</th>'
. '<td style="border:1px solid #ddd;padding:8px;text-align:right;font-weight:bold;">'.$h(number_format($capTotal,2,'.','').($currency?' '.$currency:'' )).'</td></tr>';
$html .= '<tr><th style="text-align:left;border:1px solid #ddd;padding:8px;background:#f7f7f7;">Total remboursé</th>'
. '<td style="border:1px solid #ddd;padding:8px;text-align:right;font-weight:bold;">'.$h(number_format($refTotal,2,'.','').($currency?' '.$currency:'' )).'</td></tr>';
$html .= '<tr><th style="text-align:left;border:1px solid #ddd;padding:8px;background:#f7f7f7;">Solde après remboursement</th>'
. '<td style="border:1px solid #ddd;padding:8px;text-align:right;font-weight:bold;">'.$h(number_format($remaining,2,'.','').($currency?' '.$currency:'' )).'</td></tr>';
$html .= '</table>';
// Détail des remboursements (si existants)
if (!empty($normRefunds)) {
$html .= '<table class="pp-refunds" style="border-collapse:collapse;width:100%;max-width:900px;font-family:sans-serif;font-size:14px;">';
$html .= '<tr style="background:#f7f7f7">'
. '<th style="text-align:left;border:1px solid #ddd;padding:8px;">Refund ID</th>'
. '<th style="text-align:left;border:1px solid #ddd;padding:8px;">Statut</th>'
. '<th style="text-align:right;border:1px solid #ddd;padding:8px;">Montant</th>'
. '<th style="text-align:left;border:1px solid #ddd;padding:8px;">Créé le</th>'
. '<th style="text-align:left;border:1px solid #ddd;padding:8px;">Note</th>' // ⬅️ NOUVEAU
. '<th style="text-align:left;border:1px solid #ddd;padding:8px;">Note TO PAYER</th>' // ⬅️ NOUVEAU
. '</tr>';
$statusMap = [
'S' => 'Effectué (COMPLETED)',
'P' => 'En attente (PENDING)',
'D' => 'Refusé/Échoué (DENIED/FAILED)',
'V' => 'Annulé/Void (VOIDED)',
'R' => 'Reversé (REVERSED)',
];
foreach ($normRefunds as $nrf) {
$statRaw = $nrf['status'] ?? '';
$statNice = $statusMap[$statRaw] ?? $statRaw;
$html .= '<tr>';
$html .= '<td style="border:1px solid #ddd;padding:8px;">'.$h($nrf['id']).'</td>';
$html .= '<td style="border:1px solid #ddd;padding:8px;">'.$h($statNice).'</td>';
$html .= '<td style="border:1px solid #ddd;padding:8px;text-align:right;">'.$h($moneyStr($nrf['amountArr'])).'</td>';
$html .= '<td style="border:1px solid #ddd;padding:8px;">'.$h($fmtDate($nrf['time'], $tz)).'</td>';
// Colonne "Note" (texte simple)
$html .= '<td style="border:1px solid #ddd;padding:8px;">'
. ($nrf['note'] !== '' ? $h($nrf['note']) : '')
. '</td>';
// Colonne "Note TO PAYER" (déjà formaté avec <u>…</u>, ne pas ré-échaper)
$html .= '<td style="border:1px solid #ddd;padding:8px;">'
. (!empty($nrf['note_to_payer_ui']) ? $nrf['note_to_payer_ui'] : '')
. '</td>';
$html .= '</tr>';
}
$html .= '</table>';
} else {
$html .= '<div style="font-family:sans-serif;font-size:14px;color:#555;margin:4px 0 0;">Aucun remboursement trouvé.</div>';
}
return $html;
}
function money2($v) {
return number_format((float)$v, 2, '.', '');
}
function statut_payement($eve_id){
global $objDatabase;
if($eve_id!=649){
$sqlPromoteur1 = "
SELECT
(SUM(a.ach_total) - SUM(a.fra_total) - COALESCE(SUM(rfm.ach_total), 0)) AS promoteur1
FROM
inscriptions_panier_acheteurs a
LEFT JOIN rapport_finances_membership rfm ON rfm.no_panier = a.no_panier
WHERE
a.sta_id = 3 AND a.pai_id <> 6 AND a.ach_total > 0 and a.eve_id=$eve_id
GROUP BY a.eve_id
ORDER BY a.eve_id
";
$tabPromoteur1 = $objDatabase->fxGetVar($sqlPromoteur1);
}else{
$sqlPromoteur1 = " SELECT SUM(montant) AS promoteur1
FROM (
SELECT
(SUM(a.ach_total) - SUM(a.fra_total) - COALESCE(SUM(rfm.ach_total), 0)) AS montant
FROM inscriptions_panier_acheteurs a
LEFT JOIN rapport_finances_membership rfm
ON rfm.no_panier = a.no_panier
WHERE a.sta_id = 3
AND a.pai_id <> 6
AND a.ach_total > 0
AND a.eve_id = 649
UNION ALL
SELECT
SUM(rfm.ach_total) AS montant
FROM rapport_finances_membership rfm
JOIN inscriptions_panier_acheteurs a2
ON rfm.no_panier = a2.no_panier
WHERE a2.sta_id = 3
AND a2.pai_id <> 6
AND rfm.ach_total > 0
AND rfm.eve_id_membership = 649
UNION ALL
SELECT
SUM(r.rem_montant * -1) AS montant
FROM remboursements r
WHERE r.rem_deleted = 0
AND r.eve_id = 649
AND 1 = 2
) x;
";
$tabPromoteur1 = $objDatabase->fxGetVar($sqlPromoteur1);
}
$sqlPromoteur2 = "
SELECT
(SUM(rfm.ach_total)) AS promoteur2
FROM
rapport_finances_membership rfm
JOIN inscriptions_panier_acheteurs a ON rfm.no_panier = a.no_panier
WHERE
a.sta_id = 3 AND a.pai_id <> 6 AND rfm.ach_total > 0 and a.eve_id=$eve_id
GROUP BY rfm.eve_id_membership
ORDER BY rfm.eve_id_membership
";
$tabPromoteur2 = $objDatabase->fxGetVar($sqlPromoteur2);
$sqlPromoteur3 = "
SELECT
SUM(r.rem_montant) AS remboursement
FROM
remboursements r
WHERE
r.rem_deleted = 0 AND r.tr_id = 2 and r.eve_id=$eve_id
GROUP BY r.eve_id
ORDER BY r.eve_id
";
$tabPromoteur3 = $objDatabase->fxGetVar($sqlPromoteur3);
$sqlPromoteur4 = "
SELECT
SUM(r.rem_montant) AS paiement
FROM
remboursements r
JOIN inscriptions_evenements e ON e.eve_id = r.eve_id
WHERE
r.rem_deleted = 0 AND r.tr_id = 1 and r.eve_id=$eve_id
GROUP BY e.eve_id
ORDER BY e.eve_id
";
$tabPromoteur4 = $objDatabase->fxGetVar($sqlPromoteur4);
$fltRemboursement = floatval($tabPromoteur3);
//echosl("Remboursement ".$fltRemboursement);
$fltPaiement = floatval($tabPromoteur4);
//echosl("Paiement ".$fltPaiement);
// corriger member
// $fltMontantPromoteur = floatval($tabPromoteur1[$eve_id]) + floatval($tabPromoteur2[$eve_id]);
$fltMontantPromoteur = floatval($tabPromoteur1) ;
//echosl("MontantPromoteur ".$fltMontantPromoteur);
$fltSolde = floatval($fltMontantPromoteur - ($fltPaiement + $fltRemboursement));
return $fltSolde ;
}
/**
* Renvoie le montant restant remboursable pour une capture PayPal.
*
* RÈGLES:
* - REFUNDED => 0.00
* - PARTIALLY_REFUNDED + 0.00 en BD => 0.00 (on attend que les refunds apparaissent)
* - Sinon: captured - somme(refunds visibles en BD), min=0.00
*
* @param object $paypal Ton SDK/service PayPal (doit exposer getCaptureDetails($captureId))
* @param string $captureId ID de capture PayPal (ex: "3G598638AN207873G")
* @param string &$currencyOut Mis à jour avec la devise (ex: "CAD")
* @return float Montant restant remboursable
* @throws RuntimeException si les détails de capture sont incomplets
*/
function getRefundableRemainingSimple($paypal, $captureId, &$currencyOut)
{
// 1) Détails de la capture
$cap = $paypal->getCaptureDetails($captureId);
if (!is_array($cap) || empty($cap['amount']['value']) || empty($cap['amount']['currency_code'])) {
throw new RuntimeException("Détails de capture indisponibles pour $captureId.");
}
$captured = (float) $cap['amount']['value'];
$currencyOut = (string) $cap['amount']['currency_code'];
$status = strtoupper($cap['status'] ?? '');
// 2) Si entièrement remboursée, on coupe court
if ($status === 'REFUNDED') {
return 0.0;
}
// 3) Total des remboursements visibles en BD locale
$totalRefunds = (float) sumRefundsFromLocalDB($captureId, $currencyOut);
// 4) Règle de latence: statut partiel mais rien en BD => 0.00
if ($status === 'PARTIALLY_REFUNDED' && $totalRefunds <= 0.0) {
return 0.0;
}
// 5) Calcul du restant
$remaining = $captured - $totalRefunds;
if ($remaining < 0) {
$remaining = 0.0;
}
// On arrondit à 2 décimales façon prix
return round($remaining, 2);
}
/**
* Total des remboursements déjà enregistrés pour une capture.
* Adapter l'implémentation à ton stack (CI3/CI4/PDO).
* Filtrage par devise et statuts "réussis".
*
* @param string $captureId
* @param string $currency (ex: "CAD")
* @return float
*/
function sumRefundsFromLocalDB($captureId, $currency)
{
// ==== EXEMPLE CodeIgniter 3 ====
// $CI =& get_instance();
// $CI->db->select('SUM(amount_value) AS total', false);
// $CI->db->from('paypal_refunds');
// $CI->db->where('capture_id', $captureId);
// $CI->db->where('amount_currency', $currency);
// $CI->db->where_in('status', ['COMPLETED','PARTIALLY_REFUNDED','REFUNDED']);
// // (Exclure explicitement les échecs/annulations si tu les stockes)
// // $CI->db->where_not_in('status', ['FAILED','CANCELLED']);
// $row = $CI->db->get()->row_array();
// return (float) ($row['total'] ?? 0);
// ==== EXEMPLE CodeIgniter 4 ====
// $db = \Config\Database::connect();
// $builder = $db->table('paypal_refunds');
// $builder->select('SUM(amount_value) AS total', false)
// ->where('capture_id', $captureId)
// ->where('amount_currency', $currency)
// ->whereIn('status', ['COMPLETED','PARTIALLY_REFUNDED','REFUNDED']);
// $row = $builder->get()->getRowArray();
// return (float) ($row['total'] ?? 0);
// ==== EXEMPLE PDO pur ====
// try {
// $pdo = getPdo(); // ta fonction pour récupérer le PDO
// $sql = "SELECT SUM(amount_value) AS total
// FROM paypal_refunds
// WHERE capture_id = :cap
// AND amount_currency = :cur
// AND status IN ('COMPLETED','PARTIALLY_REFUNDED','REFUNDED')";
// $stmt = $pdo->prepare($sql);
// $stmt->execute([':cap' => $captureId, ':cur' => $currency]);
// $total = $stmt->fetchColumn();
// return (float) ($total ?: 0);
// } catch (Throwable $e) {
// // En cas derreur BD, on retourne 0 (ou on relance selon ta politique)
// return 0.0;
// }
// Par défaut si tu colles ce fichier sans implémentation:
return 0.0;
}
/**
* Fusionne les remboursements PayPal avec la BD locale (clé = refund_id).
*
* @param array $paypal Tableau tel que retourné par ta fonction PayPal (avec clés: ok, capture_id, refunds[], totals, ...)
* @param PDO $pdo Connexion PDO vers la BD contenant la table des refunds (schéma fourni).
* @param string $table Nom de la table locale (par défaut 'paypal_refunds' si cest ton nom; adapte-le si besoin).
* @return array Même structure que $paypal, avec refunds[] enrichi + totaux recalculés.
*/
/**
* Fusionne les remboursements PayPal avec la BD locale (clé = refund_id).
*
* @param array $paypal Tableau tel que retourné par ta fonction PayPal (ok, capture_id, refunds[], totals, ...)
* @param object $objDatabase Ton objet DB déjà initialisé (doit exposer fxFetchAll($sql))
* @param string $table Nom de la table locale (par défaut 'paypal_refunds')
* @return array Même structure que $paypal, avec refunds[] enrichi + totaux recalculés.
*/
/**
* Enrichit le payload complet PayPal (qui contient 'delails' et 'refunds') avec les remboursements locaux.
* - Entrée: $payload tel que retourné par ton code (avec ['delails'] et ['refunds']).
* - Sortie: même payload, mêmes clés, mêmes valeurs, sauf:
* - $payload['refunds']['refunds'] est fusionné avec la BD locale (clé = refund_id)
* - $payload['refunds']['totals'] est recalculé d'après la liste fusionnée
*
* @param array $payload Payload complet PayPal (incluant 'delails' + 'refunds')
* @param object $objDatabase Ton objet DB (fxFetchAll($sql) dispo)
* @param string $table Nom de la table locale des refunds (par défaut 'paypal_refunds')
* @return array Payload complet, inchangé hors de la section 'refunds'
*/
/**
* Enrichit le payload complet PayPal (qui contient 'delails' et 'refunds') avec les remboursements locaux.
* - Entrée : $payload tel que retourné par ton code (avec ['delails'] et ['refunds']).
* - Sortie : même payload, mêmes clés/valeurs, SAUF :
* - $payload['refunds']['refunds'] fusionnée avec la BD locale (clé = refund_id)
* - $payload['refunds']['totals'] recalculée
* - DB : utilise $objDatabase->fxGetResults($sql) pour récupérer des listes.
* - Ajoute systématiquement la note TO PAYER depuis la BD :
* - 'note_to_payer' (brut)
* - 'note_to_payer_ui' : "Note <u>TO</u> <u>PAYER</u> : {valeur}"
*/
function reconcileRefundsWithLocal(array $payload ): array
{
// Copie du payload d'origine ; on ne modifiera QUE la section 'refunds'
$out = $payload;
global $objDatabase;
// -- Détails (clé 'delails' telle quelle ; fallback 'details' si jamais)
$details = [];
if (isset($payload['delails']) && is_array($payload['delails'])) {
$details = $payload['delails'];
} elseif (isset($payload['details']) && is_array($payload['details'])) {
$details = $payload['details'];
}
$table = 'inscriptions_panier_remboursement';
// -- Section refunds actuelle (préservée)
$sectionRefunds = isset($payload['refunds']) && is_array($payload['refunds']) ? $payload['refunds'] : [];
// -- Ancrages
$captureId = (string)(
$sectionRefunds['capture_id']
?? ($details['id'] ?? '')
);
$invoiceId = (string)(
$sectionRefunds['invoice_id']
?? ($details['invoice_id'] ?? '')
);
$orderId = (string)(
$sectionRefunds['order_id']
?? ($details['supplementary_data']['related_ids']['order_id'] ?? '')
);
$capCur = (string)(
$sectionRefunds['totals']['currency']
?? ($details['amount']['currency_code'] ?? '')
);
$capTotal = (float)(
$sectionRefunds['totals']['captured_total']
?? ($details['amount']['value'] ?? 0)
);
// Déterminer l'URL base pour 'refund_api' des items locaux
$anyHref = $details['links'][0]['href'] ?? '';
$apiBase = (strpos($anyHref, 'sandbox') !== false)
? 'https://api-m.sandbox.paypal.com'
: 'https://api-m.paypal.com';
// -- Liste PayPal de départ
$ppRefunds = (isset($sectionRefunds['refunds']) && is_array($sectionRefunds['refunds']))
? $sectionRefunds['refunds'] : [];
// Index par refund_id
$ppById = [];
$ppIds = [];
foreach ($ppRefunds as $r) {
$rid = (string)($r['refund_id'] ?? '');
if ($rid !== '') {
$ppById[$rid] = $r;
$ppIds[] = $rid;
}
}
// -- 1) Locaux pour refund_id connus par PayPal
$localById = [];
if (!empty($ppIds)) {
$idsIn = "'" . implode("','", array_map('addslashes', $ppIds)) . "'";
$sql = "SELECT * FROM `{$table}` WHERE `refund_id` IN ($idsIn)";
$rowsLocal = (array)$objDatabase->fxGetResults($sql);
foreach ($rowsLocal as $row) {
$localById[(string)$row['refund_id']] = $row;
}
}
// -- 2) Locaux supplémentaires (absents côté PayPal)
if (!empty($ppIds)) {
$idsNotIn = "'" . implode("','", array_map('addslashes', $ppIds)) . "'";
$sqlExtra = "SELECT * FROM `{$table}` WHERE `capture_id` = '" . addslashes($captureId) . "' AND `refund_id` NOT IN ($idsNotIn)";
} else {
$sqlExtra = "SELECT * FROM `{$table}` WHERE `capture_id` = '" . addslashes($captureId) . "'";
}
$localExtra = (array)$objDatabase->fxGetResults($sqlExtra);
// -- 3) Fusion : partir de PayPal et compléter avec la BD
$merged = [];
foreach ($ppById as $rid => $r) {
$loc = $localById[$rid] ?? null;
$m = $r; // base = PayPal
// Compléter les champs vides depuis la BD locale (sans écraser ce que PayPal fournit déjà)
if ((!isset($m['amount']) || $m['amount'] === '' || $m['amount'] === null) && $loc && $loc['amount_value'] !== null) {
$m['amount'] = (float)$loc['amount_value'];
}
if ((!isset($m['currency']) || $m['currency'] === '' || $m['currency'] === null) && $loc && !empty($loc['amount_currency'])) {
$m['currency'] = (string)$loc['amount_currency'];
}
if ((!isset($m['status']) || $m['status'] === '' || $m['status'] === null) && $loc) {
$m['status'] = (string)($loc['status'] ?? $loc['status_interne'] ?? ($m['status'] ?? ''));
}
if ((!isset($m['time']) || $m['time'] === '' || $m['time'] === null) && $loc) {
$t = $loc['update_time'] ?? $loc['create_time'] ?? null;
if ($t) {
$dt = new DateTime($t, new DateTimeZone('UTC'));
$m['time'] = $dt->format('Y-m-d\TH:i:s\Z');
}
}
if ((!isset($m['invoice_id']) || $m['invoice_id'] === '' || $m['invoice_id'] === null) && $loc && !empty($loc['invoice_id'])) {
$m['invoice_id'] = (string)$loc['invoice_id'];
}
// 🔹 Ajouter systématiquement la note TO PAYER (si dispo en BD)
if ($loc && isset($loc['note_to_payer']) && $loc['note_to_payer'] !== '') {
$m['note_to_payer'] = (string)$loc['note_to_payer'];
$m['note_to_payer_ui'] = htmlspecialchars((string)$loc['note_to_payer'], ENT_QUOTES, 'UTF-8');
}
// Bloc ms1 (trace locale non destructive)
if ($loc) {
$m['ms1'] = [
'status' => $loc['status'] ?? null,
'status_interne' => $loc['status_interne'] ?? null,
'amount_value' => $loc['amount_value'] ?? null,
'amount_currency' => $loc['amount_currency'] ?? null,
'invoice_id' => $loc['invoice_id'] ?? null,
'custom_id' => $loc['custom_id'] ?? null,
'acquirer_reference_number' => $loc['acquirer_reference_number'] ?? null,
'seller_gross' => $loc['seller_gross'] ?? null,
'seller_net' => $loc['seller_net'] ?? null,
'paypal_fee' => $loc['paypal_fee'] ?? null,
'note_to_payer' => $loc['note_to_payer'] ?? null,
'paypal_debug_id' => $loc['paypal_debug_id'] ?? null,
'idempotency_key' => $loc['idempotency_key'] ?? null,
'create_time' => $loc['create_time'] ?? null,
'update_time' => $loc['update_time'] ?? null,
// 'raw_json' => $loc['raw_json'] ?? null,
];
}
$merged[] = $m;
}
// -- 4) Ajouter les remboursements présents uniquement en local
foreach ($localExtra as $loc) {
$rid = (string)($loc['refund_id'] ?? '');
if ($rid === '') continue;
$amt = isset($loc['amount_value']) ? (float)$loc['amount_value'] : 0.0;
$cur = !empty($loc['amount_currency']) ? (string)$loc['amount_currency'] : $capCur;
$dt = null;
if (!empty($loc['update_time']) || !empty($loc['create_time'])) {
$raw = $loc['update_time'] ?: $loc['create_time'];
$dt = (new DateTime($raw, new DateTimeZone('UTC')))->format('Y-m-d\TH:i:s\Z');
}
$item = [
'refund_id' => $rid,
'amount' => abs($amt),
'currency' => $cur,
'status' => 'PENDING', // en attente de PayPal
'note' => 'En attente de confirmation PayPal',
'time' => $dt,
'paypal_reference_id' => $captureId,
'reference_type' => 'CAPTURE',
'related_transaction' => null,
'event_code' => null,
'classification' => [],
'invoice_id' => (string)($loc['invoice_id'] ?? ''),
'refund_api' => ($rid ? ($apiBase . '/v2/payments/refunds/' . $rid) : null),
'ms1' => [
'status' => $loc['status'] ?? null,
'status_interne' => $loc['status_interne'] ?? null,
'amount_value' => $loc['amount_value'] ?? null,
'amount_currency' => $loc['amount_currency'] ?? null,
'invoice_id' => $loc['invoice_id'] ?? null,
'custom_id' => $loc['custom_id'] ?? null,
'acquirer_reference_number' => $loc['acquirer_reference_number'] ?? null,
'seller_gross' => $loc['seller_gross'] ?? null,
'seller_net' => $loc['seller_net'] ?? null,
'paypal_fee' => $loc['paypal_fee'] ?? null,
'note_to_payer' => $loc['note_to_payer'] ?? null,
'paypal_debug_id' => $loc['paypal_debug_id'] ?? null,
'idempotency_key' => $loc['idempotency_key'] ?? null,
'create_time' => $loc['create_time'] ?? null,
'update_time' => $loc['update_time'] ?? null,
],
];
// 🔹 Ajouter systématiquement la note TO PAYER si dispo
if (isset($loc['note_to_payer']) && $loc['note_to_payer'] !== '') {
$item['note_to_payer'] = (string)$loc['note_to_payer'];
$item['note_to_payer_ui'] = htmlspecialchars((string)$loc['note_to_payer'], ENT_QUOTES, 'UTF-8');
}
$merged[] = $item;
}
// -- 5) Recalcul des totaux
$refunded = 0.0;
foreach ($merged as $m) {
$refunded += (float)($m['amount'] ?? 0);
}
$remaining = max(0, (float)$capTotal - $refunded);
// -- 6) Sortie : reconstruire la section refunds en conservant les autres clés d'origine
$sectionOut = $sectionRefunds; // copie intégrale
if (!isset($sectionOut['ok'])) $sectionOut['ok'] = 1;
if (!isset($sectionOut['capture_id'])) $sectionOut['capture_id'] = $captureId;
if (!isset($sectionOut['invoice_id'])) $sectionOut['invoice_id'] = $invoiceId;
if (!isset($sectionOut['order_id'])) $sectionOut['order_id'] = $orderId;
if (!isset($sectionOut['errors'])) $sectionOut['errors'] = [];
$sectionOut['refunds'] = $merged;
$sectionOut['totals'] = [
'currency' => $capCur,
'captured_total' => (float)number_format((float)$capTotal, 2, '.', ''),
'refunded_total' => (float)number_format((float)$refunded, 2, '.', ''),
'remaining' => (float)number_format((float)$remaining, 2, '.', ''),
];
$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
{
$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)]);
}
function h($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }