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

No such transfer po_* #2003

Closed patroqueeet closed 6 months ago

patroqueeet commented 6 months ago

Describe the bug

Every months first day, on web hook call I receive this exception:

ERROR 2023-11-01 02:21:38,929 log 2107788 140146881369984 Internal Server Error: /_stripe/webhook/***/
Traceback (most recent call last):
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/sentry_sdk/integrations/django/views.py", line 85, in sentry_wrapped_callback
    return callback(request, *args, **kwargs)
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/django/views/generic/base.py", line 104, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/django/utils/decorators.py", line 46, in _wrapper
    return bound_method(*args, **kwargs)
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 56, in wrapper_view
    return view_func(*args, **kwargs)
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/django/views/generic/base.py", line 143, in dispatch
    return handler(request, *args, **kwargs)
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/djstripe/views.py", line 47, in post
    trigger = WebhookEventTrigger.from_request(
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/djstripe/models/webhooks.py", line 268, in from_request
    raise e
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/djstripe/models/webhooks.py", line 249, in from_request
    obj.process(save=False, api_key=api_key)
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/djstripe/models/webhooks.py", line 363, in process
    self.event = Event.process(self.json_body, api_key=api_key)
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/djstripe/models/core.py", line 1654, in process
    ret.invoke_webhook_handlers()
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/djstripe/models/core.py", line 1666, in invoke_webhook_handlers
    webhooks.call_handlers(event=self)
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/djstripe/webhooks.py", line 98, in call_handlers
    handler_func(event=event)
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/djstripe/event_handlers.py", line 353, in other_object_webhook_handler
    _handle_crud_like_event(target_cls=target_cls, event=event)
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/djstripe/event_handlers.py", line 448, in _handle_crud_like_event
    data = target_cls(**kwargs).api_retrieve(
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/djstripe/models/base.py", line 202, in api_retrieve
    return self.stripe_class.retrieve(
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/stripe/api_resources/abstract/api_resource.py", line 12, in retrieve
    instance.refresh()
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/stripe/api_resources/abstract/api_resource.py", line 16, in refresh
    return self._request_and_refresh("get", self.instance_url())
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/stripe/api_resources/abstract/api_resource.py", line 90, in _request_and_refresh
    obj = StripeObject._request(
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/stripe/stripe_object.py", line 282, in _request
    response, api_key = requestor.request(method_, url_, params, headers)
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/stripe/api_requestor.py", line 122, in request
    resp = self.interpret_response(rbody, rcode, rheaders)
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/stripe/api_requestor.py", line 399, in interpret_response
    self.handle_error_response(rbody, rcode, resp.data, rheaders)
  File "/home/darg/darg/.ve/lib/python3.8/site-packages/stripe/api_requestor.py", line 159, in handle_error_response
    raise err
stripe.error.InvalidRequestError: Request req_Ah9ioeS1Odqn4Z: No such transfer: 'po_***'

Software versions

arnav13081994 commented 6 months ago

@patroqueeet Is this a webhook event on your account or on a connected account (yours is a platform account)?

patroqueeet commented 6 months ago

Plain account. No connected

Arnav Choudhury @.***> schrieb am Fr. 3. Nov. 2023 um 02:02:

@patroqueeet https://github.com/patroqueeet Is this a webhook event on your account or on a connected account (yours is a platform account)?

— Reply to this email directly, view it on GitHub https://github.com/dj-stripe/dj-stripe/issues/2003#issuecomment-1791762518, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAP2D7RCWYHDB3XKOLE27HDYCQ7DXAVCNFSM6AAAAAA6Y534GWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTOOJRG43DENJRHA . You are receiving this because you were mentioned.Message ID: @.***>

arnav13081994 commented 6 months ago

@patroqueeet What is your payout destination? And are you able to retrieve it using the stripe api? If so can you share the json?

patroqueeet commented 6 months ago

not exactly sure what command I should run. but when checking in stripe I found it is looking for transfers with a po_*** id, but when I past this ID into stripe search field, I get a payout obj, not a transfer obj. does this help?

Screenshot 2023-11-06 at 14 03 08
arnav13081994 commented 6 months ago

@patroqueeet Please go to Django admin and hunt the Event related to this failed event and copy-paste the entire object. You can do so by running the following in django shell as well:

from django.forms import model_to_dict
from djstripe.models import Event

evt = Event.objects.get(id="<ID_OF_FAILED_EVENT>")
print(model_to_dict(evt))
patroqueeet commented 6 months ago

@arnav13081994 that is not working, because the event is not saved to the local db because of the error described.

arnav13081994 commented 6 months ago

Please check in the webhook event triggers table for a failed event and then re-run the same command as mentioned below but for the WebhookEventTrigger object

patroqueeet commented 6 months ago

like that:

{
"id": "evt_1O7T7kBBirzkVpobVI9LZcHQ",
"object": "event",
"api_version": "2017-01-27",
"created": 1698801696,
"data": {
"object": {
"id": "po_1O7SohBBirzkVpobNNDVrry3",
"object": "transfer",
"amount": 1,
"amount_reversed": 0,
"application_fee": null,
"arrival_date": 1698796800,
"automatic": true,
"balance_transaction": "txn_1O7SohBBirzkVpobgrc2rTus",
"bank_account": {
"id": "ba_1LX1SjBBirzkVpob7bckjG4y",
"object": "bank_account",
"account_holder_name": null,
"account_holder_type": null,
"account_type": null,
"bank_name": "...",
"country": "DE",
"currency": "eur",
"fingerprint": "2bbLdAFgfBVs1iY3",
"last4": "1234",
"routing_number": "DE...",
"status": "new"
},
"created": 1698800515,
"currency": "eur",
"date": 1698796800,
"description": "STRIPE PAYOUT",
"destination": "ba_1LX1SjBBirzkVpob7bckjG4y",
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"livemode": true,
"metadata": {
},
"method": "standard",
"original_payout": null,
"recipient": null,
"reconciliation_status": "in_progress",
"reversals": {
"object": "list",
"data": [

],
"has_more": false,
"total_count": 0,
"url": "/v1/transfers/po_1O7SohBBirzkVpobNNDVrry3/reversals"
},
"reversed": false,
"reversed_by": null,
"source_transaction": null,
"source_type": "card",
"statement_descriptor": null,
"status": "in_transit",
"transfer_group": null,
"type": "bank_account"
}
},
"livemode": true,
"pending_webhooks": 2,
"request": null,
"type": "transfer.created"
}
arnav13081994 commented 6 months ago

@patroqueeet This is very interesting. I had no idea one could have a payout object id in a transfer object. Can you please run the following commands in the django shell and let me know if you get an error or a response. In case of a response, please share the JSON


import stripe
stripe.api_key="<YOUR_API_KEY>"

stripe.Payout.retrieve("po_1O7SohBBirzkVpobNNDVrry3")
stripe.Transfer.retrieve("po_1O7SohBBirzkVpobNNDVrry3")
patroqueeet commented 6 months ago
In [212]: stripe.Payout.retrieve("po_1O7SohBBirzkVpobNNDVrry3")
Out[212]:
<Payout transfer id=po_1O7SohBBirzkVpobNNDVrry3 at 0x7fad94c17a40> JSON: {
  "amount": 1,
  "amount_reversed": 0,
  "application_fee": null,
  "arrival_date": 1698796800,
  "automatic": true,
  "balance_transaction": "txn_1O7SohBBirzkVpobgrc2rTus",
  "bank_account": {
    "account_holder_name": null,
    "account_holder_type": null,
    "account_type": null,
    "bank_name": "...",
    "country": "DE",
    "currency": "eur",
    "fingerprint": "...",
    "id": "ba_1LX1SjBBirzkVpob7bckjG4y",
    "last4": "...",
    "object": "bank_account",
    "routing_number": "...",
    "status": "new"
  },
  "created": 1698800515,
  "currency": "eur",
  "date": 1698796800,
  "description": "STRIPE PAYOUT",
  "destination": "ba_1LX1SjBBirzkVpob7bckjG4y",
  "failure_balance_transaction": null,
  "failure_code": null,
  "failure_message": null,
  "id": "po_1O7SohBBirzkVpobNNDVrry3",
  "livemode": true,
  "metadata": {},
  "method": "standard",
  "object": "transfer",
  "original_payout": null,
  "recipient": null,
  "reconciliation_status": "completed",
  "reversals": {
    "data": [],
    "has_more": false,
    "object": "list",
    "total_count": 0,
    "url": "/v1/transfers/po_1O7SohBBirzkVpobNNDVrry3/reversals"
  },
  "reversed": false,
  "reversed_by": null,
  "source_transaction": null,
  "source_type": "card",
  "statement_descriptor": null,
  "status": "paid",
  "transfer_group": null,
  "type": "bank_account"
}

and

In [213]: stripe.Transfer.retrieve("po_1O7SohBBirzkVpobNNDVrry3")
<Transfer transfer id=po_1O7SohBBirzkVpobNNDVrry3 at 0x7fada425a130> JSON: {
  "amount": 1,
  "amount_reversed": 0,
  "application_fee": null,
  "arrival_date": 1698796800,
  "automatic": true,
  "balance_transaction": "txn_1O7SohBBirzkVpobgrc2rTus",
  "bank_account": {
    "account_holder_name": null,
    "account_holder_type": null,
    "account_type": null,
    "bank_name": "...",
    "country": "DE",
    "currency": "eur",
    "fingerprint": "...",
    "id": "ba_1LX1SjBBirzkVpob7bckjG4y",
    "last4": "...",
    "object": "bank_account",
    "routing_number": "...",
    "status": "new"
  },
  "created": 1698800515,
  "currency": "eur",
  "date": 1698796800,
  "description": "STRIPE PAYOUT",
  "destination": "ba_1LX1SjBBirzkVpob7bckjG4y",
  "failure_balance_transaction": null,
  "failure_code": null,
  "failure_message": null,
  "id": "po_1O7SohBBirzkVpobNNDVrry3",
  "livemode": true,
  "metadata": {},
  "method": "standard",
  "object": "transfer",
  "original_payout": null,
  "recipient": null,
  "reconciliation_status": "completed",
  "reversals": {
    "data": [],
    "has_more": false,
    "object": "list",
    "total_count": 0,
    "url": "/v1/transfers/po_1O7SohBBirzkVpobNNDVrry3/reversals"
  },
  "reversed": false,
  "reversed_by": null,
  "source_transaction": null,
  "source_type": "card",
  "statement_descriptor": null,
  "status": "paid",
  "transfer_group": null,
  "type": "bank_account"
}
arnav13081994 commented 6 months ago

not exactly sure what command I should run. but when checking in stripe I found it is looking for transfers with a po_*** id, but when I past this ID into stripe search field, I get a payout obj, not a transfer obj. does this help?

Screenshot 2023-11-06 at 14 03 08

@patroqueeet I do not understand how you got a response for stripe.Transfer.retrieve() when the screenshot shows that the object is not available. Or am I misunderstanding something?

Moreover looking at the JSON the fields are all from the Payout Object even when "object": "transfer" is mentioned in the JSON.

patroqueeet commented 6 months ago

Indeed I see your point. Still, the symptoms are as they are. Anything I can do to narrow down the source of the symptoms?

arnav13081994 commented 6 months ago

I see that you are using sentry. Can you tell me what all params are being passed to the api_retrieve call and what is self.stripe_class?

Also on this Stripe Account your api version is 2020-XXX.

patroqueeet commented 6 months ago
Screenshot 2023-11-15 at 13 04 02
arnav13081994 commented 6 months ago

Thanks. @patroqueeet Can you run stripe.Transfer.retrieve with all the params you see above like stripe_account, expand, stripe_version, api_key and id as is done here?

patroqueeet commented 6 months ago

learning: fails with version specified. succeeds with no version given. hence checked:

Screenshot 2023-11-16 at 09 10 58

what do you recommend from here? upgrade default to 2020 version?

And further checked. 2020 version is djstripe default (I did not override in settings). so means the webhook is not sending the default djstripe Stripe API version along? Do I read this like: stripe default setting is applied to web hook calls and conflicts with djstripe default setting? Just learned cannot upgrade djstripe default version to 2020. only latest is allowed. what now?

arnav13081994 commented 6 months ago

@patroqueeet Please check the api_version of the webhook endpoint that is receiving this event. It's visible in the admin. You can create a new webhook endpoint with the 2020 version

patroqueeet commented 6 months ago

now I did after finding api version was empty.

In [70]: whep = WebhookEndpoint.objects.get(pk=2)

In [71]: whep.api_version = '2020-08-27'

In [72]: whep.save()

But also found that inside Stripe each WH has an api version assigned. this is set to default 2017 in my case. so now my local model is not aligned with stripe :/ . trying to adjust to 2020 api version in Stripe is not possible, I can only select default or lastest version - will find out what it does now on Dec, 1st...

THX for instantly creating a PR to prevent similar in the future and THX for the great support. was a tricky issue, I assume...