From e0cb6ac670d674a45023041d0ba615a70997774d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Tue, 3 Mar 2020 18:16:25 +0100 Subject: [PATCH] Allow for charging customers --- uncloud/uncloud_pay/models.py | 17 ++++++++++------- uncloud/uncloud_pay/serializers.py | 5 ++++- uncloud/uncloud_pay/stripe.py | 14 +++++++++----- uncloud/uncloud_pay/views.py | 16 +++++++++------- 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/uncloud/uncloud_pay/models.py b/uncloud/uncloud_pay/models.py index fa775fc..772ab38 100644 --- a/uncloud/uncloud_pay/models.py +++ b/uncloud/uncloud_pay/models.py @@ -86,16 +86,19 @@ class PaymentMethod(models.Model): def charge(self, amount): if amount > 0: # Make sure we don't charge negative amount by errors... if self.source == 'stripe': - # 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 + stripe_customer = StripeCustomer.objects.get(owner=self.owner).stripe_id + charge_request = uncloud_pay.stripe.charge_customer(amount, stripe_customer, self.stripe_card_id) + if charge_request['error'] == None: + payment = Payment(owner=self.owner, source=self.source, amount=amount) + payment.save() # TODO: Check return status - return True + return payment + else: + raise Exception('Stripe error: {}'.format(charge_request['error'])) else: - # We do not handle that source yet. - return False + raise Exception('This payment method is unsupported/cannot be charged.') else: - return False + raise Exception('Cannot charge negative amount.') class Meta: unique_together = [['owner', 'primary']] diff --git a/uncloud/uncloud_pay/serializers.py b/uncloud/uncloud_pay/serializers.py index f5136f6..94c9b61 100644 --- a/uncloud/uncloud_pay/serializers.py +++ b/uncloud/uncloud_pay/serializers.py @@ -38,7 +38,10 @@ class PaymentMethodSerializer(serializers.ModelSerializer): class Meta: model = PaymentMethod - fields = ['source', 'description', 'primary', 'stripe_card_last4'] + fields = ['uuid', 'source', 'description', 'primary', 'stripe_card_last4'] + +class ChargePaymentMethodSerializer(serializers.Serializer): + amount = serializers.DecimalField(max_digits=10, decimal_places=2) class CreditCardSerializer(serializers.Serializer): number = serializers.IntegerField() diff --git a/uncloud/uncloud_pay/stripe.py b/uncloud/uncloud_pay/stripe.py index ab2d865..4f28d94 100644 --- a/uncloud/uncloud_pay/stripe.py +++ b/uncloud/uncloud_pay/stripe.py @@ -21,7 +21,7 @@ def handle_stripe_error(f): 'error': None } - common_message = "Currently it's not possible to make payments." + common_message = "Currently it is not possible to make payments." try: response_object = f(*args, **kwargs) response = { @@ -114,11 +114,15 @@ def get_card(customer_id, card_id): return stripe.Customer.retrieve_source(customer_id, card_id) @handle_stripe_error -def charge_customer(amount, source): +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 + adjusted_amount = int(amount * 100) return stripe.Charge.create( - amount=amount, - currenty=CURRENCY, - source=source) + amount=adjusted_amount, + currency=CURRENCY, + customer=customer_id, + source=card_id) @handle_stripe_error def create_customer(name, email): diff --git a/uncloud/uncloud_pay/views.py b/uncloud/uncloud_pay/views.py index 294b518..a6066b4 100644 --- a/uncloud/uncloud_pay/views.py +++ b/uncloud/uncloud_pay/views.py @@ -63,6 +63,8 @@ class PaymentMethodViewSet(viewsets.ModelViewSet): def get_serializer_class(self): if self.action == 'create': return CreatePaymentMethodSerializer + elif self.action == 'charge': + return ChargePaymentMethodSerializer else: return PaymentMethodSerializer @@ -99,18 +101,18 @@ class PaymentMethodViewSet(viewsets.ModelViewSet): output_serializer = PaymentMethodSerializer(payment_method) return Response(output_serializer.data) - # TODO: find a way to customize serializer for actions. - # drf-action-serializer module seems to do that. @action(detail=True, methods=['post']) def charge(self, request, pk=None): payment_method = self.get_object() serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - amount = serializer.data['amount'] - if payment_method.charge(amount): - return Response({'charged', amount}) - else: - return Response(status=status.HTTP_500_INTERNAL_ERROR) + amount = serializer.validated_data['amount'] + try: + payment = payment_method.charge(amount) + output_serializer = PaymentSerializer(payment) + return Response(output_serializer.data) + except Exception as e: + return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) ### # Admin views.