diff --git a/doc/uncloud-manual-2020-08-01.org b/doc/uncloud-manual-2020-08-01.org
index 21126bd..381bb62 100644
--- a/doc/uncloud-manual-2020-08-01.org
+++ b/doc/uncloud-manual-2020-08-01.org
@@ -373,6 +373,10 @@ Q vpn-2a0ae5c1200.ungleich.ch
*** 1.1 (cleanup 1)
**** TODO [#C] Unify ValidationError, FieldError - define proper Exception
- 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)
**** TODO [#C] Initial Generic product support
- Product
diff --git a/uncloud/templates/uncloud/index.html b/uncloud/templates/uncloud/index.html
index b40c3b4..e5a8318 100644
--- a/uncloud/templates/uncloud/index.html
+++ b/uncloud/templates/uncloud/index.html
@@ -7,8 +7,13 @@
Welcome to uncloud, checkout the following locations:
diff --git a/uncloud/urls.py b/uncloud/urls.py
index 343a83c..f163136 100644
--- a/uncloud/urls.py
+++ b/uncloud/urls.py
@@ -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/wireguardvpnsizes', netviews.WireGuardVPNSizes, basename='wireguardvpnnetworksizes')
+# Payment related
+router.register(r'v2/payment/credit-card', payviews.CreditCardViewSet, basename='credit-card')
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('openapi', get_schema_view(
@@ -92,8 +94,6 @@ urlpatterns = [
path('admin/', admin.site.urls),
path('cc/reg/', payviews.RegisterCard.as_view(), name="cc_register"),
- path('cc/list/', payviews.ListCards.as_view(), name="cc_list"),
- path('cc/delete/', payviews.DeleteCard.as_view(), name="cc_delete"),
path('', uncloudviews.UncloudIndex.as_view(), name="uncloudindex"),
]
diff --git a/uncloud_net/models.py b/uncloud_net/models.py
index c768c17..9865a08 100644
--- a/uncloud_net/models.py
+++ b/uncloud_net/models.py
@@ -6,7 +6,7 @@ from django.contrib.auth import get_user_model
from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.exceptions import FieldError, ValidationError
-from uncloud_pay.models import Order
+from uncloud_pay.models import Order, Product
class WireGuardVPNPool(models.Model):
"""
@@ -123,6 +123,19 @@ class WireGuardVPN(models.Model):
def __str__(self):
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):
"""
diff --git a/uncloud_net/services.py b/uncloud_net/services.py
index 437601d..9149f01 100644
--- a/uncloud_net/services.py
+++ b/uncloud_net/services.py
@@ -4,9 +4,24 @@ from .models import *
from .selectors import *
from .tasks import *
+
@transaction.atomic
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]
count = pool.wireguardvpn_set.count()
diff --git a/uncloud_pay/admin.py b/uncloud_pay/admin.py
index 2c72274..eb82fb7 100644
--- a/uncloud_pay/admin.py
+++ b/uncloud_pay/admin.py
@@ -88,5 +88,5 @@ admin.site.register(Bill, BillAdmin)
admin.site.register(ProductToRecurringPeriod)
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)
diff --git a/uncloud_pay/migrations/0001_initial.py b/uncloud_pay/migrations/0001_initial.py
index b1b68c5..e65f3dd 100644
--- a/uncloud_pay/migrations/0001_initial.py
+++ b/uncloud_pay/migrations/0001_initial.py
@@ -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
import django.core.validators
@@ -83,6 +83,18 @@ class Migration(migrations.Migration):
('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(
name='ProductToRecurringPeriod',
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')),
],
),
+ 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(
name='BillRecord',
fields=[
diff --git a/uncloud_pay/models.py b/uncloud_pay/models.py
index 18e6f85..abf769c 100644
--- a/uncloud_pay/models.py
+++ b/uncloud_pay/models.py
@@ -17,7 +17,6 @@ from django.utils import timezone
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.conf import settings
-import uncloud_pay.stripe
from uncloud import AMOUNT_DECIMALS, AMOUNT_MAX_DIGITS
from uncloud.models import UncloudAddress
@@ -92,6 +91,21 @@ class StripeCustomer(models.Model):
def __str__(self):
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.
@@ -148,14 +162,14 @@ class PaymentMethod(models.Model):
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)
- @property
- def stripe_card_last4(self):
- if self.source == 'stripe' and self.active:
- payment_method = uncloud_pay.stripe.get_payment_method(
- self.stripe_payment_method_id)
- return payment_method.card.last4
- else:
- return None
+ # @property
+ # def stripe_card_last4(self):
+ # if self.source == 'stripe' and self.active:
+ # payment_method = uncloud_pay.stripe.get_payment_method(
+ # self.stripe_payment_method_id)
+ # return payment_method.card.last4
+ # else:
+ # return None
@property
def active(self):
@@ -1261,3 +1275,24 @@ class ProductToRecurringPeriod(models.Model):
def __str__(self):
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)
diff --git a/uncloud_pay/serializers.py b/uncloud_pay/serializers.py
index 94f833e..84a23fd 100644
--- a/uncloud_pay/serializers.py
+++ b/uncloud_pay/serializers.py
@@ -6,13 +6,20 @@ from django.utils.translation import gettext_lazy as _
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
-
###
# Payments and Payment Methods.
@@ -21,13 +28,6 @@ class PaymentSerializer(serializers.ModelSerializer):
model = Payment
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 Meta:
model = PaymentMethod
diff --git a/uncloud_pay/stripe.py b/uncloud_pay/stripe.py
index a3dcb23..5b3bb00 100644
--- a/uncloud_pay/stripe.py
+++ b/uncloud_pay/stripe.py
@@ -1,11 +1,13 @@
import stripe
import stripe.error
import logging
+import datetime
from django.core.exceptions import ObjectDoesNotExist
from django.conf import settings
+from django.contrib.auth import get_user_model
-import uncloud_pay.models
+from .models import StripeCustomer, StripeCreditCard
CURRENCY = 'chf'
@@ -56,12 +58,12 @@ def public_api_key():
def get_customer_id_for(user):
try:
# .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:
# No entry yet - making a new one.
try:
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)
return uncloud_stripe_mapping.stripe_id
except Exception as e:
@@ -109,6 +111,7 @@ def get_customer_cards(customer_id):
customer=customer_id,
type="card",
)
+ print(stripe_cards["data"])
for stripe_card in stripe_cards["data"]:
card = {}
@@ -116,8 +119,24 @@ def get_customer_cards(customer_id):
card['last4'] = stripe_card["card"]["last4"]
card['month'] = stripe_card["card"]["exp_month"]
card['year'] = stripe_card["card"]["exp_year"]
- card['id'] = stripe_card["card"]["id"]
+ card['id'] = stripe_card["id"]
cards.append(card)
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)
+ }
+ )
diff --git a/uncloud_pay/templates/uncloud_pay/register_stripe.html b/uncloud_pay/templates/uncloud_pay/register_stripe.html
index 82aca74..76265fa 100644
--- a/uncloud_pay/templates/uncloud_pay/register_stripe.html
+++ b/uncloud_pay/templates/uncloud_pay/register_stripe.html
@@ -63,7 +63,8 @@
} else {
// Return to API on success.
document.getElementById("ungleichmessage").innerHTML
- = "Registered credit card with Stripe."
+ = "Registered credit card with
+ Stripe. Return to the main page."
}
});
});
diff --git a/uncloud_pay/views.py b/uncloud_pay/views.py
index 2f4ba8d..246e922 100644
--- a/uncloud_pay/views.py
+++ b/uncloud_pay/views.py
@@ -31,22 +31,7 @@ import uncloud_pay.stripe as uncloud_stripe
logger = logging.getLogger(__name__)
###
-# 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)
-
+# 2020-12 checked code
class RegisterCard(LoginRequiredMixin, TemplateView):
login_url = '/login/'
@@ -65,6 +50,44 @@ class RegisterCard(LoginRequiredMixin, TemplateView):
context['stripe_pk'] = uncloud_stripe.public_api_key
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):
login_url = '/login/'
@@ -80,22 +103,6 @@ class ListCards(LoginRequiredMixin, TemplateView):
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):
permission_classes = [permissions.IsAuthenticated]