Skip to content

Instantly share code, notes, and snippets.

@hypeJunction
Last active June 26, 2023 02:58
Show Gist options
  • Save hypeJunction/31b5e82daacd56b49ee419fdd417a865 to your computer and use it in GitHub Desktop.
Save hypeJunction/31b5e82daacd56b49ee419fdd417a865 to your computer and use it in GitHub Desktop.
Stripe Subscription Cancellation and Prorated Refund
<?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