Last active
June 26, 2023 02:58
-
-
Save hypeJunction/31b5e82daacd56b49ee419fdd417a865 to your computer and use it in GitHub Desktop.
Stripe Subscription Cancellation and Prorated Refund
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class StripeSubscriptionHandler { | |
/** | |
* Cancel a Stripe subscription, optionally prorating and refunding the unused amount | |
* | |
* @param string $id Stripe Subscription ID | |
* @param bool $at_period_end Type of cancellation | |
* If set to true, will cancel the subscription at its end | |
* Otherwise, will cancel the subscription immediately | |
* @param bool $prorate Issue a prorated refund | |
* If set to true, will prorate the unused part of the subscription and issue a refund | |
* | |
* @return \Stripe\Subscription | |
*/ | |
public function cancel($id, $at_period_end = true, $prorate = false) { | |
$subscription = \Stripe\Subscription::retrieve($id); | |
/* @var $subscription \Stripe\Subscription */ | |
if (!$at_period_end && $prorate) { | |
$time = new \DateTime('now', new \DateTimeZone('UTC')); | |
$refund = $this->prorate($subscription, $time); | |
if ($refund) { | |
$invoices = \Stripe\Invoice::all([ | |
'subscription' => $subscription->id, | |
]); | |
if ($invoices->count()) { | |
$invoice = $invoices->data[0]; | |
\Stripe\Refund::create([ | |
'charge' => $invoice->charge, | |
'amount' => $refund, | |
]); | |
} | |
} | |
} | |
return $subscription->cancel([ | |
'at_period_end' => $at_period_end, | |
]); | |
} | |
/** | |
* Calculate refund amount on an unsused part of the subscription | |
* | |
* @param \Stripe\Subscription $subscription Stripe Subscription | |
* @param DateTime $time Time of the cancellation | |
* | |
* @return int | |
*/ | |
public function prorate(\Stripe\Subscription $subscription, DateTime $time) { | |
$period_start = $subscription->current_period_start; | |
$period_end = $subscription->current_period_end; | |
$amount = $subscription->plan->amount; | |
$period_length = $period_end - $period_start; | |
$elapsed_since_start = $time->getTimestamp() - $period_start; | |
$refund = $amount - floor(($elapsed_since_start / $period_length) * $amount); | |
return $refund > 0 ? (int) $refund : 0; | |
} | |
} | |
class StripeSubscriptionTest extends \PHPUnit\Framework\TestCase { | |
/** | |
* @var StripeSubscriptionHandler | |
*/ | |
protected $handler; | |
public function setUp() { | |
$this->handler = new StripeSubscriptionHandler(); | |
} | |
/** | |
* @dataProvider prorationProvider | |
*/ | |
public function testProrate($start, $end, $cancel, $amount, $expected) { | |
$subscription = new \Stripe\Subscription(); | |
$start_at = new DateTime($start, new DateTimeZone('UTC')); | |
$end_at = new DateTime($end, new DateTimeZone('UTC')); | |
$cancel_at = new DateTime($cancel, new DateTimeZone('UTC')); | |
$subscription->current_period_start = $start_at->getTimestamp(); | |
$subscription->current_period_end = $end_at->getTimestamp(); | |
$subscription->plan = new \Stripe\Plan(); | |
$subscription->plan->amount = $amount; | |
$actual = $this->handler->prorate($subscription, $cancel_at); | |
$this->assertEquals($expected, $actual); | |
} | |
public function prorationProvider() { | |
return [ | |
[ | |
'Jan 1, 2018 0:00:00', | |
'Jan 31, 2018 23:59:59', | |
'Jan 5, 2018 12:00:00', | |
1000, | |
855, | |
], | |
[ | |
'Jan 1, 2018 0:00:00', | |
'Dec 31, 2018 23:59:59', | |
'Jun 30, 2018 23:59:59', | |
1000, | |
505, | |
], | |
[ | |
'Jan 5, 2018 0:00:00', | |
'Jan 5, 2018 1:00:00', | |
'Jan 5, 2018 0:30:00', | |
1000, | |
500, | |
], | |
]; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment