skip to Main Content

My aim is to send an email to the user when a Laravel cashier subscription is created (I use stripe checkout) but my cashier event listener isn’t able to find a user with the given stripe customer ID.

My guess is that I’m looking for the customer ID too soon in the subscription flow before it can be added by Cashier, I can see the webhook terminal output and it processes the subscription but when I debug my listener, the user query is null. (It can’t find a user with that customer id)

class StripeEventListener
{
    public function handle(WebhookReceived $event): void
    {
        // New subscription
        if ($event->payload['type'] === 'customer.subscription.created') {

            $customerId = $event->payload['data']['object']['customer'];

            $user = User::where('stripe_id', $customerId)->first();

            Log::debug('data from listener', ['user_result' => $user, 'customer_id' => $customerId]);

            if ($user) {
                Mail::to($user)->queue(new NewSubscriptionMail($user));
            }
        }
    }
}

The log has the following (from a feature test)

[2023-11-08 13:53:37] local.DEBUG: data from listener
{"user_result":null,"customer_id":"cus_Oy8Tp1jrJ8ORq9"}

As I use Stripe Checkout, my controller redirects to Stripe and it doesn’t handle any subscription creation itself, my only option is to listen for the event.

If anybody has any ideas, I’d really appreciate it.

2

Answers


  1. In stripe you’ll need to first register the client in order to proceed with transactions, so for example in my LoginController I’ve something like this:

    After the login is processed:

        $user = Auth::user();
        $stripeCustomer = Cashier::findBillable($user->stripe_id);
        $authUser = User::find($user->id);
        if(!$stripeCustomer){
            $authUser->createAsStripeCustomer();
        }
    

    You can even update the balance from stripe to the user right after:

        $authUser->update([
            'balance' => (float)preg_replace('/[^0-9.]/', '', $authUser->balance()),
        ]);
    

    In your User model you make sure to have the data synced between stripe and your app:

    protected static function booted(): void
    {
        static::updated(queueable(function (User $customer) {
            if ($customer->hasStripeId()) {
                $customer->syncStripeCustomerDetails();
            }
        }));
    }
    

    Then you can perform a transaction in your controller or whenever you’re doing it:

        $user = User::find(auth()->user()->id);
        $user->newSubscription('default', 'your_price_id_goes_here')->add();
    

    Now you can expect for webhook events, since you’re waiting for the customer.subscription.created I strongly recommend that you use the customer.subscription.updated event, since the created it’s created before the subscription is even confirmed, so you’ll be sending emails even when is just a intent of a subscription or transaction.

    You can do it like this to handle more than one type of $event

    public function handle(WebhookReceived $event): void
    {
        if($event->payload)
        {
            switch($event->payload['type'])
            {
                case 'customer.subscription.updated';
                    // Yor code goes here
                break;
            }
        }
    }
    
    Login or Signup to reply.
  2. If you follow the Cashier docs, you’ll see there’s a section specifically on using Checkout to create subscriptions: https://laravel.com/docs/10.x/billing#subscription-checkouts

    You should be redirecting from your controller by chaining a checkout call to a newSubscription call:

    Route::get('/subscription-checkout', function (Request $request) {
        return $request->user()
            ->newSubscription('default', 'price_monthly')
            ->checkout();
    });
    

    If you do, then Cashier creates the customer in Stripe for you (if needed) before redirected to checkout: https://github.com/laravel/cashier-stripe/blob/503d6a0ee03b4a070ee0de086eb248738debd3d7/src/Checkout.php#L80

    You then also don’t need to explicitly handle checkout-related webhooks because again, Cashier’s built-in webhook controller will create the subscription records in your database based on the webhook events Stripe sends.

    So if you are using Cashier, then you shouldn’t be trying to manually create checkout sessions, or manually handle subscription webhooks.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search