{"id":27702061,"url":"https://github.com/lmsqueezy/laravel","last_synced_at":"2025-04-25T20:40:09.980Z","repository":{"id":153040885,"uuid":"621902641","full_name":"lmsqueezy/laravel","owner":"lmsqueezy","description":"A package to easily integrate your Laravel application with Lemon Squeezy.","archived":false,"fork":false,"pushed_at":"2025-04-13T08:28:46.000Z","size":346,"stargazers_count":544,"open_issues_count":17,"forks_count":54,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-04-20T19:17:10.487Z","etag":null,"topics":["billing","laravel","lemonsqueezy","php"],"latest_commit_sha":null,"homepage":"https://lemonsqueezy.com","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lmsqueezy.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"juststeveking"}},"created_at":"2023-03-31T16:28:30.000Z","updated_at":"2025-04-20T15:47:39.000Z","dependencies_parsed_at":"2024-03-26T10:51:39.710Z","dependency_job_id":"3dff3c54-d771-48de-a018-34537cc58220","html_url":"https://github.com/lmsqueezy/laravel","commit_stats":null,"previous_names":[],"tags_count":35,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lmsqueezy%2Flaravel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lmsqueezy%2Flaravel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lmsqueezy%2Flaravel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lmsqueezy%2Flaravel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lmsqueezy","download_url":"https://codeload.github.com/lmsqueezy/laravel/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250894348,"owners_count":21504144,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["billing","laravel","lemonsqueezy","php"],"created_at":"2025-04-25T20:40:09.448Z","updated_at":"2025-04-25T20:40:09.966Z","avatar_url":"https://github.com/lmsqueezy.png","language":"PHP","funding_links":["https://github.com/sponsors/juststeveking"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\u003cimg src=\"https://github.com/lmsqueezy/laravel/raw/HEAD/art/readme-header.png\" alt=\"Readme header\"\u003e\u003c/p\u003e\n\n# Lemon Squeezy for Laravel\n\n\u003ca href=\"https://github.com/lmsqueezy/laravel/actions\"\u003e\n    \u003cimg src=\"https://github.com/lmsqueezy/laravel/actions/workflows/tests.yml/badge.svg\" alt=\"Tests\"\u003e\n\u003c/a\u003e\n\u003ca href=\"https://github.com/lmsqueezy/laravel/actions/workflows/coding-standards.yml\"\u003e\n    \u003cimg src=\"https://github.com/lmsqueezy/laravel/actions/workflows/coding-standards.yml/badge.svg\" alt=\"Coding Standards\" /\u003e\n\u003c/a\u003e\n\u003ca href=\"https://packagist.org/packages/lemonsqueezy/laravel\"\u003e\n    \u003cimg src=\"https://img.shields.io/packagist/v/lemonsqueezy/laravel\" alt=\"Latest Stable Version\"\u003e\n\u003c/a\u003e\n\u003ca href=\"https://packagist.org/packages/lemonsqueezy/laravel\"\u003e\n    \u003cimg src=\"https://img.shields.io/packagist/dt/lemonsqueezy/laravel\" alt=\"Total Downloads\"\u003e\n\u003c/a\u003e\n\nA package to easily integrate your [Laravel](https://laravel.com) application with Lemon Squeezy. It takes the pain out of setting up a checkout experience. Easily set up payments for your products or let your customers subscribe to your product plans. Handle grace periods, pause subscriptions, or offer free trials.\n\nThis package drew inspiration from [Cashier](https://github.com/laravel/cashier-stripe) which was created by [Taylor Otwell](https://twitter.com/taylorotwell).\n\nWe also recommend to read the Lemon Squeezy [docs](https://docs.lemonsqueezy.com/help) and [developer guide](https://docs.lemonsqueezy.com/guides/developer-guide).\n\n## Roadmap\n\nThe below features are not yet in this package but are planned to be added in the future:\n\n- Subscription invoices\n- [Usage Based Billing](https://github.com/lmsqueezy/laravel/issues/55)\n- Marketing emails check\n- Create discount codes\n- [Nova integration](https://github.com/lmsqueezy/laravel/issues/51)\n\n## Requirements\n\n- PHP 8.1 or higher\n- Laravel 10.0 or higher\n\n## Installation\n\nThere are a few steps you'll need to take to install the package:\n\n1. Requiring the package through Composer\n2. Creating an API Key\n3. Connecting your store\n4. Configuring the Billable Model\n5. Running Migrations\n6. Connecting to Lemon JS\n7. Setting up webhooks\n\nWe'll go over each of these below.\n\n### Composer\n\nInstall the package with composer:\n\n```bash\ncomposer require lemonsqueezy/laravel\n```\n\n### API Key\n\nNext, configure your API key. Create a new key in testing mode in [the Lemon Squeezy dashboard](https://app.lemonsqueezy.com/settings/api) and paste them in your `.env` file as shown below:\n\n```ini\nLEMON_SQUEEZY_API_KEY=your-lemon-squeezy-api-key\n```\n\nWhen you're deploying your app to production, you'll have to create a new key in production mode to work with live data.\n\n### Store Identifier\n\nYour store identifier will be used when creating checkouts for your products. Go to [your Lemon Squeezy stores settings](https://app.lemonsqueezy.com/settings/stores) and copy the Store ID (the part after the `#` sign) into the env value below:\n\n```ini\nLEMON_SQUEEZY_STORE=your-lemon-squeezy-store-id\n```\n\n### Billable Model\n\nTo make sure we can actually create checkouts for our customers, we'll need to configure a model to be our \"billable\" model. This is typical the `User` model of your app. To do this, import and use the `Billable` trait on your model:\n\n```php\nuse LemonSqueezy\\Laravel\\Billable;\n \nclass User extends Authenticatable\n{\n    use Billable;\n}\n```\n\nNow your user model will have access to methods from our package to create checkouts in Lemon Squeezy for your products. Note that you can make any model type a billable as you wish. It's not required to use one specific model class.\n\n\u003e [!NOTE]\n\u003e Every action on the library like charging, creating checkouts or subscribing will automatically create a customer in Lemon Squeezy for you and connect it to your billable. It should never be necessary to manually create a customer with the Lemon Squeezy API.  \n\n### Running Migrations\n\nThe package comes with some migrations to store data received from Lemon Squeezy by webhooks. It'll add a `lemon_squeezy_customers` table which holds all info about a customer. This table is connected to a billable model of any model type you wish. It'll also add a `lemon_squeezy_subscriptions` table which holds info about subscriptions. Install these migrations by simply running `artisan migrate`:\n\n```bash\nphp artisan migrate\n```\n\nIf you want to customize these migrations, you can [overwrite them](#overwriting-migrations).\n\n### Lemon JS\n\nLemon Squeezy uses its own JavaScript library to initiate its checkout widget. We can make use of it by loading it through the Blade directive in the `head` section of our app, right before the closing `\u003c/head\u003e` tag.\n\n```blade\n\u003chead\u003e\n    ...\n \n    @lemonJS\n\u003c/head\u003e\n```\n\n### Webhooks\n\nFinally, make sure to set up incoming webhooks. This is both needed in development as in production.\n\n#### Webhooks In Development\n\nThe easiest way to set this up while developing your app is with the `php artisan lmsqueezy:listen` command that ships with this package. This command will setup a webhook through the Lemon Squeezy API, start listening for any events and remove the webhook when quitting the command.\n\n```bash\nphp artisan lmsqueezy:listen\n```\n\nFor ngrok, if your app is not running on port `8000` pass in the port you want to tunnel using:\n```aiignore\nphp artisan lmsqueezy:listen ngrok --port=80\n```\n\nAlthough this command should always cleanup the webhook after itself, you may wish to cleanup any lingering webhooks with the `--cleanup` flag:\n\n```bash\nphp artisan lmsqueezy:listen --cleanup\n```\n\nCurrently, this command supports [Ngrok](https://ngrok.com/) and [Expose](https://github.com/beyondcode/expose).\n\n\u003e [!WARNING]  \n\u003e The `lmsqueezy:listen` command is currently not supported in Windows due to the lack of signal handling. Instead you can take the manual approach from the [webhooks in production](#webhooks-in-production) docs below. You'll still need to use a service like Ngrok or Expose to expose a publically accessible url.\n\n#### Webhooks In Production\n\nFor production, we'll need to setup things manually. The package already ships with a route so all that's left is to go to [your Lemon Squeezy's webhook settings](https://app.lemonsqueezy.com/settings/webhooks) and point the url to your app's domain. The path you should point to is `/lemon-squeezy/webhook` by default. Make sure to select all event types.\n\n\u003e [!NOTE]  \n\u003e We also very much recommend to [verify webhook signatures](#verifying-webhook-signatures) in production.\n\n#### Webhooks \u0026 CSRF Protection\n\nIncoming webhooks should not be affected by [CSRF protection](https://laravel.com/docs/csrf). To prevent this, add your webhook path to the except list of your `App\\Http\\Middleware\\VerifyCsrfToken` middleware:\n\n```php\nprotected $except = [\n    'lemon-squeezy/*',\n];\n```\n\nOr if you're using Laravel v11 and up, you should exclude `lemon-squeezy/*` in your application's `bootstrap/app.php` file:\n\n```php\n-\u003ewithMiddleware(function (Middleware $middleware) {\n    $middleware-\u003evalidateCsrfTokens(except: [\n        'lemon-squeezy/*',\n    ]);\n})\n```\n\n## Upgrading\n\nPlease review [our upgrade guide](./UPGRADE.md) when upgrading to a new version.\n\n## Configuration\n\nThe package offers various way to configure your experience with integrating with Lemon Squeezy. \n\nBy default, we don't recommend publishing the config file as most things can be configured with environment variables. Should you still want to adjust the config file, you can publish it with the following command:\n\n```bash\nphp artisan vendor:publish --tag=\"lemon-squeezy-config\"\n```\n\n### Verifying Webhook Signatures\n\nIn order to make sure that incoming webhooks are actually from Lemon Squeezy, we can configure a signing secret for them. Go to your webhook settings in the Lemon Squeezy dashboard, click on the webhook of your app and copy the signing secret into the environment variable below:\n\n```ini\nLEMON_SQUEEZY_SIGNING_SECRET=your-webhook-signing-secret\n```\n\nAny incoming webhook will now first be verified before being executed.\n\n### Overwriting Migrations\n\nLemon Squeezy for Laravel ships with some migrations to hold data sent over. If you're using something like a string based identifier for your billable model, like a UUID, or want to adjust something to the migrations you can overwrite them. First, publish these with the following command:\n\n```bash\nphp artisan vendor:publish --tag=\"lemon-squeezy-migrations\"\n```\n\nThen, ignore the package's migrations in your `AppServiceProvider`'s `register` method:\n\n```php\nuse LemonSqueezy\\Laravel\\LemonSqueezy;\n\npublic function register(): void\n{\n    LemonSqueezy::ignoreMigrations();\n}\n```\n\nNow you'll rely on your own migrations rather than the package one. Please note though that you're now responsible as well for keeping these in sync withe package one manually whenever you upgrade the package.\n\n## Commands\n\nBelow you'll find a list of commands you can run to retrieve info from Lemon Squeezy:\n\nCommand | Description\n--- | ---\n`php artisan lmsqueezy:products` | List all available products with their variants and prices\n`php artisan lmsqueezy:products 12345` | List a specific product by its ID with its variants and prices\n`php artisan lmsqueezy:licenses 12345` | List licenses generated for a given product ID\n`php artisan lmsqueezy:licenses -p 3 -s 20` | List the paginated result of all generated licenses\n`php artisan lmsqueezy:licenses --order=1234 --status=active` | List active licenses for a given order ID\n\n\n## Checkouts\n\nWith this package, you can easily create checkouts for your customers.\n\n### Single Payments\n\nFor example, to create a checkout for a single-payment, use a variant ID of a product variant you want to sell and create a checkout using the snippet below:\n\n```php\nuse Illuminate\\Http\\Request;\n \nRoute::get('/buy', function (Request $request) {\n    return $request-\u003euser()-\u003echeckout('variant-id');\n});\n```\n\nThis will automatically redirect your customer to a Lemon Squeezy checkout where the customer can buy your product.\n\n\u003e [!NOTE]\n\u003e When creating a checkout for your store, each time you redirect a checkout object or call `url` on the checkout object, an API call to Lemon Squeezy will be made. These calls are expensive and can be time and resource consuming for your app. If you are creating the same session over and over again you may want to cache these urls. \n\n#### Custom Priced Charges\n\nYou can also overwrite the amount of a product variant by calling the `charge` method on a customer:\n\n```php\nuse Illuminate\\Http\\Request;\n \nRoute::get('/buy', function (Request $request) {\n    return $request-\u003euser()-\u003echarge(2500, 'variant-id');\n});\n```\n\nThe amount should be a positive integer in cents.\n\nYou'll still need to provide a variant ID but can overwrite the price as you see fit. One thing you can do is create a \"generic\" product with a specific currency which you can dynamically charge against.\n\n### Overlay Widget\n\nInstead of redirecting your customer to a checkout screen, you can also create a checkout button which will render a checkout overlay on your page. To do this, pass the `$checkout` object to a view:\n\n```php\nuse Illuminate\\Http\\Request;\n \nRoute::get('/buy', function (Request $request) {\n    $checkout = $request-\u003euser()-\u003echeckout('variant-id');\n\n    return view('billing', ['checkout' =\u003e $checkout]);\n});\n```\n\nNow, create the button using the shipped Laravel Blade component from the package:\n\n```blade\n\u003cx-lemon-button :href=\"$checkout\" class=\"px-8 py-4\"\u003e\n    Buy Product\n\u003c/x-lemon-button\u003e\n```\n\nWhen a user clicks this button, it'll trigger the Lemon Squeezy checkout overlay. You can also, optionally request it to be rendered in dark mode:\n\n```blade\n\u003cx-lemon-button :href=\"$checkout\" class=\"px-8 py-4\" dark\u003e\n    Buy Product\n\u003c/x-lemon-button\u003e\n```\n\nIf you're checking out subscriptions, and you don't want to show the \"You will be charged...\" text, you may disable this by calling the `withoutSubscriptionPreview` method on the checkout object:\n\n```php\n$request-\u003euser()-\u003esubscribe('variant-id')\n    -\u003ewithoutSubscriptionPreview();\n```\n\nIf you want to set a different color for the checkout button you may pass a hex color code (with the leading `#` sign) through `withButtonColor`:\n\n```php\n$request-\u003euser()-\u003echeckout('variant-id')\n    -\u003ewithButtonColor('#FF2E1F');\n```\n\n### Prefill User Data\n\nYou can easily prefill user data for checkouts by overwriting the following methods on your billable model:\n\n```php\npublic function lemonSqueezyName(): ?string; // name\npublic function lemonSqueezyEmail(): ?string; // email\npublic function lemonSqueezyCountry(): ?string; // country\npublic function lemonSqueezyZip(): ?string; // zip\npublic function lemonSqueezyTaxNumber(): ?string; // tax_number\n```\n\nBy default, the attributes displayed in a comment on the right of the methods will be used.\n\nAdditionally, you may also pass this data on the fly by using the following methods:\n\n```php\nuse Illuminate\\Http\\Request;\n \nRoute::get('/buy', function (Request $request) {\n    return $request-\u003euser()-\u003echeckout('variant-id')\n        -\u003ewithName('John Doe')\n        -\u003ewithEmail('john@example.com')\n        -\u003ewithBillingAddress('US', '10038') // Country \u0026 Zip Code\n        -\u003ewithTaxNumber('123456679')\n        -\u003ewithDiscountCode('PROMO');\n});\n```\n\n### Product Details\n\nYou can overwrite additional data for product checkouts with the `withProductName` and `withDescription` methods:\n\n```php\n$request-\u003euser()-\u003echeckout('variant-id')\n    -\u003ewithProductName('Ebook')\n    -\u003ewithDescription('A thrilling novel!');\n```\n\n### Receipt Thank You\n\nAdditionally, you can customize the thank you note for the order receipt email.\n\n```php\n$request-\u003euser()-\u003echeckout('variant-id')\n    -\u003ewithThankYouNote('Thanks for your purchase!');\n```\n\n### Redirects After Purchase\n\nTo redirect customers back to your app after purchase, you may use the `redirectTo` method:\n\n```php\n$request-\u003euser()-\u003echeckout('variant-id')\n    -\u003eredirectTo(url('/'));\n```\n\nYou may also set a default url for this by configuring the `lemon-squeezy.redirect_url` in your config file:\n\n```php\n'redirect_url' =\u003e 'https://my-app.com/dashboard',\n```\n\nIn order to do this you'll need to [publish your config file](#configuration).\n\n### Expire Checkouts\n\nYou can indicate how long a checkout session should stay active by calling the `expiresAt` method on it:\n\n```php\n$request-\u003euser()-\u003echeckout('variant-id')\n    -\u003eexpiresAt(now()-\u003eaddDays(3));\n```\n\n### Custom Data\n\nYou can also [pass along custom data with your checkouts](https://docs.lemonsqueezy.com/help/checkout/passing-custom-data). To do this, send along key/value pairs with the checkout method:\n\n```php\nuse Illuminate\\Http\\Request;\n \nRoute::get('/buy', function (Request $request) {\n    return $request-\u003euser()-\u003echeckout('variant-id', custom: ['foo' =\u003e 'bar']);\n});\n```\n\nThese will then later be available in the related webhooks for you.\n\n#### Reserved Keywords\n\nWhen working with custom data there are a few reserved keywords for this library:\n\n- `billable_id`\n- `billable_type`\n- `subscription_type`\n\nAttempting to use any of these will result in an exception being thrown.\n\n## Customers\n\n### Customer Portal\n\nCustomers may easily manage their personal data like their name, email address, etc by visiting their [customer portal](https://docs.lemonsqueezy.com/guides/developer-guide/customer-portal). Lemon Squeezy for Laravel makes it easy to redirect customers to this by calling `redirectToCustomerPortal` on the billable:\n\n```php\nuse Illuminate\\Http\\Request;\n \nRoute::get('/customer-portal', function (Request $request) {\n    return $request-\u003euser()-\u003eredirectToCustomerPortal();\n});\n```\n\nIn order to call this method your billable already needs to have a subscription through Lemon Squeezy. Also, this method will perform an underlying API call so make sure to place this redirect behind a route which you can link to in your app.\n\nOptionally, you also get the signed customer portal url directly:\n\n```php\n$url = $user-\u003ecustomerPortalUrl();\n```\n\n### My Orders\n\nBesides the customer portal for managing subscriptions, [Lemon Squeezy also has a \"My Orders\" portal](https://docs.lemonsqueezy.com/help/online-store/my-orders) to manage all of your purchases for a customer account. This does involve a mixture of purchases across multiple vendors. If this is something you wish your customers can find, you can link to [`https://app.lemonsqueezy.com/my-orders`](https://app.lemonsqueezy.com/my-orders) and tell them to login with the email address they performed the purchase with.\n\n## Orders\n\nLemon Squeezy allows you to retrieve a list of all orders made for your store. You can then use this list to present all orders to your customers.\n\n### Retrieving Orders\n\nTo retrieve a list of orders for a specific customer, simply call the saved models in the database:\n\n```blade\n\u003ctable\u003e\n    @foreach ($user-\u003eorders as $order)\n        \u003ctd\u003e{{ $order-\u003eordered_at-\u003etoFormattedDateString() }}\u003c/td\u003e\n        \u003ctd\u003e{{ $order-\u003eorder_number }}\u003c/td\u003e\n        \u003ctd\u003e{{ $order-\u003esubtotal() }}\u003c/td\u003e\n        \u003ctd\u003e{{ $order-\u003ediscount() }}\u003c/td\u003e\n        \u003ctd\u003e{{ $order-\u003etax() }}\u003c/td\u003e\n        \u003ctd\u003e{{ $order-\u003etotal() }}\u003c/td\u003e\n        \u003ctd\u003e{{ $order-\u003ereceipt_url }}\u003c/td\u003e\n    @endforeach\n\u003c/table\u003e\n```\n\n### Checking Order Status\n\nTo check if an individual order is paid, you may use the `paid` method:\n\n```php\nif ($order-\u003epaid()) {\n    // ...\n}\n```\n\nBesides that, you have three other checks you can do: `pending`, `failed` \u0026 `refunded`. If the order is `refunded`, you may also use the `refunded_at` timestamp:\n\n```blade\n@if ($order-\u003erefunded())\n    Order {{ $order-\u003eorder_number }} was refunded on {{ $order-\u003erefunded_at-\u003etoFormattedDateString() }}\n@endif\n```\n\nYou can also check if an order was for a specific product:\n\n```php\nif ($order-\u003ehasProduct('your-product-id')) {\n    // ...\n}\n```\n\nOr for a specific variant:\n\n```php\nif ($order-\u003ehasVariant('your-variant-id')) {\n    // ...\n}\n```\n\nAdditionally, you may check if a customer has purchased a specific product:\n\n```php\nif ($user-\u003ehasPurchasedProduct('your-product-id')) {\n    // ...\n}\n```\n\nOr a specific variant:\n\n```php\nif ($user-\u003ehasPurchasedVariant('your-variant-id')) {\n    // ...\n}\n```\n\nThese two checks will both make sure the correct product or variant was purchased and paid for. This is useful as well if you're offering a feature in your app like lifetime access.\n\n## Subscriptions\n\n### Setting Up Subscription Products\n\nSetting up subscription products with different plans and intervals needs to be done in a specific way. Lemon Squeezy has [a good guide](https://docs.lemonsqueezy.com/guides/tutorials/saas-subscription-plans) on how to do this.\n\nAlthough you're free to choose how you set up products and plans, it's easier to go for option two and create a product for each plan type. So for example, when you have a \"Basic\" and \"Pro\" plan and both have monthly and yearly prices, it's wiser to create two separate products for these and then add two variants for each for their monthly and yearly prices.\n\nThis gives you the advantage later on to make use of the `hasProduct` method on a subscription which allows you to just check if a subscription is on a specific plan type and don't worry if it's on a monthly or yearly schedule.\n\n### Creating Subscriptions\n\nStarting subscriptions is easy. For this, we need the variant id from our product. Copy the variant id and initiate a new subscription checkout from your billable model:\n\n```php\nuse Illuminate\\Http\\Request;\n \nRoute::get('/subscribe', function (Request $request) {\n    return $request-\u003euser()-\u003esubscribe('variant-id');\n});\n```\n\nWhen 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:\n\n```php\n$subscription = $user-\u003esubscription();\n```\n\n### Checking Subscription Status\n\nOnce a customer is subscribed to your services, you can use a variety of methods to check for various states on the subscription. The most basic example, is to check if a customer is subscribed to a valid subscription:\n\n```php\nif ($user-\u003esubscribed()) {\n    // ...\n}\n```\n\nYou may use this in various places in your app like middleware, policies, etc, to offer your services. To check if an individual subscription is valid, you may use the `valid` method:\n\n```php\nif ($user-\u003esubscription()-\u003evalid()) {\n    // ...\n}\n```\n\nThis method, as well as the `subscribed` method, will return true if your subscription is active, on trial, past due, paused for free or on its cancelled grace period.\n\nYou can also check if a subscription is on a specific product:\n\n```php\nif ($user-\u003esubscription()-\u003ehasProduct('your-product-id')) {\n    // ...\n}\n```\n\nOr on a specific variant:\n\n```php\nif ($user-\u003esubscription()-\u003ehasVariant('your-variant-id')) {\n    // ...\n}\n```\n\nIf you want to check if a subscription is on a specific variant and at the same valid you can use:\n\n```php\nif ($user-\u003esubscribedToVariant('your-variant-id')) {\n    // ...\n}\n```\n\nOr if you're using [multiple subscription types](#multiple-subscriptions), you can pass a type as an extra parameter:\n\n```php\nif ($user-\u003esubscribed('swimming')) {\n    // ...\n}\n\nif ($user-\u003esubscribedToVariant('your-variant-id', 'swimming')) {\n    // ...\n}\n```\n\n#### Cancelled Status\n\nTo check if a user has cancelled their subscription you may use the `cancelled` method:\n\n```php\nif ($user-\u003esubscription()-\u003ecancelled()) {\n    // ...\n}\n```\n\nWhen they're on their grace period, you can use the `onGracePeriod` check:\n\n```php\nif ($user-\u003esubscription()-\u003eonGracePeriod()) {\n    // ...\n}\n```\n\nIf a subscription is fully cancelled and no longer on its grace period, you may use the `expired` check:\n\n```php\nif ($user-\u003esubscription()-\u003eexpired()) {\n    // ...\n}\n```\n\n#### Past Due Status\n\nIf a recurring payment for a subscription fails, the subscription will transition in a past due state. This means it's still a valid subscription but your customer will have a 2 weeks period where their payments will be retried.\n\n```php\nif ($user-\u003esubscription()-\u003epastDue()) {\n    // ...\n}\n```\n\nIn this state, you should instruct your customer to [update their payment info](#updating-payment-information). Failed payments in Lemon Squeezy are retried a couple of times. For more information on that, as well as the dunning process, head over to [the Lemon Squeezy documentation](https://docs.lemonsqueezy.com/help/online-store/recovery-dunning)\n\n#### Subscription Scopes\n\nVarious subscriptions scopes are available to query subscriptions in specific states:\n\n```php\n// Get all active subscriptions...\n$subscriptions = Subscription::query()-\u003eactive()-\u003eget();\n \n// Get all of the cancelled subscriptions for a specific user...\n$subscriptions = $user-\u003esubscriptions()-\u003ecancelled()-\u003eget();\n```\n\nHere's all available scopes:\n\n```php\nSubscription::query()-\u003eonTrial();\nSubscription::query()-\u003eactive();\nSubscription::query()-\u003epaused();\nSubscription::query()-\u003epastDue();\nSubscription::query()-\u003eunpaid();\nSubscription::query()-\u003ecancelled();\nSubscription::query()-\u003eexpired();\n```\n\n### Updating Payment Information\n\nTo allow your customer to [update their payment details](https://docs.lemonsqueezy.com/guides/developer-guide/managing-subscriptions#updating-billing-details), like their credit card info, you can redirect them with the following method:\n\n```php\nuse Illuminate\\Http\\Request;\n \nRoute::get('/update-payment-info', function (Request $request) {\n    $subscription = $request-\u003euser()-\u003esubscription();\n\n    return view('billing', [\n        'paymentMethodUrl' =\u003e $subscription-\u003eupdatePaymentMethodUrl(),\n    ]);\n});\n```\n\nAlternatively, if you want the URL to open in a more seamless way on top of your app (similar to the checkout overlay), you may use [Lemon.js](https://docs.lemonsqueezy.com/help/lemonjs/opening-overlays#updating-payment-details-overlay) to open the URL with the `LemonSqueezy.Url.Open()` method. First, pass the url to a view:\n\n```php\nuse Illuminate\\Http\\Request;\n \nRoute::get('/update-payment-info', function (Request $request) {\n    $subscription = $request-\u003euser()-\u003esubscription();\n\n    return view('billing', [\n        'paymentMethodUrl' =\u003e $subscription-\u003eupdatePaymentMethodUrl(),\n    ]);\n});\n```\n\nThen trigger it through a button:\n\n```blade\n\u003cscript defer\u003e\n    function updatePM() {\n        LemonSqueezy.Url.Open('{!! $paymentMethodUrl !!}');\n    }\n\u003c/script\u003e\n\n\u003cbutton onclick=\"updatePM()\"\u003e\n    Update payment method\n\u003c/button\u003e\n```\n\nThis requires you to have set up [Lemon.js](#lemon-js).\n\n### Changing Plans\n\nWhen a customer is subscribed to a monthly plan, they might want to upgrade to a better plan, change their payments to a yearly plan or downgrade to a cheaper plan. For these situations, you can allow them to swap plans by passing a different variant id with its product id to the `swap` method:\n\n```php\nuse App\\Models\\User;\n\n$user = User::find(1);\n\n$user-\u003esubscription()-\u003eswap('product-id', 'variant-id');\n```\n\nThis will swap the customer to their new subscription plan but billing will only be done on the next billing cycle. If you'd like to immediately invoice the customer you may use the `swapAndInvoice` method instead:\n\n```php\n$user = User::find(1);\n\n$user-\u003esubscription()-\u003eswapAndInvoice('product-id', 'variant-id');\n```\n\n\u003e [!NOTE]\n\u003e You'll notice in the above methods that you both need to provide a product ID and variant ID and might wonder why that is. Can't you derive the product ID from the variant ID? Unfortuntately that's only possible when swapping to variants between the same product. When swapping to a different product alltogether you are required to also provide the product ID in the Lemon Squeezy API. Therefor, we've made the decision to make this uniform and just always require the product ID as well.\n\n#### Prorations\n\nBy default, Lemon Squeezy will prorate amounts when changing plans. If you want to prevent this, you may use the `noProrate` method before executing the swap:\n\n```php\n$user = User::find(1);\n\n$user-\u003esubscription()-\u003enoProrate()-\u003eswap('product-id', 'variant-id');\n```\n\n### Changing The Billing Date\n\nTo change the date of the month on which your customer gets billed for their subscription, you may use the `anchorBillingCycleOn` method:\n\n```php\n$user = User::find(1);\n\n$user-\u003esubscription()-\u003eanchorBillingCycleOn(21);\n```\n\nIn the above example, the customer will now get billed on the 21st of each month going forward. For more info, see [the Lemon Squeezy docs](https://docs.lemonsqueezy.com/guides/developer-guide/managing-subscriptions#changing-the-billing-date).\n\n### Multiple Subscriptions\n\nIn some situation you may find yourself wanting to allow your customer to subscribe to multiple subscription types. For example, a gym may offer a swimming and weight lifting subscription. You can allow your customer to subscribe to either or both.\n\nTo handle the different subscriptions you may provide a `type` of subscription as the second argument to `subscribe` when starting a new one:\n\n```php\n$user = User::find(1);\n\n$checkout = $user-\u003esubscribe('variant-id', 'swimming');\n```\n\nNow you may always refer this specific subscription type by providing the `type` argument when retrieving it:\n\n```php\n$user = User::find(1);\n\n// Retrieve the swimming subscription type...\n$subscription = $user-\u003esubscription('swimming');\n\n// Swap plans for the gym subscription type...\n$user-\u003esubscription('gym')-\u003eswap('product-id', 'variant-id');\n\n// Cancel the swimming subscription...\n$user-\u003esubscription('swimming')-\u003ecancel();\n```\n\n### Pausing Subscriptions\n\nTo [pause subscriptions](https://docs.lemonsqueezy.com/guides/developer-guide/managing-subscriptions#pausing-and-unpausing-subscriptions), call the `pause` method on it:\n\n```php\n$user = User::find(1);\n\n$user-\u003esubscription()-\u003epause();\n```\n\nOptionally, provide a date when the subscription can resume:\n\n```php\n$user = User::find(1);\n\n$user-\u003esubscription()-\u003epause(\n    now()-\u003eaddDays(5)\n);\n```\n\nThis will fill in the `resumes_at` timestamp on your customer. To know if your subscription is within its paused period you can use the `onPausedPeriod` method:\n\n```php\nif ($user-\u003esubscription()-\u003eonPausedPeriod()) {\n    // ...\n}\n```\n\nTo unpause, simply call that method on the subscription:\n\n```php\n$user-\u003esubscription()-\u003eunpause();\n```\n\n#### Pause State\n\nBy default, pausing a subscription will void its usage for the remainder of the pause period. If you instead would like your customers to use your services for free, you may use the `pauseForFree` method:\n\n```php\n$user-\u003esubscription()-\u003epauseForFree();\n```\n\n### Cancelling Subscriptions\n\nTo [cancel a subscription](https://docs.lemonsqueezy.com/guides/developer-guide/managing-subscriptions#cancelling-and-resuming-subscriptions), call the `cancel` method on it:\n\n```php\n$user = User::find(1);\n\n$user-\u003esubscription()-\u003ecancel();\n```\n\nThis will set your subscription to be cancelled. If your subscription is cancelled mid-cycle, it'll enter a grace period and the `ends_at` column will be set. The customer will still have access to the services provided for the remainder of the period. You can check for its grace period by calling the `onGracePeriod` method:\n\n```php\nif ($user-\u003esubscription()-\u003eonGracePeriod()) {\n    // ...\n}\n```\n\nImmediate cancellation with Lemon Squeezy is not possible. To resume a subscription while it's still on its grace period, call the `resume` method:\n\n```php\n$user-\u003esubscription()-\u003eresume();\n```\n\nWhen a cancelled subscription reaches the end of its grace period it'll transition to a state of expired and won't be able to resume any longer.\n\n### Subscription Trials\n\nFor a thorough read on trialing in Lemon Squeezy, [have a look at their guide](https://docs.lemonsqueezy.com/guides/tutorials/saas-free-trials).\n\n#### No Payment Required\n\nTo allow people to signup for your product without having them to fill out their payment details, you may set the `trial_ends_at` column when creating them as a customer:\n\n```php\nuse App\\Models\\User;\n \n$user = User::create([\n    // ...\n]);\n \n$user-\u003ecreateAsCustomer([\n    'trial_ends_at' =\u003e now()-\u003eaddDays(10)\n]);\n```\n\nThis is what's called \"a generic trial\" because it's not attached to any subscription. You can use the `onTrial` method to check if a customer is currently trialing your app:\n\n```php\nif ($user-\u003eonTrial()) {\n    // User is within their trial period...\n}\n```\n\nOr if you specifically also want to make sure it's a generic trial, you can use the `onGenericTrial` method:\n\n```php\nif ($user-\u003eonGenericTrial()) {\n    // User is within their \"generic\" trial period...\n}\n```\n\nYou can also retrieve the ending date of the trial by calling the `trialEndsAt` method:\n\n```php\nif ($user-\u003eonTrial()) {\n    $trialEndsAt = $user-\u003etrialEndsAt();\n}\n```\n\nAs soon as your customer is ready, or after their trial has expired, they may start their subscription:\n\n```php\nuse Illuminate\\Http\\Request;\n\nRoute::get('/buy', function (Request $request) {\n    return $request-\u003euser()-\u003esubscribe('variant-id');\n});\n```\n\nPlease note that when a customer starts their subscription when they're still on their generic trial, their trial will be cancelled because they have started to pay for your product.\n\n#### Payment required\n\nAnother option is to require payment details when people want to trial your products. This means that after the trial expires, they'll immediately be subscribed to your product. To get started with this, you'll need to [configure a trial period in your product's settings](https://docs.lemonsqueezy.com/guides/tutorials/saas-free-trials#1-create-subscription-products-with-trials). Then, let a customer start a subscription:\n\n```php\nuse Illuminate\\Http\\Request;\n\nRoute::get('/buy', function (Request $request) {\n    return $request-\u003euser()-\u003esubscribe('variant-id');\n});\n```\n\nAfter your customer is subscribed, they'll enter their trial period which you configured and won't be charged until after this date. You'll need to give them the option to cancel their subscription before this time if they want.\n\nTo check if your customer is currently on their free trial, you may use the `onTrial` method on both the billable or an individual subscription:\n\n```php\nif ($user-\u003eonTrial()) {\n    // ...\n}\n \nif ($user-\u003esubscription()-\u003eonTrial()) {\n    // ...\n}\n```\n\nTo determine if a trial has expired, you may use the `hasExpiredTrial` method:\n\n```php\nif ($user-\u003ehasExpiredTrial()) {\n    // ...\n}\n \nif ($user-\u003esubscription()-\u003ehasExpiredTrial()) {\n    // ...\n}\n```\n\n##### Ending Trials Early\n\nTo end a trial with payment upfront early you may use the `endTrial` method on a subscription:\n\n```php\n$user = User::find(1);\n\n$user-\u003esubscription()-\u003eendTrial();\n```\n\nThis method will move the billing achor to the current day and thus ending any trial period the customer had.\n\n## License Keys\n\nLicense keys can be activated and validated using the license key string. A license key instance will be stored in \nthe database for each activated license. \n\n**Please note:** the billable for the license is the billable that purchased the license key, but _not necessarily the \nuser who activated the license key_, so you should establish a relationship between a license key instance and the user\ncreating it yourself.\n\nTo activate a license key and retrieve a license key instance:\n\n```php\n$user-\u003eactivateLicenseKey('your-key', 'your-reference')\n```\n\nTo validate a license key:\n```php\n$user-\u003eactivateLicenseKey('your-key', 'your-reference')\n```\n\n\n## Handling Webhooks\n\nLemon Squeezy can send your app webhooks which you can react on. By default, this package already does the bulk of the work for you. [If you've properly set up webhooks](#webhooks), it'll listen to any incoming events and update your database accordingly. We recommend enabling all event types so it's easy for you to upgrade in the future.\n\nTo listen to incoming webhooks, we have two events that will be fired:\n\n- `LemonSqueezy\\Laravel\\Events\\WebhookReceived`\n- `LemonSqueezy\\Laravel\\Events\\WebhookHandled`\n\nThe `WebhookReceived` will be fired as soon as a webhook comes in but has not been handled by the package's `WebhookController`. The `WebhookHandled` event will be fired as soon as the webhook has been processed by the package. Both events will contain the full payload of the incoming webhook.\n\nIf you want to react to these events, you'll have to create listeners for them. For example, you may want to react to a subscription being updated:\n\n```php\n\u003c?php\n \nnamespace App\\Listeners;\n \nuse LemonSqueezy\\Laravel\\Events\\WebhookHandled;\n \nclass LemonSqueezyEventListener\n{\n    /**\n     * Handle received Lemon Squeezy webhooks.\n     */\n    public function handle(WebhookHandled $event): void\n    {\n        if ($event-\u003epayload['meta']['event_name'] === 'subscription_updated') {\n            // Handle the incoming event...\n        }\n    }\n}\n```\n\nFor an example payload, [take a look at the Lemon Squeezy docs](https://docs.lemonsqueezy.com/help/webhooks/webhook-requests). \n\nLaravel v11 and up will detect the listener automatically. If you're on Laravel v10 or lower, you should wire it up in your app's `EventServiceProvider`:\n\n```php\n\u003c?php\n \nnamespace App\\Providers;\n \nuse App\\Listeners\\LemonSqueezyEventListener;\nuse Illuminate\\Foundation\\Support\\Providers\\EventServiceProvider as ServiceProvider;\nuse LemonSqueezy\\Laravel\\Events\\WebhookHandled;\n \nclass EventServiceProvider extends ServiceProvider\n{\n    protected $listen = [\n        WebhookHandled::class =\u003e [\n            LemonSqueezyEventListener::class,\n        ],\n    ];\n}\n```\n\n### Webhook Events\n\nInstead of listening to the `WebhookHandled` event, you may also subscribe to one of the following, dedicated package events that are fired after a webhook has been handled:\n\n- `LemonSqueezy\\Laravel\\Events\\OrderCreated`\n- `LemonSqueezy\\Laravel\\Events\\OrderRefunded`\n- `LemonSqueezy\\Laravel\\Events\\SubscriptionCreated`\n- `LemonSqueezy\\Laravel\\Events\\SubscriptionUpdated`\n- `LemonSqueezy\\Laravel\\Events\\SubscriptionCancelled`\n- `LemonSqueezy\\Laravel\\Events\\SubscriptionResumed`\n- `LemonSqueezy\\Laravel\\Events\\SubscriptionExpired`\n- `LemonSqueezy\\Laravel\\Events\\SubscriptionPaused`\n- `LemonSqueezy\\Laravel\\Events\\SubscriptionUnpaused`\n- `LemonSqueezy\\Laravel\\Events\\SubscriptionPaymentSuccess`\n- `LemonSqueezy\\Laravel\\Events\\SubscriptionPaymentFailed`\n- `LemonSqueezy\\Laravel\\Events\\SubscriptionPaymentRecovered`\n- `LemonSqueezy\\Laravel\\Events\\LicenseKeyCreated`\n- `LemonSqueezy\\Laravel\\Events\\LicenseKeyUpdated`\n\nAll of these events contain a billable `$model` instance and the event `$payload`. The subscription events also contain the `$subscription` object. These can be accessed through their public properties.\n\n## Changelog\n\nCheck out the [CHANGELOG](CHANGELOG.md) in this repository for all the recent changes.\n\n## License\n\nLemon Squeezy for Laravel is open-sourced software licensed under [the MIT license](LICENSE.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flmsqueezy%2Flaravel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flmsqueezy%2Flaravel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flmsqueezy%2Flaravel/lists"}