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