lmsqueezy / laravel

A package to easily integrate your Laravel application with Lemon Squeezy.
https://lemonsqueezy.com
MIT License
521 stars 51 forks source link

Using Laravel with React - billable models not filling up after successful subscription. #102

Closed VenoM9078 closed 2 months ago

VenoM9078 commented 2 months ago

Lemon Squeezy for Laravel Version

1.6

Laravel Version

10.10

PHP Version

8.1

Description

Like the title states, I'm using Laravel v10 as API, while using React on the frontend. So the way I have Lemon Squeezy set up, I get my users to click "Buy Now" - which sends a sanctum-protected API call to a custom endpoint, that eventually does:

Route::get('/subscribe', function (Request $request) {
        $user = $request->user();
        if ($user) {
            try {
                // Assuming you have configured LemonSqueezy SDK
                $checkoutUrl = $user->subscribe('xxxxxx')->url();
                return response()->json(['url' => $checkoutUrl]);
            } catch (\Exception $e) {
                return response()->json(['error' => $e->getMessage()], 500);
            }
        }
        return response()->json(['error' => 'User not authenticated'], 401);
    });

While in the frontend, I have:

import axios from "@/utils/axios";

export const subscribe = async () => {
  const response = await axios.get("/api/user/subscribe");
  if (response.data.url) {
    window.location.href = response.data.url; // Redirect to Lemon Squeezy checkout
  } else {
    console.error("Subscription error", response.data.error);
  }
};

So as you can imagine, my user is redirected to the signed Lemon Squeezy checkout, where he pays the subscription, now on success - I do have webhooks set up properly and I get everything back, including the "billable_id" - BUT, the docs state

When the customer has finished their checkout, the incoming SubscriptionCreated webhook will couple it to your billable model in the database. You can then retrieve the subscription from your billable model:

This is the bit that is not working for me. No matter how much I try, no data is saved into my database and there is no record of the subscription the test user just made.

Yes, I have appropriate test keys and test signed key in my env, and I am getting the full webhook object back, which includes the Billable model as well as all the other information about the transaction, but I'd really like the Lemon Squeezy x Laravel coupling to work, so I can easily do $user->subscribed() without going through the trials and tribulations of syncing transactions via webhook events.

Steps To Reproduce

  1. Prepare a similar Laravel v10 + React Vite environment, coupled by Laravel Sanctum.
  2. Setup Lemon Squeezy with Laravel, and prepare an API route (as I did) to generate subscription checkout, and have the React-end user access it (again, I believe I demonstrated it above in the description).
  3. With the webhooks set up and the API routes working that redirect the React user to the backend-generated subscription checkout, complete a successful transaction.
driesvints commented 2 months ago

Hmm this is odd. If the webhook is coming in properly it really should enter the subscription in your database. Do you might have the webhook payload for me to inspect?

VenoM9078 commented 2 months ago

Hmm this is odd. If the webhook is coming in properly it really should enter the subscription in your database. Do you might have the webhook payload for me to inspect?

Absolutely, here's the webhook payload I get back for subscription_created.

[2024-09-05 13:18:11] local.INFO: array (
  'data' => 
  array (
    'id' => '577993',
    'type' => 'subscriptions',
    'links' => 
    array (
      'self' => 'https://api.lemonsqueezy.com/v1/subscriptions/577993',
    ),
    'attributes' => 
    array (
      'urls' => 
      array (
        'customer_portal' => 'https://sandapi.lemonsqueezy.com/billing?expires=1725546903&test_mode=1&user=450585&signature=20ddad3b9a6a819da518b9bc4a4b9351cc3be298e83b0891995b0a635216350a',
        'update_payment_method' => 'https://sandapi.lemonsqueezy.com/subscription/577993/payment-details?expires=1725611703&signature=350f0b1c94018afd239b011fbc8b44ca85db5f0298335caf9358ddea1bc563e1',
        'customer_portal_update_subscription' => 'https://sandapi.lemonsqueezy.com/billing/577993/update?expires=1725611703&user=450585&signature=c693b7f1bc7c06670cb2f21034f9213f75cddd02b8d2a73dacb329fb88a9f1fa',
      ),
      'pause' => NULL,
      'status' => 'active',
      'ends_at' => NULL,
      'order_id' => 3507360,
      'store_id' => 32480,
      'cancelled' => false,
      'renews_at' => '2024-10-05T08:34:57.000000Z',
      'test_mode' => true,
      'user_name' => 'Muhammad Roushan',
      'card_brand' => 'visa',
      'created_at' => '2024-09-05T08:34:58.000000Z',
      'product_id' => 345681,
      'updated_at' => '2024-09-05T08:35:02.000000Z',
      'user_email' => 'roushan.venom1@gmail.com',
      'variant_id' => 509851,
      'customer_id' => 3636923,
      'product_name' => 'Individual',
      'variant_name' => 'Default',
      'order_item_id' => 3450766,
      'trial_ends_at' => NULL,
      'billing_anchor' => 5,
      'card_last_four' => '4242',
      'status_formatted' => 'Active',
      'first_subscription_item' => 
      array (
        'id' => 448394,
        'price_id' => 760160,
        'quantity' => 1,
        'created_at' => '2024-09-05T08:35:03.000000Z',
        'updated_at' => '2024-09-05T08:35:03.000000Z',
        'is_usage_based' => false,
        'subscription_id' => 577993,
      ),
    ),
    'relationships' => 
    array (
      'order' => 
      array (
        'links' => 
        array (
          'self' => 'https://api.lemonsqueezy.com/v1/subscriptions/577993/relationships/order',
          'related' => 'https://api.lemonsqueezy.com/v1/subscriptions/577993/order',
        ),
      ),
      'store' => 
      array (
        'links' => 
        array (
          'self' => 'https://api.lemonsqueezy.com/v1/subscriptions/577993/relationships/store',
          'related' => 'https://api.lemonsqueezy.com/v1/subscriptions/577993/store',
        ),
      ),
      'product' => 
      array (
        'links' => 
        array (
          'self' => 'https://api.lemonsqueezy.com/v1/subscriptions/577993/relationships/product',
          'related' => 'https://api.lemonsqueezy.com/v1/subscriptions/577993/product',
        ),
      ),
      'variant' => 
      array (
        'links' => 
        array (
          'self' => 'https://api.lemonsqueezy.com/v1/subscriptions/577993/relationships/variant',
          'related' => 'https://api.lemonsqueezy.com/v1/subscriptions/577993/variant',
        ),
      ),
      'customer' => 
      array (
        'links' => 
        array (
          'self' => 'https://api.lemonsqueezy.com/v1/subscriptions/577993/relationships/customer',
          'related' => 'https://api.lemonsqueezy.com/v1/subscriptions/577993/customer',
        ),
      ),
      'order-item' => 
      array (
        'links' => 
        array (
          'self' => 'https://api.lemonsqueezy.com/v1/subscriptions/577993/relationships/order-item',
          'related' => 'https://api.lemonsqueezy.com/v1/subscriptions/577993/order-item',
        ),
      ),
      'subscription-items' => 
      array (
        'links' => 
        array (
          'self' => 'https://api.lemonsqueezy.com/v1/subscriptions/577993/relationships/subscription-items',
          'related' => 'https://api.lemonsqueezy.com/v1/subscriptions/577993/subscription-items',
        ),
      ),
      'subscription-invoices' => 
      array (
        'links' => 
        array (
          'self' => 'https://api.lemonsqueezy.com/v1/subscriptions/577993/relationships/subscription-invoices',
          'related' => 'https://api.lemonsqueezy.com/v1/subscriptions/577993/subscription-invoices',
        ),
      ),
    ),
  ),
  'meta' => 
  array (
    'test_mode' => true,
    'event_name' => 'subscription_created',
    'webhook_id' => '72ef449b-d684-43c8-8a8c-134b3e5e30a3',
    'custom_data' => 
    array (
      'billable_id' => 'bab4170e-7dd3-4369-b434-00c4ce4e30f7',
      'billable_type' => 'App\\Models\\User',
      'subscription_type' => 'default',
    ),
  ),
)

And yes, I have also tried updating the billable_id in all LemonSqueezy migrations to string, since I'm using UUID for User model.

driesvints commented 2 months ago

I really have no idea.. this all looks good. If the billable exists then the subscription should definitely have been created in the database alongside the customer record.

VenoM9078 commented 2 months ago

Exactly, which is what boggles my mind. I've spent over 6 hours looking for solutions but no luck so far. I've resorted to manually making DB rows via the LemonSqueezy models like

$order = Order::updateOrCreate(['identifier' => $attributes['identifier']], $orderData);

Its a lot of work but unless someone can come up with a possible alternative, that's my day.

driesvints commented 2 months ago

I'm sorry @VenoM9078 but there's not much I can do here. Since this is something that seems to be happening specifically to you I'm gonna close this. I'm sorry this is happening to you. If you ever figure out why this is happening definitely follow up here.