laravel / cashier-mollie

MIT License
375 stars 63 forks source link

implementing free plan functionality #122

Closed sigmoidal closed 4 years ago

sigmoidal commented 4 years ago

What's the best way to have a free plan included alongside paid-for plans?

I tried:

a) Having two paid-for plans in cashier_plans.php and only if the user selected one of those I would switch the user from the independent, self-managed free "plan" to those managed by the package and eventually mollie. The issue with this approach was that when the user subscribed to a paid subscription and paid for it, but then cancel that subscription to go back to the free plan, the credit function did not show that the user had any credit left. (when I would have expected that the prorated remaining amount should be avaiable for implementing refund logic). Is this expected behavior or a bug? Or maybe there is a step I may have missed out?

b) (to circumvent the above) I added an additional plan in cashier_plans.php that costs 0.0 and now the credit problem went away. I can comfortably swap plans and then cancel and the remaining credit is available for the user. The issue here though is that the user has to first subscribe to the Free plan AND make the first-payment to mollie, which is an undesirable constraint to impose. Is that correct behavior? Looking at the Billable Trait, it seems that unless there is a mollie_mandate_id the user has to make a firstpayment.

I guess this is such a common matter that I probably must have missed the crucial details. I'd appreciate it if anyone had any suggestions or pointers on the best way to go about this.

sandervanhooft commented 4 years ago

You should go with option a).

Did you use $subscription->cancel()? If so, the subscription will be active until the end of the current billing cycle (what was paid for). So that's by design.

sandervanhooft commented 4 years ago

Alternatively, using $subscription->cancelAt(now()) will reimburse for the time left on the subscription.

sigmoidal commented 4 years ago

Thanks for your response.

I tried cancel() and it behaves as you designed it, it correctly goes into the grace period, although that implies the current subscription is not really cancelled. It is the renewal for the next cycle that is cancelled. I have not found a way to obtain the amount I would owe to the user if I was to refund the remaining of the current cycle.

Indeed, I also tried cancelAt(now()) with same result, except there is no grace period being entered. To reproduce the issue described I do the following simple steps:

  1. subscribe to a plan that costs XX euro.
  2. complete successfully first payment
  3. cancel the subscription
  4. invoking $user->hasCredit('EUR') is false

I was expecting that the user will be allocated a prorated credit of XX euro (in my tests since I cancelled immediately the whole amount should be the credit), which my plan was to enable the user to then request a refund for, in a subsequent step.

I'm not sure if it's just me experiencing this or this is also normal behavior.

sandervanhooft commented 4 years ago

Ok, so the customer has paid for the subscription but decides he wants out immediately, right? Usually he then cancels the subscription. Then sees that it is canceled, but he is getting what he paid for: the rest of the current billing cycle.

In some cases customers like these reach out to support, asking for 1) a refund and 2) canceling the subscription immediately.

1) refunds are not supported in Cashier yet. You can perform a refund via the Laravel Mollie client (comes with Cashier) or via Mollie's dashboard. 2) as discussed. Use cancelAt(now()).

Regarding point 2, I could think of a third parameter ($reimburse = false) to credit for unused time. But if I understand the/your scenario correctly, this would only be an intermediate step towards a refund.

sigmoidal commented 4 years ago

The problem is not the refund logic (this scenario above is just one use case, which I mention so you know the context).

Lets say that a user cancels the subscription at any point during the cycle, not just immediately. I was hopding $user->credit() returns me the amount I would have to pay the user if I wanted to refund him, calculated proportionally to the amount of time left in the cycle. Currently that function does not tell me anything, because it returns false.

Maybe I am doing it all wrong? :)

btw: thanks immensely for your responses and the work you put into this and helping us here.

sandervanhooft commented 4 years ago

Hi @sigmoidal,

Thanks for the ❤️ , really appreciated. And always good to learn about real life use cases :).

Perhaps this is helpful to you?

https://github.com/laravel/cashier-mollie/blob/899d661ea1d730fd1f15a5551cc27ddb1b338901/src/Subscription.php#L148:L187

sandervanhooft commented 4 years ago

So you could use

$reimburse_amount = $subscription->cycle_left * $plan_costs;
sigmoidal commented 4 years ago

that's really helpful, thanks for pointing me at it!!

sandervanhooft commented 4 years ago

No problem. Enjoy! 🎉

relaypilot commented 3 years ago

This was really helpful. I believe that would be good having explanation on how to use a free plan in the documentation. Also, I wish there was a documentation on Vuetify or something similar, rather than a plain Git Readme that is a bit hard to read and not complete. People could contribute to it as well.

I believe this is one of the main reason people are chosing Stripe and Paddle over Mollie (for Cashier), which is a shame because Mollie is definitely better IMO!