diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py index d7ee153..e42bb7e 100644 --- a/uncloud/uncloud/urls.py +++ b/uncloud/uncloud/urls.py @@ -61,7 +61,6 @@ router.register(r'payment-method', payviews.PaymentMethodViewSet, basename='paym router.register(r'bill', payviews.BillViewSet, basename='bill') router.register(r'order', payviews.OrderViewSet, basename='order') router.register(r'payment', payviews.PaymentViewSet, basename='payment') -router.register(r'payment-method', payviews.PaymentMethodViewSet, basename='payment-methods') # VMs router.register(r'vm/vm', vmviews.VMProductViewSet, basename='vm') diff --git a/uncloud/uncloud_pay/migrations/0017_auto_20200304_1723.py b/uncloud/uncloud_pay/migrations/0017_auto_20200304_1723.py new file mode 100644 index 0000000..3321e66 --- /dev/null +++ b/uncloud/uncloud_pay/migrations/0017_auto_20200304_1723.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0.4 on 2020-03-04 17:23 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_pay', '0016_auto_20200303_1552'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='paymentmethod', + unique_together=set(), + ), + ] diff --git a/uncloud/uncloud_pay/migrations/0018_auto_20200305_0819.py b/uncloud/uncloud_pay/migrations/0018_auto_20200305_0819.py new file mode 100644 index 0000000..e0f9087 --- /dev/null +++ b/uncloud/uncloud_pay/migrations/0018_auto_20200305_0819.py @@ -0,0 +1,13 @@ +# Generated by Django 3.0.4 on 2020-03-05 08:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_pay', '0017_auto_20200304_1723'), + ] + + operations = [ + ] diff --git a/uncloud/uncloud_pay/migrations/0019_auto_20200305_0851.py b/uncloud/uncloud_pay/migrations/0019_auto_20200305_0851.py new file mode 100644 index 0000000..d8a7c22 --- /dev/null +++ b/uncloud/uncloud_pay/migrations/0019_auto_20200305_0851.py @@ -0,0 +1,23 @@ +# Generated by Django 3.0.4 on 2020-03-05 08:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_pay', '0018_auto_20200305_0819'), + ] + + operations = [ + migrations.AddField( + model_name='paymentmethod', + name='stripe_setup_intent_id', + field=models.CharField(blank=True, max_length=32, null=True), + ), + migrations.AlterField( + model_name='paymentmethod', + name='stripe_card_id', + field=models.CharField(blank=True, max_length=32, null=True), + ), + ] diff --git a/uncloud/uncloud_pay/migrations/0020_auto_20200305_0911.py b/uncloud/uncloud_pay/migrations/0020_auto_20200305_0911.py new file mode 100644 index 0000000..9e1b677 --- /dev/null +++ b/uncloud/uncloud_pay/migrations/0020_auto_20200305_0911.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.4 on 2020-03-05 09:11 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_pay', '0019_auto_20200305_0851'), + ] + + operations = [ + migrations.RenameField( + model_name='paymentmethod', + old_name='stripe_card_id', + new_name='stripe_payment_method_id', + ), + ] diff --git a/uncloud/uncloud_pay/models.py b/uncloud/uncloud_pay/models.py index 17afbcb..1e54b9e 100644 --- a/uncloud/uncloud_pay/models.py +++ b/uncloud/uncloud_pay/models.py @@ -119,27 +119,31 @@ class PaymentMethod(models.Model): primary = models.BooleanField(default=True) # Only used for "Stripe" source - stripe_card_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) @property def stripe_card_last4(self): - if self.source == 'stripe': - card_request = uncloud_pay.stripe.get_card( - StripeCustomer.objects.get(owner=self.owner).stripe_id, - self.stripe_card_id) - if card_request['error'] == None: - return card_request['response_object']['last4'] - else: - return None + 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): + if self.source == 'stripe' and self.stripe_payment_method_id != None: + return True + else: + return False def charge(self, amount): if amount > 0: # Make sure we don't charge negative amount by errors... if self.source == 'stripe': stripe_customer = StripeCustomer.objects.get(owner=self.owner).stripe_id - charge_request = uncloud_pay.stripe.charge_customer(amount, stripe_customer, self.stripe_card_id) + charge_request = uncloud_pay.stripe.charge_customer( + amount, stripe_customer, self.stripe_payment_method_id) if charge_request['error'] == None: payment = Payment(owner=self.owner, source=self.source, amount=amount) payment.save() # TODO: Check return status @@ -163,7 +167,8 @@ class PaymentMethod(models.Model): return None class Meta: - unique_together = [['owner', 'primary']] + #API_keyunique_together = [['owner', 'primary']] + pass ### # Bills & Payments. diff --git a/uncloud/uncloud_pay/serializers.py b/uncloud/uncloud_pay/serializers.py index 60ddc75..328dec1 100644 --- a/uncloud/uncloud_pay/serializers.py +++ b/uncloud/uncloud_pay/serializers.py @@ -29,7 +29,7 @@ class PaymentMethodSerializer(serializers.ModelSerializer): class Meta: model = PaymentMethod - fields = ['uuid', 'source', 'description', 'primary', 'stripe_card_last4'] + fields = ['uuid', 'source', 'description', 'primary', 'stripe_card_last4', 'active'] class ChargePaymentMethodSerializer(serializers.Serializer): amount = serializers.DecimalField(max_digits=10, decimal_places=2) @@ -41,11 +41,10 @@ class CreditCardSerializer(serializers.Serializer): cvc = serializers.IntegerField() class CreatePaymentMethodSerializer(serializers.ModelSerializer): - credit_card = CreditCardSerializer() - + please_visit = serializers.CharField(read_only=True) class Meta: model = PaymentMethod - fields = ['source', 'description', 'primary', 'credit_card'] + fields = ['source', 'description', 'primary', 'please_visit'] ### # Orders & Products. diff --git a/uncloud/uncloud_pay/stripe.py b/uncloud/uncloud_pay/stripe.py index 4f28d94..72399c8 100644 --- a/uncloud/uncloud_pay/stripe.py +++ b/uncloud/uncloud_pay/stripe.py @@ -10,6 +10,10 @@ import uncloud.secrets # Static stripe configuration used below. CURRENCY = 'chf' +# README: We use the Payment Intent API as described on +# https://stripe.com/docs/payments/save-and-reuse + +# For internal use only. stripe.api_key = uncloud.secrets.STRIPE_KEY # Helper (decorator) used to catch errors raised by stripe logic. @@ -82,6 +86,9 @@ class CreditCard(): # Actual Stripe logic. +def public_api_key(): + return uncloud.settings.STRIPE_PUBLIC_KEY + def get_customer_id_for(user): try: # .get() raise if there is no matching entry. @@ -99,15 +106,17 @@ def get_customer_id_for(user): return None @handle_stripe_error -def create_card(customer_id, credit_card): - 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 - }) +def create_setup_intent(customer_id): + return stripe.SetupIntent.create(customer=customer_id) + +@handle_stripe_error +def get_setup_intent(setup_intent_id): + return stripe.SetupIntent.retrieve(setup_intent_id) + +def get_payment_method(payment_method_id): + return stripe.PaymentMethod.retrieve(payment_method_id) + +## Legacy @handle_stripe_error def get_card(customer_id, card_id): @@ -116,13 +125,16 @@ def get_card(customer_id, card_id): @handle_stripe_error def charge_customer(amount, customer_id, card_id): # Amount is in CHF but stripes requires smallest possible unit. - # See https://stripe.com/docs/api/charges/create + # https://stripe.com/docs/api/payment_intents/create#create_payment_intent-amount adjusted_amount = int(amount * 100) - return stripe.Charge.create( - amount=adjusted_amount, - currency=CURRENCY, - customer=customer_id, - source=card_id) + return stripe.PaymentIntent.create( + amount=adjusted_amount, + currency=CURRENCY, + customer=customer_id, + payment_method=card_id, + off_session=True, + confirm=True, + ) @handle_stripe_error def create_customer(name, email): diff --git a/uncloud/uncloud_pay/templates/stripe-payment.html.j2 b/uncloud/uncloud_pay/templates/stripe-payment.html.j2 new file mode 100644 index 0000000..6c59740 --- /dev/null +++ b/uncloud/uncloud_pay/templates/stripe-payment.html.j2 @@ -0,0 +1,76 @@ + + +
+