forked from uncloud/uncloud
[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…
Reference in a new issue