dj-stripe / dj-stripe

dj-stripe automatically syncs your Stripe Data to your local database as pre-implemented Django Models allowing you to use the Django ORM, in your code, to work with the data making it easier and faster.
https://dj-stripe.dev
MIT License
1.56k stars 474 forks source link

Invalid Subscription Item ID Causing Sync to Fail #2007

Closed stanmattingly closed 4 months ago

stanmattingly commented 6 months ago

Describe the bug When there is a subscription item for proration or other Stripe business logic, the underlying API request fails and the parent record is skipped.

During sync: Skipping **some_stripe_id**: Request **some_request_id**: Invalid subscription_item id: **some_item_id**

To Reproduce

  1. Create a subscription with proration
  2. Webhook / syncs fail due to invalid Subscription Item

Expected behavior Not sure how this should behave exactly, but it is causing a significant effect for us in terms of account provisioning when these seemingly special subscription items are present for prorated based line items

DJStripe Version 2.8.3

dustinblanchard commented 6 months ago

@jleclanche Any thoughts here? If there is an easy way to identify these special Subscription Items, @stanmattingly and I can potentially work on a PR.

jleclanche commented 6 months ago

Very difficult to say without access to the stripe account.

arnav13081994 commented 6 months ago

@stanmattingly What dj-stripe version are you on?

stanmattingly commented 6 months ago

@arnav13081994 We are on 2.8.3

arnav13081994 commented 5 months ago

@stanmattingly Not able to reproduce this issue. Please share the SubscriptionItem json if possible. It might be some case that we might have missed.

stanmattingly commented 5 months ago

@arnav13081994 sorry for the delayed response here. We just had another come through that is failing to sync. Here is what the item json looks like:

{ amount: -20807, amount_excluding_tax: -20807, currency: 'usd', description: 'Unused time on Basic after 26 Jan 2023', discount_amounts: [ { amount: 0, discount: 'di_1MUcN9BrbFy7JWmB6zU6YBv7' } ], discountable: False, discounts: [], id: 'il_1MUcN9BrbFy7JWmBGWNb4Zt9', invoice_item: 'ii_1MUcN9BrbFy7JWmBCYdLtzTl', object: 'line_item' }

In the same error stack, the api is returning that the invoice is not existent as well. This is 100% related to proration line items that stripe automatically generates.

mikedom commented 4 months ago

I'm getting a sync failure as well from any event that is attached to a customer that changed their subscription (so there is a proration amount). The error from dj-stripe is:

InvalidRequestError at /stripe/webhook/<wh_id>/
Request req_<req_id>: Invalid subscription_item id: si_<si_id>
Some of the data looks like:
       "object": "line_item",
            "amount": -917,
            "amount_excluding_tax": -917,
            "currency": "usd",
            "description": "Unused time on <product> after 11 Dec 2023",

I'm using version 2.8.3

dustinblanchard commented 4 months ago

@arnav13081994 @jleclanche

We've ended up monkey patching StripeModel._stripe_object_to_line_items to ignore any with "unused time" in the description. This feels like a hacky approach but we couldn't find anything else to key off of.

        @classmethod
        def presshook_stripe_object_to_line_items(
            cls, target_cls, data, invoice, api_key=djstripe_settings.STRIPE_SECRET_KEY
        ):
            lines = data.get("lines")
            if not lines:
                return []

            lineitems = []
            for line in lines.auto_paging_iter():
                # Skip line items that cause errors from proration
                if "unused time" in line.get("description", "").lower():
                    continue

                if invoice.id:
                    save = True
                    line.setdefault("invoice", invoice.id)

                else:
                    # Don't save invoice items for ephemeral invoices
                    save = False

                line.setdefault("customer", invoice.customer.id)
                line.setdefault("date", int(dateformat.format(invoice.created, "U")))

                item, _ = target_cls._get_or_create_from_stripe_object(
                    line, refetch=False, save=save, api_key=api_key
                )
                lineitems.append(item)

            return lineitems

        StripeModel._stripe_object_to_line_items = presshook_stripe_object_to_line_items
arnav13081994 commented 4 months ago

@dustinblanchard Turns out the root cause of this issue is that the SubscriptionItems referred in the JSON are actually deleted and hence the sync was failing as it should have. Raised a PR to just ignore such errors so at least the sync will no longer fail.