162 lines
5.0 KiB
PHP
162 lines
5.0 KiB
PHP
<?php
|
|
|
|
namespace App\Services\PayPal;
|
|
|
|
use App\Models\CheckoutSession;
|
|
use App\Models\Package;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Str;
|
|
|
|
class PayPalOrderService
|
|
{
|
|
public function __construct(private readonly PayPalClient $client) {}
|
|
|
|
/**
|
|
* @param array{return_url?: string|null, cancel_url?: string|null, locale?: string|null, request_id?: string|null} $options
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function createOrder(CheckoutSession $session, Package $package, array $options = []): array
|
|
{
|
|
$currency = strtoupper((string) ($session->currency ?: $package->currency ?: 'EUR'));
|
|
$total = $this->formatAmount((float) $session->amount_total);
|
|
$subtotal = $this->formatAmount((float) $session->amount_subtotal);
|
|
$discount = $this->formatAmount((float) $session->amount_discount);
|
|
|
|
$amount = [
|
|
'currency_code' => $currency,
|
|
'value' => $total,
|
|
];
|
|
|
|
if ($subtotal !== null && $discount !== null) {
|
|
$amount['breakdown'] = [
|
|
'item_total' => [
|
|
'currency_code' => $currency,
|
|
'value' => $subtotal,
|
|
],
|
|
'discount' => [
|
|
'currency_code' => $currency,
|
|
'value' => $discount,
|
|
],
|
|
];
|
|
}
|
|
|
|
$purchaseUnit = [
|
|
'reference_id' => 'package-'.$package->id,
|
|
'description' => Str::limit($package->name ?? 'Package', 127, ''),
|
|
'custom_id' => $session->id,
|
|
'amount' => $amount,
|
|
];
|
|
|
|
$applicationContext = array_filter([
|
|
'brand_name' => config('app.name', 'Fotospiel'),
|
|
'landing_page' => 'NO_PREFERENCE',
|
|
'user_action' => 'PAY_NOW',
|
|
'shipping_preference' => 'NO_SHIPPING',
|
|
'locale' => $this->resolveLocale($options['locale'] ?? $session->locale),
|
|
'return_url' => $options['return_url'] ?? null,
|
|
'cancel_url' => $options['cancel_url'] ?? null,
|
|
], static fn ($value) => $value !== null && $value !== '');
|
|
|
|
$payload = [
|
|
'intent' => 'CAPTURE',
|
|
'purchase_units' => [$purchaseUnit],
|
|
'application_context' => $applicationContext,
|
|
];
|
|
|
|
$headers = [];
|
|
$requestId = $options['request_id'] ?? $session->id;
|
|
if (is_string($requestId) && $requestId !== '') {
|
|
$headers['PayPal-Request-Id'] = $requestId;
|
|
}
|
|
|
|
return $this->client->post('/v2/checkout/orders', $payload, $headers);
|
|
}
|
|
|
|
/**
|
|
* @param array{request_id?: string|null} $options
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function captureOrder(string $orderId, array $options = []): array
|
|
{
|
|
$headers = [];
|
|
$requestId = $options['request_id'] ?? null;
|
|
if (is_string($requestId) && $requestId !== '') {
|
|
$headers['PayPal-Request-Id'] = $requestId;
|
|
}
|
|
|
|
return $this->client->post(sprintf('/v2/checkout/orders/%s/capture', $orderId), [], $headers);
|
|
}
|
|
|
|
public function resolveApproveUrl(array $payload): ?string
|
|
{
|
|
$links = Arr::get($payload, 'links', []);
|
|
if (! is_array($links)) {
|
|
return null;
|
|
}
|
|
|
|
foreach ($links as $link) {
|
|
if (! is_array($link)) {
|
|
continue;
|
|
}
|
|
|
|
if (($link['rel'] ?? null) === 'approve') {
|
|
return is_string($link['href'] ?? null) ? $link['href'] : null;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function resolveCaptureId(array $payload): ?string
|
|
{
|
|
$captureId = Arr::get($payload, 'purchase_units.0.payments.captures.0.id');
|
|
|
|
return is_string($captureId) && $captureId !== '' ? $captureId : null;
|
|
}
|
|
|
|
/**
|
|
* @return array{currency?: string, total?: float}
|
|
*/
|
|
public function resolveTotals(array $payload): array
|
|
{
|
|
$amount = Arr::get($payload, 'purchase_units.0.payments.captures.0.amount')
|
|
?? Arr::get($payload, 'purchase_units.0.amount');
|
|
|
|
if (! is_array($amount)) {
|
|
return [];
|
|
}
|
|
|
|
$currency = Arr::get($amount, 'currency_code');
|
|
$total = Arr::get($amount, 'value');
|
|
|
|
return array_filter([
|
|
'currency' => is_string($currency) ? strtoupper($currency) : null,
|
|
'total' => is_numeric($total) ? (float) $total : null,
|
|
], static fn ($value) => $value !== null);
|
|
}
|
|
|
|
protected function resolveLocale(?string $locale): ?string
|
|
{
|
|
if (! $locale) {
|
|
return null;
|
|
}
|
|
|
|
$normalized = str_replace('_', '-', $locale);
|
|
|
|
return match (strtolower($normalized)) {
|
|
'de', 'de-de', 'de-at', 'de-ch' => 'de-DE',
|
|
'en', 'en-gb' => 'en-GB',
|
|
default => 'en-US',
|
|
};
|
|
}
|
|
|
|
protected function formatAmount(float $amount): ?string
|
|
{
|
|
if (! is_finite($amount)) {
|
|
return null;
|
|
}
|
|
|
|
return number_format($amount, 2, '.', '');
|
|
}
|
|
}
|