Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.

Commit 7025452

Browse files
fix: Handle edge case of past invoice older than last period
1 parent 5a6d0c4 commit 7025452

File tree

2 files changed

+26
-18
lines changed

2 files changed

+26
-18
lines changed

services/billing.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import re
33
from abc import ABC, abstractmethod
44
from datetime import datetime
5+
from dateutil.relativedelta import relativedelta
56

67
import stripe
78
from django.conf import settings
@@ -164,18 +165,25 @@ def delete_subscription(self, owner: Owner):
164165
)
165166
stripe.SubscriptionSchedule.release(subscription_schedule_id)
166167

167-
# we give a auto-refund grace period of 24 hours for a monthly subscription or 72 hours for a yearly subscription
168-
current_subscription_timestamp = subscription["current_period_start"]
169-
difference = datetime.now() - datetime.fromtimestamp(current_subscription_timestamp)
168+
# we give an auto-refund grace period of 24 hours for a monthly subscription or 72 hours for a yearly subscription
169+
# current_subscription_timestamp = subscription["current_period_start"]
170+
current_subscription_datetime = datetime.fromtimestamp(subscription["current_period_start"])
171+
differenceFromNow = datetime.now() - current_subscription_datetime
172+
170173
subscription_plan_interval = subscription.plan.interval if subscription.plan is not None else None
171-
should_refund_grace_period = (subscription_plan_interval == "month" and difference.days < 1) or (subscription_plan_interval == "year" and difference.days < 3)
172-
if should_refund_grace_period:
174+
within_refund_grace_period = (subscription_plan_interval == "month" and differenceFromNow.days < 1) or (subscription_plan_interval == "year" and differenceFromNow.days < 3)
175+
if within_refund_grace_period:
173176
stripe.Subscription.cancel(owner.stripe_subscription_id)
174177

175178
invoices_list = stripe.Invoice.list(subscription=owner.stripe_subscription_id, status="paid")
176179
created_refund = False
180+
# there could be multiple invoices that need to be refunded such as if the user increased seats within the grace period
177181
for invoice in invoices_list["data"]:
178-
if invoice["charge"] is not None and invoice["amount_remaining"] == 0 and invoice["created"] < current_subscription_timestamp:
182+
start_of_last_period = current_subscription_datetime - relativedelta(months=1) if subscription_plan_interval == "month" else current_subscription_datetime - relativedelta(years=1)
183+
184+
# refund if the invoice has a charge, it has been fully paid, the creation time was before the start of the current subscription's start and the creation time was after the start of the last period
185+
invoice_created_datetime = datetime.fromtimestamp(invoice["created"])
186+
if invoice["charge"] is not None and invoice["amount_remaining"] == 0 and invoice_created_datetime < current_subscription_datetime and invoice_created_datetime >= start_of_last_period:
179187
stripe.Refund.create(invoice["charge"])
180188
created_refund = True
181189
if created_refund == True:

services/tests/test_billing.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import json
22
from unittest.mock import MagicMock, patch
3-
from freezegun import freeze_time
43

54
import requests
65
from django.conf import settings
76
from django.test import TestCase
7+
from freezegun import freeze_time
88
from stripe import InvalidRequestError
99

1010
from codecov_auth.models import Service
@@ -291,8 +291,8 @@ def test_delete_subscription_without_schedule_modifies_subscription_to_delete_at
291291
)
292292
subscription_params = {
293293
"schedule_id": stripe_schedule_id,
294-
"start_date": 1639628096,
295-
"end_date": 1644107871,
294+
"start_date": 1489799420,
295+
"end_date": 1492477820,
296296
"quantity": 10,
297297
"name": plan,
298298
"id": 215,
@@ -311,7 +311,7 @@ def test_delete_subscription_without_schedule_modifies_subscription_to_delete_at
311311
assert owner.plan_activated_users == [4, 6, 3]
312312
assert owner.plan_user_count == 9
313313

314-
@freeze_time("2021-12-22T00:00:00")
314+
@freeze_time("2017-03-22T00:00:00")
315315
@patch("services.billing.stripe.Refund.create")
316316
@patch("services.billing.stripe.Subscription.modify")
317317
@patch("services.billing.stripe.Subscription.retrieve")
@@ -330,8 +330,8 @@ def test_delete_subscription_with_schedule_releases_schedule_and_cancels_subscri
330330
)
331331
subscription_params = {
332332
"schedule_id": stripe_schedule_id,
333-
"start_date": 1639628096,
334-
"end_date": 1644107871,
333+
"start_date": 1489799420,
334+
"end_date": 1492477820,
335335
"quantity": 10,
336336
"name": plan,
337337
"id": 215,
@@ -352,7 +352,7 @@ def test_delete_subscription_with_schedule_releases_schedule_and_cancels_subscri
352352
assert owner.plan_activated_users == [4, 6, 3]
353353
assert owner.plan_user_count == 9
354354

355-
@freeze_time("2021-12-17T00:00:00")
355+
@freeze_time("2017-03-18T00:00:00")
356356
@patch("services.billing.stripe.Subscription.modify")
357357
@patch("services.billing.stripe.Customer.modify")
358358
@patch("services.billing.stripe.Refund.create")
@@ -377,8 +377,8 @@ def test_delete_subscription_with_schedule_releases_schedule_and_cancels_subscri
377377
)
378378
subscription_params = {
379379
"schedule_id": stripe_schedule_id,
380-
"start_date": 1639628096,
381-
"end_date": 1644107871,
380+
"start_date": 1489799420,
381+
"end_date": 1492477820,
382382
"quantity": 10,
383383
"name": plan,
384384
"id": 215,
@@ -405,7 +405,7 @@ def test_delete_subscription_with_schedule_releases_schedule_and_cancels_subscri
405405
assert owner.plan_activated_users == [4, 6, 3]
406406
assert owner.plan_user_count == 9
407407

408-
@freeze_time("2021-12-19T00:00:00")
408+
@freeze_time("2017-03-19T00:00:00")
409409
@patch("services.billing.stripe.Subscription.modify")
410410
@patch("services.billing.stripe.Customer.modify")
411411
@patch("services.billing.stripe.Refund.create")
@@ -430,8 +430,8 @@ def test_delete_subscription_with_schedule_releases_schedule_and_cancels_subscri
430430
)
431431
subscription_params = {
432432
"schedule_id": stripe_schedule_id,
433-
"start_date": 1639628096,
434-
"end_date": 1644107871,
433+
"start_date": 1489799420,
434+
"end_date": 1492477820,
435435
"quantity": 10,
436436
"name": plan,
437437
"id": 215,

0 commit comments

Comments
 (0)