Інтеграція Stripe Billing для SaaS-підписок
Stripe Billing — найбільш повна готова реалізація біллінгу для SaaS на ринку. Покриває підписки, пробні періоди, апгрейди/даунгрейди, prorated billing, метерировану витрату, автоматичний retry при невдачі. Реалізація з нуля займе місяці — Stripe Billing скорочує це до 1–2 тижнів.
Ключові сутності
Product → Price → Subscription → Invoice → PaymentIntent. Продукт — це план (Basic, Pro, Enterprise). До продукту привʼязуються кілька Price — щомісячна та щорічна. Підписка привʼязує customer до Price. Invoice виставляється автоматично на початку кожного періоду.
Створення продуктів та цін
// Створити продукт (один раз при настройці)
$product = \Stripe\Product::create([
'name' => 'Pro Plan',
'metadata' => ['plan_id' => 'pro'],
]);
// Щомісячна ціна
$monthlyPrice = \Stripe\Price::create([
'product' => $product->id,
'unit_amount' => 2900, // $29.00
'currency' => 'usd',
'recurring' => ['interval' => 'month'],
'lookup_key' => 'pro_monthly', // для пошуку без збереження ID
]);
// Щорічна (зі знижкою)
$yearlyPrice = \Stripe\Price::create([
'product' => $product->id,
'unit_amount' => 27900, // $279.00
'currency' => 'usd',
'recurring' => ['interval' => 'year'],
'lookup_key' => 'pro_yearly',
]);
Реєстрація користувача та створення підписки
// При реєстрації — створити Stripe Customer
$stripeCustomer = \Stripe\Customer::create([
'email' => $user->email,
'name' => $user->name,
'metadata' => ['user_id' => $user->id],
]);
$user->update(['stripe_customer_id' => $stripeCustomer->id]);
// Створити підписку з trial
$subscription = \Stripe\Subscription::create([
'customer' => $user->stripe_customer_id,
'items' => [['price' => 'pro_monthly']],
'trial_period_days' => 14,
'payment_behavior' => 'default_incomplete',
'payment_settings' => ['save_default_payment_method' => 'on_subscription'],
'expand' => ['latest_invoice.payment_intent'],
]);
// Повернути client_secret для підтвердження карти на фронтенді
$clientSecret = $subscription->latest_invoice->payment_intent->client_secret;
payment_behavior: default_incomplete означає, що підписка створюється, але активується лише після успішного першого платежу. Важливо для безплатних trial: карта привʼязується, але не списується.
Апгрейд/даунгрейд
public function changePlan(User $user, string $newPriceLookupKey): void
{
$prices = \Stripe\Price::all(['lookup_keys' => [$newPriceLookupKey]]);
$newPrice = $prices->data[0];
$subscription = \Stripe\Subscription::retrieve($user->stripe_subscription_id);
\Stripe\Subscription::update($subscription->id, [
'items' => [[
'id' => $subscription->items->data[0]->id,
'price' => $newPrice->id,
]],
'proration_behavior' => 'create_prorations', // або 'none' для щорічних
'billing_cycle_anchor'=> 'unchanged',
]);
}
При апгрейді з proration Stripe автоматично кредитує невикористаний час поточного плану та виставляє invoice на різницю.
Webhook: синхронізація статусів
Вся бізнес-логіка повинна будуватися на webhook-ах, не на синхронних відповідях API. События для обробки:
protected array $handlers = [
'customer.subscription.created' => 'onSubscriptionCreated',
'customer.subscription.updated' => 'onSubscriptionUpdated',
'customer.subscription.deleted' => 'onSubscriptionCancelled',
'invoice.payment_succeeded' => 'onInvoicePaid',
'invoice.payment_failed' => 'onInvoicePaymentFailed',
'customer.subscription.trial_will_end'=> 'onTrialEndingSoon',
];
public function onInvoicePaymentFailed(array $event): void
{
$subscription = $event['data']['object']['subscription'];
$user = User::where('stripe_subscription_id', $subscription)->firstOrFail();
// Не блокувати відразу — Stripe робить retry
// next_payment_attempt є в invoice
$nextRetry = $event['data']['object']['next_payment_attempt'];
Notification::send($user, new PaymentFailedNotification($nextRetry));
}
Customer Portal
Stripe Customer Portal — готовий UI для управління підпискою (зміна карти, скасування, історія інвойсів). Не потрібно писати з нуля:
$session = \Stripe\BillingPortal\Session::create([
'customer' => $user->stripe_customer_id,
'return_url' => route('dashboard'),
]);
return redirect($session->url);
Конфігурується в Stripe Dashboard: дозволити/заборонити зміну плану, скасування, завантаження інвойсів.
Metered billing
Для SaaS з оплатою по витраті (API вызови, сховище, користувачі):
// Price з metered billing
$price = \Stripe\Price::create([
'product' => $product->id,
'currency' => 'usd',
'recurring' => [
'interval' => 'month',
'usage_type' => 'metered',
'aggregate_usage'=> 'sum',
],
'billing_scheme' => 'per_unit',
'unit_amount' => 1, // $0.01 за одиницю
]);
// Репортити витрату (раз на годину або в кінці періоду)
\Stripe\SubscriptionItem::createUsageRecord(
$subscriptionItemId,
['quantity' => $apiCallsThisPeriod, 'action' => 'set']
);
action: set встановлює абсолютне значення, increment додає до поточного. set безпечніше при retry.







