emeefe / subscriptions
Laravel 订阅管理包
Requires
- php: >=7.0.0
- laravel/framework: ~5.8|~6.0|~7.0
Requires (Dev)
- orchestra/testbench: ^3.6
README
安装
-
通过 composer 安装包
composer require emeefe/subscriptions
-
发布资源(迁移和配置文件)
php artisan vendor:publish --provider='Emeefe\Subscriptions\SubscriptionsServiceProvider'
-
配置
配置文件 emeefe.subscriptions
允许在执行迁移之前或之后(如果需要重命名)定义要使用/创建的表名,还允许指定将使用的模型。默认配置如下
[ 'tables' => [ 'plans' => 'plans', 'plan_types' => 'plan_types', 'plan_features' => 'plan_features', 'plan_type_feature' => 'plan_type_feature', 'plan_feature_values' => 'plan_feature_values', 'plan_periods' => 'plan_periods', 'plan_subscriptions' => 'plan_subscriptions', 'plan_subscription_usage' => 'plan_subscription_usage', ], 'models' => [ 'plan' => \Emeefe\Subscriptions\Models\Plan::class, 'feature' => \Emeefe\Subscriptions\Models\PlanFeature::class, 'period' => \Emeefe\Subscriptions\Models\PlanPeriod::class, 'subscription' => \Emeefe\Subscriptions\Models\PlanSubscription::class, 'type' => \Emeefe\Subscriptions\Models\PlanType::class, ] ]
- 执行迁移
php artisan migrate
订阅
计划类型 (PlanType)
一种计划类型包含一组允许的特性,可以用于区分多个计划类型,如企业计划、用户计划、存储计划等。
- 一种计划类型可以关联一个或多个特性。
计划特性 (PlanFeature)
计划特性允许定义计划类型可能具有的特性、权限等。
- 一个特性可以是计数的(
limit
)或非计数的(feature
)。 - 一个特性不存储其限制,只存储其基本信息。
- 一个特性可以关联一个或多个计划类型,允许不同类型的计划共享相同的特性。
计划 (Plan)
一个计划属于一个计划类型,并且可以关联该类型特性的限制(通过关系),前提是这些特性是计数的(limit
)。
- 默认计划:默认计划是在用户或其他实例订阅时默认获取的计划,同一个类型计划中只能有一个默认计划。
- 一个计划只属于一个计划类型
- 计划可以是
visible
或hidden
(隐藏)
计划周期 (PlanPeriod)
计划周期指示一个周期的持续时间,用户或实例直接订阅周期,而不是计划,因为周期可以变化。
- 一个周期可以有成本或免费。
- 一个周期属于一个计划。
- 可以有试用期天数。
- 可以变化持续时间。
- 可以循环:每隔一定时间需要续订(
recurring
)。 - 可以非循环:不能续订。
- 可以有限:定义了时间单位(天、月、年)以及单位数量,例如 5 天、6 个月、1 年,超过这个周期后不再重复,订阅结束。(
limited
) - 可以无限:可以没有定义时间单位和单位数量,换句话说,永不失效。(
unlimited
)
- 可以有限:定义了时间单位(天、月、年)以及单位数量,例如 5 天、6 个月、1 年,超过这个周期后不再重复,订阅结束。(
- 可以循环:每隔一定时间需要续订(
- 计划周期可以有
visible
或hidden
可见性。 - 可以有续订容忍天数。
- 同一个计划中只能有一个默认周期。
订阅 (PlanSubscription)
订阅是用户或其他实例与计划通过周期建立的关系,订阅获取计划及其周期的当前信息并将其保存在订阅中,类似于创建副本,以避免当计划、价格等数据发生变化时产生副作用,可以通过监听事件来定义这些情况的行为。
当创建订阅时,即使它是独立的,也不依赖于计划和周期,尽管可以通过获取到这些模型的关系来获取更新信息或其他必要操作。
- 订阅与多态关系一起工作,可以与任何模型相关联,而不仅仅是与用户相关联。
- 在创建时保持计划和周期的信息。
- 容忍天数
- 价格
- 货币
- 循环周期
- 功能限制
- 一个模型只能订阅同一类型计划中的一个周期,因此要订阅同一类型计划的另一个周期,必须首先取消现有订阅。换句话说,只能存在零个或一个与模型相关的未取消订阅。
- 根据订阅的周期,订阅的持续时间可能有所不同
- 可以循环:每隔一定时间需要续订(
recurring
)。 - 可以非循环:不能续订。
- 可能是有限的:定义了一个时间单位,如天、月和年,以及时间单位的数量,例如 5天、6个月、1年,在此周期结束后不再重复,无法续订并会被取消。(
limited
) - 可能是无限的:可以不定义时间单位和时间单位的数量,换句话说,永远不会过期。可以取消但无法续订。(
unlimited
)
- 可能是有限的:定义了一个时间单位,如天、月和年,以及时间单位的数量,例如 5天、6个月、1年,在此周期结束后不再重复,无法续订并会被取消。(
- 可以循环:每隔一定时间需要续订(
- 订阅可以根据其时间有效性具有以下状态
- 试用期间订阅:即使被取消,也仍然处于试用期的订阅。
- 活动订阅:
- 有限订阅,仅在订阅的正常周期内,无论是否取消,都必须考虑一个试用天数的订阅不是活动的。
- 未取消的无限订阅
- 在容忍范围内的过期订阅:尚未取消、已过期但仍在容忍范围内的订阅,无限订阅永远不会显示此状态。
- 过期订阅(完全过期):
- 尚未取消、已过期且已超过容忍天数的订阅。如果存在续订,则将从当前日期开始。
- 已取消的无限订阅
- 除了上述状态之外,还有其他条件可以用于比较订阅。
- 已取消订阅:已取消的订阅,无法续订。必须创建新的订阅。
- 有效订阅:这是比较状态,以确定订阅者是否仍然有权访问订阅。
- 未取消且未过期的订阅
- 已取消但未过期的订阅,在这种情况下忽略容忍天数。
- 无限订阅:永远不会结束的订阅。
- 有限订阅:具有到期日期的订阅。
创建计划类型
创建计划类型是通过其模型 PlanType
进行的,方法如下
use Emeefe\Subscriptions\Models\PlanType; ... $planType = new PlanType(); $planType->type = 'user_plan'; $planType->description = 'The user plan for basic subscriptions on profile' $planType->save();
创建功能
要创建计划功能,是通过其模型 PlanFeature
进行的,方法如下
use Emeefe\Subscriptions\Models\PlanFeature; ... $planFeature = new PlanFeature(); $planFeature->display_name = 'Allowed images on galery'; $planFeature->code = 'gallery_images'; $planFeature->description = 'The number of images a user can have in his gallery'; $planFeature->type = PlanFeature::TYPE_LIMIT; $planFeature->metadata = [ 'formats' => ['jpg', 'png'], 'max_size_bytes' => '1024' ]; $planFeature->save();
需要注意的是,属性 code
被定义为缩写,因为它可以用于更简单、更具描述性的查询。
模型 PlanFeature
提供了两个常量来定义类型,这些是
PlanFeature::TYPE_LIMIT
PlanFeature::TYPE_FEATURE
属性 metadata
被转换为 array
,因此可以直接处理数组分配,这些将以 JSON 格式保存。
分配功能到计划类型
将功能分配给计划类型是通过使用 attachFeature
方法进行的,方法如下
$planType->attachFeature($limitFeature) ->attachFeature($unlimitFeature);
这只是一个对
$planType->features()->saveMany([ $limitFeature, $unlimitFeature ]);
从计划类型中验证和获取特征
计划类型允许验证是否存在与其关联的特征以及获取这些特征
if($planType->hasFeature('gallery_images')){ $theFeature = $planType->getFeatureByCode('gallery_images')->id }
创建计划
一旦我们有了计划类型及其关联的特征,我们就可以在计划类型内创建一个新的计划,这通过以下方式使用其Plan
模型来实现
use Emeefe\Subscriptions\Models\Plan; ... $plan = new Plan(); $plan->display_name = 'Free'; $plan->code = 'free'; $plan->description = 'Free plan for users'; $plan->type_id = $planType->id; $plan->is_default = true; $plan->metadata = [ 'order' => 1 ]; $plan->is_visible = true; $plan->save();
code
属性可以在计划中重复,只要它们是不同类型的,这在内部的saving
事件中实现。如果已存在该类型的代码,则将抛出Emeefe\Subscriptions\RepeatedCodeException
异常
为了指示一个计划是计划类型内的默认计划,将is_default
属性设置为true
。如果发生这种情况,并且计划类型内已有一个默认计划,则旧默认计划将不再是默认的,将其is_default
属性设置为false
。这得益于模型的saving
事件。
默认情况下,计划是可见的,除非在is_visible
字段中指定为false
,应用全局作用域以返回仅限可见的计划,在模型部分中解释了这一点。
分配和获取类型限制的特征限制
要分配一个类型为limit
的特征在特定计划中的限制,通过以下方式使用Plan
模型来实现
$plan->assignFeatureLimitByCode('images_feature', 10); $plan->getFeatureLimitByCode('images_feature'); ... if(!$plan->hasFeature('images_feature')){ throw new \Exception("You do not have the feature"); }
仅允许将限制分配给类型为limit
的特征,请参阅模型文档中的更多案例。
如果没有为类型为limit
的特征分配限制并尝试获取其限制,则将返回0
。
创建计划周期
要创建计划周期,必须使用由Subscriptions
类的period
方法返回的PeriodBuilder
构建器,而不是直接使用PlanPeriod
模型,以避免不一致。
构建器具有以下结构:period(string $displayName, string $code, Plan $plan)
其中
$displayName
: 分配给周期的名称$code
: 分配给周期的代码,必须在计划周期内是唯一的$plan
: 将与之关联的周期计划实例。
可用的PeriodBuilder
方法和它们的默认操作是
setPrice(float $price)
定义周期价格
$price
: 要分配的价格
如果没有调用此方法或执行值小于0,则将价格定义为0
。
setCurrency(string $currency)
定义与周期一起使用的货币
$currency
: 要分配的价格,ISO 4217的3个字符
如果没有调用此方法,则将货币定义为MXN
。
setTrialDays(int $trialDays)
定义周期将有的试用期天数。
$trialDays
: 试用期天数
如果没有调用此方法或执行值小于0,则将试用期天数定义为0
。
setRecurringPeriod(int $count, string $unit)
通过指定单位和数量来定义周期为可重复的。
$count
: 单位数量$unit
: 周期单位,使用PlanPeriod::UNIT_DAY
、PlanPeriod::UNIT_MONTH
和PlanPeriod::UNIT_YEAR
常数
如果没有调用此方法或setLimitedNonRecurringPeriod
方法,则将定义为一个无限的非重复周期。
setLimitedNonRecurringPeriod(int $count, string $unit)
定义周期为非重复的,并分配其唯一周期将有的单位和数量。
$count
: 单位数量$unit
: 周期单位,使用PlanPeriod::UNIT_DAY
、PlanPeriod::UNIT_MONTH
和PlanPeriod::UNIT_YEAR
常数
如果没有调用此方法或setRecurringPeriod
方法,则将定义为一个无限的非重复周期。
setHidden()
定义计划周期为隐藏的。如果没有调用此方法,则计划周期将定义为可见的。
setToleranceDays(int $toleranceDays)
定义在完成周期后可容忍的天数。
$toleranceDays
:容忍天数
如果没有调用此方法或执行值小于0,则将试用期天数定义为0
。
setDefault()
将周期定义为默认周期,同一个计划中只能有一个默认周期,因此如果已经存在默认周期,则当前周期将替换为默认周期。
create()
通过在数据库中创建一个新的实例来结束周期的构建,这将返回一个PlanPeriod
实例。
重要:如果未调用任何setRecurringPeriod
和setLimitedNonRecurringPeriod
方法,则订阅将是无限的,忽略试用期和容忍天数。
使用示例
use Subscriptions; use Emeefe\Subscriptions\Models\PlanPeriod; ... $period = Subscriptions::period('Monthly', 'monthly', $plan) ->setPrice(100) ->setTrialDays(10) ->setRecurringPeriod(1, PlanPeriod::UNIT_MONTH) ->setToleranceDays(5) ->create();
订阅模型
如前所述,使用多态关系以使任何模型都能订阅周期,使用更简单、更清晰。为此,使用以下方式的CanSubscribe
特质:
use Emeefe\Subscriptions\Traits\CanSubscribe; ... class User extends Model{ use CanSubscribe; }
if($user->subscribeTo($period)){ echo "Suscrito"; }else{ echo "No se pueden tener dos suscripciones sobre el mismo tipo de plan"; }
完成这些后,将创建一个与模型(本例中为用户)、计划和周期相关联的订阅。
函数的结构如下
subscribeTo(PlanPeriod $period, int $periodCount = 1)
$period
:要订阅的周期实例$periodCount
:模型最初订阅的周期循环数,仅当不是无限时
对于月度周期的订阅,使用以下特殊规则
- 如果订阅在29日、30日或31日,而这些日期不是所有月份都有的,那么在加上一个月时,将保持相同的日期或最近的较小日期,同时考虑起始日期,例如
- 一个模型在2020年1月31日订阅一个月,则到期日为2020年2月29日。
- 如果此类订阅在下一个月续订,则不会将到期日定义为3月29日,而是保留31日,定义为2020年3月31日。
- 每个月的以下日期将是4月30日、5月31日、6月30日等。
验证订阅
可以通过方法hasSubscription($planTypeOrType)
验证模型是否订阅了某类计划,其中$planTypeOrType
是模型类型的实例或计划类型的type
属性中定义的string
。
if($user->hasSubscription('user_membership')){ echo "El usuario está suscrito al tipo de plan con tipo = user_membership"; }else{ echo "El usuario no está suscrito al tipo de plan con tipo = user_membership"; }
获取订阅
要获取当前订阅,请使用方法currentSubscription($planTypeOrType)
,当前订阅是针对模型创建的最后一个订阅,即使订阅已取消,该方法也会返回订阅。
if($user->currentSubscription($planType)){ echo "El usuario tiene una suscripción"; }else{ echo "El usuario no tiene una suscripción"; }
续订订阅
当一个订阅是周期性的,它可以由整数个周期续订。非周期性订阅不能续订。
$subscription = $user->currentSubscription($planType); if($subscription->renew(3)){ echo "La suscripción ha sido renovada 3 periodos"; }else{ echo "La suscripción no puede ser renovada"; }
取消订阅
可以通过订阅的cancel()
方法取消订阅。
$subscription = $user->currentSubscription($planType); if($subscription->cancel()){ echo "La suscripción ha sido cancelada"; }else{ echo "La suscripción ya ha sido cancelada"; }
更新订阅
通过将有效订阅更改为新订阅(无论是同一计划还是其他计划),通过CanSubscribe
特质的updateSubscriptionTo
方法更新订阅。
订阅更新实际上只是取消有效订阅并将其分配给新订阅,当以这里解释的方式执行更新时,不会触发CancelSubscription
事件,而是触发UpdatedSubscription
事件。
重要:另一个需要注意的是,在更改计划时,会同步新订阅的特征限制,将新订阅的消费定义为旧订阅的消费与新订阅限制的最小值,如果需要在应用程序逻辑中执行额外操作,则必须在UpdatedSubscription
事件的监听器中执行。
... if($user->updateSubscriptionTo($newPeriod)){ echo "La suscripción ha sido actualizada"; }else{ echo "La actualización de suscripción no se pudo realizar"; }
模型
PlanType
方法
hasFeature(string $featureCode)
检查计划类型是否包含具有代码 $featureCode
的功能,并根据情况返回一个 boolean
。如果 $featureCode
不存在或未与类型关联,则返回 false
。
attachFeature(PlanFeature $planFeature)
将 PlanFeature
实例分配给计划类型,并返回 PlanType
实例以链式分配多个功能。如果发送一个已绑定的功能,则忽略分配而不会返回错误。
getFeatureByCode(string $featureCode)
通过在 $featureCode
中传递的代码获取 PlanFeature
实例,如果不存在与计划类型的关联,则返回 null
。
关系
features
获取与计划类型相关的 PlanFeatures
集合。
plans
获取与计划类型相关的 Plan
集合。
subscriptions
获取与计划类型相关的 PlanSubscription
集合。
PlanFeature
作用域
scopeLimitType($query)
按类型 limit
过滤功能
scopeFeatureType($query)
按类型 feature
过滤功能
Plan
计划模型应用全局作用域以始终返回可见的计划,这可以通过下面的 withHiddens
作用域来禁用。
方法
assignFeatureLimitByCode(int $limit, string $featureCode)
将类型为 limit
的功能及其限制分配给计划。如果尚未分配限制,则分配它,并在已定义限制的情况下更新它。
$limit
:要分配的限制,大于或等于1的数字$featureCode
:功能代码
返回
true
:当成功分配功能和限制时false
:当无法分配限制时,因为类型计划中不存在功能或功能不是类型limit
assignUnlimitFeatureByCode(string $featureCode)
将类型为 feature
的功能分配给计划。
$featureCode
:功能代码
返回
true
:当成功分配功能时false
:当无法分配功能时,因为类型计划中不存在功能或功能不是类型feature
getFeatureLimitByCode($featureCode)
根据其代码获取类型为 limit
的功能的限制。
$featureCode
:功能代码
返回
- 大于0的整数:当功能存在于类型计划中且已注册限制时
0
:当功能存在于类型计划中但未分配限制时-1
:当功能不存在于类型计划中或存在但不是类型limit
时
hasFeature(string $featureCode)
检查计划类型是否关联了功能。
$featureCode
:功能代码
返回
true
:当功能存在于计划类型中时false
:当功能不存在于计划类型中时
setAsDefault()
将计划设置为类型计划中的默认计划,如果已经存在一个默认计划,则重新分配该特性,更新新默认计划并从旧默认计划中删除该特性。
返回 bool
setAsVisible()
将计划定义为可见
返回 bool
setAsHidden()
将计划定义为隐藏
返回 bool
关系
type
获取计划关联的计划类型。
features
通过类型获取与计划关联的功能集合。
作用域
scopeByType($query, string $type)
根据其类型键获取计划。
$type
:计划类型键
scopeVisible($query)
过滤可见计划
scopewithHiddens($query)
允许获取隐藏计划
scopeHidden($query)
过滤隐藏计划,在获取它们之前需要调用 scopewithHiddens($query)
PlanPeriod
PlanPeriod模型应用全局作用域以始终返回可见周期,这可以通过下面的withHiddens
作用域来禁用。
方法
isRecurring()
检查周期是否为重复周期
返回 bool
isLimitedNonRecurring()
检查周期是否为有限非重复周期
返回 bool
isUnlimitedNonRecurring()
检查周期是否为无限非重复周期
返回 bool
isVisible()
检查周期是否可见
返回 bool
isHidden()
检查周期是否隐藏
isDefault
检查周期是否是计划类型内的默认周期
返回 bool
isFree()
检查周期是否免费,换句话说,其价格为0
返回 bool
hasTrial()
检查周期是否有试用期
返回 bool
setAsDefault()
在计划中将周期定义为默认周期,如果已存在一个默认周期,则重新分配此特性,更新新的周期并移除旧默认周期的特性。
返回 bool
setAsVisible()
将周期定义为可见
返回 bool
setAsHidden()
将周期定义为隐藏
返回 bool
关系
plan
返回关联的计划
subscriptions
返回关联的订阅
作用域
scopeVisible($query)
过滤可见周期
scopewithHiddens($query)
允许获取隐藏周期
scopeHidden($query)
过滤隐藏周期,必须先调用scopeWithHiddens($query)
来获取它们
PlanSubscription
方法
isOnTrial()
检查订阅是否处于试用期。
返回
true
:如果处于试用期false
:如果不处于试用期
isActive()
检查订阅是否处于正常期。
返回
true
:如果处于活跃状态false
:如果不处于活跃状态
isValid()
检查订阅是否有效。
返回
true
:如果有效false
:如果无效
isExpiredWithTolerance()
检查订阅是否已达到到期日但处于容限期,这对于验证付款非常有用。
返回
true
:如果处于容限期false
:如果没有处于容限期
isFullExpired()
检查订阅是否已过期且不在容限期内,这对于验证付款非常有用。
返回
true
:如果已过期false
:如果未过期
isCancelled()
检查订阅是否已取消
返回
true
:如果已取消false
:如果未取消
isUnlimited()
检查订阅是否为无限期
返回
true
:如果是无限期false
:如果不是无限期
isLimited()
检查订阅是否为有限期
返回
true
:如果是有限期false
:如果不是有限期
remainingTrialDays()
返回剩余的试用期天数
返回
int
:剩余天数
renew(int $periods = 1)
如果订阅是重复的且未取消,则续订订阅
$periods
:续订周期数,默认为1
返回
true
:如果订阅是重复的且成功续订false
:如果订阅是非重复的或已取消
cancel(string $reason = null)
如果订阅未取消,则取消订阅。如果取消一个无限期的订阅,则将其到期日设定为取消日期。
$reason
:取消订阅的原因。
返回 bool
hasFeature(string $featureCode)
通过其代码检查订阅是否具有特性。
$featureCode
:功能代码
返回 bool
consumeFeature(string $featureCode, int $units = 1)
只要订阅没有取消,就可以消费一个特征订阅中可用的单位。
$featureCode
:功能代码$units
:要消费的单位,默认为1
返回 bool
true
:如果可以消费false
:如果无法消费,因为已经达到上限或要消费的单位大于可用单位。
unconsumeFeature(string $featureCode, int $units = 1)
只要订阅没有取消,就可以从特征订阅中撤销一个消费过的单位。
$featureCode
:功能代码$units
:要撤销消费的单位,默认为1
返回 bool
true
:如果可以撤销消费false
:如果无法撤销消费,因为消费过的单位数量为0
getUnitsOf(string $featureCode)
返回与订阅相关的特征限制总数
$featureCode
:功能代码
返回int
或null
int
:如果可以获取总数null
:如果特征没有关联或不是limit
类型
getUsageOf(string $featureCode)
返回与订阅相关的特征使用情况
$featureCode
:功能代码
返回int
或null
int
:如果可以获取使用情况null
:如果特征没有关联或不是limit
类型
getRemainingOf(string $featureCode)
返回与订阅相关的特征剩余使用情况
$featureCode
:功能代码
返回int
或null
int
:如果可以获取剩余使用情况null
:如果特征没有关联或不是limit
类型
关系
period
返回相关周期
subscriber
返回通过多态关系关联的订阅者模型
plan_type
返回相关计划的类型
作用域
scopeByType($query, PlanType $planType)
按计划类型过滤订阅
scopeCanceled($query)
过滤已取消的订阅
scopeFree($query)
过滤免费订阅,其中其price
字段为0
recurring($query)
过滤周期性订阅
事件
此包提供事件,这些事件在最重要的各种情况下被触发,或允许我们根据大多数可能的案例调整订阅。
Emeefe\Subscriptions\Events\FeatureLimitChangeOnPlan
:当一个计划中的特征限制更新时触发,无论是首次分配限制还是更新时。
$event->plan
:分配特征限制的计划$event->feature
:将被分配限制的特征$event->limit
:分配的新限制
Emeefe\Subscriptions\Events\PlanPeriodChange
:当一个计划的周期在某些字段(如price
、currency
、trial_days
、period_unit
、period_count
、is_recurring
、is_visible
或tolerance_days
)更新时触发。
$event->oldPlanPeriod
:旧计划周期$event->newPlanPeriod
:更新后的计划周期
Emeefe\Subscriptions\Events\NewFeatureOnPlan
:当一个特征被分配到一个计划时触发
$event->plan
:分配特征的计划$event->feature
:分配的特征$event->limit
:如果特征是limit
类型,则定义了限制,否则为null
Emeefe\Subscriptions\Events\NewSubscription
:当通过周期将模型订阅到计划时触发
$event->model
:订阅的模型$event->subscription
:创建的订阅
Emeefe\Subscriptions\Events\RenewSubscription
:当订阅被续订/延长时触发
$event->model
:属于订阅的模型$event->subscription
:续订的订阅$event->cycles
:续订的周期数
Emeefe\Subscriptions\Events\CancelSubscription
当使用 cancel
方法取消订阅时触发。当是使用 CanSubscribe
特性的 updateSubscriptionTo
方法更新计划时,不会调用此事件,并将取消原因设置为 PlanSubscription::CANCEL_REASON_UPDATE_SUBSCRIPTION
$event->subscription
: 被取消的订阅$event->reason
: 在cancel($reason)
中提供的取消原因
Emeefe\Subscriptions\Events\FeatureConsumed
当订阅的一个功能被消费时触发
$event->subscription
: 被消费的订阅$event->model
: 被订阅的模型$event->units
: 消费的单位
Emeefe\Subscriptions\Events\FeatureUnconsumed
当订阅的一个功能被“退回”时触发
$event->subscription
: 被消费的订阅$event->model
: 被订阅的模型$event->units
: 被退回的单位
Emeefe\Subscriptions\Events\FeatureRemovedFromPlan
当一个功能被从计划中删除时触发
$event->plan
: 删除功能所属的计划$event->feature
: 从计划中删除的功能
Emeefe\Subscriptions\Events\UpdatedSubscription
当使用 CanSubscribe
特性的 updateSubscriptionTo
方法更新订阅时触发
$event->model
: 被订阅的模型$event->oldSubscription
: 旧的订阅$event->subscription
: 新的订阅