788 lines
30 KiB
PHP
788 lines
30 KiB
PHP
<?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 d’extraire debug_id du message d’exception 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 n’existe 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 n’explose 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 l’API 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 d’erreur 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 c’est 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'); }
|