---
title: "Fakturácia cez Stripe — Purchases for Laravel | Roundly"
description: "Fakturácia v Stripe — zákazníci, predplatné na miesta aj podľa spotreby, zmiešané intervaly, skúšobné obdobie bez karty, jedno na vlastníka, merače."
url: https://roundly-consulting.com/sk/open-source/navody/purchases-for-laravel/fakturacia-stripe
language: sk
---

[Všetky balíky](https://roundly-consulting.com/sk/open-source.md)

[Purchases for Laravel](https://roundly-consulting.com/sk/open-source/navody/purchases-for-laravel.md)

# Fakturácia cez Stripe

Purchases::stripe() vytvára a mení veci v Stripe cez jeho REST API a HTTP klienta Laravelu — bez Stripe SDK. Prichádzajúce webhooky udržiavajú odchádzajúci stav lokálne synchronizovaný. Fakturovať vie len Stripe; Apple a Google predávajú vo vlastných obchodoch. Fakturácia vyžaduje PURCHASES\_STRIPE\_SECRET a každé volanie používa pripnutú verziu API:

```php
use RoundlyConsulting\Purchases\Facades\Purchases;

Purchases::canBill('stripe');   // true — the provider implements Contracts\SupportsBilling
Purchases::canBill('apple');    // false
Purchases::stripe();            // StripeBilling; UnsupportedCapabilityException without a registered Stripe provider

// Dependency injection — the same object behind the facade
public function __construct(private PurchasesManager $purchases) {}
$this->purchases->stripe()->subscriptions()->updateQuantity($seatItem, 11);
```

| Sub-accessor | Metóda | Akcia |
| --- | --- | --- |
| `customers()` | `sync($owner, CustomerData $data) / id($owner) / owner($customerId)` | `SyncStripeCustomerAction` |
| `subscriptions()` | `create($owner, CreateSubscriptionData $data)` | `CreateStripeSubscriptionAction` |
| `subscriptions()` | `checkout($owner, CheckoutSubscriptionData $data)` | `CreateStripeCheckoutSessionAction` |
| `subscriptions()` | `updateQuantity() / swap() / addItem() / removeItem() / change()` | `ChangeStripeSubscriptionAction` |
| `subscriptions()` | `cancel() / cancelAtPeriodEnd()` | `CancelStripeSubscriptionAction` |
| `subscriptions()` | `resume()` | `ResumeStripeSubscriptionAction` |
| `subscriptions()` | `preview($subscription, ?SubscriptionChange $change)` | `PreviewStripeInvoiceAction` |
| `meters()` | `create() / find() / findOrCreate()` | `CreateStripeMeterAction / FindStripeMeterAction` |
| `meters()` | `report() / queue()` | `ReportStripeMeterEventAction / ReportStripeMeterEvent job` |
| — | `portal($owner, ?$returnUrl, ?$configuration)` | `CreateStripePortalSessionAction` |

Predplatné je uložené predplatné Stripe alebo jeho id (sub\_…), položka uložený SubscriptionItem alebo jeho id (si\_…); uložené predplatné iného poskytovateľa sa odmietne cez BillingException skôr, než sa čokoľvek odošle. Každý zápis posiela Idempotency-Key — váš alebo nové UUID —, ktorý nesie každá StripeException, takže neúspešné volanie zopakujete s tým istým kľúčom.

## Zákazníci — jeden na vlastníka

```php
use RoundlyConsulting\Purchases\DataTransferObjects\Stripe\{AddressData, CustomerData, TaxIdData};

$customer = Purchases::stripe()->customers()->sync($company, new CustomerData(
    name: 'Acme s.r.o.',
    email: 'billing@acme.test',
    address: new AddressData(line1: 'Hlavná 1', city: 'Bratislava', postalCode: '81101', country: 'SK'),
    taxIds: [new TaxIdData('eu_vat', 'SK2020123456')],
    metadata: ['plan' => 'team'],
    preferredLocale: 'sk',
));

Purchases::stripe()->customers()->id($company);        // 'cus_…' or null
Purchases::stripe()->customers()->owner('cus_…');      // the owner model or null
```

Vlastníkom je ľubovoľný uložený Eloquent model. Bez zákazníka ho sync() vytvorí a prepojí; s existujúcim aktualizuje len polia, ktoré nastavíte, a zoznam taxIds sa stane presne týmto zoznamom. Prepojenie žije v billing\_customers cez kontrakt BillingCustomers — ak id už ukladáte inde, naviažte vlastnú implementáciu. Keď sa uloží predplatné alebo platba, ktorá ešte nemá vlastníka, prepojí sa s vlastníkom svojho zákazníka.

## Predplatné

```php
use RoundlyConsulting\Purchases\DataTransferObjects\Stripe\{CreateSubscriptionData, SubscriptionPrice};
use RoundlyConsulting\Purchases\Providers\Stripe\Enums\{CollectionMethod, PaymentBehavior};

$subscription = Purchases::stripe()->subscriptions()->create($company, new CreateSubscriptionData(
    prices: [
        SubscriptionPrice::licensed('price_seats', 10),   // per seat, quantity 10
        SubscriptionPrice::metered('price_storage_gb'),   // usage-based: no quantity
    ],
    name: 'team',                                         // the local row's name, what subscribedTo() checks
    trialDays: 7,
    automaticTax: true,
    collectionMethod: CollectionMethod::ChargeAutomatically, // or SendInvoice + daysUntilDue
    paymentBehavior: PaymentBehavior::DefaultIncomplete,
    metadata: ['company' => (string) $company->getKey()],
), idempotencyKey: "start-{$company->getKey()}");
```

Odpoveď sa hneď uloží lokálne cez pipeline webhookov, s vlastníkom a názvom name; pri aktívnom predplatnom alebo predplatnom v skúšobnom období sa spustí SubscriptionStarted. Voľby, ktoré necháte null, sa neposielajú, takže platia predvolené hodnoty Stripe.

## Flexibilný billing mode a zmiešané intervaly

```php
use RoundlyConsulting\Purchases\Providers\Stripe\Enums\BillingMode;

Purchases::stripe()->subscriptions()->create($company, new CreateSubscriptionData(
    prices: [
        SubscriptionPrice::licensed('price_licence_yearly'),   // billed every year
        SubscriptionPrice::metered('price_api_calls_monthly'), // usage, billed every month
    ],
    name: 'platform',
    billingMode: BillingMode::Flexible,   // billing_mode[type]=flexible
));
```

- billingMode posiela billing\_mode\[type\]; ak nie je nastavený, použije sa predvolená hodnota purchases.settings.stripe.billing\_mode, inak sa nepošle nič — Stripe má od verzie API 2025-09-30.clover predvolene flexible, teda už na pripnutej 2026-05-27.dahlia. Billing mode vyžaduje 2025-06-30.basil alebo novšiu: pri staršej pripnutej api\_version volanie vyhodí BillingException skôr, než sa opýta Stripe. Predplatné prejde z classic na flexible, nikdy späť.
- Zmiešané intervaly vyžadujú flexibilný režim a interval každej položky musí byť násobkom najkratšieho (1 mesiac + 1 rok; nie týždeň + mesiac). Stripe účtuje každú položku za jej vlastné obdobie.
- Lokálne je ends\_at najneskorší koniec obdobia položky — dátum, do ktorého je zaplatená ročná licencia; vlastné obdobie každej položky je v meta.
- Checkout predplatné so zmiešanými intervalmi vytvoriť nevie (použite create()) a cancelAtPeriodEnd() ho zruší k najskoršiemu koncu obdobia položky.

## Skúšobné obdobie bez platobnej metódy

```php
use RoundlyConsulting\Purchases\Providers\Stripe\Enums\MissingPaymentMethod;

Purchases::stripe()->subscriptions()->create($company, new CreateSubscriptionData(
    prices: [SubscriptionPrice::licensed('price_team')],
    trialDays: 14,
    trialEndBehavior: MissingPaymentMethod::Pause,   // trial_settings[end_behavior][missing_payment_method]
));
```

Skúšobné obdobie platobnú metódu nepotrebuje. Čo sa stane, keď skončí bez nej, určuje trialEndBehavior — inak purchases.settings.stripe.missing\_payment\_method, inak create\_invoice zo Stripe. trialEndBehavior bez skúšobného obdobia je BillingException:

| MissingPaymentMethod | V Stripe | Lokálne |
| --- | --- | --- |
| `Cancel` | hneď ho zruší (customer.subscription.deleted) | Canceled, SubscriptionCanceled |
| `Pause` | pozastaví ho; až do obnovenia žiadne faktúry (customer.subscription.paused) | OnHold, SubscriptionOnHold; po obnovení späť do Completed so SubscriptionResumed + SubscriptionRenewed |
| `CreateInvoice` | aj tak vystaví faktúru (predvolené v Stripe); nezaplatené prejde do past\_due | InGracePeriod (alebo OnHold pri settings.stripe.past\_due = on\_hold) |

Tri dni pred koncom skúšobného obdobia pošle Stripe customer.subscription.trial\_will\_end, ktorý spustí SubscriptionTrialWillEnd — vhodný moment poslať zákazníka do Customer Portalu, aby pridal platobnú metódu. Pozastavené predplatné sa obnoví z Portalu alebo z hostovanej stránky faktúry; balík volanie na endpoint /resume v Stripe nemá.

## Jedno predplatné na vlastníka

```php
// config/purchases.php — or PURCHASES_ONE_SUBSCRIPTION_PER_OWNER=true
'subscriptions' => [
    'one_per_owner' => true,
    'lock' => ['store' => 'redis', 'wait' => 10],   // a store with atomic locks, shared by every server
],

Purchases::for($company)->hasOngoingSubscription();   // what the limit checks
Subscription::query()->ongoing()->get();              // every status but canceled, failed and refunded
```

- Pri zapnutom purchases.subscriptions.one\_per\_owner vyhodia create() aj checkout() BillingException a nič neodošlú, kým má vlastník predplatné, ktoré neskončilo — v akomkoľvek stave okrem canceled, failed (expirované) a refunded, od ľubovoľného poskytovateľa. Predplatné zrušené ku koncu obdobia sa ráta, kým neskončí.
- Kontrola, volanie Stripe aj lokálne uloženie bežia pod jedným atomickým cache zámkom na vlastníka, takže z dvoch súbežných spustení druhé počká najviac lock.wait sekúnd, uvidí prvé a odmietne sa — alebo, ak zámok zostane obsadený, vyhodí BillingException („Another subscription is being started“).
- Checkout vytvorí predplatné až po zaplatení, takže dve relácie otvorené pred zaplatením ho môžu spustiť obe — zapnite preto aj nastavenie Checkout v Stripe „Limit customers to one subscription“. Predplatné spustené mimo balíka sa nikdy neblokuje a webhooky vždy uložia, čo Stripe nahlási.

## Checkout v režime predplatného

```php
use RoundlyConsulting\Purchases\DataTransferObjects\Stripe\CheckoutSubscriptionData;
use RoundlyConsulting\Purchases\Providers\Stripe\Enums\PaymentMethodCollection;

$session = Purchases::stripe()->subscriptions()->checkout($company, new CheckoutSubscriptionData(
    prices: [SubscriptionPrice::licensed('price_seats', 10), SubscriptionPrice::metered('price_storage_gb')],
    successUrl: route('billing.done').'?session={CHECKOUT_SESSION_ID}',
    cancelUrl: route('billing'),
    trialDays: 7,
    paymentMethodCollection: PaymentMethodCollection::IfRequired,   // a free trial without a card
    trialEndBehavior: MissingPaymentMethod::Cancel,
    automaticTax: true,          // also sends customer_update[address]=auto, which Stripe Tax needs
));

return redirect($session->url);
```

Predplatné, ktoré Checkout vytvorí, príde cez webhook a s vlastníkom sa prepojí cez svojho zákazníka. PaymentMethodCollection::IfRequired si platobnú metódu vypýta, len keď je niečo splatné hneď, takže bezplatné skúšobné obdobie začne bez nej; trialEndBehavior a billingMode idú do subscription\_data a padajú na tie isté predvolené hodnoty z konfigurácie ako create().

## Zmeny, zrušenie, merače a Portal

```php
use RoundlyConsulting\Purchases\Providers\Stripe\Enums\ProrationBehavior;

$seats = $subscription->itemFor('price_seats');                       // the stored item for a price

Purchases::stripe()->subscriptions()->updateQuantity($seats, $company->members()->count(), ProrationBehavior::CreateProrations);
Purchases::stripe()->subscriptions()->swap($seats, 'price_seats_annual', quantity: 11, proration: ProrationBehavior::AlwaysInvoice);
Purchases::stripe()->subscriptions()->addItem($subscription, 'price_api_calls');          // metered: no quantity
Purchases::stripe()->subscriptions()->removeItem('si_…', ProrationBehavior::None, clearUsage: true);

Purchases::stripe()->subscriptions()->cancelAtPeriodEnd($subscription);   // POST cancel_at_period_end=true
Purchases::stripe()->subscriptions()->resume($subscription);              // POST cancel_at_period_end=false
Purchases::stripe()->subscriptions()->cancel($subscription, invoiceNow: true, prorate: true); // DELETE, now
```

Každá zmena je jedno POST /v1/subscriptions/{id}, uloží sa lokálne a pri novom počte či cene sa spustí SubscriptionUpdated. ProrationBehavior je CreateProrations (predvolené), AlwaysInvoice alebo None; viac zmien naraz pošlete jedným change() so SubscriptionChange a preview() ukáže faktúru ešte pred potvrdením. resume() vezme späť zrušenie naplánované ku koncu obdobia — nie je to endpoint /resume v Stripe.

```php
use RoundlyConsulting\Purchases\DataTransferObjects\Stripe\MeterData;
use RoundlyConsulting\Purchases\Providers\Stripe\Enums\MeterAggregation;

$meter = Purchases::stripe()->meters()->findOrCreate(new MeterData(
    eventName: 'storage_gb_days',
    displayName: 'Storage (GB-days)',
    aggregation: MeterAggregation::Sum,
));

Purchases::stripe()->meters()->report(
    $company,                                   // an owner with a Stripe customer, or 'cus_…'
    'storage_gb_days',
    12.5,                                       // int, float or decimal string — sent as an exact decimal string
    timestamp: now(),
    identifier: "storage-{$company->id}-".today()->toDateString(),
);

$session = Purchases::stripe()->portal($company, returnUrl: route('settings.billing'));
return redirect($session->url);
```

Spotreba sa hlási do Stripe Billing Meters ako presný desatinný reťazec; identifier (ak ho nezadáte, vygeneruje sa) slúži zároveň ako kľúč idempotencie a queue() zafixuje zákazníka, identifier aj čas už pri zaradení do fronty. URL Portalu platí krátko, preto ju vytvorte pri každej návšteve.

## Chyby

Každé zlyhanie Stripe je typovaná StripeException — karta, neplatná požiadavka, autentifikácia, limit požiadaviek, idempotencia, API alebo spojenie — s httpStatus, stripeType, stripeCode, declineCode, param, requestId a idempotencyKey. Správa je od Stripe, pričom všetko, čo vyzerá ako tajný kľúč, sa nahradí \[redacted\], a žiadna výnimka si neponechá HTTP odpoveď. Problémy zistené pred odoslaním vyhodia BillingException; pozrite Enumy a výnimky.

## Prejavte lásku k open source

Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.

[Ďalšie spôsoby podpory vrátane kryptomien](https://roundly-consulting.com/sk/podporte-nas.md)

Odoslaním daru súhlasíte s našimi [podmienkami prijímania darov](https://roundly-consulting.com/sk/podmienky-darov.md).

[Podporte našu open-source prácu (otvorí sa v novej karte)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Pridajte sa k nám na Patreone (otvorí sa v novej karte)](https://www.patreon.com/cw/roundly)

## Chcete to zabudovať do svojho produktu?

Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.

[Ponuka do 48 hodín](https://roundly-consulting.com/sk/kontakt.md) [Prezrieť všetky balíky](https://roundly-consulting.com/sk/open-source/navody/purchases-for-laravel.md)
