Skip to content

Commit

Permalink
feature get update remove card (#40)
Browse files Browse the repository at this point in the history
* tests merged and extended, one test skipped (not sure about the stripe api)

* explicit implementation of the stripe api

* In the middle of mocking stripe

* Added Validation Error

* CR fix

* Fix bug

* Updated logic, test, and fxed bug

* Comments and permissions update and tests update
  • Loading branch information
hash committed Jan 8, 2018
1 parent 0b9b0d4 commit 42b5c46
Show file tree
Hide file tree
Showing 7 changed files with 342 additions and 109 deletions.
24 changes: 21 additions & 3 deletions aa_stripe/api.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import simplejson as json
import stripe
from rest_framework import status
from rest_framework.generics import CreateAPIView, ListCreateAPIView, RetrieveAPIView, get_object_or_404
from rest_framework.generics import (CreateAPIView, ListCreateAPIView, RetrieveAPIView, RetrieveUpdateDestroyAPIView,
get_object_or_404)
from rest_framework.mixins import RetrieveModelMixin
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response

from aa_stripe.models import StripeCard, StripeCoupon, StripeCustomer, StripeWebhook
from aa_stripe.serializers import (StripeCardCreateSerializer, StripeCardListSerializer, StripeCouponSerializer,
StripeCustomerRetriveSerializer, StripeCustomerSerializer, StripeWebhookSerializer)
from aa_stripe.permissions import IsCardOwner
from aa_stripe.serializers import (StripeCardCreateSerializer, StripeCardListSerializer, StripeCardUpdateSerializer,
StripeCouponSerializer, StripeCustomerRetriveSerializer, StripeCustomerSerializer,
StripeWebhookSerializer)
from aa_stripe.settings import stripe_settings


Expand All @@ -30,6 +33,21 @@ def get_serializer_class(self):
return self.serializer_class


class StripeCardsDetailsAPI(RetrieveUpdateDestroyAPIView):
queryset = StripeCard.objects.all()
serializer_class = StripeCardListSerializer
permission_classes = (
IsAuthenticated,
IsCardOwner,
)
lookup_field = "stripe_card_id"

def get_serializer_class(self):
if hasattr(self, "request") and self.request.method in ("PATCH", "PUT"):
return StripeCardUpdateSerializer
return self.serializer_class


class CouponDetailsAPI(RetrieveAPIView):
queryset = StripeCoupon.objects.all()
serializer_class = StripeCouponSerializer
Expand Down
5 changes: 4 additions & 1 deletion aa_stripe/api_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
from django.conf.urls import url
from rest_framework.routers import DefaultRouter

from aa_stripe.api import CouponDetailsAPI, CustomersAPI, StripeCardsAPI, WebhookAPI
from aa_stripe.api import CouponDetailsAPI, CustomersAPI, StripeCardsAPI, StripeCardsDetailsAPI, WebhookAPI

urlpatterns = [
url(r"^aa-stripe/coupons/(?P<coupon_id>.*)$", CouponDetailsAPI.as_view(), name="stripe-coupon-details"),
url(r"^aa-stripe/customers$", CustomersAPI.as_view(), name="stripe-customers"),
url(r"^aa-stripe/customers/cards$", StripeCardsAPI.as_view(), name="stripe-customers-cards"),
url(r"^aa-stripe/customers/cards/(?P<stripe_card_id>.*)$",
StripeCardsDetailsAPI.as_view(),
name="stripe-customers-cards-details"),
url(r"^aa-stripe/webhooks$", WebhookAPI.as_view(), name="stripe-webhooks")
]

Expand Down
53 changes: 53 additions & 0 deletions aa_stripe/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,59 @@ def create_at_stripe(self):
self.save()
return self

def update_at_stripe(self, stripe_token, set_default):
is_default = self.customer.default_card.pk is self.pk
# When the card is not the default card we need to change
# the default_source attribute before setting the source attribute on customer
# because setting source attribute will overwrite the default_source
# and we do not allow unsetting the default card hence
# we overwrite set_default here if card is default
set_default = is_default if not set_default else set_default

stripe.api_key = stripe_settings.API_KEY

customer = stripe.Customer.retrieve(self.customer.stripe_customer_id)
# When a card is updated with setting source_token to source field on customer
# Stripe is genereating new card id for the card
new_card_id = None

if not is_default and set_default:
customer.default_source = self.stripe_card_id
customer.save()
customer.source = stripe_token
customer.save()
new_card_id = customer.default_source
else:
if is_default:
customer.source = stripe_token
customer.save()
new_card_id = customer.default_source
else:
original_default_source = customer.default_source
customer.default_source = self.stripe_card_id
customer.save()
customer.source = stripe_token
customer.save()
new_card_id = customer.default_source
customer.default_source = original_default_source
customer.save()

updated_card = customer.sources.retrieve(new_card_id)

self.stripe_card_id = updated_card["id"]
self.last4 = updated_card["last4"]
self.exp_month = updated_card["exp_month"]
self.exp_year = updated_card["exp_year"]
self.stripe_response = updated_card

self.save()

if not is_default and set_default:
self.customer.default_card = self
self.customer.save()

return self

def delete(self, *args, **kwargs):
card = self._retrieve_from_stripe(set_deleted=True)
if card:
Expand Down
9 changes: 9 additions & 0 deletions aa_stripe/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
from rest_framework.permissions import BasePermission

from aa_stripe.models import StripeCustomer


class IsCardOwner(BasePermission):
def has_object_permission(self, request, view, obj):
return obj.customer == StripeCustomer.get_latest_active_customer_for_user(request.user)
30 changes: 27 additions & 3 deletions aa_stripe/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import stripe
from rest_framework.exceptions import ValidationError
from rest_framework.serializers import JSONField, ModelSerializer
from rest_framework.serializers import BooleanField, CharField, JSONField, ModelSerializer

from aa_stripe.models import StripeCard, StripeCoupon, StripeCustomer, StripeWebhook

Expand All @@ -17,14 +17,38 @@ class Meta:
fields = ["stripe_card_id", "last4", "exp_month", "exp_year"]


class StripeCardUpdateSerializer(ModelSerializer):
stripe_token = CharField(write_only=True)
set_default = BooleanField(write_only=True, required=False)

def update(self, instance, validated_data):
if validated_data.get("stripe_token"):
try:
user = self.context["request"].user
stripe_token = validated_data.pop("stripe_token")
set_default = validated_data.pop("set_default", None)
return instance.update_at_stripe(stripe_token, set_default)
except stripe.StripeError as e:
logging.error(
"[AA-Stripe] updating card failed for user {user.id}: {error}".format(user=user, error=e)
)
raise ValidationError({"stripe_error": e._message})

return instance

class Meta:
model = StripeCard
fields = ["stripe_js_response", "stripe_token", "set_default"]


class StripeCardCreateSerializer(ModelSerializer):
stripe_js_response = JSONField()

def create(self, validated_data):
instance = None
if validated_data.get("stripe_js_response"):
try:
user = self.context['request'].user
user = self.context["request"].user
stripe_js_response = validated_data.pop("stripe_js_response")
customer = StripeCustomer.get_latest_active_customer_for_user(user)
instance = StripeCard.objects.create(
Expand Down Expand Up @@ -67,7 +91,7 @@ def create(self, validated_data):
if validated_data.get("stripe_js_response"):
# Create a Customer
try:
user = self.context['request'].user
user = self.context["request"].user
stripe_js_response = validated_data.pop("stripe_js_response")
instance = StripeCustomer.objects.create(user=user, stripe_js_response=stripe_js_response)
card_data = stripe_js_response["card"]
Expand Down

0 comments on commit 42b5c46

Please sign in to comment.