Skip to content

Commit

Permalink
Changes from PR #1909
Browse files Browse the repository at this point in the history
  • Loading branch information
arnav13081994 committed Apr 20, 2023
1 parent 206f125 commit fb8750b
Show file tree
Hide file tree
Showing 20 changed files with 579 additions and 161 deletions.
2 changes: 1 addition & 1 deletion djstripe/admin/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ def _post_clean(self):
url=url,
description=self.cleaned_data.get("description"),
enabled_events=self.cleaned_data.get("enabled_events"),
metadata=self.cleaned_data.get("metadata"),
metadata=self.cleaned_data.get("metadata", {}),
disabled=(not self.cleaned_data.get("enabled")),
)
except InvalidRequestError as e:
Expand Down
52 changes: 46 additions & 6 deletions djstripe/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from datetime import timedelta
from typing import Dict, List, Optional, Type

from django.core.exceptions import FieldDoesNotExist
from django.db import IntegrityError, models, transaction
from django.utils import dateformat, timezone
from stripe.api_resources.abstract.api_resource import APIResource
Expand Down Expand Up @@ -204,20 +205,49 @@ def api_retrieve(self, api_key=None, stripe_account=None):
)

@classmethod
def _api_create(cls, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs):
def get_or_create_idempotency_key(cls, action, idempotency_key):
"""
Creates and returns an idempotency_key if not given.
"""
# Prefer passed in idempotency_key.
if not idempotency_key:
# Create idempotency_key
idempotency_key = djstripe_settings.create_idempotency_key(
object_type=cls.__name__.lower(),
action=action,
livemode=djstripe_settings.STRIPE_LIVE_MODE,
)

return idempotency_key

@classmethod
def _api_create(
cls, idempotency_key=None, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs
):
"""
Call the stripe API's create operation for this model.
:param api_key: The api key to use for this request. \
Defaults to djstripe_settings.STRIPE_SECRET_KEY.
:type api_key: string
"""
with transaction.atomic():
# Get or Create idempotency_key
idempotency_key = cls.get_or_create_idempotency_key(
action="create", idempotency_key=idempotency_key
)

return cls.stripe_class.create(
api_key=api_key,
stripe_version=djstripe_settings.STRIPE_API_VERSION,
**kwargs,
)
stripe_obj = cls.stripe_class.create(
api_key=api_key,
idempotency_key=idempotency_key,
stripe_version=djstripe_settings.STRIPE_API_VERSION,
**kwargs,
)

# Update the action of the idempotency_key by appending stripe_obj.id to it
IdempotencyKey.update_action_field(idempotency_key, stripe_obj)

return stripe_obj

def _api_delete(self, api_key=None, stripe_account=None, **kwargs):
"""
Expand Down Expand Up @@ -1087,3 +1117,13 @@ def __str__(self):
@property
def is_expired(self) -> bool:
return timezone.now() > self.created + timedelta(hours=24)

@staticmethod
def update_action_field(uuid, stripe_obj):
# Update the action of the idempotency_key by appending stripe_obj.id to it
idempotency_key_object_qs = IdempotencyKey.objects.filter(uuid=uuid)
if idempotency_key_object_qs.exists():
idempotency_key_object = idempotency_key_object_qs.get()
if idempotency_key_object.action.split(":")[-1] == "":
idempotency_key_object.action += stripe_obj["id"]
idempotency_key_object.save()
78 changes: 54 additions & 24 deletions djstripe/models/billing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Optional, Union

import stripe
from django.db import models
from django.db import models, transaction
from django.utils import timezone
from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy as _
Expand All @@ -26,7 +26,7 @@
from ..managers import SubscriptionManager
from ..settings import djstripe_settings
from ..utils import QuerySetMock, get_friendly_currency_amount, get_id_from_stripe_data
from .base import StripeModel
from .base import IdempotencyKey, StripeModel
from .core import Customer

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -2385,24 +2385,41 @@ class Meta(StripeModel.Meta):
verbose_name = "Tax ID"

@classmethod
def _api_create(cls, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs):
def _api_create(
cls, idempotency_key=None, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs
):
"""
Call the stripe API's create operation for this model.
:param api_key: The api key to use for this request. \
Defaults to djstripe_settings.STRIPE_SECRET_KEY.
:type api_key: string
"""
with transaction.atomic():
# Get or Create idempotency_key
idempotency_key = cls.get_or_create_idempotency_key(
action="create", idempotency_key=idempotency_key
)

if not kwargs.get("id"):
raise KeyError("Customer Object ID is missing")
if not kwargs.get("id"):
raise KeyError("Customer Object ID is missing")

try:
Customer.objects.get(id=kwargs["id"])
except Customer.DoesNotExist:
raise
try:
Customer.objects.get(id=kwargs["id"])
except Customer.DoesNotExist:
raise

return stripe.Customer.create_tax_id(api_key=api_key, **kwargs)
stripe_obj = stripe.Customer.create_tax_id(
api_key=api_key,
idempotency_key=idempotency_key,
stripe_version=djstripe_settings.STRIPE_API_VERSION,
**kwargs,
)

# Update the action of the idempotency_key by appending stripe_obj.id to it
IdempotencyKey.update_action_field(idempotency_key, stripe_obj)

return stripe_obj

def api_retrieve(self, api_key=None, stripe_account=None):
"""
Expand Down Expand Up @@ -2551,32 +2568,45 @@ def __str__(self):
return f"Usage for {self.subscription_item} ({self.action}) is {self.quantity}"

@classmethod
def _api_create(cls, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs):
def _api_create(
cls, idempotency_key=None, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs
):
"""
Call the stripe API's create operation for this model.
:param api_key: The api key to use for this request. \
Defaults to djstripe_settings.STRIPE_SECRET_KEY.
:type api_key: string
"""
with transaction.atomic():
# Get or Create idempotency_key
idempotency_key = cls.get_or_create_idempotency_key(
action="create", idempotency_key=idempotency_key
)

if not kwargs.get("id"):
raise KeyError("SubscriptionItem Object ID is missing")
if not kwargs.get("id"):
raise KeyError("SubscriptionItem Object ID is missing")

try:
SubscriptionItem.objects.get(id=kwargs["id"])
except SubscriptionItem.DoesNotExist:
raise
try:
SubscriptionItem.objects.get(id=kwargs["id"])
except SubscriptionItem.DoesNotExist:
raise

usage_stripe_data = stripe.SubscriptionItem.create_usage_record(
api_key=api_key, **kwargs
)
usage_stripe_data = stripe.SubscriptionItem.create_usage_record(
api_key=api_key,
idempotency_key=idempotency_key,
stripe_version=djstripe_settings.STRIPE_API_VERSION,
**kwargs,
)

# Update the action of the idempotency_key by appending usage_stripe_data.id to it
IdempotencyKey.update_action_field(idempotency_key, usage_stripe_data)

# ! Hack: there is no way to retrieve a UsageRecord object from Stripe,
# ! which is why we create and sync it right here
cls.sync_from_stripe_data(usage_stripe_data, api_key=api_key)
# ! Hack: there is no way to retrieve a UsageRecord object from Stripe,
# ! which is why we create and sync it right here
cls.sync_from_stripe_data(usage_stripe_data, api_key=api_key)

return usage_stripe_data
return usage_stripe_data

@classmethod
def create(cls, **kwargs):
Expand Down
47 changes: 30 additions & 17 deletions djstripe/models/connect.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import stripe
from django.db import models
from django.db import models, transaction

from djstripe.utils import get_friendly_currency_amount

Expand All @@ -15,7 +15,7 @@
)
from ..managers import TransferManager
from ..settings import djstripe_settings
from .base import StripeBaseModel, StripeModel
from .base import IdempotencyKey, StripeBaseModel, StripeModel


# TODO Implement Full Webhook event support for ApplicationFee and ApplicationFee Refund Objects
Expand Down Expand Up @@ -316,27 +316,40 @@ def __str__(self):
return str(self.transfer)

@classmethod
def _api_create(cls, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs):
def _api_create(
cls, idempotency_key=None, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs
):
"""
Call the stripe API's create operation for this model.
:param api_key: The api key to use for this request. \
Defaults to djstripe_settings.STRIPE_SECRET_KEY.
:type api_key: string
"""

if not kwargs.get("id"):
raise KeyError("Transfer Object ID is missing")

try:
Transfer.objects.get(id=kwargs["id"])
except Transfer.DoesNotExist:
raise

return stripe.Transfer.create_reversal(
api_key=api_key,
stripe_version=djstripe_settings.STRIPE_API_VERSION,
**kwargs,
)
with transaction.atomic():
# Get or Create idempotency_key
idempotency_key = cls.get_or_create_idempotency_key(
action="create", idempotency_key=idempotency_key
)

if not kwargs.get("id"):
raise KeyError("Transfer Object ID is missing")

try:
Transfer.objects.get(id=kwargs["id"])
except Transfer.DoesNotExist:
raise

stripe_obj = stripe.Transfer.create_reversal(
api_key=api_key,
idempotency_key=idempotency_key,
stripe_version=djstripe_settings.STRIPE_API_VERSION,
**kwargs,
)

# Update the action of the idempotency_key by appending stripe_obj.id to it
IdempotencyKey.update_action_field(idempotency_key, stripe_obj)

return stripe_obj

def api_retrieve(self, api_key=None, stripe_account=None):
"""
Expand Down
23 changes: 11 additions & 12 deletions djstripe/models/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from ..settings import djstripe_settings
from ..signals import WEBHOOK_SIGNALS
from ..utils import get_friendly_currency_amount, get_id_from_stripe_data
from .base import IdempotencyKey, StripeModel, logger
from .base import StripeModel, logger


def _sanitise_price(price=None, plan=None, **kwargs):
Expand Down Expand Up @@ -403,6 +403,7 @@ def refund(
reason: str = None,
api_key: str = None,
stripe_account: str = None,
idempotency_key: str = None,
) -> "Refund":
"""
Initiate a refund. Returns the refund object.
Expand All @@ -426,6 +427,7 @@ def refund(
reason=reason,
api_key=api_key or self.default_api_key,
stripe_account=stripe_account,
idempotency_key=idempotency_key,
)

return Refund.sync_from_stripe_data(
Expand Down Expand Up @@ -833,7 +835,7 @@ def get_or_create(
return cls.objects.get(subscriber=subscriber, livemode=livemode), False
except cls.DoesNotExist:
action = f"create:{subscriber.pk}"
idempotency_key = djstripe_settings.get_idempotency_key(
idempotency_key = djstripe_settings.create_idempotency_key(
"customer", action, livemode
)
return (
Expand Down Expand Up @@ -983,8 +985,9 @@ def add_invoice_item(
description=None,
discountable=None,
invoice=None,
metadata=None,
metadata={},
subscription=None,
idempotency_key=None,
):
"""
Adds an arbitrary charge or credit to the customer's upcoming invoice.
Expand Down Expand Up @@ -1047,6 +1050,7 @@ def add_invoice_item(
invoice=invoice,
metadata=metadata,
subscription=subscription,
idempotency_key=idempotency_key,
)

return InvoiceItem.sync_from_stripe_data(
Expand Down Expand Up @@ -1134,13 +1138,6 @@ def purge(self):
# deleted upstream in Stripe
self.deleted = True

if self.subscriber:
# Delete the idempotency key used by Customer.create()
# So re-creating a customer for this subscriber before the key expires
# doesn't return the older Customer data
idempotency_key_action = f"customer:create:{self.subscriber.pk}"
IdempotencyKey.objects.filter(action=idempotency_key_action).delete()

self.subscriber = None

# Remove sources
Expand Down Expand Up @@ -1237,7 +1234,7 @@ def subscription(self):
else:
return subscriptions.first()

def send_invoice(self):
def send_invoice(self, idempotency_key=None):
"""
Pay and send the customer's latest invoice.
Expand All @@ -1247,7 +1244,9 @@ def send_invoice(self):
from .billing import Invoice

try:
invoice = Invoice._api_create(customer=self.id)
invoice = Invoice._api_create(
customer=self.id, idempotency_key=idempotency_key
)
invoice.pay()
return True
except InvalidRequestError: # TODO: Check this for a more
Expand Down

0 comments on commit fb8750b

Please sign in to comment.