From 4ad737ed904a508601808a4623152dc76395471f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Mon, 2 Mar 2020 22:26:40 +0100 Subject: [PATCH] Initial stripe playground --- uncloud/uncloud/secrets_sample.py | 3 + uncloud/uncloud/settings.py | 5 ++ .../0013_paymentmethod_stripe_card_id.py | 18 ++++++ uncloud/uncloud_pay/models.py | 5 +- uncloud/uncloud_pay/serializers.py | 29 ++++++++- uncloud/{uncloud => uncloud_pay}/stripe.py | 59 +++++++++++++++++++ uncloud/uncloud_pay/views.py | 8 ++- 7 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 uncloud/uncloud_pay/migrations/0013_paymentmethod_stripe_card_id.py rename uncloud/{uncloud => uncloud_pay}/stripe.py (55%) diff --git a/uncloud/uncloud/secrets_sample.py b/uncloud/uncloud/secrets_sample.py index 36ff0df..464662f 100644 --- a/uncloud/uncloud/secrets_sample.py +++ b/uncloud/uncloud/secrets_sample.py @@ -14,4 +14,7 @@ LDAP_ADMIN_DN="" LDAP_ADMIN_PASSWORD="" LDAP_SERVER_URI = "" +# Stripe (Credit Card payments) +STRIPE_API_key="" + SECRET_KEY="dx$iqt=lc&yrp^!z5$ay^%g5lhx1y3bcu=jg(jx0yj0ogkfqvf" diff --git a/uncloud/uncloud/settings.py b/uncloud/uncloud/settings.py index c6c89d5..f28e0f4 100644 --- a/uncloud/uncloud/settings.py +++ b/uncloud/uncloud/settings.py @@ -176,3 +176,8 @@ USE_TZ = True STATIC_URL = '/static/' stripe.api_key = uncloud.secrets.STRIPE_KEY + +############ +# Stripe + +STRIPE_API_KEY = uncloud.secrets.STRIPE_API_KEY diff --git a/uncloud/uncloud_pay/migrations/0013_paymentmethod_stripe_card_id.py b/uncloud/uncloud_pay/migrations/0013_paymentmethod_stripe_card_id.py new file mode 100644 index 0000000..df7c065 --- /dev/null +++ b/uncloud/uncloud_pay/migrations/0013_paymentmethod_stripe_card_id.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.3 on 2020-03-02 20:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_pay', '0012_orderrecord'), + ] + + operations = [ + migrations.AddField( + model_name='paymentmethod', + name='stripe_card_id', + field=models.CharField(blank=True, max_length=32, null=True), + ), + ] diff --git a/uncloud/uncloud_pay/models.py b/uncloud/uncloud_pay/models.py index 9cbeb48..a29dc3c 100644 --- a/uncloud/uncloud_pay/models.py +++ b/uncloud/uncloud_pay/models.py @@ -143,10 +143,13 @@ class PaymentMethod(models.Model): description = models.TextField() primary = models.BooleanField(default=True) + # Only used for "Stripe" source + stripe_card_id = models.CharField(max_length=32, blank=True, null=True) + def charge(self, amount): if amount > 0: # Make sure we don't charge negative amount by errors... if self.source == 'stripe': - # TODO: wire to strip, see meooow-payv1/strip_utils.py + # TODO: wire to stripe, see meooow-payv1/strip_utils.py payment = Payment(owner=self.owner, source=self.source, amount=amount) payment.save() # TODO: Check return status diff --git a/uncloud/uncloud_pay/serializers.py b/uncloud/uncloud_pay/serializers.py index e3ac0eb..6c6c04e 100644 --- a/uncloud/uncloud_pay/serializers.py +++ b/uncloud/uncloud_pay/serializers.py @@ -7,6 +7,8 @@ from functools import reduce from uncloud_vm.serializers import VMProductSerializer from uncloud_vm.models import VMProduct +import uncloud_pay.stripe as stripe + # TODO: remove magic numbers for decimal fields class BillRecordSerializer(serializers.Serializer): order = serializers.CharField() @@ -27,10 +29,35 @@ class PaymentSerializer(serializers.ModelSerializer): model = Payment fields = ['owner', 'amount', 'source', 'timestamp'] +class CreditCardSerializer(serializers.Serializer): + number = serializers.IntegerField() + exp_month = serializers.IntegerField() + exp_year = serializers.IntegerField() + cvc = serializers.IntegerField() + class PaymentMethodSerializer(serializers.ModelSerializer): class Meta: model = PaymentMethod - fields = '__all__' + fields = ['source', 'description', 'primary'] + +class CreatePaymentMethodSerializer(serializers.ModelSerializer): + credit_card = CreditCardSerializer() + + class Meta: + model = PaymentMethod + fields = ['source', 'description', 'primary', 'credit_card'] + + def create(self, validated_data): + credit_card = stripe.CreditCard(**validated_data.pop('credit_card')) + user = self.context['request'].user + customer = stripe.create_customer(user.username, user.email) + # TODO check customer error + customer_id = customer['response_object']['id'] + stripe_card = stripe.create_card(customer_id, credit_card) + # TODO: check credit card error + validated_data['stripe_card_id'] = stripe_card['response_object']['id'] + payment_method = PaymentMethod.objects.create(**validated_data) + return payment_method class ProductSerializer(serializers.Serializer): vms = VMProductSerializer(many=True, read_only=True) diff --git a/uncloud/uncloud/stripe.py b/uncloud/uncloud_pay/stripe.py similarity index 55% rename from uncloud/uncloud/stripe.py rename to uncloud/uncloud_pay/stripe.py index ce35fd9..6399a1a 100644 --- a/uncloud/uncloud/stripe.py +++ b/uncloud/uncloud_pay/stripe.py @@ -1,5 +1,16 @@ import stripe +import stripe.error +import logging +from django.conf import settings + +# Static stripe configuration used below. +CURRENCY = 'chf' + +# Register stripe (secret) API key from config. +stripe.api_key = settings.STRIPE_API_KEY + +# Helper (decorator) used to catch errors raised by stripe logic. def handle_stripe_error(f): def handle_problems(*args, **kwargs): response = { @@ -53,3 +64,51 @@ def handle_stripe_error(f): return response return handle_problems + +# Convenience CC container, also used for serialization. +class CreditCard(): + number = None + exp_year = None + exp_month = None + cvc = None + + def __init__(self, number, exp_month, exp_year, cvc): + self.number=number + self.exp_year = exp_year + self.exp_month = exp_month + self.cvc = cvc + +# Actual Stripe logic. + +@handle_stripe_error +def create_card(customer_id, credit_card): + # Test settings + credit_card.number = "5555555555554444" + + return stripe.Customer.create_source( + customer_id, + card={ + 'number': credit_card.number, + 'exp_month': credit_card.exp_month, + 'exp_year': credit_card.exp_year, + 'cvc': credit_card.cvc + }) + +@handle_stripe_error +def get_card(customer_id, card_id): + return stripe.Card.retrieve_source(customer_id, card_id) + +@handle_stripe_error +def charge_customer(amount, source): + return stripe.Charge.create( + amount=amount, + currenty=CURRENCY, + source=source) + +@handle_stripe_error +def create_customer(name, email): + return stripe.Customer.create(name=name, email=email) + +@handle_stripe_error +def get_customer(customer_id): + return stripe.Customer.retrieve(customer_id) diff --git a/uncloud/uncloud_pay/views.py b/uncloud/uncloud_pay/views.py index 9ed57c8..aaee9de 100644 --- a/uncloud/uncloud_pay/views.py +++ b/uncloud/uncloud_pay/views.py @@ -57,9 +57,15 @@ class UserViewSet(viewsets.ReadOnlyModelViewSet): return get_user_model().objects.all() class PaymentMethodViewSet(viewsets.ModelViewSet): - serializer_class = PaymentMethodSerializer permission_classes = [permissions.IsAuthenticated] + def get_serializer_class(self): + if self.action == 'create': + return CreatePaymentMethodSerializer + else: + return PaymentMethodSerializer + + def get_queryset(self): return PaymentMethod.objects.filter(owner=self.request.user)