Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changing subscription plan #264

Open
ignaciocunado opened this issue Aug 30, 2024 · 4 comments
Open

Changing subscription plan #264

ignaciocunado opened this issue Aug 30, 2024 · 4 comments

Comments

@ignaciocunado
Copy link
Contributor

Whilst implementing functionality for users to be able to change their subscription plan/quantity, we came across the following scenario:

  1. A user has a yearly subscription and wants to downgrade into a monthly subscription (this should happen at the end of their current cycle since they agreed to pay for a year)
  2. Using swapNextCycle, their subscription attribute next_plan is set for a monthly subscription and a new order item is scheduled for the end of their cycle.
  3. This same user now wants to increase their subscription count (eg. from 1 to 2) which should happen immediately since this is considered an upgrade.

Problem: If we use the current functionality, a new cycle will start when upgrading their subscription count. This will cause the scheduled order item containing the next_plan to be removed.

When should the plan downgrade happen?

@ignaciocunado
Copy link
Contributor Author

I know this is a pretty specific use case, but we wanted to see if maybe anybody else had come across this scenario and if there was a possible fix for this.

I will submit a PR if I find a possible and suitable implementation.

@sandervanhooft
Copy link
Collaborator

sandervanhooft commented Aug 30, 2024

Hi @ignaciocunado ,

Thanks for sharing this. The use case makes sense. People change their minds all the time, especially subscription customers ;).

Problem: If we use the current functionality, a new cycle will start when upgrading their subscription count. This will cause the scheduled order item containing the next_plan to be removed.

Were you able to confirm the behavior you're describing? The next_plan is actually stored on the Subscription model, not on the scheduled OrderItem. If possible, could you provide a failing test?

@ignaciocunado
Copy link
Contributor Author

ignaciocunado commented Sep 1, 2024

/** @test */
    public function swapPlanThenCount()
    {
        $user = $this->getUserWithZeroBalance();
        $subscription = $this->getSubscriptionForUser($user);

        // Swap to new plan
        $subscription = $subscription->swapNextCycle('weekly-20-1')->fresh();
        $this->assertEquals('monthly-10-1', $subscription->plan);
        $this->assertEquals('weekly-20-1', $subscription->next_plan);
        $cycle_should_have_started_at = now()->subWeeks(2);
        $cycle_should_end_at = $cycle_should_have_started_at->copy()->addMonth();
        $new_order_item = $subscription->scheduledOrderItem;
        $this->assertCarbon($cycle_should_end_at, $new_order_item->process_at, 1); // based on previous plan's cycle
        $this->assertEquals(2200, $new_order_item->total);
        $this->assertEquals(200, $new_order_item->tax);
        $this->assertEquals('Twice as expensive monthly subscription', $new_order_item->description);
        Event::assertNotDispatched(SubscriptionPlanSwapped::class);

        // Increase subscription count
        $subscription = $subscription->incrementQuantity(1, false)->fresh();
        $this->assertEquals(2, $subscription->quantity);
        $new_order_item = $subscription->scheduledOrderItem;
        $this->assertNotNull($subscription->next_plan);
        $this->assertEquals($subscription->plan, 'monthly-10-1'); // Plan has not been updated
        $this->assertEquals(4400, $new_order_item->total); // Plan not updated on the scheduled order item (twice as before as quantity is doubled). First assertion that fails
        $this->assertEquals(400, $new_order_item->tax); // Same here
        $this->assertEquals('Twice as expensive monthly subscription', $new_order_item->description);
    }
Time: 00:01.175, Memory: 52.50 MB

There was 1 failure:

1) Laravel\Cashier\Tests\SwapSubscriptionPlanTest::swapPlanThenCount
Failed asserting that 2200 matches expected 4400.

/Users/ignacunado/Herd/laravel-cashier-mollie/tests/SwapSubscriptionPlanTest.php:332

FAILURES!
Tests: 232, Assertions: 1778, Failures: 1.
Script ./vendor/bin/phpunit tests handling the test event returned with error code 1

This test shows that when updating the plan at the end of the cycle and then using incrementQuantity, a new orderItem is scheduled and processed with the same plan and the new quantity. Even though the next_plan attribute on the subscription model remains set, there are no scheduled order items for this new plan, so I guess it will never be changed?

@ignaciocunado
Copy link
Contributor Author

So I guess that there are (at least) two options here with the current functionality.

  1. If a function like restartCycleWithModifications is called which schedules a new order item at now() is called and the subscription object has the next_plan attribute set (meaning the plan should change for the next cycle), then we change the plan on this order item (immediately). This has flaws as a user could terminate a yearly subscription before the end of the cycle and get their money back (set next plan to a weekly subscription, change quantity and then cancel at the end of the week).
  2. If a function like restartCycleWithModifications is called which schedules a new order item at now() is called and the subscription object has the next_plan attribute set (meaning the plan should change for the next cycle), then we change the plan for the next order item. This is also kind of bad. Say a user has a yearly subscription ending in February, and have set it to downgrade to a monthly one which will take effect in February. In January the user decides to increment their subscription quantity by 1. Then they would need to wait a whole other year to swap to a monthly subscription as the cycle restarts.

Again, pretty specific scenarios but they can happen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants