Stripe webhooks are critical for reliable payment processing - they ensure you don't miss any payment updates even if the customer closes their browser during the checkout process.
Before we get to that, suppose site.com/product/123 returns the chargeCheckout configured on your billable model. It expects an amount, product name and optional quantity, and may have some extra stuff in there.
I think what you are say you are doing is redirecting to your /charge-checkout with the required and optional parameters if present, which would then create a checkout session, and send the customer to Stripe's checkout page.
After this, there is a default URL to notify you of a payment, or a default cancellation redirect which I think is just / at your domain. You can override with success_url and cancel_url.
So the steps are:
Create your single charge checkout, with $billableModel->checkoutCharge(). This redirects to Stripe's payment page.
Stripe handles things.
Stripe sends customer back to you, to your success or cancellation URL.
A webhook is called with a payload, some kind of event with data which you may handle.
For instance:
$checkout = $user->checkoutCharge(3600, 'coffee mug', 3, [
'success_url' => 'https://store.com/payment/success',
'cancel_url' => 'https://store.com/payment/cancelled',
]);
// Do something with $checkout (which is Checkout class) if you need to
Cashier automatically handles common webhook events from Stripe, but the simple case is that you get a redirect to your success and cancelled urls. Cashier is very much focused on other events like subscriptions and the like.
If you want to explicitly handle something, you can:
class WebhookController extends \Laravel\Cashier\Http\Controllers\WebhookController
{
protected function handleCheckoutSessionCompleted($payload)
{
// Your custom logic here
}
}
Cashier has some magic to handle events by the name of your handle... methods in a class that extends the WebhookController.
If you need to do something more than what Cashier provides, you can do it like this, and completely override the webhook handler.
public function handleWebhook(Request $request)
{
$payload = $request->all();
switch ($payload['type']) {
case 'checkout.session.completed':
// Handle successful checkout
break;
case 'checkout.session.expired':
// Handle expired checkout
break;
}
}
Find out what the payload looks like from Stripe's documentation on checkout events.
// Using Cashier's extended controller:
Route::post(
'stripe/webhook',
[MyWebhookController::class, 'handleWebhook']
)->name('cashier.webhook');
// Or your completely custom controller, which is StripeController with a method handleWebhook:
Route::post(
'stripe/webhook',
[StripeController::class, 'handleWebhook']
);
Look in config/cashier.php for some configuration options. But it mostly depends on whether you just want either a redirect to a success or cancellation page, or you want that and a custom handled event from Stripe, in which case you must meddle with the webhook handler or define your own.
So the flow is this:
Create checkout session with:
// return this, or do something with it and return, Cashier redirects to Stripe checkout
$billableModel->checkoutCharge(12345, 'Item', 1, [
'success_url' => 'https://something',
'cancel_url' => 'https://something', ... may be other meta data here if you want to research that
]);
Stripe checkout handles the next part, generates a checkout.session.completed if it was successful, otherwise may be a checkout.session.expired, and then the customer is redirected back to your defined success or cancelled URL.
To get anything specific, you need to handle the webhook.
- When customers are ready to complete their purchase, your application creates a new Checkout Session.
- The Checkout Session provides a URL that redirects customers to a Stripe-hosted payment page.
- Customers enter their payment details on the payment page and complete the transaction.
- After the transaction, a webhook fulfils the order using the checkout.session.completed event.
To be clear "webhook fulfills the order using the checkout.session.completed event" means you are sent a big JSON blob to process with your webhook handler. It will tell you what happened in there. You can find the event details here: https://docs.stripe.com/api/checkout/sessions/object
If payment_status is 'paid' and status is 'complete' then you can fulfill the order.