Как обрабатывать файл customer.subscription.created, если организация (клиент) еще не существует в Stripe webhook?

Я использую Django и Stripe для мультитенантной SaaS-системы. Я регистрирую пользователей и организации вручную из view и создаю клиента и подписку с помощью Stripe API перед сохранением в базе данных.

Вот мой веб-сайт:

class StripeWebhookView(APIView):
    permission_classes = [AllowAny]

    def post(self, request, *args, **kwargs):
        payload = request.body
        sig_header = request.headers.get('stripe-signature')

        try:
            event = stripe.Webhook.construct_event(
                payload=payload,
                sig_header=sig_header,
                secret=settings.STRIPE_SIGNING_SECRET
            )
        except Exception:
            return Response(status=400)

        event_type = event['type']
        data_object = event['data']['object']

        if event_type == 'invoice.payment_succeeded':
            billing_reason = data_object.get('billing_reason')
            organization = OrganizationModel.objects.get(
                stripe_customer_id='cus_Sr7NCr3urlBdws'
            )
            if billing_reason == 'subscription_create':
                print(data_object)

        elif event_type == 'customer.subscription.created':
            subscription = data_object
            customer_id = subscription.get("customer")

            try:
                organization = OrganizationModel.objects.get(
                    stripe_customer_id=customer_id
                )
                # handle subscription creation logic here
            except OrganizationModel.DoesNotExist:
                # I don't know what to do here if my tenant doesn't exist yet

        return Response(status=200)

Моя проблема

Иногда webhook customer.subscription.created поступает до того, как организация (и схема клиента) будет создана в базе данных.

Поскольку Stripe не гарантирует порядок в webhook, это нарушает логику. Событие не может быть обработано, поскольку организации еще нет. Если я отвечу 200 "ОК", Stripe не будет повторять попытку. Если я верну значение 400, Stripe повторит попытку слишком много раз, и это все равно может не сработать, пока организация не будет создана.

Мой вопрос

В соответствии с рекомендациями Stripe:

  • Как я должен безопасно обрабатывать такие события, как customer.subscription.created, когда клиент/организация, возможно, еще не существует?
  • Рекомендуется ли временно сохранять события (в таблице, подобной WebhookEventModel), и обрабатывать их позже, как только клиент будет создан?
  • Или есть лучший способ отложить обработку этих событий?
  • Как команды обычно справляются с такими условиями гонки?

Заранее благодарю.

Первое предложение

Я просмотрел функцию post в вашем TestRegisterView несколько раз, и мне все еще непонятно, почему вам нужно отложить создание организации до тех пор, пока вы не создадите клиента и подписку с помощью Stripe SDK.

Итак, моим первым предложением было бы отложить создание любых объектов Stripe до тех пор, пока после необходимые объекты не будут созданы в вашей базе данных. Я предполагаю, что у вас есть на то причина, и предлагаю другое предложение, но я думаю, вам следует серьезно подумать над этим. Возможно, это более элегантный способ, которым вы сейчас это делаете, но я все же думаю, что вам следует отложить создание клиентов и подписки.

Второе предложение

Другой подход, который может решить эту проблему, заключается в добавлении Сельдерея в ваш проект. В любом случае, наличие асинхронной очереди задач для обработки веб-запросов Stripe - хорошая идея, особенно по мере роста вашей базы данных и увеличения времени выполнения запросов.

В частности, в @shared_task декораторе есть функция, которая может здесь сработать. Когда вы оформляете функцию задачи, вы можете указать количество повторных попыток и использовать стратегию отмены. В вашем случае вы могли бы указать повторные попытки с экспоненциальной задержкой. Это позволило бы выполнить обработку полезной нагрузки события webhook с ошибкой один раз (или более), подождать некоторое время, в течение которого организация будет создана, а затем повторить операцию.

Такой декоратор может выглядеть следующим образом

@app.task(autoretry_for=(OrganizationModel.DoesNotExist,), retry_backoff=True)
def task:
    # your code for handling the webhook event goes here

просто напишите логику опроса для вашего приложения, возможно, планируя опрос каждые 30 минут для всех вызовов stripe api

Вернуться на верх