1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-14 07:02:34 +01:00
invoiceninja/app/PaymentDrivers/PayPalPPCPPaymentDriver.php

613 lines
19 KiB
PHP
Raw Normal View History

2023-10-16 03:05:19 +02:00
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
2024-04-12 06:15:41 +02:00
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
2023-10-16 03:05:19 +02:00
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\PaymentDrivers;
2023-12-13 07:10:29 +01:00
use Str;
use Carbon\Carbon;
2023-10-16 03:05:19 +02:00
use App\Models\Invoice;
use App\Models\SystemLog;
2023-12-13 07:10:29 +01:00
use App\Models\GatewayType;
use App\Models\PaymentType;
use Illuminate\Http\Request;
use App\Jobs\Util\SystemLogger;
2023-10-16 03:05:19 +02:00
use App\Utils\Traits\MakesHash;
2023-12-13 07:10:29 +01:00
use App\Exceptions\PaymentFailed;
2023-10-16 03:05:19 +02:00
use Illuminate\Support\Facades\Http;
2023-12-13 07:10:29 +01:00
use App\PaymentDrivers\PayPal\PayPalWebhook;
2023-10-16 03:05:19 +02:00
class PayPalPPCPPaymentDriver extends BaseDriver
{
use MakesHash;
public $token_billing = false;
public $can_authorise_credit_card = false;
private $omnipay_gateway;
private float $fee = 0;
public const SYSTEM_LOG_TYPE = SystemLog::TYPE_PAYPAL_PPCP;
private string $api_endpoint_url = '';
private string $paypal_payment_method = '';
2023-10-18 04:31:04 +02:00
private ?int $gateway_type_id = null;
2023-10-16 06:48:45 +02:00
protected mixed $access_token = null;
2023-10-16 03:05:19 +02:00
protected ?Carbon $token_expiry = null;
private array $funding_options = [
3 => 'paypal',
1 => 'card',
25 => 'venmo',
// 9 => 'sepa',
// 12 => 'bancontact',
// 17 => 'eps',
// 15 => 'giropay',
// 13 => 'ideal',
// 26 => 'mercadopago',
// 27 => 'mybank',
2023-12-07 09:32:08 +01:00
28 => 'paylater',
2023-10-16 03:05:19 +02:00
// 16 => 'p24',
// 7 => 'sofort'
];
2023-10-16 06:48:45 +02:00
/**
2023-11-26 08:41:42 +01:00
* Return an array of
2023-10-16 06:48:45 +02:00
* enabled gateway payment methods
*
* @return array
*/
2023-10-16 03:05:19 +02:00
public function gatewayTypes(): array
{
2023-10-16 07:23:04 +02:00
return collect($this->company_gateway->fees_and_limits)
2023-11-26 08:41:42 +01:00
->filter(function ($fee) {
2023-10-16 07:23:04 +02:00
return $fee->is_enabled;
2023-11-26 08:41:42 +01:00
})->map(function ($fee, $key) {
2023-10-16 07:23:04 +02:00
return (int)$key;
})->toArray();
2024-01-14 05:05:00 +01:00
2023-10-18 04:31:04 +02:00
}
private function getPaymentMethod($gateway_type_id): int
{
$method = PaymentType::PAYPAL;
2023-11-26 08:41:42 +01:00
match($gateway_type_id) {
2023-10-18 04:31:04 +02:00
"1" => $method = PaymentType::CREDIT_CARD_OTHER,
2023-11-22 04:06:51 +01:00
"3" => $method = PaymentType::PAYPAL,
2023-10-18 04:31:04 +02:00
"25" => $method = PaymentType::VENMO,
2023-12-07 09:32:08 +01:00
"28" => $method = PaymentType::PAY_LATER,
2023-10-18 04:31:04 +02:00
};
return $method;
}
2024-01-14 05:05:00 +01:00
private function getFundingOptions(): string
2023-10-18 04:31:04 +02:00
{
$enums = [
1 => 'card',
2023-11-22 04:06:51 +01:00
3 => 'paypal',
2023-10-18 04:31:04 +02:00
25 => 'venmo',
2023-12-07 09:32:08 +01:00
28 => 'paylater',
2023-10-18 04:31:04 +02:00
// 9 => 'sepa',
// 12 => 'bancontact',
// 17 => 'eps',
// 15 => 'giropay',
// 13 => 'ideal',
// 26 => 'mercadopago',
// 27 => 'mybank',
// 28 => 'paylater',
// 16 => 'p24',
// 7 => 'sofort'
];
$funding_options = '';
foreach($this->company_gateway->fees_and_limits as $key => $value) {
if($value->is_enabled) {
2024-01-14 05:05:00 +01:00
$funding_options .= $enums[$key].',';
2023-10-18 04:31:04 +02:00
}
}
return rtrim($funding_options, ',');
2023-10-16 03:05:19 +02:00
}
/**
* Initialize the Paypal gateway.
2023-11-26 08:41:42 +01:00
*
2023-10-16 03:05:19 +02:00
* Attempt to generate and return the access token.
*
* @return self
*/
public function init(): self
{
2023-12-13 10:04:52 +01:00
$this->api_endpoint_url = 'https://api-m.paypal.com';
// $this->api_endpoint_url = 'https://api-m.sandbox.paypal.com';
$secret = config('ninja.paypal.secret');
$client_id = config('ninja.paypal.client_id');
2023-10-16 03:05:19 +02:00
2023-11-26 08:41:42 +01:00
if($this->access_token && $this->token_expiry && $this->token_expiry->isFuture()) {
2023-10-16 03:05:19 +02:00
return $this;
2023-11-26 08:41:42 +01:00
}
2023-10-16 03:05:19 +02:00
$response = Http::withBasicAuth($client_id, $secret)
->withHeaders(['Content-Type' => 'application/x-www-form-urlencoded'])
->withQueryParameters(['grant_type' => 'client_credentials'])
->post("{$this->api_endpoint_url}/v1/oauth2/token");
if($response->successful()) {
$this->access_token = $response->json()['access_token'];
$this->token_expiry = now()->addSeconds($response->json()['expires_in'] - 60);
} else {
throw new PaymentFailed('Unable to gain access token from Paypal. Check your configuration', 401);
}
return $this;
}
2024-01-14 05:05:00 +01:00
2023-12-13 07:10:29 +01:00
/**
* Payment method setter
*
* @param mixed $payment_method_id
* @return self
*/
public function setPaymentMethod($payment_method_id): self
2023-10-16 03:05:19 +02:00
{
if(!$payment_method_id) {
return $this;
}
2023-10-18 04:31:04 +02:00
$this->gateway_type_id = $payment_method_id;
2023-10-16 03:05:19 +02:00
$this->paypal_payment_method = $this->funding_options[$payment_method_id];
return $this;
}
public function authorizeView($payment_method)
{
// PayPal doesn't support direct authorization.
return $this;
}
public function authorizeResponse($request)
{
// PayPal doesn't support direct authorization.
return $this;
}
2024-01-14 05:05:00 +01:00
2023-12-13 07:10:29 +01:00
/**
* Checks whether payments are enabled on the account
*
* @return self
*/
private function checkPaymentsReceivable(): self
{
2023-11-06 10:01:27 +01:00
2023-11-26 08:41:42 +01:00
if($this->company_gateway->getConfigField('status') != 'activated') {
2023-11-06 10:01:27 +01:00
if (class_exists(\Modules\Admin\Services\PayPal\PayPalService::class)) {
$pp = new \Modules\Admin\Services\PayPal\PayPalService($this->company_gateway->company, $this->company_gateway->user);
$pp->updateMerchantStatus($this->company_gateway);
$this->company_gateway = $this->company_gateway->fresh();
$config = $this->company_gateway->getConfig();
2023-11-26 08:41:42 +01:00
if($config->status == 'activated') {
2023-11-06 10:01:27 +01:00
return $this;
2023-11-26 08:41:42 +01:00
}
2023-11-06 10:01:27 +01:00
}
throw new PaymentFailed('Unable to accept payments at this time, please contact PayPal for more information.', 401);
}
2024-01-14 05:05:00 +01:00
return $this;
2024-01-14 05:05:00 +01:00
}
2024-01-14 05:05:00 +01:00
2023-12-13 07:10:29 +01:00
/**
* Presents the Payment View to the client
*
* @param mixed $data
* @return void
*/
2023-10-16 03:05:19 +02:00
public function processPaymentView($data)
{
$this->init()->checkPaymentsReceivable();
2023-10-16 03:05:19 +02:00
$data['gateway'] = $this;
$this->payment_hash->data = array_merge((array) $this->payment_hash->data, ['amount' => $data['total']['amount_with_fee']]);
$this->payment_hash->save();
$data['client_id'] = config('ninja.paypal.client_id');
2023-10-16 06:48:45 +02:00
$data['token'] = $this->getClientToken();
2023-10-16 03:05:19 +02:00
$data['order_id'] = $this->createOrder($data);
2023-10-16 07:29:34 +02:00
$data['funding_source'] = $this->paypal_payment_method;
2023-10-18 04:31:04 +02:00
$data['gateway_type_id'] = $this->gateway_type_id;
2023-10-29 10:33:26 +01:00
$data['merchantId'] = $this->company_gateway->getConfigField('merchantId');
2023-11-30 07:44:34 +01:00
$data['currency'] = $this->client->currency()->code;
2023-11-06 13:31:48 +01:00
// nlog($data['merchantId']);
2024-01-14 05:05:00 +01:00
2023-10-16 07:29:34 +02:00
return render('gateways.paypal.ppcp.pay', $data);
2023-10-16 06:48:45 +02:00
}
2024-01-14 05:05:00 +01:00
2023-12-13 07:10:29 +01:00
/**
* Processes the payment response
*
* @param mixed $request
* @return void
*/
2023-10-16 03:05:19 +02:00
public function processPaymentResponse($request)
{
2023-12-06 02:09:43 +01:00
2023-10-18 04:31:04 +02:00
$request['gateway_response'] = str_replace("Error: ", "", $request['gateway_response']);
2023-10-16 07:23:04 +02:00
$response = json_decode($request['gateway_response'], true);
2023-10-16 08:00:37 +02:00
2023-12-06 02:09:43 +01:00
//capture
$orderID = $response['orderID'];
2024-01-25 10:33:47 +01:00
2024-02-13 05:25:18 +01:00
if($this->company_gateway->require_shipping_address) {
2024-01-25 10:33:47 +01:00
2024-02-13 05:25:18 +01:00
$shipping_data =
2024-01-25 10:33:47 +01:00
[[
"op" => "replace",
"path" => "/purchase_units/@reference_id=='default'/shipping/address",
"value" => [
"address_line_1" => strlen($this->client->shipping_address1) > 1 ? $this->client->shipping_address1 : $this->client->address1,
"address_line_2" => $this->client->shipping_address2,
"admin_area_2" => strlen($this->client->shipping_city) > 1 ? $this->client->shipping_city : $this->client->city,
"admin_area_1" => strlen($this->client->shipping_state) > 1 ? $this->client->shipping_state : $this->client->state,
"postal_code" => strlen($this->client->shipping_postal_code) > 1 ? $this->client->shipping_postal_code : $this->client->postal_code,
"country_code" => $this->client->present()->shipping_country_code(),
],
]];
2024-02-13 05:25:18 +01:00
2024-01-25 10:33:47 +01:00
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}", 'patch', $shipping_data);
}
2024-03-19 02:17:25 +01:00
try {
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}/capture", 'post', ['body' => '']);
} catch(\Exception $e) {
//Rescue for duplicate invoice_id
if(stripos($e->getMessage(), 'DUPLICATE_INVOICE_ID') !== false) {
$_invoice = collect($this->payment_hash->data->invoices)->first();
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
$new_invoice_number = $invoice->number."_".Str::random(5);
$update_data =
[[
"op" => "replace",
"path" => "/purchase_units/@reference_id=='default'/invoice_id",
"value" => $new_invoice_number,
]];
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}", 'patch', $update_data);
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}/capture", 'post', ['body' => '']);
}
}
2023-12-06 02:09:43 +01:00
$response = $r;
2023-10-18 04:31:04 +02:00
if(isset($response['status']) && $response['status'] == 'COMPLETED' && isset($response['purchase_units'])) {
2023-10-16 03:05:19 +02:00
2023-10-16 07:23:04 +02:00
$data = [
2023-10-18 04:31:04 +02:00
'payment_type' => $this->getPaymentMethod($request->gateway_type_id),
2023-12-06 02:09:43 +01:00
'amount' => $response['purchase_units'][0]['payments']['captures'][0]['amount']['value'],
2023-10-16 07:23:04 +02:00
'transaction_reference' => $response['purchase_units'][0]['payments']['captures'][0]['id'],
'gateway_type_id' => GatewayType::PAYPAL,
];
2023-10-16 03:05:19 +02:00
2023-10-16 07:23:04 +02:00
$payment = $this->createPayment($data, \App\Models\Payment::STATUS_COMPLETED);
2023-10-16 03:05:19 +02:00
2023-10-16 07:23:04 +02:00
SystemLogger::dispatch(
['response' => $response, 'data' => $data],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_PAYPAL,
$this->client,
$this->client->company,
);
2023-10-16 03:05:19 +02:00
2023-10-16 07:23:04 +02:00
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
2023-10-16 03:05:19 +02:00
2023-10-16 07:23:04 +02:00
} else {
2023-10-16 03:05:19 +02:00
2023-11-26 08:41:42 +01:00
if(isset($response['headers']) ?? false) {
2023-10-18 04:31:04 +02:00
unset($response['headers']);
2023-11-26 08:41:42 +01:00
}
2024-01-14 05:05:00 +01:00
2023-10-16 07:23:04 +02:00
SystemLogger::dispatch(
['response' => $response],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_PAYPAL,
$this->client,
$this->client->company,
);
2023-10-16 03:05:19 +02:00
2023-10-18 04:31:04 +02:00
$message = $response['body']['details'][0]['description'] ?? 'Payment failed. Please try again.';
2024-01-14 05:05:00 +01:00
2023-10-18 04:31:04 +02:00
throw new PaymentFailed($message, 400);
2023-10-16 07:23:04 +02:00
}
2023-10-16 03:05:19 +02:00
}
2024-01-14 05:05:00 +01:00
2023-12-13 07:10:29 +01:00
public function getOrder(string $order_id)
{
$this->init();
2023-10-16 03:05:19 +02:00
2023-12-13 07:10:29 +01:00
$r = $this->gatewayRequest("/v2/checkout/orders/{$order_id}", 'get', ['body' => '']);
2023-11-22 04:06:51 +01:00
2023-12-13 07:10:29 +01:00
return $r->json();
}
2023-12-06 02:09:43 +01:00
2023-12-13 07:10:29 +01:00
/**
* Generates a client token for the payment form.
*
* @return string
*/
2023-12-06 02:09:43 +01:00
private function getClientToken(): string
2023-11-22 04:06:51 +01:00
{
2023-12-06 02:09:43 +01:00
$r = $this->gatewayRequest('/v1/identity/generate-token', 'post', ['body' => '']);
if($r->successful()) {
return $r->json()['client_token'];
}
2024-01-14 05:05:00 +01:00
2023-12-06 02:09:43 +01:00
throw new PaymentFailed('Unable to gain client token from Paypal. Check your configuration', 401);
2023-11-22 04:06:51 +01:00
}
2024-01-14 05:05:00 +01:00
2023-12-13 07:10:29 +01:00
/**
* Builds the payment request.
*
* @return array
*/
2023-12-06 02:09:43 +01:00
private function paymentSource(): array
2023-11-22 04:06:51 +01:00
{
2023-12-06 02:09:43 +01:00
/** we only need to support paypal as payment source until as we are only using hosted payment buttons */
return $this->injectPayPalPaymentSource();
2024-01-14 05:05:00 +01:00
2023-11-22 04:06:51 +01:00
}
private function injectPayPalPaymentSource(): array
{
return [
"paypal" => [
"name" => [
"given_name" => $this->client->present()->first_name(),
"surname" => $this->client->present()->last_name(),
],
"email_address" => $this->client->present()->email(),
"address" => $this->getBillingAddress(),
2024-01-14 05:05:00 +01:00
"experience_context" => [
2023-11-22 04:06:51 +01:00
"user_action" => "PAY_NOW"
],
],
];
}
2024-01-14 05:05:00 +01:00
2023-12-13 07:10:29 +01:00
/**
* Creates the PayPal Order object
*
* @param array $data
* @return string
*/
2023-10-16 03:05:19 +02:00
private function createOrder(array $data): string
{
$_invoice = collect($this->payment_hash->data->invoices)->first();
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
2023-11-26 08:41:42 +01:00
$description = collect($invoice->line_items)->map(function ($item) {
2023-10-23 13:10:41 +02:00
return $item->notes;
})->implode("\n");
2023-10-16 03:05:19 +02:00
$order = [
2024-01-14 05:05:00 +01:00
2023-11-06 13:31:48 +01:00
"intent" => "CAPTURE",
2023-11-22 04:06:51 +01:00
"payment_source" => $this->paymentSource(),
2023-11-06 13:31:48 +01:00
"purchase_units" => [
[
2023-12-13 07:10:29 +01:00
"custom_id" => $this->payment_hash->hash,
"description" => ctrans('texts.invoice_number').'# '.$invoice->number,
2023-11-06 13:31:48 +01:00
"invoice_id" => $invoice->number,
"payee" => [
"merchant_id" => $this->company_gateway->getConfigField('merchantId'),
],
"payment_instruction" => [
2023-10-19 08:26:52 +02:00
"disbursement_mode" => "INSTANT",
],
2023-11-22 04:06:51 +01:00
$this->getShippingAddress(),
2023-11-06 13:31:48 +01:00
"amount" => [
"value" => (string)$data['amount_with_fee'],
2024-01-14 05:05:00 +01:00
"currency_code" => $this->client->currency()->code,
2023-11-06 13:31:48 +01:00
"breakdown" => [
"item_total" => [
"currency_code" => $this->client->currency()->code,
"value" => (string)$data['amount_with_fee']
]
]
],
2024-01-14 05:05:00 +01:00
"items" => [
2023-11-06 13:31:48 +01:00
[
"name" => ctrans('texts.invoice_number').'# '.$invoice->number,
2024-01-27 05:43:37 +01:00
"description" => mb_substr($description, 0, 127),
2023-11-06 13:31:48 +01:00
"quantity" => "1",
"unit_amount" => [
"currency_code" => $this->client->currency()->code,
"value" => (string)$data['amount_with_fee']
],
],
],
2023-10-19 08:26:52 +02:00
],
2023-11-06 13:31:48 +01:00
]
];
2023-11-24 09:23:45 +01:00
2024-01-14 05:05:00 +01:00
2023-11-26 08:41:42 +01:00
if($shipping = $this->getShippingAddress()) {
2024-01-23 21:31:06 +01:00
$order['purchase_units'][0]["shipping"] = $shipping;
2023-11-24 09:23:45 +01:00
}
2023-10-16 03:05:19 +02:00
$r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order);
// nlog($r->json());
2023-10-16 06:48:45 +02:00
2023-10-16 03:05:19 +02:00
return $r->json()['id'];
}
2023-11-26 08:41:42 +01:00
private function getBillingAddress(): array
{
2023-11-26 08:41:42 +01:00
return
[
"address_line_1" => $this->client->address1,
"address_line_2" => $this->client->address2,
"admin_area_2" => $this->client->city,
"admin_area_1" => $this->client->state,
"postal_code" => $this->client->postal_code,
"country_code" => $this->client->country->iso_3166_2,
];
}
2023-11-24 09:23:45 +01:00
private function getShippingAddress(): ?array
{
2023-11-26 08:41:42 +01:00
return $this->company_gateway->require_shipping_address ?
2023-11-22 04:06:51 +01:00
[
2024-01-23 21:31:06 +01:00
"address" =>
2023-11-22 04:06:51 +01:00
[
2024-01-25 10:33:47 +01:00
"address_line_1" => strlen($this->client->shipping_address1) > 1 ? $this->client->shipping_address1 : $this->client->address1,
2023-11-22 04:06:51 +01:00
"address_line_2" => $this->client->shipping_address2,
2024-01-25 10:33:47 +01:00
"admin_area_2" => strlen($this->client->shipping_city) > 1 ? $this->client->shipping_city : $this->client->city,
"admin_area_1" => strlen($this->client->shipping_state) > 1 ? $this->client->shipping_state : $this->client->state,
"postal_code" => strlen($this->client->shipping_postal_code) > 1 ? $this->client->shipping_postal_code : $this->client->postal_code,
2023-11-22 04:06:51 +01:00
"country_code" => $this->client->present()->shipping_country_code(),
],
]
2024-02-13 05:25:18 +01:00
2023-11-24 09:23:45 +01:00
: null;
2023-11-22 04:06:51 +01:00
}
2024-01-14 05:05:00 +01:00
2023-12-13 07:10:29 +01:00
/**
* Generates the gateway request
*
* @param string $uri
* @param string $verb
* @param array $data
* @param ?array $headers
* @return \Illuminate\Http\Client\Response
*/
2023-10-16 03:05:19 +02:00
public function gatewayRequest(string $uri, string $verb, array $data, ?array $headers = [])
{
$this->init();
2024-01-14 05:05:00 +01:00
2023-10-16 03:05:19 +02:00
$r = Http::withToken($this->access_token)
->withHeaders($this->getHeaders($headers))
->{$verb}("{$this->api_endpoint_url}{$uri}", $data);
if($r->successful()) {
return $r;
}
2023-12-06 02:09:43 +01:00
SystemLogger::dispatch(
['response' => $r->body()],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_PAYPAL,
$this->client,
$this->client->company,
);
2023-10-16 03:05:19 +02:00
throw new PaymentFailed("Gateway failure - {$r->body()}", 401);
}
2024-01-14 05:05:00 +01:00
2023-12-13 07:10:29 +01:00
/**
* Generates the request headers
*
* @param array $headers
* @return array
*/
2023-10-16 03:05:19 +02:00
private function getHeaders(array $headers = []): array
{
return array_merge([
'Accept' => 'application/json',
'Content-type' => 'application/json',
'Accept-Language' => 'en_US',
2023-10-19 08:33:22 +02:00
'PayPal-Partner-Attribution-Id' => 'invoiceninja_SP_PPCP',
'PayPal-Request-Id' => Str::uuid()->toString(),
2023-10-16 03:05:19 +02:00
], $headers);
}
2023-12-13 07:10:29 +01:00
public function processWebhookRequest(Request $request)
2023-10-16 03:05:19 +02:00
{
2024-01-14 05:05:00 +01:00
2023-12-13 10:04:52 +01:00
// nlog(json_encode($request->all()));
2023-12-13 07:10:29 +01:00
$this->init();
2023-10-16 03:05:19 +02:00
2023-12-13 07:10:29 +01:00
PayPalWebhook::dispatch($request->all(), $request->headers->all(), $this->access_token);
2023-10-16 03:05:19 +02:00
}
2024-03-16 02:36:40 +01:00
public function auth(): bool
{
try {
$this->init()->getClientToken();
return true;
}
catch(\Exception $e) {
2023-12-13 07:10:29 +01:00
2024-03-16 02:36:40 +01:00
}
return false;
}
2024-03-18 07:10:12 +01:00
public function importCustomers()
{
2024-03-19 00:38:54 +01:00
// $response = $this->gatewayRequest('/v1/reporting/transactions', 'get', ['fields' => 'all','page_size' => 500,'start_date' => '2024-02-01T00:00:00-0000', 'end_date' => '2024-03-01T00:00:00-0000']);
2024-03-18 07:10:12 +01:00
2024-03-19 00:38:54 +01:00
// nlog($response->json());
return true;
}
2023-11-26 08:41:42 +01:00
}