Rechtstexte und B2C-Vertragsabläufe absichern

This commit is contained in:
2026-08-22 14:31:56 +02:00
parent d320a4fd7a
commit a31a235422
58 changed files with 2502 additions and 316 deletions
+44 -6
View File
@@ -151,6 +151,12 @@ function stripe_create_checkout_session(string $priceId, string $customerEmail,
// .deleted-Events liefern kein Checkout-Session-Objekt, aber die
// Metadaten der Subscription, darueber loesen wir tenant_id auf.
'subscription_data' => ['metadata' => $metadata],
'locale' => 'de',
'custom_text' => [
'submit' => [
'message' => 'Monatlich kündbar zum Ende der laufenden Abrechnungsperiode. Es gelten die vorab bestätigten AGB und die Widerrufsbelehrung.',
],
],
];
if ($existingCustomerId !== null) {
@@ -175,6 +181,17 @@ function stripe_create_billing_portal_session(string $customerId, string $return
$result = stripe_request('POST', 'billing_portal/sessions', [
'customer' => $customerId,
'return_url' => $returnUrl,
'locale' => 'de',
// Als gezielter Stripe-Flow sind Navigation und andere Portalaktionen
// ausgeblendet. Tarifwechsel und Kündigung bleiben damit in unseren
// rechtlich dokumentierten Bestell- bzw. Kündigungsabläufen.
'flow_data' => [
'type' => 'payment_method_update',
'after_completion' => [
'type' => 'redirect',
'redirect' => ['return_url' => $returnUrl],
],
],
]);
if (!$result['ok']) {
return ['ok' => false, 'url' => null, 'error' => $result['error']];
@@ -247,9 +264,9 @@ function stripe_get_subscription(string $subscriptionId): array
}
/**
* Switches an existing active subscription to a different Price in place
* (upgrade or downgrade between paid tiers), instead of creating a brand
* new Checkout Session. Stripe prorates the difference automatically.
* Switches an existing active subscription to a different Price in place.
* The billing cycle stays unchanged and no unclear mid-cycle debit/credit is
* created; the new full monthly amount applies at the next renewal.
* Also clears any pending cancel_at_period_end, since choosing a plan is
* an explicit signal the tenant wants to keep the subscription running.
*
@@ -259,7 +276,7 @@ function stripe_update_subscription_price(string $subscriptionId, string $subscr
{
$result = stripe_request('POST', "subscriptions/{$subscriptionId}", [
'items' => [['id' => $subscriptionItemId, 'price' => $newPriceId]],
'proration_behavior' => 'create_prorations',
'proration_behavior' => 'none',
'cancel_at_period_end' => 'false',
]);
@@ -271,8 +288,10 @@ function stripe_update_subscription_price(string $subscriptionId, string $subscr
}
/**
* Cancels a subscription immediately (not at period end) - used when a
* tenant downgrades all the way to the free plan from the admin UI.
* Cancels a subscription immediately. This is reserved for exceptional
* workflows such as a confirmed withdrawal; ordinary cancellations use
* stripe_schedule_subscription_cancellation() so paid access remains until
* the end of the already paid billing period.
*
* @return array{ok: bool, error: ?string}
*/
@@ -286,6 +305,25 @@ function stripe_cancel_subscription(string $subscriptionId): array
return ['ok' => true, 'error' => null];
}
/**
* @return array{ok: bool, current_period_end: ?string, error: ?string}
*/
function stripe_schedule_subscription_cancellation(string $subscriptionId): array
{
$result = stripe_request('POST', "subscriptions/{$subscriptionId}", [
'cancel_at_period_end' => 'true',
]);
if (!$result['ok']) {
return ['ok' => false, 'current_period_end' => null, 'error' => $result['error']];
}
$periodEnd = isset($result['data']['current_period_end'])
? date('Y-m-d H:i:s', (int)$result['data']['current_period_end'])
: null;
return ['ok' => true, 'current_period_end' => $periodEnd, 'error' => null];
}
/**
* @return array{id: string, type: string, data: array}|null
*/