Closed bpuig closed 3 years ago
Now this makes sense. Care to give a thought @boryn ?
This should also fix #74 because you can only have one thing, trial or subscription, they will not collide.
Could you prepare a beta v5 release? Would be much easier to give it a try
Could you prepare a beta v5 release? Would be much easier to give it a try
I'm afraid that would be not possible, it would mean I'd have to merge this PR, you can use the branch this PR is on: https://github.com/bpuig/laravel-subby/tree/new_trial_behaviour
OK
Thank you for this implementation, seems to be much more logical. I share my first thoughts:
trial_ends_at
("7 days") and when they pay in the 3rd day of trial, the starts_at
should start at trial_ends_at
and not now(). We should not "punish" them when they quickly decide to pay for our app. (Just when they decide to cancel, we by default allow them to use the app up to the end of the period).renew()
should start a new period with reset, fresh features. We need to consider as well 30 or even 90 days trials. And they may renew (actually start) subscription with a different plan.trial_ends_at
is '2021-05-24 05:03:09'starts_at
set to now(), let's say '2021-06-17 05:21:20' and ends_at
23 days from now(): '2021-07-10 05:21:20'starts_at
set to '2021-05-24 05:04:14' and ends_at
set to '2021-06-24 05:04:14'.Shouldn't trial_mode
field be copied from plan
to plan_subscriptions
as well? The plan definition can be changed in the meantime and we should use the definition from upon trial creation.
starts_at
and leave ends_at
as null
which would mean active indefinitely? With the free plan nobody would care to renew it every month...It's usual to display to the user information when their subscription ends (either with trial or without). We can grab the information if it is trial or not (isOnTrial()
) but I consider getting the end of the period very cumbersome with conditional logic (if on trial, get trial_ends_at
, if not on trial get ends_at
). That's why I'd recommend something more generic inside the PlanSubscription.php
like:
public function getPeriodEndsAtAttribute()
{
if ($this->isOnTrial()) {
return $this->trial_ends_at;
}
return $this->ends_at;
}
I strongly believe the user should have the right to use their free trial up to the initially set
trial_ends_at
("7 days") and when they pay in the 3rd day of trial, thestarts_at
should start attrial_ends_at
and not now(). We should not "punish" them when they quickly decide to pay for our app. (Just when they decide to cancel, we by default allow them to use the app up to the end of the period).
Done, now you get the remaining days! But trial still ends because I don't want to have trial and subscription at same time. Either one or the other.
In this mode shouldn't the usage be cleared? They used what they got for free during the trial, and with
renew()
should start a new period with reset, fresh features. We need to consider as well 30 or even 90 days trials. And they may renew (actually start) subscription with a different plan.
I don't think so, because if you get 7 days trial, consume everything and renew, you'd get 1 month of usage for free because you used all the available features and then got renewed with a clear usage. Trial means you just get free time, not features.
I have made such a simulation:
The way I calculate now won't make that happen.
Shouldn't
trial_mode
field be copied fromplan
toplan_subscriptions
as well? The plan definition can be changed in the meantime and we should use the definition from upon trial creation.
Yes it should, all the trial data.
With the plan of price 0, I think the subscription should be valid "forever". Now, upon creating a new subscription, I got it just valid until 17 of July. Maybe with price 0 we should just set
starts_at
and leaveends_at
asnull
which would mean active indefinitely? With the free plan nobody would care to renew it every month...
You should get a subscription that renews every given invoice period. For the amount of 0€, so free.
It's usual to display to the user information when their subscription ends (either with trial or without). We can grab the information if it is trial or not (
isOnTrial()
) but I consider getting the end of the period very cumbersome with conditional logic (if on trial, gettrial_ends_at
, if not on trial getends_at
). That's why I'd recommend something more generic inside thePlanSubscription.php
like:public function getPeriodEndsAtAttribute() { if ($this->isOnTrial()) { return $this->trial_ends_at; } return $this->ends_at; }
I'll take a look
And what about the problem with doubling/quadrupling the allowance of the features? It won't happen now?
Too many things to remember but I think I was wrong and you were right, that features where reset by periods or something like that. Also renewal does not clear usage now. Multiple renewal should be OK.
It took the way that we have features more decoupled of subscription time. You subscribe for X time and features do their own thing with their own reset periods. While you are subscribed you can use them, if you are not, you can't.
I think I found another issue(s).
Renewal date for features is retrieved by:
$period = new Period($this->resettable_interval, $this->resettable_period, $dateFrom ?? now());
And $dateFrom
is set here:
if ($feature->resettable_period) {
// Set expiration date when the usage record is new or doesn't have one.
if (is_null($usage->valid_until)) {
// Set date from subscription creation date so the reset
// period match the period specified by the subscription's plan.
$usage->valid_until = $feature->getResetDate($this->created_at);
} elseif ($usage->hasExpired()) {
// If the usage record has been expired, let's assign
// a new expiration date and reset the uses to zero.
$usage->valid_until = $feature->getResetDate($usage->valid_until);
$usage->used = 0;
}
}
$feature->getResetDate($this->created_at);
The creation date does not mean anything right now.
Soooooo... what happens if subscriber does not use the app for 1 period.... When usage is going to be recorded again it will be set to next month from the previous period and use one. Subscribers would get 1 usage for every period they've been out. Super weird
Example:
valid_until
is current valid_until
Jan 31st +1 month, so is now end of February. Used is 1.valid_until
is current valid_until
Feb 28th +1 month, so is now end of March. Used is 1.valid_until
is current valid_until
March 31st +1 month, so is now end of April. Used is 1.valid_until
is current month. Used is 2.
They used 4 times and end up with 2. :fearful: Sorry for all that commits, I don't know what happened.
I strongly believe the user should have the right to use their free trial up to the initially set
trial_ends_at
("7 days") and when they pay in the 3rd day of trial, thestarts_at
should start attrial_ends_at
and not now(). We should not "punish" them when they quickly decide to pay for our app. (Just when they decide to cancel, we by default allow them to use the app up to the end of the period).Done, now you get the remaining days! But trial still ends because I don't want to have trial and subscription at same time. Either one or the other.
So how does it work now? Upon renewal, trial_ends_at
is set to starts_at
at now()? And ends_at
is prolonged by "30 days" from the previous value of trial_ends_at
?
In this mode shouldn't the usage be cleared? They used what they got for free during the trial, and with
renew()
should start a new period with reset, fresh features. We need to consider as well 30 or even 90 days trials. And they may renew (actually start) subscription with a different plan.I don't think so, because if you get 7 days trial, consume everything and renew, you'd get 1 month of usage for free because you used all the available features and then got renewed with a clear usage. Trial means you just get free time, not features.
I don't understand "Trial means you just get free time, not features". Companies often give 7-day trial for the "Pro" plan and allow users both to have time and to test the features for free. I rather see this scenario. And later when you subscribe, you get fresh set of features. Maybe it should be on an option then?
With the plan of price 0, I think the subscription should be valid "forever". Now, upon creating a new subscription, I got it just valid until 17 of July. Maybe with price 0 we should just set
starts_at
and leaveends_at
asnull
which would mean active indefinitely? With the free plan nobody would care to renew it every month...You should get a subscription that renews every given invoice period. For the amount of 0€, so free.
But should I (developer) care about refreshing it every month? Normally renewal comes "from outside" (payment). When we set a free 0€ subscription, it is not natural that we additionaly should care about prolonging it every month. It just should be infinite.
I think I found another issue(s).
Do you have an idea how to solve it? I think we need a check whether a date is inside a subscription period and if $usage->valid_until
is inside the active subscription, we should probably use start of the subscription period? BUT we do not have this, it should be manually calculated: ends_at
minus invoice period.
Buuuut... then feature usage is not decoupled from subscriptions... And sub period can be a year, and feature a month...
Maybe there should be a loop for incrementing $usage->valid_until
until we land on "current" period before doing the real feature record? I mean something like this:
_valid_until = valid_until + resettable period if now() "inside" (validuntil + resettable period) then break the loop and record feature usage
(of course when valid_until is already in current period, there is no loop necessary)
Wait... You already solved it at https://github.com/bpuig/laravel-subby/pull/86/commits/e335d97d90bfbd1758c5f71cd884801ca283d49f ! Funny thing that we came to the same conclusion of doing it in a loop :)
So how does it work now? Upon renewal,
trial_ends_at
is set tostarts_at
at now()? Andends_at
is prolonged by "30 days" from the previous value oftrial_ends_at
?
Trial ends at now
and then:
I don't understand "Trial means you just get free time, not features". Companies often give 7-day trial for the "Pro" plan and allow users both to have time and to test the features for free. I rather see this scenario. And later when you subscribe, you get fresh set of features. Maybe it should be on an option then?
Then you are giving a month set of features for free, do you really want that? I can look into making it an option, makes sense, not economically, but makes sense... 😆
But should I (developer) care about refreshing it every month? Normally renewal comes "from outside" (payment). When we set a free 0€ subscription, it is not natural that we additionaly should care about prolonging it every month. It just should be infinite.
Subscriptions should auto-renew until canceled. I know there is the payment thing that should be sorted sometime, but right now the package is a "bubble" that should not care what happens in the outside world (payment platform).
I don't understand "Trial means you just get free time, not features". Companies often give 7-day trial for the "Pro" plan and allow users both to have time and to test the features for free. I rather see this scenario. And later when you subscribe, you get fresh set of features. Maybe it should be on an option then?
Then you are giving a month set of features for free, do you really want that? I can look into making it an option, makes sense, not economically, but makes sense...
Sometimes you need to give a user something totally for free (even the Pro "sub") so that they get to know your solution and only later ask them to pay. If the trial is longer, eg. for 30 days and they later prolong, they need to have fresh usage limits. And even if trial is 7-14 days, when they decided to prolong, they get a new subscription and in my opinion, they need to get fresh usage limits as well. I consider a free trial to be a real free trial. So I think, it would be good implementing such an option.
But should I (developer) care about refreshing it every month? Normally renewal comes "from outside" (payment). When we set a free 0€ subscription, it is not natural that we additionaly should care about prolonging it every month. It just should be infinite.
Subscriptions should auto-renew until canceled. I know there is the payment thing that should be sorted sometime, but right now the package is a "bubble" that should not care what happens in the outside world (payment platform).
Hmm... So I was not aware of the auto-renewal of free subscriptions. Where does it happen in the code?
If the trial is longer, eg. for 30 days and they later prolong, they need to have fresh usage limits.
I'll try to implement the behavior somehow 👍
Hmm... So I was not aware of the auto-renewal of free subscriptions. Where does it happen in the code?
I meant YOU need to auto-renew the subscriptions. The package was (is) thought that you handle renewals when subscription ends, and you should keep track of renewals.
Hmm... So I was not aware of the auto-renewal of free subscriptions. Where does it happen in the code?
I meant YOU need to auto-renew the subscriptions. The package was (is) thought that you handle renewals when subscription ends, and you should keep track of renewals.
That's natural that subscriptions with the price need to have the outside event to trigger the renewal. But the free subscriptions don't even demand the outside world, they should just exist and be available. And that's why I talk about some special handling of the free subscriptions, inside the package "bubble" itself.
AFAIR this manual renewing of free subscriptions is not mentioned in the docs? Or I didn't notice that. Common sense tells me that if the subscription is free, it's just can be used forever and no external steps need to be taken. It would be a big nuisance to set up an extra scheduler job just to keep free subs "alive".
IMHO it would be reasonable to fill the starts_at
and leave the ends_at
with null
, we'd need a new method isFree()
(true if price equals 0 (and maybe if ends_at
is null)) and in such a case the isActive()
would return true if || $this->isFree()
. hasEnded()
should as well return false if isFree()
.
I think this is almost good to go. I have been away for some time so I'm not fully fresh about what we were talking.
This branch accepts renewal by periods. Also renewal does not clear usage just manipulates periods, since that is done by the usage model automatically.
If you want to take a look at it, I'll live it open some more time in case I find anything more.
Exceptions for free subscriptions will not be made, they will be treated as a regular subscription with 0 price. This will keep the package more coherent and with the minimum exceptions and variants possible as long as it is usable.
:smile:
Thank you! I hope to be able to test it the next week and will give some feedback
Discarded #75, now this is a bit simpler.
New behavior
Trial modes
There are two available trial modes:
inside
oroutside
. This defines how the trial will be counted when renewal time is due. Also renewal only affects subscription period, it can be renewed for as many periods as you want.USAGE WILL NOT BE CLEARED when user has had trial time. This is what gives sense to both methods. If you need to clear, you can do it manually upon renewal.
When a new subscription to a plan is made:
If plan has trial
If plan has trial, subscriber does not have subscription but only a trial. Subscription period starts and ends at
null
and this is considered subscription is not made. Because in a real case scenario, when a subscriber has a trial it does not have a subscription yet, so the invoice period is made and charged after the trial has ended.Renewal when trial is "inside"
If trial mode is
inside
; when trial ends and is renewed invoice period will have substracted the days of trial that have been used.Example: 7 day trial in a 30 day subscription period.
In summary: this is NOT a free trial. User always ends up paying the full price for full period.
Renewal when trial is "outside"
If trial mode is
outside
; when trial ends and is renewed, invoice period will start at the moment it's renewed.Example: 7 day trial in a 30 day subscription period.
In summary: this is IS a free trial. User does not pay for the trial period, but for the next subscription period.
If plan does not have trial
If plan does not have trial, subscriber has subscription. Because when a plan does not have trial, a new subscription activates a new invoicing period.
TODO: