emericanec / lemon-pay
一个易于将您的 Laravel 应用程序与 Lemon Squeezy 集成的包。
Requires
- php: ~8.1.0|~8.2.0
- guzzlehttp/guzzle: ^7.0
- laravel/framework: ^9.19
- moneyphp/money: ^4.0
- nesbot/carbon: ^2.0
Requires (Dev)
- orchestra/testbench: ^8.0
- pestphp/pest: ^2.0
Suggests
- ext-intl: Allows for more locales besides the default "en" when formatting money values.
This package is not auto-updated.
Last update: 2024-09-25 21:07:08 UTC
README
Lemon Squeezy for Laravel
一个易于将您的 Laravel 应用程序与 Lemon Squeezy 集成的包。它简化了设置结账体验的过程。轻松设置产品的付款或让您的客户订阅您的产品计划。处理宽限期、暂停订阅或提供免费试用。
此包受到了 Cashier 的启发,由 Taylor Otwell 创建。
Lemon Squeezy for Laravel 由 Dries Vints 维护。任何对 资助此包开发 的赞助都将是极大的赞赏 ❤️
我们还建议您阅读 Lemon Squeezy 的 文档 和 开发者指南。
此包仍在开发中。只要没有 v1.0.0 版本,v0.x 版本的发布中可能会发生重大更改。不会提供 v0.x 版本之间的升级路径。
路线图
以下功能尚未包含在此包中,但计划在未来添加
- 订阅发票
- 许可证密钥
- 营销电子邮件检查
- 产品及变体列表
- 自定义定价结账
- 创建折扣代码
要求
- PHP 8.1 或更高版本
- Laravel 10.0 或更高版本
安装
您需要执行几个步骤来安装此包
- 通过 Composer 需求包
- 创建 API 密钥
- 连接您的商店
- 配置可收费模型
- 连接到 Lemon JS
- 设置 webhook
以下将详细介绍这些步骤。
Composer
使用 Composer 安装包
composer require lemonsqueezy/laravel
API 密钥
接下来,配置您的 API 密钥。在 Lemon Squeezy 控制面板 中以测试模式创建一个新的密钥,并将其粘贴到下面的 .env
文件中,如下所示
LEMON_SQUEEZY_API_KEY=your-lemon-squeezy-api-key
当您将应用程序部署到生产环境时,您必须创建一个新的生产模式密钥以处理实时数据。
商店标识符
您的商店标识符将在创建产品结账时使用。转到 您的 Lemon Squeezy 通用设置,并将商店 ID(# 符号后面的部分)复制到下面的 env 值中
LEMON_SQUEEZY_STORE=your-lemon-squeezy-store-id
可收费模型
为了确保我们实际上可以为我们的客户创建结账,我们需要配置一个模型作为我们的 "可收费" 模型。这通常是应用程序的 User
模型。为此,在您的模型上导入并使用 Billable
trait
use LemonSqueezy\Laravel\Billable; class User extends Authenticatable { use Billable; }
现在,您的用户模型将有权访问我们包中的方法,以在 Lemon Squeezy 中为您的产品创建结账。
Lemon JS
Lemon Squeezy 使用自己的 JavaScript 库来初始化其结账小部件。我们可以通过在我们的应用程序的 head
部分的 Blade 指令中加载它,在 </head>
标签之前,来利用它。
<head> ... @lemonJS </head>
Webhooks
最后,请确保设置好传入的Webhook。这在开发和生产环境中都是必需的。前往您的Lemon Squeezy的Webhook设置,并将URL指向您公开的本地应用。您可以使用Ngrok、Expose或您偏好的其他工具。
请确保选择所有事件类型。您应该指向的路径是默认的/lemon-squeezy/webhook
。强烈建议您验证Webhook签名。
Webhooks & CSRF保护
传入的Webhook不应受CSRF保护的影响。为了防止这种情况,请将您的Webhook路径添加到您的App\Http\Middleware\VerifyCsrfToken
中间件的排除列表中
protected $except = [ 'lemon-squeezy/*', ];
升级
在升级到新版本时,请查看我们的升级指南。
配置
该软件包提供了多种配置方式,以便您能够更好地与Lemon Squeezy集成。
验证Webhook签名
为了确保传入的Webhook确实是来自Lemon Squeezy,我们可以为它们配置一个签名密钥。前往Lemon Squeezy仪表板中的Webhook设置,点击您的应用Webhook,并将签名密钥复制到下面的环境变量中
LEMON_SQUEEZY_SIGNING_SECRET=your-webhook-signing-secret
现在,任何传入的Webhook在执行之前都会首先被验证。
结账
使用此软件包,您可以为您的客户轻松创建结账。
单次付款
例如,要为单次付款创建结账,使用您想要销售的产品变体的变体ID,并使用以下代码片段创建结账
use Illuminate\Http\Request; Route::get('/buy', function (Request $request) { return $request->user()->checkout('variant-id'); });
这将自动将您的客户重定向到Lemon Squeezy结账页面,客户可以在那里购买您的产品。
注意 当为您的商店创建结账时,每次您重定向结账对象或调用结账对象的
url
方法时,都会对Lemon Squeezy进行一次API调用。这些调用非常昂贵,并且可能会消耗您的应用的时间和资源。如果您需要反复创建相同的会话,您可能需要缓存这些URL。
覆盖小部件
除了将客户重定向到结账屏幕外,您还可以创建一个结账按钮,该按钮将在您的页面上渲染结账覆盖层。为此,将$checkout
对象传递到视图
use Illuminate\Http\Request; Route::get('/buy', function (Request $request) { $checkout = $request->user()->checkout('variant-id'); return view('billing', ['checkout' => $checkout]); });
现在,使用软件包提供的Laravel Blade组件创建按钮
<x-lemon-button :href="$checkout" class="px-8 py-4"> Buy Product </x-lemon-button>
当用户点击此按钮时,它将触发Lemon Squeezy结账覆盖层。您还可以选择将其渲染为暗模式
<x-lemon-button :href="$checkout" class="px-8 py-4" dark> Buy Product </x-lemon-button>
如果您正在结账订阅,并且不想显示“您将被收费...”文本,您可以通过在结账对象上调用withoutSubscriptionPreview
方法来禁用它
$request->user()->subscribe('variant-id') ->withoutSubscriptionPreview();
如果您想为结账按钮设置不同的颜色,您可以通过withButtonColor
传递十六进制颜色代码(带前导#
符号)
$request->user()->checkout('variant-id') ->withButtonColor('#FF2E1F');
预填用户数据
您可以通过覆盖以下方法在可收费模型上轻松预填结账的用户数据
public function lemonSqueezyName(): ?string; // name public function lemonSqueezyEmail(): ?string; // email public function lemonSqueezyCountry(): ?string; // country public function lemonSqueezyZip(): ?string; // zip public function lemonSqueezyTaxNumber(): ?string; // tax_number
默认情况下,将使用方法右侧注释中显示的属性。
此外,您还可以通过以下方法动态传递此数据
use Illuminate\Http\Request; Route::get('/buy', function (Request $request) { return $request->user()->checkout('variant-id') ->withName('John Doe') ->withEmail('john@example.com') ->withBillingAddress('US', '10038') // Country & Zip Code ->withTaxNumber('123456679') ->withDiscountCode('PROMO'); });
购买后的重定向
要将在购买后将客户重定向回您的应用,请使用redirectTo
方法
$request->user()->checkout('variant-id') ->redirectTo(url('/'));
您还可以通过配置配置文件中的lemon-squeezy.redirect_url
来设置此默认URL
'redirect_url' => 'https://my-app.com/dashboard',
结账过期
您可以通过调用其上的expiresAt
方法来指定结账会话应该保持活跃的时间
$request->user()->checkout('variant-id') ->expiresAt(now()->addDays(3));
自定义数据
您还可以通过结账传递自定义数据。为此,请使用结账方法发送键/值对
use Illuminate\Http\Request; Route::get('/buy', function (Request $request) { return $request->user()->checkout('variant-id', custom: ['foo' => 'bar']); });
这些数据将在相关的webhooks中稍后可用。
保留关键字
在处理自定义数据时,该库有一些保留关键字
billable_id
billable_type
subscription_type
尝试使用这些关键字中的任何一个都会抛出异常。
订阅
设置订阅产品
设置不同计划和间隔的订阅产品需要以特定方式进行。Lemon Squeezy有一个很好的指南,介绍了如何进行此操作。
虽然您可以选择如何设置产品和计划,但选择第二个选项为每个计划类型创建一个产品会更简单。例如,当您有一个“基本”和“专业”计划,并且两者都有月度和年费时,最好为这些计划创建两个不同的产品,然后为每个产品的月度和年费添加两个变体。
这使您可以在以后利用hasProduct
方法来检查订阅,这允许您仅检查订阅是否在特定计划类型上,而无需担心它是按月还是按年付费。
创建订阅
开始订阅很简单。为此,我们需要产品的变体ID。复制变体ID,并从您的收费模型启动新的订阅结账
use Illuminate\Http\Request; Route::get('/subscribe', function (Request $request) { return $request->user()->subscribe('variant-id'); });
当客户完成结账后, incoming SubscriptionCreated
webhook会将它与数据库中的收费模型相关联。然后您可以从收费模型检索订阅
$subscription = $user->subscription();
检查订阅状态
一旦客户订阅了您的服务,您可以使用各种方法来检查订阅的各种状态。最基本的一个例子是检查客户是否订阅了有效的订阅
if ($user->subscribed()) { // ... }
您可以在应用程序的多个地方使用此方法,如中间件、策略等,以提供您的服务。要检查单个订阅是否有效,您可以使用valid
方法
if ($user->subscription()->valid()) { // ... }
此方法以及subscribed
方法将返回true,如果您的订阅是活跃的、试用期的、逾期、暂停免费或在其取消宽限期内。
您还可以检查订阅是否在特定产品上
if ($user->subscription()->hasProduct('your-product-id')) { // ... }
或在特定变体上
if ($user->subscription()->hasVariant('your-variant-id')) { // ... }
如果您想检查订阅是否在特定变体上,同时有效,则可以使用
if ($user->subscribedToVariant('your-variant-id')) { // ... }
或者如果您正在使用多个订阅类型,您可以将类型作为额外参数传递
if ($user->subscribed('swimming')) { // ... } if ($user->subscribedToVariant('your-variant-id', 'swimming')) { // ... }
已取消状态
要检查用户是否已取消其订阅,您可以使用cancelled
方法
if ($user->subscription()->cancelled()) { // ... }
当他们处于宽限期时,您可以使用onGracePeriod
检查
if ($user->subscription()->onGracePeriod()) { // ... }
如果订阅已完全取消且不再处于宽限期,您可以使用expired
检查
if ($user->subscription()->expired()) { // ... }
逾期状态
如果订阅的定期付款失败,订阅将转换为逾期状态。这意味着它仍然是有效的订阅,但您的客户将在两周内支付将重试。
if ($user->subscription()->pastDue()) { // ... }
在此状态下,您应指导客户更新他们的支付信息。在Lemon Squeezy中,失败的付款将重试几次。有关此以及催收过程的更多信息,请参阅Lemon Squeezy文档
订阅范围
提供了多种订阅范围,用于查询特定状态的订阅
// Get all active subscriptions... $subscriptions = Subscription::query()->active()->get(); // Get all of the cancelled subscriptions for a specific user... $subscriptions = $user->subscriptions()->cancelled()->get();
以下是所有可用的范围
Subscription::query()->onTrial(); Subscription::query()->active(); Subscription::query()->paused(); Subscription::query()->pastDue(); Subscription::query()->unpaid(); Subscription::query()->cancelled(); Subscription::query()->expired();
更新支付信息
为了允许客户更新他们的支付详情,例如信用卡信息,您可以按照以下方法重定向他们
use Illuminate\Http\Request; Route::get('/update-payment-info', function (Request $request) { $subscription = $request->user()->subscription(); return view('billing', [ 'paymentMethodUrl' => $subscription->updatePaymentMethodUrl(), ]); });
或者,如果您想以更无缝的方式在您的应用上打开URL(类似于结账覆盖层),您可以使用Lemon.js,通过LemonSqueezy.Url.Open()
方法打开URL。首先,将URL传递给视图
use Illuminate\Http\Request; Route::get('/update-payment-info', function (Request $request) { $subscription = $request->user()->subscription(); return view('billing', [ 'paymentMethodUrl' => $subscription->updatePaymentMethodUrl(), ]); });
然后通过按钮触发它
<script defer> function updatePM() { LemonSqueezy.Url.Open('{!! $paymentMethodUrl !!}'); } </script> <button onclick="updatePM()"> Update payment method </button>
这要求您已设置Lemon.js。
更改计划
当客户订阅了月度计划时,他们可能想要升级到更好的计划,将他们的支付方式改为年付计划或降级到更便宜的计划。在这些情况下,您可以允许他们通过将不同的变体ID传递给swap
方法来切换计划
use App\Models\User; $user = User::find(1); $user->subscription()->swap('variant-id');
这将使客户切换到新的订阅计划,但计费将仅在下一个计费周期进行。如果您想立即向客户开账单,则可以使用swapAndInvoice
方法代替
$user = User::find(1); $user->subscription()->swapAndInvoice('variant-id');
分摊
默认情况下,Lemon Squeezy在更改计划时会按比例分配金额。如果您想防止这种情况,您可以在执行交换之前使用noProrate
方法
$user = User::find(1); $user->subscription()->noProrate()->swap('variant-id');
多个订阅
在某些情况下,您可能希望允许客户订阅多种订阅类型。例如,健身房可能提供游泳和举重订阅。您可以允许客户订阅其中一种或两种。
要处理不同的订阅,您可以在启动新的订阅时将订阅的type
作为subscribe
的第二个参数提供
$user = User::find(1); $checkout = $user->subscribe('variant-id', 'swimming');
现在,您可以通过在检索时提供type
参数来始终引用此特定订阅类型
$user = User::find(1); // Retrieve the swimming subscription type... $subscription = $user->subscription('swimming'); // Swap plans for the gym subscription type... $user->subscription('gym')->swap('variant-id'); // Cancel the swimming subscription... $user->subscription('swimming')->cancel();
暂停订阅
要暂停订阅,请在订阅上调用pause
方法
$user = User::find(1); $user->subscription()->pause();
可选地,提供一个日期,在此日期后订阅可以恢复
$user = User::find(1); $user->subscription()->pause( now()->addDays(5) );
这将填写客户的resumes_at
时间戳。要了解您的订阅是否处于暂停期间,您可以使用onPausedPeriod
方法
if ($user->subscription()->onPausedPeriod()) { // ... }
要取消暂停,只需在订阅上调用该方法
$user->subscription()->unpause();
暂停状态
默认情况下,暂停订阅将在暂停期间剩余时间内取消其使用。如果您希望客户免费使用您的服务,则可以使用pauseForFree
方法
$user->subscription()->pauseForFree();
取消订阅
要取消订阅,请在订阅上调用cancel
方法
$user = User::find(1); $user->subscription()->cancel();
这将使您的订阅设置为取消。如果您的订阅在周期中途取消,它将进入宽限期,并将设置ends_at
列。客户仍然可以访问该周期剩余提供的服务。您可以通过调用onGracePeriod
方法检查其宽限期
if ($user->subscription()->onGracePeriod()) { // ... }
使用Lemon Squeezy无法立即取消。要在宽限期仍然有效时恢复订阅,请调用resume
方法
$user->subscription()->resume();
当取消的订阅达到宽限期的结束时,它将过渡到过期状态,并且无法再恢复。
订阅试用期
关于在 Lemon Squeezy 中进行试用的详细说明,请查看他们的指南:点击这里.
无需付款
为了让人们能够注册您的产品而不需要填写付款详情,您可以在创建客户时设置 trial_ends_at
列表。
use App\Models\User; $user = User::create([ // ... ]); $user->createAsCustomer([ 'trial_ends_at' => now()->addDays(10) ]);
这被称为“通用试用”,因为它没有与任何订阅相关联。您可以使用 onTrial
方法来检查客户是否正在试用您的应用程序。
if ($user->onTrial()) { // User is within their trial period... }
或者如果您还想确保这是一个通用试用,您可以使用 onGenericTrial
方法。
if ($user->onGenericTrial()) { // User is within their "generic" trial period... }
您还可以通过调用 trialEndsAt
方法来检索试用的结束日期。
if ($user->onTrial()) { $trialEndsAt = $user->trialEndsAt(); }
一旦您的客户准备就绪,或者试用期结束后,他们可以开始他们的订阅。
use Illuminate\Http\Request; Route::get('/buy', function (Request $request) { return $request->user()->subscribe('variant-id'); });
请注意,当客户在通用试用期间开始订阅时,他们的试用将被取消,因为他们已经开始为您的产品付款。
需要付款
另一种选择是,当人们想要试用您的产品时要求他们提供付款详情。这意味着试用期结束后,他们将立即订阅您的产品。要开始此操作,您需要在产品设置中 配置试用期限。然后,让客户开始订阅。
use Illuminate\Http\Request; Route::get('/buy', function (Request $request) { return $request->user()->subscribe('variant-id'); });
您的客户订阅后,他们将进入您配置的试用期,并且在您配置的日期之前不会收费。如果他们想在这个时间之前取消订阅,您需要给他们这个选项。
要检查您的客户是否正在免费试用中,您可以在可计费或单个订阅上使用 onTrial
方法。
if ($user->onTrial()) { // ... } if ($user->subscription()->onTrial()) { // ... }
要确定试用是否已过期,您可以使用 hasExpiredTrial
方法。
if ($user->hasExpiredTrial()) { // ... } if ($user->subscription()->hasExpiredTrial()) { // ... }
处理 Webhooks
Lemon Squeezy 可以向您的应用程序发送 webhooks,您可以对它们做出反应。默认情况下,此包已经为您做了大部分工作。 如果您已正确设置 webhooks,它将监听任何传入的事件并相应地更新您的数据库。我们建议启用所有事件类型,这样您在未来升级时会更方便。
要监听传入的 webhooks,我们有两个将被触发的事件
LemonSqueezy\Laravel\Events\WebhookReceived
LemonSqueezy\Laravel\Events\WebhookHandled
WebhookReceived
事件将在 webhook 到来但尚未由包的 WebhookController
处理时触发。当 webhook 被包处理时,将触发 WebhookHandled
事件。这两个事件都将包含传入 webhook 的完整有效负载。
如果您想对这些事件做出反应,您必须创建监听器。例如,您可能想对订阅更新做出反应。
<?php namespace App\Listeners; use LemonSqueezy\Laravel\Events\WebhookHandled; class LemonSqueezyEventListener { /** * Handle received Lemon Squeezy webhooks. */ public function handle(WebhookHandled $event): void { if ($event->payload['meta']['event_name'] === 'subscription_updated') { // Handle the incoming event... } } }
有关示例有效负载,请参阅 Lemon Squeezy API 文档。
一旦您有了监听器,您需要在应用程序的 EventServiceProvider
中连接它。
<?php namespace App\Providers; use App\Listeners\LemonSqueezyEventListener; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use LemonSqueezy\Laravel\Events\WebhookHandled; class EventServiceProvider extends ServiceProvider { protected $listen = [ WebhookHandled::class => [ LemonSqueezyEventListener::class, ], ]; }
Webhook 事件
除了监听 WebhookHandled
事件之外,您还可以订阅以下在 webhook 被处理后触发的专用包事件之一
LemonSqueezy\Laravel\Events\SubscriptionCreated
LemonSqueezy\Laravel\Events\SubscriptionUpdated
LemonSqueezy\Laravel\Events\SubscriptionCancelled
LemonSqueezy\Laravel\Events\SubscriptionResumed
LemonSqueezy\Laravel\Events\SubscriptionExpired
LemonSqueezy\Laravel\Events\SubscriptionPaused
LemonSqueezy\Laravel\Events\SubscriptionUnpaused
所有这些事件都包含一个可计费的 $model
实例、一个 $subscription
对象和一个事件 $payload
。它们可以通过它们的公共属性访问。
变更日志
请查看此存储库中的所有最新更改的 CHANGELOG。
许可
适用于 Laravel 的 Lemon Squeezy 是一个开源软件,许可协议为 MIT 许可协议。