<commit_before># -*- coding: utf-8 -*-
"""
.. module:: djstripe.event_handlers.

   :synopsis: dj-stripe - webhook event handlers for the various models

.. moduleauthor:: <NAME> (@wahuneke)
.. moduleauthor:: <NAME> (@akavanau)
.. moduleauthor:: <NAME> (@lskillen)

Stripe docs for Events: https://stripe.com/docs/api#events
Stripe docs for Webhooks: https://stripe.com/docs/webhooks

TODO: Implement webhook event handlers for all the models that need to respond to webhook events.

NOTE: Event data is not guaranteed to be in the correct API version format. See #116.
      When writing a webhook handler, make sure to first re-retrieve the object you wish to
      process.

"""
from __future__ import absolute_import, division, print_function, unicode_literals

import logging

from . import webhooks
from .enums import SourceType
from .models import Card, Charge, Coupon, Customer, Invoice, InvoiceItem, Plan, Subscription, Transfer
from .utils import convert_tstamp


logger = logging.getLogger(__name__)


@webhooks.handler_all
def customer_event_attach(event):
    """Make the related customer available on the event for all handlers to use.

    Does not create Customer objects.
    """
    event.customer = None
    crud_type = CrudType.determine(event=event, exact=True)

    if event.category == "customer" and crud_type.valid:
        customer_stripe_id = event.data.get("object", {}).get("id")
    else:
        customer_stripe_id = event.data.get("object", {}).get("customer")

    if customer_stripe_id:
        try:
            event.customer = Customer.objects.get(stripe_id=customer_stripe_id)
        except Customer.DoesNotExist:
            pass


@webhooks.handler("customer")
def customer_webhook_handler(event):
    """Handle updates to customer objects.

    First determines the crud_type and then handles the event if a customer exists locally.
    As customers are tied to local users, djstripe will not create customers that
    do not already exist locally.

    Docs and an example customer webhook response: https://stripe.com/docs/api#customer_object
    """
    if event.customer:
        # As customers are tied to local users, djstripe will not create
        # customers that do not already exist locally.
        _handle_crud_like_event(target_cls=Customer, event=event, crud_exact=True, crud_valid=True)


@webhooks.handler("customer.discount")
def customer_discount_webhook_handler(event):
    """Handle updates to customer discount objects.

    Docs: https://stripe.com/docs/api#discounts

    Because there is no concept of a "Discount" model in dj-stripe (due to the
    lack of a stripe id on them), this is a little different to the other
    handlers.
    """

    crud_type = CrudType.determine(event=event)
    discount_data = event.data.get("object", {})
    coupon_data = discount_data.get("coupon", {})

    if crud_type.created or crud_type.updated:
        coupon, _ = _handle_crud_like_event(
            target_cls=Coupon,
            event=event,
            data=coupon_data,
            stripe_id=coupon_data.get("id")
        )
        coupon_start = discount_data.get("start")
        coupon_end = discount_data.get("end")
    else:
        coupon = None
        coupon_start = None
        coupon_end = None

    event.customer.coupon = coupon
    event.customer.coupon_start = convert_tstamp(coupon_start)
    event.customer.coupon_end = convert_tstamp(coupon_end)
    event.customer.save()


@webhooks.handler("customer.source")
def customer_source_webhook_handler(event):
    """Handle updates to customer payment-source objects.

    Docs: https://stripe.com/docs/api#customer_object-sources.
    """
    customer_data = event.data.get("object", {})
    source_type = customer_data.get("object", {})

    # TODO: handle other types of sources (https://stripe.com/docs/api#customer_object-sources)
    if source_type == SourceType.card:
        _handle_crud_like_event(target_cls=Card, event=event)


@webhooks.handler("customer.subscription")
def customer_subscription_webhook_handler(event):
    """Handle updates to customer subscription objects.

    Docs an example subscription webhook response: https://stripe.com/docs/api#subscription_object
    """
    _handle_crud_like_event(target_cls=Subscription, event=event)


@webhooks.handler("transfer", "charge", "coupon", "invoice", "invoiceitem", "plan")
def other_object_webhook_handler(event):
    """Handle updates to transfer, charge, invoice, invoiceitem and plan objects.

    Docs for:
    - charge: https://stripe.com/docs/api#charges
    - coupon: https://stripe.com/docs/api#coupons<commit_msg>Work around broken customer.source.deleted webhook handling<commit_after># -*- coding: utf-8 -*-
"""
.. module:: djstripe.event_handlers.

   :synopsis: dj-stripe - webhook event handlers for the various models

.. moduleauthor:: <NAME> (@wahuneke)
.. moduleauthor:: <NAME> (@akavanau)
.. moduleauthor:: <NAME> (@lskillen)

Stripe docs for Events: https://stripe.com/docs/api#events
Stripe docs for Webhooks: https://stripe.com/docs/webhooks

TODO: Implement webhook event handlers for all the models that need to respond to webhook events.

NOTE: Event data is not guaranteed to be in the correct API version format. See #116.
      When writing a webhook handler, make sure to first re-retrieve the object you wish to
      process.

"""
from __future__ import absolute_import, division, print_function, unicode_literals

import logging

from . import webhooks
from .enums import SourceType
from .models import Card, Charge, Coupon, Customer, Invoice, InvoiceItem, PaymentMethod, Plan, Subscription, Transfer
from .utils import convert_tstamp


logger = logging.getLogger(__name__)


@webhooks.handler_all
def customer_event_attach(event):
    """Make the related customer available on the event for all handlers to use.

    Does not create Customer objects.
    """
    event.customer = None
    crud_type = CrudType.determine(event=event, exact=True)

    if event.category == "customer" and crud_type.valid:
        customer_stripe_id = event.data.get("object", {}).get("id")
    else:
        customer_stripe_id = event.data.get("object", {}).get("customer")

    if customer_stripe_id:
        try:
            event.customer = Customer.objects.get(stripe_id=customer_stripe_id)
        except Customer.DoesNotExist:
            pass


@webhooks.handler("customer")
def customer_webhook_handler(event):
    """Handle updates to customer objects.

    First determines the crud_type and then handles the event if a customer exists locally.
    As customers are tied to local users, djstripe will not create customers that
    do not already exist locally.

    Docs and an example customer webhook response: https://stripe.com/docs/api#customer_object
    """
    if event.customer:
        # As customers are tied to local users, djstripe will not create
        # customers that do not already exist locally.
        _handle_crud_like_event(target_cls=Customer, event=event, crud_exact=True, crud_valid=True)


@webhooks.handler("customer.discount")
def customer_discount_webhook_handler(event):
    """Handle updates to customer discount objects.

    Docs: https://stripe.com/docs/api#discounts

    Because there is no concept of a "Discount" model in dj-stripe (due to the
    lack of a stripe id on them), this is a little different to the other
    handlers.
    """

    crud_type = CrudType.determine(event=event)
    discount_data = event.data.get("object", {})
    coupon_data = discount_data.get("coupon", {})

    if crud_type.created or crud_type.updated:
        coupon, _ = _handle_crud_like_event(
            target_cls=Coupon,
            event=event,
            data=coupon_data,
            stripe_id=coupon_data.get("id")
        )
        coupon_start = discount_data.get("start")
        coupon_end = discount_data.get("end")
    else:
        coupon = None
        coupon_start = None
        coupon_end = None

    event.customer.coupon = coupon
    event.customer.coupon_start = convert_tstamp(coupon_start)
    event.customer.coupon_end = convert_tstamp(coupon_end)
    event.customer.save()


@webhooks.handler("customer.source")
def customer_source_webhook_handler(event):
    """Handle updates to customer payment-source objects.

    Docs: https://stripe.com/docs/api#customer_object-sources.
    """
    customer_data = event.data.get("object", {})
    source_type = customer_data.get("object", {})

    # TODO: handle other types of sources (https://stripe.com/docs/api#customer_object-sources)
    if source_type == SourceType.card:
        if event.verb.endswith("deleted") and customer_data:
            # On customer.source.deleted, we do not delete the object, we merely unlink it.
            # customer = Customer.objects.get(stripe_id=customer_data["id"])
            # NOTE: for now, customer.sources still points to Card
            # Also, https://github.com/dj-stripe/dj-stripe/issues/576
            Card.objects.filter(stripe_id=customer_data.get("id", "")).delete()
            PaymentMethod.objects.filter(id=customer_data.get("id", "")).delete()
        else:
            _handle_crud_like_event(target_cls=Card, event=event)


@webhooks.handler("customer.subscription")
def customer_subscription_webhook_handler(event):
    """Handle updates to customer subscription objects.

    Docs an example subscription webhook response: https://stripe.com/docs/api#subscription_object
    """
    _handle_crud_like_event(target_cls=Subscription, event=event)


@webhooks.handler("transfer", "charge", "coupon", "invoice", "invoiceitem", "plan")
def other_object_webhook_handler(event):
    """Handle updates to transfer, charge, invoice, invoiceitem and plan objects.

    Docs for:
    - charge: https://stripe.com/docs/api#charges
    - coupon: https://stripe.com/docs/api#coupons
{'fraction_non_alphanumeric': 0.10243407707910751, 'fraction_numerical': 0.0011156186612576065, 'mean_word_length': 3.6339285714285716, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 19, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3797800', 'n_tokens_mistral': 2970, 'n_tokens_neox': 2965, 'n_words': 792}