forked from uncloud/uncloud
implement credit card listing
This commit is contained in:
parent
e2c4a19049
commit
e225bf1cc0
12 changed files with 183 additions and 63 deletions
|
@ -373,6 +373,10 @@ Q vpn-2a0ae5c1200.ungleich.ch
|
||||||
*** 1.1 (cleanup 1)
|
*** 1.1 (cleanup 1)
|
||||||
**** TODO [#C] Unify ValidationError, FieldError - define proper Exception
|
**** TODO [#C] Unify ValidationError, FieldError - define proper Exception
|
||||||
- What do we use for model errors
|
- What do we use for model errors
|
||||||
|
**** TODO [#C] Cleanup the results handling in celery
|
||||||
|
- Remove the results broker?
|
||||||
|
- Setup app to ignore results?
|
||||||
|
- Actually use results?
|
||||||
*** 1.0 (initial release)
|
*** 1.0 (initial release)
|
||||||
**** TODO [#C] Initial Generic product support
|
**** TODO [#C] Initial Generic product support
|
||||||
- Product
|
- Product
|
||||||
|
|
|
@ -7,8 +7,13 @@
|
||||||
Welcome to uncloud, checkout the following locations:
|
Welcome to uncloud, checkout the following locations:
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/api/">The API</a>
|
<li><a href="{% url 'api-root' %}">The uncloud API</a>
|
||||||
<li><a href="/cc/reg/">The CC registration</a>
|
<li><a href="{% url 'cc_register' %}">Register a credit card</a>
|
||||||
|
(this is required to be done via Javascript so that we never see
|
||||||
|
your credit card, but it is sent directly to stripe)
|
||||||
|
|
||||||
|
You can list your credit card via the API.
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -72,10 +72,12 @@ router.register(r'v1/user/register', authviews.AccountManagementViewSet, basenam
|
||||||
router.register(r'v2/net/wireguardvpn', netviews.WireGuardVPNViewSet, basename='wireguardvpnnetwork')
|
router.register(r'v2/net/wireguardvpn', netviews.WireGuardVPNViewSet, basename='wireguardvpnnetwork')
|
||||||
router.register(r'v2/net/wireguardvpnsizes', netviews.WireGuardVPNSizes, basename='wireguardvpnnetworksizes')
|
router.register(r'v2/net/wireguardvpnsizes', netviews.WireGuardVPNSizes, basename='wireguardvpnnetworksizes')
|
||||||
|
|
||||||
|
# Payment related
|
||||||
|
router.register(r'v2/payment/credit-card', payviews.CreditCardViewSet, basename='credit-card')
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(r'api/', include(router.urls)),
|
path(r'api/', include(router.urls), name='api'),
|
||||||
|
|
||||||
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), # for login to REST API
|
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), # for login to REST API
|
||||||
path('openapi', get_schema_view(
|
path('openapi', get_schema_view(
|
||||||
|
@ -92,8 +94,6 @@ urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
|
|
||||||
path('cc/reg/', payviews.RegisterCard.as_view(), name="cc_register"),
|
path('cc/reg/', payviews.RegisterCard.as_view(), name="cc_register"),
|
||||||
path('cc/list/', payviews.ListCards.as_view(), name="cc_list"),
|
|
||||||
path('cc/delete/<payment_method_id>', payviews.DeleteCard.as_view(), name="cc_delete"),
|
|
||||||
|
|
||||||
path('', uncloudviews.UncloudIndex.as_view(), name="uncloudindex"),
|
path('', uncloudviews.UncloudIndex.as_view(), name="uncloudindex"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.contrib.auth import get_user_model
|
||||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
from django.core.exceptions import FieldError, ValidationError
|
from django.core.exceptions import FieldError, ValidationError
|
||||||
|
|
||||||
from uncloud_pay.models import Order
|
from uncloud_pay.models import Order, Product
|
||||||
|
|
||||||
class WireGuardVPNPool(models.Model):
|
class WireGuardVPNPool(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -123,6 +123,19 @@ class WireGuardVPN(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.address} ({self.pool_index})"
|
return f"{self.address} ({self.pool_index})"
|
||||||
|
|
||||||
|
def create_product(self):
|
||||||
|
"""
|
||||||
|
Ensure we have a product for the WireguardVPN
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Product.objects.get_or_create(
|
||||||
|
# name="WireGuardVPN",
|
||||||
|
# description="Wireguard VPN",
|
||||||
|
# currency=Currency.CHF,
|
||||||
|
# config=
|
||||||
|
|
||||||
|
|
||||||
class WireGuardVPNFreeLeases(models.Model):
|
class WireGuardVPNFreeLeases(models.Model):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -4,9 +4,24 @@ from .models import *
|
||||||
from .selectors import *
|
from .selectors import *
|
||||||
from .tasks import *
|
from .tasks import *
|
||||||
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create_wireguard_vpn(owner, public_key, network_mask):
|
def create_wireguard_vpn(owner, public_key, network_mask):
|
||||||
|
# Check if the user has a membership.
|
||||||
|
#------------------------------------
|
||||||
|
# If yes, user is eligible for API access and 2 VPNs
|
||||||
|
# If user already has 2 VPNs, we deduct from the credit
|
||||||
|
# If deduction is higher than the allowed credit, we fail
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Check if the user has suitable balance
|
||||||
|
# Create order
|
||||||
|
#
|
||||||
|
return create_wireguard_vpn_tech(owner, public_key, network_mask)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def create_wireguard_vpn_tech(owner, public_key, network_mask):
|
||||||
pool = get_suitable_pools(network_mask)[0]
|
pool = get_suitable_pools(network_mask)[0]
|
||||||
count = pool.wireguardvpn_set.count()
|
count = pool.wireguardvpn_set.count()
|
||||||
|
|
||||||
|
|
|
@ -88,5 +88,5 @@ admin.site.register(Bill, BillAdmin)
|
||||||
admin.site.register(ProductToRecurringPeriod)
|
admin.site.register(ProductToRecurringPeriod)
|
||||||
admin.site.register(Product, ProductAdmin)
|
admin.site.register(Product, ProductAdmin)
|
||||||
|
|
||||||
for m in [ Order, BillRecord, BillingAddress, RecurringPeriod, VATRate, StripeCustomer ]:
|
for m in [ Order, BillRecord, BillingAddress, RecurringPeriod, VATRate, StripeCustomer, StripeCreditCard ]:
|
||||||
admin.site.register(m)
|
admin.site.register(m)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 3.1 on 2020-12-13 10:38
|
# Generated by Django 3.1 on 2020-12-28 22:19
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
|
@ -83,6 +83,18 @@ class Migration(migrations.Migration):
|
||||||
('description', models.TextField(blank=True, default='')),
|
('description', models.TextField(blank=True, default='')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='StripeCreditCard',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('card_name', models.CharField(default='My credit card', max_length=128)),
|
||||||
|
('card_id', models.CharField(max_length=32)),
|
||||||
|
('last4', models.CharField(max_length=4)),
|
||||||
|
('brand', models.CharField(max_length=64)),
|
||||||
|
('expiry_date', models.DateField()),
|
||||||
|
('owner', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ProductToRecurringPeriod',
|
name='ProductToRecurringPeriod',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -140,6 +152,15 @@ class Migration(migrations.Migration):
|
||||||
('replaces', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='replaced_by', to='uncloud_pay.order')),
|
('replaces', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='replaced_by', to='uncloud_pay.order')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Membership',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('starting_date', models.DateField(blank=True, null=True)),
|
||||||
|
('ending_date', models.DateField(blank=True, null=True)),
|
||||||
|
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='BillRecord',
|
name='BillRecord',
|
||||||
fields=[
|
fields=[
|
||||||
|
|
|
@ -17,7 +17,6 @@ from django.utils import timezone
|
||||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
import uncloud_pay.stripe
|
|
||||||
from uncloud import AMOUNT_DECIMALS, AMOUNT_MAX_DIGITS
|
from uncloud import AMOUNT_DECIMALS, AMOUNT_MAX_DIGITS
|
||||||
from uncloud.models import UncloudAddress
|
from uncloud.models import UncloudAddress
|
||||||
|
|
||||||
|
@ -92,6 +91,21 @@ class StripeCustomer(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.owner.username
|
return self.owner.username
|
||||||
|
|
||||||
|
|
||||||
|
class StripeCreditCard(models.Model):
|
||||||
|
owner = models.OneToOneField( get_user_model(),
|
||||||
|
on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
card_name = models.CharField(null=False, max_length=128, default="My credit card")
|
||||||
|
card_id = models.CharField(null=False, max_length=32)
|
||||||
|
last4 = models.CharField(null=False, max_length=4)
|
||||||
|
brand = models.CharField(null=False, max_length=64)
|
||||||
|
expiry_date = models.DateField(null=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.card_name}: {self.brand} {self.last4} ({self.expiry_date})"
|
||||||
|
|
||||||
|
|
||||||
###
|
###
|
||||||
# Payments and Payment Methods.
|
# Payments and Payment Methods.
|
||||||
|
|
||||||
|
@ -148,14 +162,14 @@ class PaymentMethod(models.Model):
|
||||||
stripe_payment_method_id = models.CharField(max_length=32, blank=True, null=True)
|
stripe_payment_method_id = models.CharField(max_length=32, blank=True, null=True)
|
||||||
stripe_setup_intent_id = models.CharField(max_length=32, blank=True, null=True)
|
stripe_setup_intent_id = models.CharField(max_length=32, blank=True, null=True)
|
||||||
|
|
||||||
@property
|
# @property
|
||||||
def stripe_card_last4(self):
|
# def stripe_card_last4(self):
|
||||||
if self.source == 'stripe' and self.active:
|
# if self.source == 'stripe' and self.active:
|
||||||
payment_method = uncloud_pay.stripe.get_payment_method(
|
# payment_method = uncloud_pay.stripe.get_payment_method(
|
||||||
self.stripe_payment_method_id)
|
# self.stripe_payment_method_id)
|
||||||
return payment_method.card.last4
|
# return payment_method.card.last4
|
||||||
else:
|
# else:
|
||||||
return None
|
# return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def active(self):
|
def active(self):
|
||||||
|
@ -1261,3 +1275,24 @@ class ProductToRecurringPeriod(models.Model):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.product} - {self.recurring_period} (default: {self.is_default})"
|
return f"{self.product} - {self.recurring_period} (default: {self.is_default})"
|
||||||
|
|
||||||
|
|
||||||
|
class Membership(models.Model):
|
||||||
|
owner = models.ForeignKey(get_user_model(),
|
||||||
|
on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
starting_date = models.DateField(blank=True, null=True)
|
||||||
|
ending_date = models.DateField(blank=True, null=True)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def user_has_membership(user, when):
|
||||||
|
"""
|
||||||
|
Return true if user has membership at a point of time,
|
||||||
|
return false if that is not the case
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# cls.objects.filter(owner=user,
|
||||||
|
# starting_date)
|
||||||
|
|
|
@ -6,13 +6,20 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
###
|
###
|
||||||
# Checked code
|
# 2020-12 Checked code
|
||||||
|
|
||||||
|
|
||||||
|
class StripeCreditCardSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = StripeCreditCard
|
||||||
|
exclude = ['card_id', "owner" ]
|
||||||
|
read_only_fields = [ "last4", "brand", "expiry_date" ]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Unchecked code
|
# Unchecked code
|
||||||
|
|
||||||
|
|
||||||
###
|
###
|
||||||
# Payments and Payment Methods.
|
# Payments and Payment Methods.
|
||||||
|
|
||||||
|
@ -21,13 +28,6 @@ class PaymentSerializer(serializers.ModelSerializer):
|
||||||
model = Payment
|
model = Payment
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
class PaymentMethodSerializer(serializers.ModelSerializer):
|
|
||||||
stripe_card_last4 = serializers.IntegerField()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = PaymentMethod
|
|
||||||
fields = [ 'source', 'description', 'primary', 'stripe_card_last4', 'active']
|
|
||||||
|
|
||||||
class UpdatePaymentMethodSerializer(serializers.ModelSerializer):
|
class UpdatePaymentMethodSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PaymentMethod
|
model = PaymentMethod
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import stripe
|
import stripe
|
||||||
import stripe.error
|
import stripe.error
|
||||||
import logging
|
import logging
|
||||||
|
import datetime
|
||||||
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
import uncloud_pay.models
|
from .models import StripeCustomer, StripeCreditCard
|
||||||
|
|
||||||
CURRENCY = 'chf'
|
CURRENCY = 'chf'
|
||||||
|
|
||||||
|
@ -56,12 +58,12 @@ def public_api_key():
|
||||||
def get_customer_id_for(user):
|
def get_customer_id_for(user):
|
||||||
try:
|
try:
|
||||||
# .get() raise if there is no matching entry.
|
# .get() raise if there is no matching entry.
|
||||||
return uncloud_pay.models.StripeCustomer.objects.get(owner=user).stripe_id
|
return StripeCustomer.objects.get(owner=user).stripe_id
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
# No entry yet - making a new one.
|
# No entry yet - making a new one.
|
||||||
try:
|
try:
|
||||||
customer = create_customer(user.username, user.email)
|
customer = create_customer(user.username, user.email)
|
||||||
uncloud_stripe_mapping = uncloud_pay.models.StripeCustomer.objects.create(
|
uncloud_stripe_mapping = StripeCustomer.objects.create(
|
||||||
owner=user, stripe_id=customer.id)
|
owner=user, stripe_id=customer.id)
|
||||||
return uncloud_stripe_mapping.stripe_id
|
return uncloud_stripe_mapping.stripe_id
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -109,6 +111,7 @@ def get_customer_cards(customer_id):
|
||||||
customer=customer_id,
|
customer=customer_id,
|
||||||
type="card",
|
type="card",
|
||||||
)
|
)
|
||||||
|
print(stripe_cards["data"])
|
||||||
|
|
||||||
for stripe_card in stripe_cards["data"]:
|
for stripe_card in stripe_cards["data"]:
|
||||||
card = {}
|
card = {}
|
||||||
|
@ -116,8 +119,24 @@ def get_customer_cards(customer_id):
|
||||||
card['last4'] = stripe_card["card"]["last4"]
|
card['last4'] = stripe_card["card"]["last4"]
|
||||||
card['month'] = stripe_card["card"]["exp_month"]
|
card['month'] = stripe_card["card"]["exp_month"]
|
||||||
card['year'] = stripe_card["card"]["exp_year"]
|
card['year'] = stripe_card["card"]["exp_year"]
|
||||||
card['id'] = stripe_card["card"]["id"]
|
card['id'] = stripe_card["id"]
|
||||||
|
|
||||||
cards.append(card)
|
cards.append(card)
|
||||||
|
|
||||||
return cards
|
return cards
|
||||||
|
|
||||||
|
def sync_cards_for_user(user):
|
||||||
|
customer_id = get_customer_id_for(user)
|
||||||
|
cards = get_customer_cards(customer_id)
|
||||||
|
|
||||||
|
for card in cards:
|
||||||
|
StripeCreditCard.objects.get_or_create(card_id=card['id'],
|
||||||
|
owner = user,
|
||||||
|
defaults = {
|
||||||
|
'last4': card['last4'],
|
||||||
|
'brand': card['brand'],
|
||||||
|
'expiry_date': datetime.date(card['year'],
|
||||||
|
card['month'],
|
||||||
|
1)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
@ -63,7 +63,8 @@
|
||||||
} else {
|
} else {
|
||||||
// Return to API on success.
|
// Return to API on success.
|
||||||
document.getElementById("ungleichmessage").innerHTML
|
document.getElementById("ungleichmessage").innerHTML
|
||||||
= "Registered credit card with Stripe."
|
= "Registered credit card with
|
||||||
|
Stripe. <a href="/">Return to the main page.</a>"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,22 +31,7 @@ import uncloud_pay.stripe as uncloud_stripe
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
###
|
###
|
||||||
# Payments and Payment Methods.
|
# 2020-12 checked code
|
||||||
|
|
||||||
class PaymentViewSet(viewsets.ReadOnlyModelViewSet):
|
|
||||||
serializer_class = PaymentSerializer
|
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return Payment.objects.filter(owner=self.request.user)
|
|
||||||
|
|
||||||
class OrderViewSet(viewsets.ReadOnlyModelViewSet):
|
|
||||||
serializer_class = OrderSerializer
|
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return Order.objects.filter(owner=self.request.user)
|
|
||||||
|
|
||||||
|
|
||||||
class RegisterCard(LoginRequiredMixin, TemplateView):
|
class RegisterCard(LoginRequiredMixin, TemplateView):
|
||||||
login_url = '/login/'
|
login_url = '/login/'
|
||||||
|
@ -65,6 +50,44 @@ class RegisterCard(LoginRequiredMixin, TemplateView):
|
||||||
context['stripe_pk'] = uncloud_stripe.public_api_key
|
context['stripe_pk'] = uncloud_stripe.public_api_key
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class CreditCardViewSet(mixins.RetrieveModelMixin,
|
||||||
|
mixins.UpdateModelMixin,
|
||||||
|
mixins.DestroyModelMixin,
|
||||||
|
mixins.ListModelMixin,
|
||||||
|
viewsets.GenericViewSet):
|
||||||
|
|
||||||
|
serializer_class = StripeCreditCardSerializer
|
||||||
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
def list(self, request):
|
||||||
|
uncloud_stripe.sync_cards_for_user(self.request.user)
|
||||||
|
return super().list(request)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return StripeCreditCard.objects.filter(owner=self.request.user)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Payments and Payment Methods.
|
||||||
|
|
||||||
|
class PaymentViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
serializer_class = PaymentSerializer
|
||||||
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return Payment.objects.filter(owner=self.request.user)
|
||||||
|
|
||||||
|
class OrderViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
serializer_class = OrderSerializer
|
||||||
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return Order.objects.filter(owner=self.request.user)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ListCards(LoginRequiredMixin, TemplateView):
|
class ListCards(LoginRequiredMixin, TemplateView):
|
||||||
login_url = '/login/'
|
login_url = '/login/'
|
||||||
|
|
||||||
|
@ -80,22 +103,6 @@ class ListCards(LoginRequiredMixin, TemplateView):
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
class DeleteCard(LoginRequiredMixin, TemplateView):
|
|
||||||
login_url = '/login/'
|
|
||||||
|
|
||||||
template_name = "uncloud_pay/delete_stripe_card.html"
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
customer_id = uncloud_stripe.get_customer_id_for(self.request.user)
|
|
||||||
cards = uncloud_stripe.get_customer_cards(customer_id)
|
|
||||||
|
|
||||||
context = super().get_context_data(**kwargs)
|
|
||||||
context['cards'] = cards
|
|
||||||
context['username'] = self.request.user
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class PaymentMethodViewSet(viewsets.ModelViewSet):
|
class PaymentMethodViewSet(viewsets.ModelViewSet):
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue