Subscriptions
Create a subscription
use CashierBundle\Contract\BillableEntityInterface;
use CashierBundle\Service\SubscriptionService;
final readonly class SubscriptionController
{
public function __construct(
private SubscriptionService $subscriptionService,
) {
}
public function create(BillableEntityInterface $user): void
{
$subscription = $user->newSubscription('default', 'price_monthly')
->trialDays(14)
->create('pm_card_visa');
}
}SubscriptionBuilder capabilities
$builder = $user->newSubscription('default');
$builder->price('price_monthly', quantity: 1);
$builder->meteredPrice('price_metered');
$builder->trialDays(14);
$builder->withCoupon('coupon_xxx');
$builder->withPromotionCode('promo_xxx');
$builder->withMetadata(['segment' => 'pro']);
$builder->withOptions(['collection_method' => 'charge_automatically']);
$builder->withBillingThresholds(['threshold_cycles' => 3]);
$builder->anchorBillingCycleOn(new \DateTime('2025-01-01'));
$builder->withPaymentBehavior('default_incomplete');
$builder->noProrate(); // or prorate()Check state
$user->subscribed('default');
$user->onTrial('default');
$subscription = $user->subscription('default');Full lifecycle
| Method | Description |
|---|---|
active() | Status active or trialing |
valid() | active OR onTrial OR onGracePeriod — common use case for “subscription grants access to service” |
onGracePeriod() | Cancelled subscription but ends_at in the future — access should still be granted |
paused() | Stripe paused subscription |
notPaused() | Stripe non-paused subscription |
onPausedGracePeriod() | paused subscription with grace period |
ended() | Cancelled AND ends_at in the past |
incomplete() | Payment failure status (configure Cashier::$deactivateIncomplete) |
pastDue() | Pending payment status (configure Cashier::$deactivatePastDue) |
recurring() | Active, not in trial, not in grace period |
$subscription->active();
$subscription->pastDue();
$subscription->paused();
$subscription->onGracePeriod();Evolve the subscription
$subscriptionService->swap($subscription, 'price_yearly'); // in-place swap, default proration
$subscriptionService->swapAndInvoice($subscription, 'price_yearly'); // + invoice the proration now
$subscriptionService->updateQuantity($subscription, 5);
$subscriptionService->incrementQuantity($subscription, 2);
$subscriptionService->decrementQuantity($subscription, 1);
$subscriptionService->cancel($subscription); // end of period
$subscriptionService->cancel($subscription, immediately: true); // immediate
$subscriptionService->resume($subscription); // requires onGracePeriod() to be trueMulti-price
For a subscription with multiple prices (base + add-on):
$user->newSubscription('default')
->price('price_monthly_base')
->price('price_add_on', 1)
->create('pm_card_visa');Evolve a multi-plan subscription after creation — add or remove an add-on, or target a specific price when several items coexist:
$subscriptionService->addPrice($subscription, 'price_add_on', quantity: 1);
$subscriptionService->removePrice($subscription, 'price_add_on'); // refuses the last item
// Deterministic targeting on multi-item subscriptions
$subscriptionService->swap($subscription, 'price_pro', fromPrice: 'price_basic');
$subscriptionService->updateQuantity($subscription, 10, price: 'price_seats');
$subscriptionService->incrementQuantity($subscription, 3, price: 'price_seats');When a subscription has several items, quantity and swap operations require the target
price (or fromPrice); otherwise they throw instead of guessing.
SubscriptionItem
Each price in a subscription corresponds to a SubscriptionItem:
$subscription->items; // Collection of SubscriptionItem
$item = $subscription->items()->first();
$item->stripePrice; // price_xxx
$item->quantity;
$item->meterId; // for usage-based
$item->meterEventName;Usage-based billing (Meters API)
Metered usage is reported through Stripe’s Meters API via MeterService. Each metered
SubscriptionItem stores the meter event name it reports to.
// Create a meter once (defaults to sum aggregation)
$meterService->createMeter('API requests', 'api_requests');
// Report usage for a metered item (uses its meter_event_name + the customer's Stripe id)
$item = $subscription->items()->first();
$meterService->reportEventForItem($item, 100);
// Or report an arbitrary meter event directly
$meterService->reportEvent('api_requests', 100, $customer->getStripeId());From the CLI: php bin/console cashier:report-usage <local-item-id> <quantity>.
Checkout subscription
If you prefer a subscription created via Stripe Checkout, use CheckoutService::createSubscription().
Last updated on