Files
fotospiel-app/app/Services/PayPal/PayPalOrderService.php
2026-02-04 12:18:14 +01:00

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, '.', '');
}
}