prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  T - the API to support subscription management
  The backend pieces needed for https://github.com/mdn/kuma/issues/6703
  
  That way we, can Reactify the subscription management page. 
  
  What we need is to endpoints:
  
  1. Getting your subscriptions (plural but it's probably never more than 1)
  2. Cancel a subscription (or cancel them all if that's easier)
  
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `kuma/users/stripe_utils.py`
  Content:
  ```
  1 from datetime import datetime
  2 
  3 import stripe
  4 from django.conf import settings
  5 from django.utils import timezone
  6 
  7 from kuma.core.urlresolvers import reverse
  8 from kuma.wiki.templatetags.jinja_helpers import absolutify
  9 
  10 from .models import UserSubscription
  11 
  12 
  13 def retrieve_stripe_subscription(customer):
  14     for subscription in customer.subscriptions.list().auto_paging_iter():
  15         # We have to use array indexing syntax, as stripe uses dicts to
  16         # represent its objects (dicts come with an .items method)
  17         for item in subscription["items"].auto_paging_iter():
  18             if item.plan.id == settings.STRIPE_PLAN_ID:
  19                 return subscription
  20 
  21     return None
  22 
  23 
  24 def retrieve_and_synchronize_subscription_info(user):
  25     """For the given user, if it has as 'stripe_customer_id' retrieve the info
  26     about the subscription if it's there. All packaged in a way that is
  27     practical for the stripe_subscription.html template.
  28 
  29     Also, whilst doing this check, we also verify that the UserSubscription record
  30     for this user is right. Doing that check is a second-layer check in case
  31     our webhooks have failed us.
  32     """
  33     subscription_info = None
  34     stripe_customer = get_stripe_customer(user)
  35     if stripe_customer:
  36         stripe_subscription_info = get_stripe_subscription_info(stripe_customer)
  37         if stripe_subscription_info:
  38             source = stripe_customer.default_source
  39             if source.object == "card":
  40                 card = source
  41             elif source.object == "source":
  42                 card = source.card
  43             else:
  44                 raise ValueError(
  45                     f"unexpected stripe customer default_source of type {source.object!r}"
  46                 )
  47 
  48             subscription_info = {
  49                 "id": stripe_subscription_info.id,
  50                 "amount": stripe_subscription_info.plan.amount,
  51                 "brand": card.brand,
  52                 "expires_at": f"{card.exp_month}/{card.exp_year}",
  53                 "last4": card.last4,
  54                 # Cards that are part of a "source" don't have a zip
  55                 "zip": card.get("address_zip", None),
  56                 # TODO: Deprecated. Only used in the Edit Profile view
  57                 "next_payment_at": datetime.fromtimestamp(
  58                     stripe_subscription_info.current_period_end
  59                 ),
  60             }
  61 
  62             # To perfect the synchronization, take this opportunity to make sure
  63             # we have an up-to-date record of this.
  64             UserSubscription.set_active(user, stripe_subscription_info.id)
  65         else:
  66             # The user has a stripe_customer_id but no active subscription
  67             # on the current settings.STRIPE_PLAN_ID! Perhaps it has been cancelled
  68             # and not updated in our own records.
  69             for user_subscription in UserSubscription.objects.filter(
  70                 user=user, canceled__isnull=True
  71             ):
  72                 user_subscription.canceled = timezone.now()
  73                 user_subscription.save()
  74 
  75     return subscription_info
  76 
  77 
  78 def create_stripe_customer_and_subscription_for_user(user, email, stripe_token):
  79     customer = (
  80         stripe.Customer.retrieve(user.stripe_customer_id)
  81         if user.stripe_customer_id
  82         else None
  83     )
  84     if not customer or customer.email != email:
  85         customer = stripe.Customer.create(email=email, source=stripe_token,)
  86         user.stripe_customer_id = customer.id
  87         user.save()
  88 
  89     subscription = retrieve_stripe_subscription(customer)
  90     if not subscription:
  91         subscription = stripe.Subscription.create(
  92             customer=customer.id, items=[{"plan": settings.STRIPE_PLAN_ID}],
  93         )
  94 
  95     UserSubscription.set_active(user, subscription.id)
  96 
  97 
  98 def cancel_stripe_customer_subscriptions(user):
  99     """Delete all subscriptions for a Stripe customer."""
  100     assert user.stripe_customer_id
  101     customer = stripe.Customer.retrieve(user.stripe_customer_id)
  102     for sub in customer.subscriptions.data:
  103         s = stripe.Subscription.retrieve(sub.id)
  104         UserSubscription.set_canceled(user, s.id)
  105         s.delete()
  106 
  107 
  108 def get_stripe_customer(user):
  109     if settings.STRIPE_PLAN_ID and user.stripe_customer_id:
  110         return stripe.Customer.retrieve(
  111             user.stripe_customer_id, expand=["default_source"]
  112         )
  113 
  114 
  115 def get_stripe_subscription_info(stripe_customer):
  116     return retrieve_stripe_subscription(stripe_customer)
  117 
  118 
  119 def create_missing_stripe_webhook():
  120     url_path = reverse("users.stripe_hooks")
  121     url = (
  122         "https://" + settings.STRIPE_WEBHOOK_HOSTNAME + url_path
  123         if settings.STRIPE_WEBHOOK_HOSTNAME
  124         else absolutify(url_path)
  125     )
  126 
  127     # From https://stripe.com/docs/api/webhook_endpoints/create
  128     events = (
  129         # "Occurs whenever an invoice payment attempt succeeds."
  130         "invoice.payment_succeeded",
  131         # "Occurs whenever a customer’s subscription ends."
  132         # Also, if you go into the Stripe Dashboard, click Billing, Subscriptions,
  133         # and find a customer and click the "Cancel subscription" button, this
  134         # triggers.
  135         "customer.subscription.deleted",
  136     )
  137 
  138     for webhook in stripe.WebhookEndpoint.list().auto_paging_iter():
  139         if webhook.url == url and set(events) == set(webhook.enabled_events):
  140             return
  141 
  142     stripe.WebhookEndpoint.create(
  143         url=url, enabled_events=events,
  144     )
  145
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/kuma/users/stripe_utils.py b/kuma/users/stripe_utils.py
  --- a/kuma/users/stripe_utils.py
  +++ b/kuma/users/stripe_utils.py
  @@ -64,7 +64,7 @@
               UserSubscription.set_active(user, stripe_subscription_info.id)
           else:
               # The user has a stripe_customer_id but no active subscription
  -            # on the current settings.STRIPE_PLAN_ID! Perhaps it has been cancelled
  +            # on the current settings.STRIPE_PLAN_ID! Perhaps it has been canceled
               # and not updated in our own records.
               for user_subscription in UserSubscription.objects.filter(
                   user=user, canceled__isnull=True
  @@ -82,7 +82,7 @@
           else None
       )
       if not customer or customer.email != email:
  -        customer = stripe.Customer.create(email=email, source=stripe_token,)
  +        customer = stripe.Customer.create(email=email, source=stripe_token)
           user.stripe_customer_id = customer.id
           user.save()
   
  @@ -99,10 +99,13 @@
       """Delete all subscriptions for a Stripe customer."""
       assert user.stripe_customer_id
       customer = stripe.Customer.retrieve(user.stripe_customer_id)
  +    canceled = []
       for sub in customer.subscriptions.data:
           s = stripe.Subscription.retrieve(sub.id)
           UserSubscription.set_canceled(user, s.id)
           s.delete()
  +        canceled.append(s)
  +    return canceled
   
   
   def get_stripe_customer(user):
  
verification_info: {"golden_diff": "diff --git a/kuma/users/stripe_utils.py b/kuma/users/stripe_utils.py\n--- a/kuma/users/stripe_utils.py\n+++ b/kuma/users/stripe_utils.py\n@@ -64,7 +64,7 @@\n             UserSubscription.set_active(user, stripe_subscription_info.id)\n         else:\n             # The user has a stripe_customer_id but no active subscription\n-            # on the current settings.STRIPE_PLAN_ID! Perhaps it has been cancelled\n+            # on the current settings.STRIPE_PLAN_ID! Perhaps it has been canceled\n             # and not updated in our own records.\n             for user_subscription in UserSubscription.objects.filter(\n                 user=user, canceled__isnull=True\n@@ -82,7 +82,7 @@\n         else None\n     )\n     if not customer or customer.email != email:\n-        customer = stripe.Customer.create(email=email, source=stripe_token,)\n+        customer = stripe.Customer.create(email=email, source=stripe_token)\n         user.stripe_customer_id = customer.id\n         user.save()\n \n@@ -99,10 +99,13 @@\n     \"\"\"Delete all subscriptions for a Stripe customer.\"\"\"\n     assert user.stripe_customer_id\n     customer = stripe.Customer.retrieve(user.stripe_customer_id)\n+    canceled = []\n     for sub in customer.subscriptions.data:\n         s = stripe.Subscription.retrieve(sub.id)\n         UserSubscription.set_canceled(user, s.id)\n         s.delete()\n+        canceled.append(s)\n+    return canceled\n \n \n def get_stripe_customer(user):\n", "issue": "T - the API to support subscription management\nThe backend pieces needed for https://github.com/mdn/kuma/issues/6703\r\n\r\nThat way we, can Reactify the subscription management page. \r\n\r\nWhat we need is to endpoints:\r\n\r\n1. Getting your subscriptions (plural but it's probably never more than 1)\r\n2. Cancel a subscription (or cancel them all if that's easier)\r\n\r\n\n", "before_files": [{"content": "from datetime import datetime\n\nimport stripe\nfrom django.conf import settings\nfrom django.utils import timezone\n\nfrom kuma.core.urlresolvers import reverse\nfrom kuma.wiki.templatetags.jinja_helpers import absolutify\n\nfrom .models import UserSubscription\n\n\ndef retrieve_stripe_subscription(customer):\n    for subscription in customer.subscriptions.list().auto_paging_iter():\n        # We have to use array indexing syntax, as stripe uses dicts to\n        # represent its objects (dicts come with an .items method)\n        for item in subscription[\"items\"].auto_paging_iter():\n            if item.plan.id == settings.STRIPE_PLAN_ID:\n                return subscription\n\n    return None\n\n\ndef retrieve_and_synchronize_subscription_info(user):\n    \"\"\"For the given user, if it has as 'stripe_customer_id' retrieve the info\n    about the subscription if it's there. All packaged in a way that is\n    practical for the stripe_subscription.html template.\n\n    Also, whilst doing this check, we also verify that the UserSubscription record\n    for this user is right. Doing that check is a second-layer check in case\n    our webhooks have failed us.\n    \"\"\"\n    subscription_info = None\n    stripe_customer = get_stripe_customer(user)\n    if stripe_customer:\n        stripe_subscription_info = get_stripe_subscription_info(stripe_customer)\n        if stripe_subscription_info:\n            source = stripe_customer.default_source\n            if source.object == \"card\":\n                card = source\n            elif source.object == \"source\":\n                card = source.card\n            else:\n                raise ValueError(\n                    f\"unexpected stripe customer default_source of type {source.object!r}\"\n                )\n\n            subscription_info = {\n                \"id\": stripe_subscription_info.id,\n                \"amount\": stripe_subscription_info.plan.amount,\n                \"brand\": card.brand,\n                \"expires_at\": f\"{card.exp_month}/{card.exp_year}\",\n                \"last4\": card.last4,\n                # Cards that are part of a \"source\" don't have a zip\n                \"zip\": card.get(\"address_zip\", None),\n                # TODO: Deprecated. Only used in the Edit Profile view\n                \"next_payment_at\": datetime.fromtimestamp(\n                    stripe_subscription_info.current_period_end\n                ),\n            }\n\n            # To perfect the synchronization, take this opportunity to make sure\n            # we have an up-to-date record of this.\n            UserSubscription.set_active(user, stripe_subscription_info.id)\n        else:\n            # The user has a stripe_customer_id but no active subscription\n            # on the current settings.STRIPE_PLAN_ID! Perhaps it has been cancelled\n            # and not updated in our own records.\n            for user_subscription in UserSubscription.objects.filter(\n                user=user, canceled__isnull=True\n            ):\n                user_subscription.canceled = timezone.now()\n                user_subscription.save()\n\n    return subscription_info\n\n\ndef create_stripe_customer_and_subscription_for_user(user, email, stripe_token):\n    customer = (\n        stripe.Customer.retrieve(user.stripe_customer_id)\n        if user.stripe_customer_id\n        else None\n    )\n    if not customer or customer.email != email:\n        customer = stripe.Customer.create(email=email, source=stripe_token,)\n        user.stripe_customer_id = customer.id\n        user.save()\n\n    subscription = retrieve_stripe_subscription(customer)\n    if not subscription:\n        subscription = stripe.Subscription.create(\n            customer=customer.id, items=[{\"plan\": settings.STRIPE_PLAN_ID}],\n        )\n\n    UserSubscription.set_active(user, subscription.id)\n\n\ndef cancel_stripe_customer_subscriptions(user):\n    \"\"\"Delete all subscriptions for a Stripe customer.\"\"\"\n    assert user.stripe_customer_id\n    customer = stripe.Customer.retrieve(user.stripe_customer_id)\n    for sub in customer.subscriptions.data:\n        s = stripe.Subscription.retrieve(sub.id)\n        UserSubscription.set_canceled(user, s.id)\n        s.delete()\n\n\ndef get_stripe_customer(user):\n    if settings.STRIPE_PLAN_ID and user.stripe_customer_id:\n        return stripe.Customer.retrieve(\n            user.stripe_customer_id, expand=[\"default_source\"]\n        )\n\n\ndef get_stripe_subscription_info(stripe_customer):\n    return retrieve_stripe_subscription(stripe_customer)\n\n\ndef create_missing_stripe_webhook():\n    url_path = reverse(\"users.stripe_hooks\")\n    url = (\n        \"https://\" + settings.STRIPE_WEBHOOK_HOSTNAME + url_path\n        if settings.STRIPE_WEBHOOK_HOSTNAME\n        else absolutify(url_path)\n    )\n\n    # From https://stripe.com/docs/api/webhook_endpoints/create\n    events = (\n        # \"Occurs whenever an invoice payment attempt succeeds.\"\n        \"invoice.payment_succeeded\",\n        # \"Occurs whenever a customer\u2019s subscription ends.\"\n        # Also, if you go into the Stripe Dashboard, click Billing, Subscriptions,\n        # and find a customer and click the \"Cancel subscription\" button, this\n        # triggers.\n        \"customer.subscription.deleted\",\n    )\n\n    for webhook in stripe.WebhookEndpoint.list().auto_paging_iter():\n        if webhook.url == url and set(events) == set(webhook.enabled_events):\n            return\n\n    stripe.WebhookEndpoint.create(\n        url=url, enabled_events=events,\n    )\n", "path": "kuma/users/stripe_utils.py"}], "after_files": [{"content": "from datetime import datetime\n\nimport stripe\nfrom django.conf import settings\nfrom django.utils import timezone\n\nfrom kuma.core.urlresolvers import reverse\nfrom kuma.wiki.templatetags.jinja_helpers import absolutify\n\nfrom .models import UserSubscription\n\n\ndef retrieve_stripe_subscription(customer):\n    for subscription in customer.subscriptions.list().auto_paging_iter():\n        # We have to use array indexing syntax, as stripe uses dicts to\n        # represent its objects (dicts come with an .items method)\n        for item in subscription[\"items\"].auto_paging_iter():\n            if item.plan.id == settings.STRIPE_PLAN_ID:\n                return subscription\n\n    return None\n\n\ndef retrieve_and_synchronize_subscription_info(user):\n    \"\"\"For the given user, if it has as 'stripe_customer_id' retrieve the info\n    about the subscription if it's there. All packaged in a way that is\n    practical for the stripe_subscription.html template.\n\n    Also, whilst doing this check, we also verify that the UserSubscription record\n    for this user is right. Doing that check is a second-layer check in case\n    our webhooks have failed us.\n    \"\"\"\n    subscription_info = None\n    stripe_customer = get_stripe_customer(user)\n    if stripe_customer:\n        stripe_subscription_info = get_stripe_subscription_info(stripe_customer)\n        if stripe_subscription_info:\n            source = stripe_customer.default_source\n            if source.object == \"card\":\n                card = source\n            elif source.object == \"source\":\n                card = source.card\n            else:\n                raise ValueError(\n                    f\"unexpected stripe customer default_source of type {source.object!r}\"\n                )\n\n            subscription_info = {\n                \"id\": stripe_subscription_info.id,\n                \"amount\": stripe_subscription_info.plan.amount,\n                \"brand\": card.brand,\n                \"expires_at\": f\"{card.exp_month}/{card.exp_year}\",\n                \"last4\": card.last4,\n                # Cards that are part of a \"source\" don't have a zip\n                \"zip\": card.get(\"address_zip\", None),\n                # TODO: Deprecated. Only used in the Edit Profile view\n                \"next_payment_at\": datetime.fromtimestamp(\n                    stripe_subscription_info.current_period_end\n                ),\n            }\n\n            # To perfect the synchronization, take this opportunity to make sure\n            # we have an up-to-date record of this.\n            UserSubscription.set_active(user, stripe_subscription_info.id)\n        else:\n            # The user has a stripe_customer_id but no active subscription\n            # on the current settings.STRIPE_PLAN_ID! Perhaps it has been canceled\n            # and not updated in our own records.\n            for user_subscription in UserSubscription.objects.filter(\n                user=user, canceled__isnull=True\n            ):\n                user_subscription.canceled = timezone.now()\n                user_subscription.save()\n\n    return subscription_info\n\n\ndef create_stripe_customer_and_subscription_for_user(user, email, stripe_token):\n    customer = (\n        stripe.Customer.retrieve(user.stripe_customer_id)\n        if user.stripe_customer_id\n        else None\n    )\n    if not customer or customer.email != email:\n        customer = stripe.Customer.create(email=email, source=stripe_token)\n        user.stripe_customer_id = customer.id\n        user.save()\n\n    subscription = retrieve_stripe_subscription(customer)\n    if not subscription:\n        subscription = stripe.Subscription.create(\n            customer=customer.id, items=[{\"plan\": settings.STRIPE_PLAN_ID}],\n        )\n\n    UserSubscription.set_active(user, subscription.id)\n\n\ndef cancel_stripe_customer_subscriptions(user):\n    \"\"\"Delete all subscriptions for a Stripe customer.\"\"\"\n    assert user.stripe_customer_id\n    customer = stripe.Customer.retrieve(user.stripe_customer_id)\n    canceled = []\n    for sub in customer.subscriptions.data:\n        s = stripe.Subscription.retrieve(sub.id)\n        UserSubscription.set_canceled(user, s.id)\n        s.delete()\n        canceled.append(s)\n    return canceled\n\n\ndef get_stripe_customer(user):\n    if settings.STRIPE_PLAN_ID and user.stripe_customer_id:\n        return stripe.Customer.retrieve(\n            user.stripe_customer_id, expand=[\"default_source\"]\n        )\n\n\ndef get_stripe_subscription_info(stripe_customer):\n    return retrieve_stripe_subscription(stripe_customer)\n\n\ndef create_missing_stripe_webhook():\n    url_path = reverse(\"users.stripe_hooks\")\n    url = (\n        \"https://\" + settings.STRIPE_WEBHOOK_HOSTNAME + url_path\n        if settings.STRIPE_WEBHOOK_HOSTNAME\n        else absolutify(url_path)\n    )\n\n    # From https://stripe.com/docs/api/webhook_endpoints/create\n    events = (\n        # \"Occurs whenever an invoice payment attempt succeeds.\"\n        \"invoice.payment_succeeded\",\n        # \"Occurs whenever a customer\u2019s subscription ends.\"\n        # Also, if you go into the Stripe Dashboard, click Billing, Subscriptions,\n        # and find a customer and click the \"Cancel subscription\" button, this\n        # triggers.\n        \"customer.subscription.deleted\",\n    )\n\n    for webhook in stripe.WebhookEndpoint.list().auto_paging_iter():\n        if webhook.url == url and set(events) == set(webhook.enabled_events):\n            return\n\n    stripe.WebhookEndpoint.create(\n        url=url, enabled_events=events,\n    )\n", "path": "kuma/users/stripe_utils.py"}]}