diff --git a/billing.md b/billing.md index b789c387664..03041931595 100644 --- a/billing.md +++ b/billing.md @@ -11,6 +11,9 @@ - [Tax Configuration](#tax-configuration) - [Logging](#logging) - [Using Custom Models](#using-custom-models) +- [Quickstart](#quickstart) + - [Selling Products](#quickstart-selling-products) + - [Selling Subscriptions](#quickstart-selling-subscriptions) - [Customers](#customers) - [Retrieving Customers](#retrieving-customers) - [Creating Customers](#creating-customers) @@ -246,6 +249,199 @@ After defining your model, you may instruct Cashier to use your custom model via Cashier::useSubscriptionItemModel(SubscriptionItem::class); } + +## Quickstart + + +### Selling Products + +> **Note** +> Before utilizing Stripe Checkout, you should define Products with fixed prices in your Stripe dashboard. In addition, you should [configure Cashier's webhook handling](#handling-stripe-webhooks). + +Offering product and subscription billing via your application can be intimidating. However, thanks to Cashier and [Stripe Checkout](https://stripe.com/payments/checkout), you can easily build modern, robust payment integrations. + +To charge customers for non-recurring, single-charge products, we'll utilize Cashier to direct customers to Stripe Checkout, where they will provide their payment details and confirm their purchase. Once the payment has been made via Checkout, the customer will be redirected to a success URL of your choosing within your application: + + use Illuminate\Http\Request; + use Stripe\Checkout\Session; + use Stripe\Customer; + + Route::get('/checkout', function (Request $request) { + $price = $request->price_id; + + $quantity = $request->quantity; + + return $request->user()->checkout([$price => $quantity], [ + 'success_url' => route('checkout-success'), + 'cancel_url' => route('checkout-cancel'), + ]); + })->name('checkout'); + + Route::view('checkout.success')->name('checkout-success'); + Route::view('checkout.cancel')->name('checkout-cancel'); + +As you can see in the example above, we will utilize Cashier's provided `checkout` method to redirect the customer to Stripe Checkout for a given "price identifier". When using Stripe, "prices" usually refer to specific products or subscriptions. + +The `checkout` method will automatically create a customer in Stripe and connect it to the corresponding user in your application's database. After completing the checkout session, the customer will be redirected to a dedicated success or cancellation page where you can display an informational message to the customer. + + +#### Tracking Orders + +By default, Cashier doesn't store individual order information when selling products. However, you can easily listen to the webhooks dispatched by Stripe and raised via events by Cashier to store order information in your database. + +To get started, listen for the `WebhookReceived` event dispatched by Cashier. In this case, we are particularly interested in the `checkout.session.complete` webhook. Typically, you should register the event listener in the `boot` method of one of your application's service providers: + + use App\Listeners\StoreOrder; + use Illuminate\Support\Facades\Event; + use Laravel\Cashier\Events\WebhookReceived; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Event::listen(WebhookReceived::class, StoreOrder::class); + } + +In this example, the `StoreOrder` listener might look like the following: + + namespace App\Listeners; + + use Laravel\Cashier\Cashier; + use Laravel\Cashier\Events\WebhookReceived; + + class StoreOrder + { + /** + * Handle the incoming Cashier webhook event. + */ + public function handle(WebhookReceived $event): void + { + if ($event->payload['type'] !== 'checkout.session.completed') { + return; + } + + $checkoutSession = $payload['data']['object']; + + if (! $billable = Cashier::findBillable($checkoutSession['customer'])) { + return; + } + + // Store the customer's order... + $billable->orders()->create(...); + } + } + +Please refer to Stripe's documentation for more information on the [payload contained by the `checkout.session.completed` webhook](https://stripe.com/docs/api/checkout/sessions/object). + + +### Selling Subscriptions + +> **Note** +> Before utilizing Stripe Checkout, you should define Products with fixed prices in your Stripe dashboard. In addition, you should [configure Cashier's webhook handling](#handling-stripe-webhooks). + +Offering product and subscription billing via your application can be intimidating. However, thanks to Cashier and [Stripe Checkout](https://stripe.com/payments/checkout), you can easily build modern, robust payment integrations. + +To learn how to sell subscriptions using Cashier and Stripe Checkout, let's consider the simple scenario of a subscription service with a basic monthly (`price_basic_monthly`) and yearly (`price_basic_yearly`) plan. These two prices could be grouped under a "Basic" product (`pro_basic`) in our Stripe dashboard. In addition, our subscription service might offer an Expert plan as `pro_expert`. + +First, let's discover how a customer can subscribe to our services. Of course, you can imagine the customer might click a "subscribe" button for the Basic plan on our application's pricing page. This button or link should direct the user to a Laravel route which creates the Stripe Checkout session for their chosen plan: + + use Illuminate\Http\Request; + + Route::get('/subscription-checkout', function (Request $request) { + return $request->user() + ->newSubscription('default', 'price_basic_monthly') + ->trialDays(5) + ->allowPromotionCodes() + ->checkout([ + 'success_url' => route('your-success-route'), + 'cancel_url' => route('your-cancel-route'), + ]); + }); + +As you can see in the example above, we will redirect the customer to a Stripe Checkout session which will allow them to subscribe to our Basic plan. After a successful checkout or cancellation, the customer will be redirected back to the URL we provided to the `checkout` method. To know when their subscription has actually started (since some payment methods require a few seconds to process), we'll also need to [configure Cashier's webhook handling](#handling-stripe-webhooks). + +Now that customers can start subscriptions, we need to restrict certain portions of our application so that only subscribed users can access them. Of course, we can always determine a user's current subscription status via the `subscribed` method provided by Cashier's `Billable` trait: + +```blade +@if ($user->subscribed()) +

You are subscribed.

+@endif +``` + +We can even easily determine if a user is subscribed to specific product or price: + +```blade +@if ($user->subscribedToProduct('pro_basic')) +

You are subscribed to our Basic product.

+@endif + +@if ($user->subscribedToPrice('price_basic_monthly')) +

You are subscribed to our monthly Basic plan.

+@endif +``` + + +#### Building A Subscribed Middleware + +For convenience, you may wish to create a [middleware](/docs/{{version}}/middleware) which determines if the incoming request is from a subscribed user. Once this middleware has been defined, you may easily assign it to a route to prevent users that are not subscribed from accessing the route: + + user()?->subscribed()) { + // Redirect user to billing page and ask them to subscribe... + return redirect('/billing'); + } + + return $next($request); + } + } + +Once the middleware has been defined, you may assign it to a route: + + use App\Http\Middleware\Subscribed; + + Route::get('/dashboard', function () { + // ... + })->middleware([Subscribed::class]); + + +#### Allowing Customers To Manage Their Billing Plan + +Of course, customers may want to change their subscription plan to another product or "tier". The easiest way to allow this is by directing customers to Stripe's [Customer Billing Portal](https://stripe.com/docs/no-code/customer-portal), which provides a hosted user interface that allows customers to download invoices, update their payment method, and change subscription plans. + +First, define a link or button within your application that directs users to a Laravel route which we will utilize to initiate a Billing Portal session: + +```blade + + Billing + +``` + +Next, let's define the route that initiates a Stripe Customer Billing Portal session and redirects the user to the Portal. The `redirectToBillingPortal` method accepts the URL that users should be returned to when exiting the Portal: + + use Illuminate\Http\Request; + + Route::get('/billing', function (Request $request) { + return $request->user()->redirectToBillingPortal(route('dashboard')); + })->middleware(['auth'])->name('billing'); + +> **Note** +> As long as you have configured Cashier's webhook handling, Cashier will automatically keep your application's Cashier-related database tables in sync by inspecting the incoming webhooks from Stripe. So, for example, when a user cancels their subscription via Stripe's Customer Billing Portal, Cashier will receive the corresponding webhook and mark the subscription as "cancelled" in your application's database. + ## Customers