[credit card] implement payment
This commit is contained in:
		
					parent
					
						
							
								e225bf1cc0
							
						
					
				
			
			
				commit
				
					
						1b06d8ee03
					
				
			
		
					 16 changed files with 290 additions and 64 deletions
				
			
		| 
						 | 
					@ -47,12 +47,12 @@ router.register(r'v1/service/generic', serviceviews.GenericServiceProductViewSet
 | 
				
			||||||
router.register(r'v1/my/address', payviews.BillingAddressViewSet, basename='billingaddress')
 | 
					router.register(r'v1/my/address', payviews.BillingAddressViewSet, basename='billingaddress')
 | 
				
			||||||
router.register(r'v1/my/bill', payviews.BillViewSet, basename='bill')
 | 
					router.register(r'v1/my/bill', payviews.BillViewSet, basename='bill')
 | 
				
			||||||
router.register(r'v1/my/order', payviews.OrderViewSet, basename='order')
 | 
					router.register(r'v1/my/order', payviews.OrderViewSet, basename='order')
 | 
				
			||||||
router.register(r'v1/my/payment', payviews.PaymentViewSet, basename='payment')
 | 
					#router.register(r'v1/my/payment', payviews.PaymentViewSet, basename='payment')
 | 
				
			||||||
router.register(r'v1/my/payment-method', payviews.PaymentMethodViewSet, basename='payment-method')
 | 
					router.register(r'v1/my/payment-method', payviews.PaymentMethodViewSet, basename='payment-method')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# admin/staff urls
 | 
					# admin/staff urls
 | 
				
			||||||
router.register(r'v1/admin/bill', payviews.AdminBillViewSet, basename='admin/bill')
 | 
					router.register(r'v1/admin/bill', payviews.AdminBillViewSet, basename='admin/bill')
 | 
				
			||||||
router.register(r'v1/admin/payment', payviews.AdminPaymentViewSet, basename='admin/payment')
 | 
					#router.register(r'v1/admin/payment', payviews.AdminPaymentViewSet, basename='admin/payment')
 | 
				
			||||||
router.register(r'v1/admin/order', payviews.AdminOrderViewSet, basename='admin/order')
 | 
					router.register(r'v1/admin/order', payviews.AdminOrderViewSet, basename='admin/order')
 | 
				
			||||||
router.register(r'v1/admin/vmhost', vmviews.VMHostViewSet)
 | 
					router.register(r'v1/admin/vmhost', vmviews.VMHostViewSet)
 | 
				
			||||||
router.register(r'v1/admin/vmcluster', vmviews.VMClusterViewSet)
 | 
					router.register(r'v1/admin/vmcluster', vmviews.VMClusterViewSet)
 | 
				
			||||||
| 
						 | 
					@ -74,6 +74,7 @@ router.register(r'v2/net/wireguardvpnsizes', netviews.WireGuardVPNSizes, basenam
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Payment related
 | 
					# Payment related
 | 
				
			||||||
router.register(r'v2/payment/credit-card', payviews.CreditCardViewSet, basename='credit-card')
 | 
					router.register(r'v2/payment/credit-card', payviews.CreditCardViewSet, basename='credit-card')
 | 
				
			||||||
 | 
					router.register(r'v2/payment/payment', payviews.PaymentViewSet, basename='payment')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
urlpatterns = [
 | 
					urlpatterns = [
 | 
				
			||||||
| 
						 | 
					@ -83,16 +84,13 @@ urlpatterns = [
 | 
				
			||||||
    path('openapi', get_schema_view(
 | 
					    path('openapi', get_schema_view(
 | 
				
			||||||
        title="uncloud",
 | 
					        title="uncloud",
 | 
				
			||||||
        description="uncloud API",
 | 
					        description="uncloud API",
 | 
				
			||||||
        version="1.0.0"
 | 
					        version="2.0.0"
 | 
				
			||||||
    ), name='openapi-schema'),
 | 
					    ), name='openapi-schema'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # web/ = stuff to view in the browser
 | 
					    path('admin/', admin.site.urls),
 | 
				
			||||||
#    path('web/vpn/create/', netviews.WireGuardVPNCreateView.as_view(), name="vpncreate"),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    path('login/', authviews.LoginView.as_view(), name="login"),
 | 
					    path('login/', authviews.LoginView.as_view(), name="login"),
 | 
				
			||||||
    path('logout/', authviews.LogoutView.as_view(), name="logout"),
 | 
					    path('logout/', authviews.LogoutView.as_view(), name="logout"),
 | 
				
			||||||
    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('', uncloudviews.UncloudIndex.as_view(), name="uncloudindex"),
 | 
					    path('', uncloudviews.UncloudIndex.as_view(), name="uncloudindex"),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,10 +10,8 @@ from django.core.files.temp import NamedTemporaryFile
 | 
				
			||||||
from django.http import FileResponse
 | 
					from django.http import FileResponse
 | 
				
			||||||
from django.template.loader import render_to_string
 | 
					from django.template.loader import render_to_string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
from uncloud_pay.models import *
 | 
					from uncloud_pay.models import *
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
class BillRecordInline(admin.TabularInline):
 | 
					class BillRecordInline(admin.TabularInline):
 | 
				
			||||||
    model = BillRecord
 | 
					    model = BillRecord
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -85,8 +83,17 @@ class BillAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
admin.site.register(Bill, BillAdmin)
 | 
					admin.site.register(Bill, BillAdmin)
 | 
				
			||||||
admin.site.register(ProductToRecurringPeriod)
 | 
					 | 
				
			||||||
admin.site.register(Product, ProductAdmin)
 | 
					admin.site.register(Product, ProductAdmin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
for m in [ Order, BillRecord, BillingAddress, RecurringPeriod, VATRate, StripeCustomer, StripeCreditCard ]:
 | 
					for m in [
 | 
				
			||||||
 | 
					        BillRecord,
 | 
				
			||||||
 | 
					        BillingAddress,
 | 
				
			||||||
 | 
					        Order,
 | 
				
			||||||
 | 
					        Payment,
 | 
				
			||||||
 | 
					        ProductToRecurringPeriod,
 | 
				
			||||||
 | 
					        RecurringPeriod,
 | 
				
			||||||
 | 
					        StripeCreditCard,
 | 
				
			||||||
 | 
					        StripeCustomer,
 | 
				
			||||||
 | 
					        VATRate,
 | 
				
			||||||
 | 
					]:
 | 
				
			||||||
    admin.site.register(m)
 | 
					    admin.site.register(m)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										24
									
								
								uncloud_pay/migrations/0002_auto_20201228_2244.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								uncloud_pay/migrations/0002_auto_20201228_2244.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,24 @@
 | 
				
			||||||
 | 
					# Generated by Django 3.1 on 2020-12-28 22:44
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import django.core.validators
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('uncloud_pay', '0001_initial'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='payment',
 | 
				
			||||||
 | 
					            name='currency',
 | 
				
			||||||
 | 
					            field=models.CharField(choices=[('CHF', 'Swiss Franc'), ('EUR', 'Euro'), ('USD', 'US Dollar')], default='CHF', max_length=32),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='payment',
 | 
				
			||||||
 | 
					            name='amount',
 | 
				
			||||||
 | 
					            field=models.DecimalField(decimal_places=2, max_digits=10, validators=[django.core.validators.MinValueValidator(0)]),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
							
								
								
									
										28
									
								
								uncloud_pay/migrations/0003_auto_20201228_2256.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								uncloud_pay/migrations/0003_auto_20201228_2256.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,28 @@
 | 
				
			||||||
 | 
					# Generated by Django 3.1 on 2020-12-28 22:56
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('uncloud_pay', '0002_auto_20201228_2244'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='order',
 | 
				
			||||||
 | 
					            name='currency',
 | 
				
			||||||
 | 
					            field=models.CharField(choices=[('CHF', 'Swiss Franc')], default='CHF', max_length=32),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='payment',
 | 
				
			||||||
 | 
					            name='currency',
 | 
				
			||||||
 | 
					            field=models.CharField(choices=[('CHF', 'Swiss Franc')], default='CHF', max_length=32),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='product',
 | 
				
			||||||
 | 
					            name='currency',
 | 
				
			||||||
 | 
					            field=models.CharField(choices=[('CHF', 'Swiss Franc')], default='CHF', max_length=32),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
							
								
								
									
										18
									
								
								uncloud_pay/migrations/0004_stripecreditcard_active.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								uncloud_pay/migrations/0004_stripecreditcard_active.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					# Generated by Django 3.1 on 2020-12-28 23:34
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('uncloud_pay', '0003_auto_20201228_2256'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='stripecreditcard',
 | 
				
			||||||
 | 
					            name='active',
 | 
				
			||||||
 | 
					            field=models.BooleanField(default=True),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
							
								
								
									
										18
									
								
								uncloud_pay/migrations/0005_auto_20201228_2335.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								uncloud_pay/migrations/0005_auto_20201228_2335.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					# Generated by Django 3.1 on 2020-12-28 23:35
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('uncloud_pay', '0004_stripecreditcard_active'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='stripecreditcard',
 | 
				
			||||||
 | 
					            name='active',
 | 
				
			||||||
 | 
					            field=models.BooleanField(default=False),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
							
								
								
									
										21
									
								
								uncloud_pay/migrations/0006_auto_20201228_2337.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								uncloud_pay/migrations/0006_auto_20201228_2337.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					# Generated by Django 3.1 on 2020-12-28 23:37
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.conf import settings
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					import django.db.models.deletion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
 | 
				
			||||||
 | 
					        ('uncloud_pay', '0005_auto_20201228_2335'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='stripecreditcard',
 | 
				
			||||||
 | 
					            name='owner',
 | 
				
			||||||
 | 
					            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
							
								
								
									
										17
									
								
								uncloud_pay/migrations/0007_auto_20201228_2338.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								uncloud_pay/migrations/0007_auto_20201228_2338.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,17 @@
 | 
				
			||||||
 | 
					# Generated by Django 3.1 on 2020-12-28 23:38
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('uncloud_pay', '0006_auto_20201228_2337'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AddConstraint(
 | 
				
			||||||
 | 
					            model_name='stripecreditcard',
 | 
				
			||||||
 | 
					            constraint=models.UniqueConstraint(condition=models.Q(active=True), fields=('owner',), name='one_active_card_per_user'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
							
								
								
									
										18
									
								
								uncloud_pay/migrations/0008_payment_external_reference.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								uncloud_pay/migrations/0008_payment_external_reference.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					# Generated by Django 3.1 on 2020-12-29 00:04
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('uncloud_pay', '0007_auto_20201228_2338'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='payment',
 | 
				
			||||||
 | 
					            name='external_reference',
 | 
				
			||||||
 | 
					            field=models.CharField(default='', max_length=256),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
							
								
								
									
										18
									
								
								uncloud_pay/migrations/0009_auto_20201229_0037.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								uncloud_pay/migrations/0009_auto_20201229_0037.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					# Generated by Django 3.1 on 2020-12-29 00:37
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('uncloud_pay', '0008_payment_external_reference'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='payment',
 | 
				
			||||||
 | 
					            name='external_reference',
 | 
				
			||||||
 | 
					            field=models.CharField(blank=True, default='', max_length=256, null=True),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
							
								
								
									
										19
									
								
								uncloud_pay/migrations/0010_auto_20201229_0042.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								uncloud_pay/migrations/0010_auto_20201229_0042.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,19 @@
 | 
				
			||||||
 | 
					# Generated by Django 3.1 on 2020-12-29 00:42
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					import django.utils.timezone
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('uncloud_pay', '0009_auto_20201229_0037'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='payment',
 | 
				
			||||||
 | 
					            name='timestamp',
 | 
				
			||||||
 | 
					            field=models.DateTimeField(default=django.utils.timezone.now),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
| 
						 | 
					@ -64,8 +64,8 @@ class Currency(models.TextChoices):
 | 
				
			||||||
    Possible currencies to be billed
 | 
					    Possible currencies to be billed
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    CHF   = 'CHF', _('Swiss Franc')
 | 
					    CHF   = 'CHF', _('Swiss Franc')
 | 
				
			||||||
    EUR   = 'EUR', _('Euro')
 | 
					#    EUR   = 'EUR', _('Euro')
 | 
				
			||||||
    USD   = 'USD', _('US Dollar')
 | 
					#    USD   = 'USD', _('US Dollar')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_balance_for_user(user):
 | 
					def get_balance_for_user(user):
 | 
				
			||||||
| 
						 | 
					@ -93,28 +93,30 @@ class StripeCustomer(models.Model):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StripeCreditCard(models.Model):
 | 
					class StripeCreditCard(models.Model):
 | 
				
			||||||
    owner = models.OneToOneField( get_user_model(),
 | 
					    owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
 | 
				
			||||||
            on_delete=models.CASCADE)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    card_name = models.CharField(null=False, max_length=128, default="My credit card")
 | 
					    card_name = models.CharField(null=False, max_length=128, default="My credit card")
 | 
				
			||||||
    card_id = models.CharField(null=False, max_length=32)
 | 
					    card_id = models.CharField(null=False, max_length=32)
 | 
				
			||||||
    last4 = models.CharField(null=False, max_length=4)
 | 
					    last4 = models.CharField(null=False, max_length=4)
 | 
				
			||||||
    brand = models.CharField(null=False, max_length=64)
 | 
					    brand = models.CharField(null=False, max_length=64)
 | 
				
			||||||
    expiry_date = models.DateField(null=False)
 | 
					    expiry_date = models.DateField(null=False)
 | 
				
			||||||
 | 
					    active = models.BooleanField(default=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        constraints = [
 | 
				
			||||||
 | 
					            models.UniqueConstraint(fields=['owner'],
 | 
				
			||||||
 | 
					                                    condition=Q(active=True),
 | 
				
			||||||
 | 
					                                    name='one_active_card_per_user')
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __str__(self):
 | 
					    def __str__(self):
 | 
				
			||||||
        return f"{self.card_name}: {self.brand} {self.last4} ({self.expiry_date})"
 | 
					        return f"{self.card_name}: {self.brand} {self.last4} ({self.expiry_date})"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
###
 | 
					 | 
				
			||||||
# Payments and Payment Methods.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Payment(models.Model):
 | 
					class Payment(models.Model):
 | 
				
			||||||
    owner = models.ForeignKey(get_user_model(),
 | 
					    owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
 | 
				
			||||||
            on_delete=models.CASCADE)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    amount = models.DecimalField(
 | 
					    amount = models.DecimalField(
 | 
				
			||||||
            default=0.0,
 | 
					 | 
				
			||||||
            max_digits=AMOUNT_MAX_DIGITS,
 | 
					            max_digits=AMOUNT_MAX_DIGITS,
 | 
				
			||||||
            decimal_places=AMOUNT_DECIMALS,
 | 
					            decimal_places=AMOUNT_DECIMALS,
 | 
				
			||||||
            validators=[MinValueValidator(0)])
 | 
					            validators=[MinValueValidator(0)])
 | 
				
			||||||
| 
						 | 
					@ -128,21 +130,18 @@ class Payment(models.Model):
 | 
				
			||||||
                                  ('unknown', 'Unknown')
 | 
					                                  ('unknown', 'Unknown')
 | 
				
			||||||
                              ),
 | 
					                              ),
 | 
				
			||||||
                              default='unknown')
 | 
					                              default='unknown')
 | 
				
			||||||
    timestamp = models.DateTimeField(editable=False, auto_now_add=True)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # We override save() in order to active products awaiting payment.
 | 
					    timestamp = models.DateTimeField(default=timezone.now)
 | 
				
			||||||
    def save(self, *args, **kwargs):
 | 
					 | 
				
			||||||
        # _state.adding is switched to false after super(...) call.
 | 
					 | 
				
			||||||
        being_created = self._state.adding
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        unpaid_bills_before_payment = Bill.get_unpaid_for(self.owner)
 | 
					    currency = models.CharField(max_length=32, choices=Currency.choices, default=Currency.CHF)
 | 
				
			||||||
        super(Payment, self).save(*args, **kwargs) # Save payment in DB.
 | 
					 | 
				
			||||||
        unpaid_bills_after_payment = Bill.get_unpaid_for(self.owner)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        newly_paid_bills = list(
 | 
					    external_reference = models.CharField(max_length=256, default="", null=True, blank=True)
 | 
				
			||||||
                set(unpaid_bills_before_payment) - set(unpaid_bills_after_payment))
 | 
					
 | 
				
			||||||
        for bill in newly_paid_bills:
 | 
					    def __str__(self):
 | 
				
			||||||
            bill.activate_products()
 | 
					        return f"{self.amount}{self.currency} from {self.owner} via {self.source} on {self.timestamp}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					# Payments and Payment Methods.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PaymentMethod(models.Model):
 | 
					class PaymentMethod(models.Model):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,17 +4,33 @@ from uncloud_auth.serializers import UserSerializer
 | 
				
			||||||
from django.utils.translation import gettext_lazy as _
 | 
					from django.utils.translation import gettext_lazy as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .models import *
 | 
					from .models import *
 | 
				
			||||||
 | 
					import uncloud_pay.stripe as uncloud_stripe
 | 
				
			||||||
 | 
					
 | 
				
			||||||
###
 | 
					###
 | 
				
			||||||
# 2020-12 Checked code
 | 
					# 2020-12 Checked code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
class StripeCreditCardSerializer(serializers.ModelSerializer):
 | 
					class StripeCreditCardSerializer(serializers.ModelSerializer):
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
        model = StripeCreditCard
 | 
					        model = StripeCreditCard
 | 
				
			||||||
        exclude = ['card_id', "owner" ]
 | 
					        exclude = [ "card_id", "owner" ]
 | 
				
			||||||
        read_only_fields = [ "last4", "brand", "expiry_date" ]
 | 
					        read_only_fields = [ "last4", "brand", "expiry_date" ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PaymentSerializer(serializers.ModelSerializer):
 | 
				
			||||||
 | 
					    owner = serializers.HiddenField(default=serializers.CurrentUserDefault())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = Payment
 | 
				
			||||||
 | 
					        fields = '__all__'
 | 
				
			||||||
 | 
					        read_only_fields = [ "external_reference", "source", "timestamp" ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def validate(self, data):
 | 
				
			||||||
 | 
					        payment_intent = uncloud_stripe.charge_customer(data['owner'],
 | 
				
			||||||
 | 
					                                                        data['amount'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        data["external_reference"] = payment_intent["id"]
 | 
				
			||||||
 | 
					        data["source"] = "stripe"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
################################################################################
 | 
					################################################################################
 | 
				
			||||||
| 
						 | 
					@ -23,10 +39,6 @@ class StripeCreditCardSerializer(serializers.ModelSerializer):
 | 
				
			||||||
###
 | 
					###
 | 
				
			||||||
# Payments and Payment Methods.
 | 
					# Payments and Payment Methods.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PaymentSerializer(serializers.ModelSerializer):
 | 
					 | 
				
			||||||
    class Meta:
 | 
					 | 
				
			||||||
        model = Payment
 | 
					 | 
				
			||||||
        fields = '__all__'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UpdatePaymentMethodSerializer(serializers.ModelSerializer):
 | 
					class UpdatePaymentMethodSerializer(serializers.ModelSerializer):
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@ import stripe.error
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.core.exceptions import ObjectDoesNotExist
 | 
					from django.core.exceptions import ObjectDoesNotExist, ValidationError
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.contrib.auth import get_user_model
 | 
					from django.contrib.auth import get_user_model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -80,20 +80,6 @@ def get_setup_intent(setup_intent_id):
 | 
				
			||||||
def get_payment_method(payment_method_id):
 | 
					def get_payment_method(payment_method_id):
 | 
				
			||||||
    return stripe.PaymentMethod.retrieve(payment_method_id)
 | 
					    return stripe.PaymentMethod.retrieve(payment_method_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@handle_stripe_error
 | 
					 | 
				
			||||||
def charge_customer(amount, customer_id, card_id):
 | 
					 | 
				
			||||||
    # Amount is in CHF but stripes requires smallest possible unit.
 | 
					 | 
				
			||||||
    # https://stripe.com/docs/api/payment_intents/create#create_payment_intent-amount
 | 
					 | 
				
			||||||
    adjusted_amount = int(amount * 100)
 | 
					 | 
				
			||||||
    return stripe.PaymentIntent.create(
 | 
					 | 
				
			||||||
        amount=adjusted_amount,
 | 
					 | 
				
			||||||
        currency=CURRENCY,
 | 
					 | 
				
			||||||
        customer=customer_id,
 | 
					 | 
				
			||||||
        payment_method=card_id,
 | 
					 | 
				
			||||||
        off_session=True,
 | 
					 | 
				
			||||||
        confirm=True,
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@handle_stripe_error
 | 
					@handle_stripe_error
 | 
				
			||||||
def create_customer(name, email):
 | 
					def create_customer(name, email):
 | 
				
			||||||
    return stripe.Customer.create(name=name, email=email)
 | 
					    return stripe.Customer.create(name=name, email=email)
 | 
				
			||||||
| 
						 | 
					@ -111,7 +97,6 @@ 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 = {}
 | 
				
			||||||
| 
						 | 
					@ -129,7 +114,21 @@ def sync_cards_for_user(user):
 | 
				
			||||||
    customer_id = get_customer_id_for(user)
 | 
					    customer_id = get_customer_id_for(user)
 | 
				
			||||||
    cards = get_customer_cards(customer_id)
 | 
					    cards = get_customer_cards(customer_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    active_cards = StripeCreditCard.objects.filter(owner=user,
 | 
				
			||||||
 | 
					                                                   active=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if len(active_cards) > 0:
 | 
				
			||||||
 | 
					        has_active_card = True
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        has_active_card = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for card in cards:
 | 
					    for card in cards:
 | 
				
			||||||
 | 
					        active = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not has_active_card:
 | 
				
			||||||
 | 
					            active = True
 | 
				
			||||||
 | 
					            has_active_card = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        StripeCreditCard.objects.get_or_create(card_id=card['id'],
 | 
					        StripeCreditCard.objects.get_or_create(card_id=card['id'],
 | 
				
			||||||
                                               owner = user,
 | 
					                                               owner = user,
 | 
				
			||||||
                                               defaults = {
 | 
					                                               defaults = {
 | 
				
			||||||
| 
						 | 
					@ -137,6 +136,36 @@ def sync_cards_for_user(user):
 | 
				
			||||||
                                                   'brand': card['brand'],
 | 
					                                                   'brand': card['brand'],
 | 
				
			||||||
                                                   'expiry_date': datetime.date(card['year'],
 | 
					                                                   'expiry_date': datetime.date(card['year'],
 | 
				
			||||||
                                                                                card['month'],
 | 
					                                                                                card['month'],
 | 
				
			||||||
                                                                                1)
 | 
					                                                                                1),
 | 
				
			||||||
 | 
					                                                   'active': active
 | 
				
			||||||
                                                   }
 | 
					                                                   }
 | 
				
			||||||
                                               )
 | 
					                                               )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@handle_stripe_error
 | 
				
			||||||
 | 
					def charge_customer(user, amount, currency='CHF'):
 | 
				
			||||||
 | 
					    # Amount is in CHF but stripes requires smallest possible unit.
 | 
				
			||||||
 | 
					    # https://stripe.com/docs/api/payment_intents/create#create_payment_intent-amount
 | 
				
			||||||
 | 
					    # FIXME: might need to be adjusted for other currencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if currency == 'CHF':
 | 
				
			||||||
 | 
					        adjusted_amount = int(amount * 100)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return Exception("Programming error: unsupported currency")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        card = StripeCreditCard.objects.get(owner=user,
 | 
				
			||||||
 | 
					                                     active=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    except StripeCreditCard.DoesNotExist:
 | 
				
			||||||
 | 
					        raise ValidationError("No active credit card - cannot create payment")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    customer_id = get_customer_id_for(user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return stripe.PaymentIntent.create(
 | 
				
			||||||
 | 
					        amount=adjusted_amount,
 | 
				
			||||||
 | 
					        currency=currency,
 | 
				
			||||||
 | 
					        customer=customer_id,
 | 
				
			||||||
 | 
					        payment_method=card.card_id,
 | 
				
			||||||
 | 
					        off_session=True,
 | 
				
			||||||
 | 
					        confirm=True,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -63,8 +63,7 @@
 | 
				
			||||||
		      } else {
 | 
							      } else {
 | 
				
			||||||
			      // Return to API on success.
 | 
								      // Return to API on success.
 | 
				
			||||||
			      document.getElementById("ungleichmessage").innerHTML
 | 
								      document.getElementById("ungleichmessage").innerHTML
 | 
				
			||||||
		              = "Registered credit card with
 | 
							              = "Registered credit card with Stripe."
 | 
				
			||||||
		      Stripe. <a href="/">Return to the main page.</a>"
 | 
					 | 
				
			||||||
		      }
 | 
							      }
 | 
				
			||||||
	      });
 | 
						      });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -68,17 +68,18 @@ class CreditCardViewSet(mixins.RetrieveModelMixin,
 | 
				
			||||||
        return StripeCreditCard.objects.filter(owner=self.request.user)
 | 
					        return StripeCreditCard.objects.filter(owner=self.request.user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PaymentViewSet(viewsets.ModelViewSet):
 | 
				
			||||||
###
 | 
					 | 
				
			||||||
# Payments and Payment Methods.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class PaymentViewSet(viewsets.ReadOnlyModelViewSet):
 | 
					 | 
				
			||||||
    serializer_class = PaymentSerializer
 | 
					    serializer_class = PaymentSerializer
 | 
				
			||||||
    permission_classes = [permissions.IsAuthenticated]
 | 
					    permission_classes = [permissions.IsAuthenticated]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_queryset(self):
 | 
					    def get_queryset(self):
 | 
				
			||||||
        return Payment.objects.filter(owner=self.request.user)
 | 
					        return Payment.objects.filter(owner=self.request.user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					# Payments and Payment Methods.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class OrderViewSet(viewsets.ReadOnlyModelViewSet):
 | 
					class OrderViewSet(viewsets.ReadOnlyModelViewSet):
 | 
				
			||||||
    serializer_class = OrderSerializer
 | 
					    serializer_class = OrderSerializer
 | 
				
			||||||
    permission_classes = [permissions.IsAuthenticated]
 | 
					    permission_classes = [permissions.IsAuthenticated]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue