stripe_customer_id)) { return $owner->stripe_customer_id; } $customer = $this->stripe->client()->customers->create([ 'email' => $owner->email, 'name' => trim(($owner->firstname ?? '').' '.($owner->lastname ?? '')) ?: null, 'metadata' => [ 'mivita_type' => $owner instanceof Customer ? 'customer' : 'user', 'mivita_id' => (string) $owner->getKey(), ], ]); $owner->forceFill(['stripe_customer_id' => $customer->id])->save(); return $customer->id; } /** * Stripe-Customer des Endkunden aus dem Checkout — mit Rechnungsname und * -anschrift. Ein vorhandener Customer wird wiederverwendet und dabei mit * den aktuellen Rechnungsdaten aktualisiert (der Kunde kann seine Anschrift * zwischen zwei Bestellungen geändert haben). */ public function customerIdForShoppingUser(ShoppingUser $shoppingUser): string { $payload = $this->customerPayloadFor($shoppingUser); if (! empty($shoppingUser->stripe_customer_id)) { $this->updateCustomer($shoppingUser->stripe_customer_id, $payload); return $shoppingUser->stripe_customer_id; } $customer = $this->stripe->client()->customers->create($payload); $shoppingUser->forceFill(['stripe_customer_id' => $customer->id])->save(); return $customer->id; } /** * Aktualisiert Name/Anschrift eines bestehenden Customers — best effort. * Ein Fehler hier darf einen laufenden Checkout niemals kippen; die * Zahlung funktioniert auch mit veralteten Stammdaten am Customer. * * @param array $payload */ public function updateCustomer(string $customerId, array $payload): void { try { $this->stripe->client()->customers->update($customerId, $payload); } catch (\Throwable $e) { Log::channel('stripe')->warning('Stripe-Customer konnte nicht aktualisiert werden: '.$e->getMessage(), [ 'customer' => $customerId, ]); } } /** * Löscht einen Stripe-Customer (DSGVO Art. 17, Phase SE1.6). Die Charges * bleiben bei Stripe erhalten — sie unterliegen der Aufbewahrungspflicht. * Best effort: ein Fehler darf die Löschung im System nicht blockieren. */ public function deleteCustomer(string $customerId): bool { try { $this->stripe->client()->customers->delete($customerId); return true; } catch (\Throwable $e) { Log::channel('stripe')->warning('Stripe-Customer konnte nicht gelöscht werden: '.$e->getMessage(), [ 'customer' => $customerId, ]); return false; } } /** * Stripe-Customer-Payload aus den Rechnungsdaten eines Endkunden. * * @return array */ public function customerPayloadFor(ShoppingUser $shoppingUser): array { $payload = [ 'name' => self::customerNameFor($shoppingUser), 'email' => $shoppingUser->billing_email ?: null, 'metadata' => [ 'mivita_type' => 'shopping_user', 'mivita_id' => (string) $shoppingUser->id, ], ]; if ($shoppingUser->member_id) { $payload['metadata']['mivita_member_id'] = (string) $shoppingUser->member_id; } if ($shoppingUser->number) { $payload['metadata']['mivita_customer_number'] = (string) $shoppingUser->number; } if ($shoppingUser->billing_phone) { $payload['phone'] = $shoppingUser->billing_phone; } $address = self::billingAddressFor($shoppingUser); if ($address !== null) { $payload['address'] = $address; } return $payload; } /** * Anzeigename für Stripe: "Vorname Nachname (Firma)". * * Der Personenname steht vorn, weil im System und in der Kommunikation * über den Namen gearbeitet wird — Firmennamen kennt kaum jemand * (Stakeholder-Entscheid 2026-08-26). Reine Firmenkunden ohne * Personennamen erscheinen unter der Firma. */ public static function customerNameFor(ShoppingUser $shoppingUser): ?string { $company = trim((string) $shoppingUser->billing_company); $person = trim(($shoppingUser->billing_firstname ?? '').' '.($shoppingUser->billing_lastname ?? '')); if ($person === '') { return $company !== '' ? $company : null; } return $company !== '' ? $person.' ('.$company.')' : $person; } /** * Rechnungsanschrift im Stripe-Format; null, wenn keine Straße hinterlegt ist. * * @return array|null */ private static function billingAddressFor(ShoppingUser $shoppingUser): ?array { if (empty($shoppingUser->billing_address)) { return null; } return [ 'line1' => $shoppingUser->billing_address, 'line2' => $shoppingUser->billing_address_2 ?: null, 'postal_code' => $shoppingUser->billing_zipcode ?: null, 'city' => $shoppingUser->billing_city ?: null, 'country' => $shoppingUser->billing_country->code ?? null, ]; } }