• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kobotoolbox / kpi / 30021995917 / 1
82%
master: 76%

Build:
Build:
LAST BUILD BRANCH: dev-1635-cleanup-legacy-kobocat-kml-logic
DEFAULT BRANCH: master
Ran 23 Jul 2026 03:49PM UTC
Files 892
Run time 49s
Badge
Embed ▾
README BADGES
x

If you need to use a raster PNG badge, change the '.svg' to '.png' in the link

Markdown

Textile

RDoc

HTML

Rst

23 Jul 2026 03:45PM UTC coverage: 49.355% (+3.7%) from 45.647%
30021995917.1

push

github

web-flow
feat(stripe): add webhook handling to update status of unpaid subscriptions DEV-2399 (#7254)

### đŸ“Ŗ Summary
Update our Stripe billing integration so that certain subscription plans
(like Teams) can remain in an "unpaid" status indefinitely instead of
automatically canceling after payment retries fail.

### 💭 Notes
- I have updated the Teams plans in the Stripe test sandbox to include a
product metadata key `'preserve_unpaid_status': true`.
- After this pr is approved, the Stripe production environment will need
to be updated with this key as well as changing the Subscriptions'
status behavior from canceling to marking as unpaid.

### 👀 Preview steps

1. â„šī¸ Have a an active `djstripe` webhook handling
2. In the kpi shell:
3. 🔴 [on main] Run a mock customer.subscription.updated signal in the
shell for an unpaid subscription
4. Notice that all unpaid behaviors fallback to our default Stripe
configuration which is to move them to cancelled
5. đŸŸĸ [on PR] Run the following test block to test our new webhook
signal:
```
from unittest.mock import MagicMock
from djstripe.models import Subscription
from kobo.apps.stripe.signals import handle_unpaid_subscription

# Grab any local subscription to use for the target
sub = Subscription.objects.first()
if not sub:
    print("âš ī¸ No subscriptions found. Please create one to run this preview.")
else:
    original_cancel = sub.cancel
    sub.cancel = MagicMock()

    # 1. Simulate 'preserve_unpaid_status': 'true'
    mock_event_preserved = MagicMock()
    mock_event_preserved.data = {
        "object": {
            "id": sub.id,
            "status": "unpaid",
            "items": {"data": [{"price": {"product": {"metadata": {"preserve_unpaid_status": "true"}}}}]}
        }
    }
    sub.cancel.reset_mock()
    handle_unpaid_subscription(sender=None, event=mock_event_preserved, instance=sub)

    # đŸŸĸ Notice that this did NOT trigger the cancel method
    print("Preserve Status Checked:", "PASS (Subscription not canceled... (continued)

4426 of 12644 branches covered (35.0%)

18478 of 37439 relevant lines covered (49.35%)

0.49 hits per line

Source Files on job 30021995917.1
  • Tree
  • List 892
  • Changed 109
  • Source Changed 0
  • Coverage Changed 109
Coverage ∆ File Lines Relevant Covered Missed Hits/Line Branch Hits Branch Misses
  • Back to Build 30021995917
  • 3f66d2af on github
  • Prev Job for on main (#30008784565.4)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc