541 lines
22 KiB
PHP
541 lines
22 KiB
PHP
<?php
|
||
global $vPaypal_devise,$vPaypaladv_SANDBOX;
|
||
/**
|
||
*
|
||
* This PayPal Checkout API handler class is a custom PHP library to handle the PayPal REST API calls.
|
||
*
|
||
* @class PaypalCheckout
|
||
* @author CodexWorld
|
||
* @link https://www.codexworld.com
|
||
* @version 1.0
|
||
*/
|
||
|
||
// Include the configuration file
|
||
include_once 'config.php';
|
||
|
||
class PaypalCheckout
|
||
{
|
||
public $paypalAuthAPI = PAYPAL_SANDBOX ? 'https://api-m.sandbox.paypal.com/v1/oauth2/token' : 'https://api-m.paypal.com/v1/oauth2/token';
|
||
public $paypalAPI = PAYPAL_SANDBOX ? 'https://api-m.sandbox.paypal.com/v2/checkout' : 'https://api-m.paypal.com/v2/checkout';
|
||
public $paypalClientID = PAYPAL_SANDBOX ? PAYPAL_SANDBOX_CLIENT_ID : PAYPAL_PROD_CLIENT_ID;
|
||
private $paypalSecret = PAYPAL_SANDBOX ? PAYPAL_SANDBOX_CLIENT_SECRET : PAYPAL_PROD_CLIENT_SECRET;
|
||
|
||
public function generateAccessToken()
|
||
{
|
||
|
||
$ch = curl_init();
|
||
curl_setopt($ch, CURLOPT_VERBOSE, 1);
|
||
curl_setopt($ch, CURLOPT_URL, $this->paypalAuthAPI);
|
||
curl_setopt($ch, CURLOPT_HEADER, false);
|
||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||
curl_setopt($ch, CURLOPT_POST, true);
|
||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||
curl_setopt($ch, CURLOPT_USERPWD, $this->paypalClientID . ":" . $this->paypalSecret);
|
||
curl_setopt($ch, CURLOPT_POSTFIELDS, "grant_type=client_credentials");
|
||
$auth_response = json_decode(curl_exec($ch));
|
||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
|
||
if ($http_code != 200 && !empty($auth_response->error)) {
|
||
throw new Exception('Failed to generate Access Token: ' . $auth_response->error . ' >>> ' . $auth_response->error_description);
|
||
}
|
||
|
||
if (!empty($auth_response)) {
|
||
return $auth_response->access_token;
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
public function createOrder($productInfo, $paymentSource)
|
||
{
|
||
$accessToken = $this->generateAccessToken();
|
||
if (empty($accessToken)) {
|
||
return false;
|
||
} else {
|
||
$postParams = $productInfo;
|
||
|
||
$ch = curl_init();
|
||
curl_setopt($ch, CURLOPT_URL, $this->paypalAPI . '/orders/');
|
||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
|
||
|
||
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'Authorization: Bearer ' . $accessToken));
|
||
curl_setopt($ch, CURLOPT_POST, true);
|
||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postParams));
|
||
|
||
$api_resp = curl_exec($ch);
|
||
$api_data = json_decode($api_resp, true);
|
||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
|
||
if ($http_code != 200 && $http_code != 201 && $http_code != 422 ) {
|
||
throw new Exception('Failed to create Order sl (' . $http_code . '): ' . $api_resp . json_encode($postParams));
|
||
}
|
||
|
||
return !empty($api_data) && ($http_code == 200 || $http_code == 201 || $http_code == 422) ? $api_data : false;
|
||
}
|
||
}
|
||
|
||
public function captureOrder($orderId)
|
||
{
|
||
$accessToken = $this->generateAccessToken();
|
||
if (empty($accessToken)) {
|
||
return false;
|
||
} else {
|
||
$ch = curl_init();
|
||
curl_setopt($ch, CURLOPT_URL, $this->paypalAPI . '/orders/' . $orderId . '/capture');
|
||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
|
||
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'Authorization: Bearer ' . $accessToken));
|
||
|
||
curl_setopt($ch, CURLOPT_POST, true);
|
||
$api_resp = curl_exec($ch);
|
||
$api_data = json_decode($api_resp, true);
|
||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
$curlsl_code = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
|
||
// $result = urldecode ($ch );
|
||
|
||
|
||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
|
||
|
||
if ($http_code != 200 && $http_code != 201) {
|
||
|
||
throw new Exception('Failed to create Order sl4(' . $http_code . '): ' . '1- ' . $api_resp);
|
||
}
|
||
|
||
curl_close($ch);
|
||
return !empty($api_data) && ($http_code == 200 || $http_code == 201) ? $api_data : false;
|
||
}
|
||
}
|
||
public function showOrder($orderId)
|
||
{
|
||
$accessToken = $this->generateAccessToken();
|
||
if (empty($accessToken)) {
|
||
return false;
|
||
} else {
|
||
$ch = curl_init();
|
||
curl_setopt($ch, CURLOPT_URL, $this->paypalAPI . '/payments/authorizations/' . $orderId );
|
||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
|
||
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'Authorization: Bearer ' . $accessToken));
|
||
|
||
curl_setopt($ch, CURLOPT_POST, true);
|
||
|
||
$api_resp = curl_exec($ch);
|
||
$api_data = json_decode($api_resp, true);
|
||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
$curlsl_code = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
|
||
// $result = urldecode ($ch );
|
||
|
||
|
||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
|
||
|
||
if ($http_code != 200 && $http_code != 201) {
|
||
|
||
throw new Exception('Failed showOrder(' . $http_code . '): ' . '1- ' . $api_resp);
|
||
}
|
||
|
||
curl_close($ch);
|
||
return !empty($api_data) && ($http_code == 200 || $http_code == 201) ? $api_data : false;
|
||
}
|
||
}
|
||
public function setInvoiceId($orderId,$invoiceId)
|
||
{
|
||
$accessToken = $this->generateAccessToken(); // même mécanique que createOrder/captureOrder
|
||
$url = $this->paypalAPI . '/orders/' . rawurlencode($orderId);
|
||
|
||
$my_custom_id = $invoiceId;
|
||
$ops = [
|
||
['op' => 'add', 'path' => "/purchase_units/@reference_id=='default'/custom_id", 'value' => $my_custom_id],
|
||
];
|
||
|
||
$ch = curl_init($url);
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_CUSTOMREQUEST => 'PATCH',
|
||
CURLOPT_HTTPHEADER => [
|
||
'Authorization: Bearer ' . $accessToken,
|
||
'Content-Type: application/json'
|
||
],
|
||
CURLOPT_POSTFIELDS => json_encode($ops),
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_TIMEOUT => 20,
|
||
]);
|
||
|
||
$body = curl_exec($ch);
|
||
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
$curlerr = curl_error($ch);
|
||
curl_close($ch);
|
||
|
||
if ($curlerr) {
|
||
throw new \Exception('PayPal PATCH error (cURL): ' . $curlerr);
|
||
}
|
||
|
||
// PayPal renvoie 204 No Content quand le PATCH réussit
|
||
if ($httpcode === 204 || $httpcode === 200) {
|
||
return true;
|
||
}
|
||
|
||
// Si échec: on lève une exception avec le code HTTP et, si présent, le corps
|
||
throw new \Exception('PayPal PATCH /orders failed (HTTP ' . $httpcode . '): ' . ($body ?: 'no body'));
|
||
}
|
||
|
||
/**
|
||
* Rembourse une capture PayPal (total ou partiel).
|
||
*
|
||
* @param string $captureId ID de la capture (ex: $captures[0]['id'])
|
||
* @param string|null $amount Montant du remboursement (null = remboursement total)
|
||
* @param string|null $currency Devise (ex: 'CAD'); si null on prend la devise de la classe/config
|
||
* @param string|null $note Note visible par le payer (optionnel)
|
||
* @param string|null $invoiceId ID de facture de référence côté PayPal (optionnel)
|
||
* @param string|null $idempotency Clé unique pour éviter les doubles remboursements (optionnel)
|
||
* @return array Réponse PayPal décodée
|
||
* @throws \Exception En cas d’erreur HTTP ou cURL
|
||
*/
|
||
public function refundCapture(
|
||
string $captureId,
|
||
?string $amount = null,
|
||
?string $currency = null,
|
||
?string $note = null,
|
||
?string $invoiceId = null,
|
||
?string $idempotency = null
|
||
): array {
|
||
if ($captureId === '') {
|
||
throw new \InvalidArgumentException('captureId requis');
|
||
}
|
||
|
||
$accessToken = $this->generateAccessToken();
|
||
if (empty($accessToken)) {
|
||
throw new \Exception('Impossible de générer le token d’accès PayPal');
|
||
}
|
||
|
||
// Devise par défaut
|
||
if ($currency === null || $currency === '') {
|
||
$currency = $this->currency ?? (function () {
|
||
global $vPaypal_devise;
|
||
return $vPaypal_devise ?? 'CAD';
|
||
})();
|
||
}
|
||
|
||
$payload = [];
|
||
if ($amount !== null && $amount !== '') {
|
||
$payload['amount'] = [
|
||
'value' => number_format((float)$amount, 2, '.', ''),
|
||
'currency_code' => $currency,
|
||
];
|
||
}
|
||
if ($note) $payload['note_to_payer'] = mb_strimwidth($note, 0, 255, '');
|
||
if ($invoiceId) $payload['invoice_id'] = $invoiceId;
|
||
|
||
// ✅ Base API ABSOLUE selon sandbox/live
|
||
$base = (strpos($this->paypalAPI, 'sandbox') !== false)
|
||
? 'https://api-m.sandbox.paypal.com'
|
||
: 'https://api-m.paypal.com';
|
||
|
||
$url = $base . '/v2/payments/captures/' . rawurlencode($captureId) . '/refund';
|
||
|
||
$headers = [
|
||
'Content-Type: application/json',
|
||
'Authorization: Bearer ' . $accessToken,
|
||
];
|
||
if ($idempotency) $headers[] = 'PayPal-Request-Id: ' . $idempotency;
|
||
|
||
$ch = curl_init();
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_URL => $url,
|
||
CURLOPT_RETURNTRANSFER => 1,
|
||
CURLOPT_SSL_VERIFYPEER => false,
|
||
CURLOPT_HTTPHEADER => $headers,
|
||
CURLOPT_POST => true,
|
||
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
||
// (Optionnel) pour loguer le PayPal-Debug-Id:
|
||
// CURLOPT_HEADER => true,
|
||
]);
|
||
|
||
$resp = curl_exec($ch);
|
||
$curlerr = curl_error($ch);
|
||
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
|
||
if ($curlerr) throw new \Exception('PayPal refund error (cURL): ' . $curlerr);
|
||
|
||
$data = json_decode($resp, true);
|
||
|
||
if ($httpcode >= 200 && $httpcode < 300) {
|
||
return $data ?: ['status' => 'OK'];
|
||
}
|
||
|
||
// Affiche l'erreur JSON si dispo; sinon le body brut
|
||
$msg = $resp ?: 'no body';
|
||
throw new \Exception('PayPal refund failed (HTTP ' . $httpcode . '): ' . $msg);
|
||
}
|
||
|
||
public function getRefundDetails(string $refundId): array {
|
||
if ($refundId === '') throw new \InvalidArgumentException('refundId requis');
|
||
$accessToken = $this->generateAccessToken();
|
||
if (empty($accessToken)) throw new \Exception('Impossible de générer le token d’accès PayPal');
|
||
|
||
$url = rtrim($this->paypalAPI, '/') . '/payments/refunds/' . rawurlencode($refundId);
|
||
$ch = curl_init();
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_URL => $url,
|
||
CURLOPT_RETURNTRANSFER => 1,
|
||
CURLOPT_SSL_VERIFYPEER => false,
|
||
CURLOPT_HTTPHEADER => [
|
||
'Content-Type: application/json',
|
||
'Authorization: Bearer ' . $accessToken,
|
||
],
|
||
]);
|
||
$resp = curl_exec($ch);
|
||
$curlerr = curl_error($ch);
|
||
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
|
||
if ($curlerr) throw new \Exception('PayPal getRefundDetails error (cURL): ' . $curlerr);
|
||
$data = json_decode($resp, true);
|
||
if ($httpcode >= 200 && $httpcode < 300) return $data ?: [];
|
||
throw new \Exception('PayPal getRefundDetails failed (HTTP ' . $httpcode . '): ' . ($resp ?: 'no body'));
|
||
}
|
||
public function getCaptureDetails(string $captureId): array {
|
||
if ($captureId === '') throw new \InvalidArgumentException('captureId requis');
|
||
|
||
$accessToken = $this->generateAccessToken();
|
||
if (empty($accessToken)) throw new \Exception('Impossible de générer le token d’accès PayPal');
|
||
|
||
// Base API absolue (voir section 2)
|
||
$base = (strpos($this->paypalAPI, 'sandbox') !== false)
|
||
? 'https://api-m.sandbox.paypal.com'
|
||
: 'https://api-m.paypal.com';
|
||
|
||
$url = $base . '/v2/payments/captures/' . rawurlencode($captureId);
|
||
|
||
$ch = curl_init();
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_URL => $url,
|
||
CURLOPT_RETURNTRANSFER => 1,
|
||
CURLOPT_SSL_VERIFYPEER => false,
|
||
CURLOPT_HTTPHEADER => [
|
||
'Content-Type: application/json',
|
||
'Authorization: Bearer ' . $accessToken,
|
||
],
|
||
]);
|
||
$resp = curl_exec($ch);
|
||
$curlerr = curl_error($ch);
|
||
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
|
||
if ($curlerr) throw new \Exception('PayPal getCaptureDetails error (cURL): ' . $curlerr);
|
||
|
||
$data = json_decode($resp, true);
|
||
if ($httpcode >= 200 && $httpcode < 300) return $data ?: [];
|
||
throw new \Exception('getCaptureDetails failed (HTTP ' . $httpcode . '): ' . ($resp ?: 'no body'));
|
||
}
|
||
|
||
/**
|
||
* Retourne les remboursements liés à une capture (tableau, pas d'affichage).
|
||
* - Cherche via Transaction Search autour de la date de la capture.
|
||
* - Inclut les écritures NON encore comptabilisées (PENDING).
|
||
* - Reconnaît plusieurs codes d'événements de refund (pas seulement T1107).
|
||
*
|
||
* @param array $capture tableau /v2/payments/captures/{id}
|
||
* @param int $hoursBefore fenêtre avant create_time (défaut 24h)
|
||
* @param int $hoursAfter fenêtre après update_time (défaut 120h)
|
||
* @return array { ok, capture_id, invoice_id, order_id, refunds[], totals{}, errors[] }
|
||
* @throws Exception
|
||
*/
|
||
public function getRefundsForCapture(array $capture, int $hoursBefore = 24, int $hoursAfter = 120): array
|
||
{
|
||
$errors = [];
|
||
$capId = $capture['id'] ?? '';
|
||
$invId = $capture['invoice_id'] ?? '';
|
||
$ordId = $capture['supplementary_data']['related_ids']['order_id'] ?? '';
|
||
$capAmt = isset($capture['amount']['value']) ? (float)$capture['amount']['value'] : 0.0;
|
||
$capCur = $capture['amount']['currency_code'] ?? '';
|
||
$tCreate = $capture['create_time'] ?? '';
|
||
$tUpdate = $capture['update_time'] ?? $tCreate;
|
||
|
||
if ($capId === '' || $tCreate === '') {
|
||
throw new \InvalidArgumentException('Capture id ou create_time manquants');
|
||
}
|
||
|
||
// Fenêtre de recherche
|
||
$t0 = new \DateTime($tCreate);
|
||
$tU = new \DateTime($tUpdate);
|
||
$startIso = $t0->sub(new \DateInterval('PT'.max(0,$hoursBefore).'H'))->format(\DateTime::ATOM);
|
||
$endIso = $tU->add(new \DateInterval('PT'.max(1,$hoursAfter).'H'))->format(\DateTime::ATOM);
|
||
|
||
// OAuth + base api-m
|
||
$token = $this->generateAccessToken();
|
||
if (!$token) throw new \Exception('Token PayPal indisponible');
|
||
$base = (strpos((string)$this->paypalAPI, 'sandbox') !== false)
|
||
? 'https://api-m.sandbox.paypal.com'
|
||
: 'https://api-m.paypal.com';
|
||
|
||
// Fetch paginé /v1/reporting/transactions
|
||
$fetchTx = function(array $params) use ($base, $token): array {
|
||
$page = 1; $pageSize = $params['page_size'] ?? 200; $all = [];
|
||
do {
|
||
$params['page'] = $page;
|
||
$url = $base . '/v1/reporting/transactions?' . http_build_query($params);
|
||
$ch = curl_init($url);
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_HTTPHEADER => ['Authorization: Bearer '.$token, 'Accept: application/json'],
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_TIMEOUT => 60,
|
||
]);
|
||
$res = curl_exec($ch);
|
||
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
$err = curl_error($ch);
|
||
curl_close($ch);
|
||
if ($http !== 200) throw new \Exception("Transaction Search HTTP $http: ".($res ?: $err ?: ''));
|
||
$json = json_decode($res, true);
|
||
$rows = $json['transaction_details'] ?? [];
|
||
$all = array_merge($all, $rows);
|
||
$page++;
|
||
} while (count($rows) >= $pageSize);
|
||
return $all;
|
||
};
|
||
|
||
// On inclut aussi les écritures non "balance-affecting" pour capter les PENDING
|
||
$rows = $fetchTx([
|
||
'start_date' => $startIso,
|
||
'end_date' => $endIso,
|
||
'fields' => 'all',
|
||
'page_size' => 200,
|
||
'balance_affecting_records_only' => 'N',
|
||
]);
|
||
|
||
// Codes d’événements refund (selon tenants)
|
||
$refundCodes = ['T1107','T1108','T1109','T1110','T0407'];
|
||
|
||
$refunds = [];
|
||
$seen = []; // dédoublonnage par refund_id
|
||
|
||
foreach ($rows as $row) {
|
||
$ti = $row['transaction_info'] ?? [];
|
||
$eid = $ti['transaction_id'] ?? '';
|
||
$ev = $ti['transaction_event_code'] ?? '';
|
||
$subj = strtolower($ti['transaction_subject'] ?? '');
|
||
$amt = isset($ti['transaction_amount']['value']) ? (float)$ti['transaction_amount']['value'] : null;
|
||
$cur = $ti['transaction_amount']['currency_code'] ?? null;
|
||
$stat = $ti['transaction_status'] ?? '';
|
||
$ts = $ti['transaction_updated_date'] ?? ($ti['transaction_initiation_date'] ?? '');
|
||
$pref = $ti['paypal_reference_id'] ?? ''; // souvent = capture_id
|
||
$inv = $ti['invoice_id'] ?? '';
|
||
|
||
// Relations additionnelles
|
||
$parents = [];
|
||
foreach ((array)($ti['extended_related_transactions'] ?? []) as $rt) {
|
||
if (!empty($rt['parent_transaction_id'])) $parents[] = $rt['parent_transaction_id'];
|
||
if (!empty($rt['paypal_reference_id'])) $parents[] = $rt['paypal_reference_id'];
|
||
}
|
||
|
||
// 1) Doit ressembler à un refund
|
||
$looksLikeRefund = in_array($ev, $refundCodes, true) || strpos($subj, 'refund') !== false;
|
||
if (!$looksLikeRefund) continue;
|
||
|
||
// 2) ANCRAGE OBLIGATOIRE : capture_id OU (si dispo) invoice_id
|
||
$strongMatch =
|
||
($pref === $capId) ||
|
||
in_array($capId, $parents, true) ||
|
||
($invId !== '' && $inv === $invId);
|
||
|
||
if (!$strongMatch) continue; // ← plus de match “montant négatif seul”
|
||
|
||
// 3) Normalisation
|
||
$rid = $eid;
|
||
if ($rid && !isset($seen[$rid])) {
|
||
$seen[$rid] = true;
|
||
$refunds[] = [
|
||
'refund_id' => $rid,
|
||
'amount' => abs((float)($amt ?? 0)),
|
||
'currency' => $cur ?: $capCur,
|
||
'status' => $stat, // S/P/…
|
||
'time' => $ts,
|
||
'paypal_reference_id' => $pref,
|
||
'event_code' => $ev,
|
||
];
|
||
}
|
||
}
|
||
|
||
// Totaux
|
||
$refunded = 0.0;
|
||
foreach ($refunds as $r) $refunded += (float)$r['amount'];
|
||
$remaining = max(0, $capAmt - $refunded);
|
||
|
||
return [
|
||
'ok' => true,
|
||
'capture_id' => $capId,
|
||
'invoice_id' => $invId,
|
||
'order_id' => $ordId,
|
||
'refunds' => $refunds,
|
||
'totals' => [
|
||
'currency' => $capCur,
|
||
'captured_total' => (float)number_format($capAmt, 2, '.', ''),
|
||
'refunded_total' => (float)number_format($refunded, 2, '.', ''),
|
||
'remaining' => (float)number_format($remaining, 2, '.', ''),
|
||
],
|
||
'errors' => $errors,
|
||
];
|
||
}
|
||
public function debugReportAroundCapture(array $capture, int $hoursBefore = 24, int $hoursAfter = 120): array
|
||
{
|
||
$capId = $capture['id'] ?? '';
|
||
$invId = $capture['invoice_id'] ?? '';
|
||
$tCreate = $capture['create_time'] ?? '';
|
||
$tUpdate = $capture['update_time'] ?? $tCreate;
|
||
|
||
$t0 = new \DateTime($tCreate);
|
||
$tU = new \DateTime($tUpdate);
|
||
$startIso = $t0->sub(new \DateInterval('PT'.max(0,$hoursBefore).'H'))->format(\DateTime::ATOM);
|
||
$endIso = $tU->add(new \DateInterval('PT'.max(1,$hoursAfter).'H'))->format(\DateTime::ATOM);
|
||
|
||
$token = $this->generateAccessToken();
|
||
$base = (strpos((string)$this->paypalAPI, 'sandbox') !== false)
|
||
? 'https://api-m.sandbox.paypal.com'
|
||
: 'https://api-m.paypal.com';
|
||
|
||
$url = $base.'/v1/reporting/transactions?'.http_build_query([
|
||
'start_date' => $startIso,
|
||
'end_date' => $endIso,
|
||
'fields' => 'all',
|
||
'page_size' => 200,
|
||
'balance_affecting_records_only' => 'N',
|
||
]);
|
||
$ch = curl_init($url);
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_HTTPHEADER => ['Authorization: Bearer '.$token, 'Accept: application/json'],
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_TIMEOUT => 60,
|
||
]);
|
||
$res = curl_exec($ch);
|
||
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
if ($http !== 200) throw new \Exception("Transaction Search HTTP $http: ".($res ?: ''));
|
||
|
||
$json = json_decode($res, true);
|
||
$rows = $json['transaction_details'] ?? [];
|
||
|
||
$out = [];
|
||
foreach ($rows as $row) {
|
||
$ti = $row['transaction_info'] ?? [];
|
||
$out[] = [
|
||
'id' => $ti['transaction_id'] ?? '',
|
||
'event' => $ti['transaction_event_code'] ?? '',
|
||
'subject' => $ti['transaction_subject'] ?? '',
|
||
'amount' => $ti['transaction_amount']['value'] ?? '',
|
||
'currency' => $ti['transaction_amount']['currency_code'] ?? '',
|
||
'D/C' => $ti['debit_or_credit'] ?? '',
|
||
'ref' => $ti['paypal_reference_id'] ?? '',
|
||
'invoice' => $ti['invoice_id'] ?? '',
|
||
'status' => $ti['transaction_status'] ?? '',
|
||
'time' => $ti['transaction_updated_date'] ?? ($ti['transaction_initiation_date'] ?? ''),
|
||
];
|
||
}
|
||
return ['capture_id'=>$capId,'invoice_id'=>$invId,'window'=>[$startIso,$endIso],'rows'=>$out];
|
||
}
|
||
|
||
}
|