Allow for charging customers

This commit is contained in:
fnux 2020-03-03 18:16:25 +01:00
parent 94a39ed81d
commit e0cb6ac670
4 changed files with 32 additions and 20 deletions

View file

@ -86,16 +86,19 @@ class PaymentMethod(models.Model):
def charge(self, amount): def charge(self, amount):
if amount > 0: # Make sure we don't charge negative amount by errors... if amount > 0: # Make sure we don't charge negative amount by errors...
if self.source == 'stripe': if self.source == 'stripe':
# TODO: wire to stripe, see meooow-payv1/strip_utils.py stripe_customer = StripeCustomer.objects.get(owner=self.owner).stripe_id
payment = Payment(owner=self.owner, source=self.source, amount=amount) charge_request = uncloud_pay.stripe.charge_customer(amount, stripe_customer, self.stripe_card_id)
payment.save() # TODO: Check return status 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: else:
# We do not handle that source yet. raise Exception('This payment method is unsupported/cannot be charged.')
return False
else: else:
return False raise Exception('Cannot charge negative amount.')
class Meta: class Meta:
unique_together = [['owner', 'primary']] unique_together = [['owner', 'primary']]

View file

@ -38,7 +38,10 @@ class PaymentMethodSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = PaymentMethod 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): class CreditCardSerializer(serializers.Serializer):
number = serializers.IntegerField() number = serializers.IntegerField()

View file

@ -21,7 +21,7 @@ def handle_stripe_error(f):
'error': None 'error': None
} }
common_message = "Currently it's not possible to make payments." common_message = "Currently it is not possible to make payments."
try: try:
response_object = f(*args, **kwargs) response_object = f(*args, **kwargs)
response = { response = {
@ -114,11 +114,15 @@ def get_card(customer_id, card_id):
return stripe.Customer.retrieve_source(customer_id, card_id) return stripe.Customer.retrieve_source(customer_id, card_id)
@handle_stripe_error @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( return stripe.Charge.create(
amount=amount, amount=adjusted_amount,
currenty=CURRENCY, currency=CURRENCY,
source=source) customer=customer_id,
source=card_id)
@handle_stripe_error @handle_stripe_error
def create_customer(name, email): def create_customer(name, email):

View file

@ -63,6 +63,8 @@ class PaymentMethodViewSet(viewsets.ModelViewSet):
def get_serializer_class(self): def get_serializer_class(self):
if self.action == 'create': if self.action == 'create':
return CreatePaymentMethodSerializer return CreatePaymentMethodSerializer
elif self.action == 'charge':
return ChargePaymentMethodSerializer
else: else:
return PaymentMethodSerializer return PaymentMethodSerializer
@ -99,18 +101,18 @@ class PaymentMethodViewSet(viewsets.ModelViewSet):
output_serializer = PaymentMethodSerializer(payment_method) output_serializer = PaymentMethodSerializer(payment_method)
return Response(output_serializer.data) 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']) @action(detail=True, methods=['post'])
def charge(self, request, pk=None): def charge(self, request, pk=None):
payment_method = self.get_object() payment_method = self.get_object()
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
amount = serializer.data['amount'] amount = serializer.validated_data['amount']
if payment_method.charge(amount): try:
return Response({'charged', amount}) payment = payment_method.charge(amount)
else: output_serializer = PaymentSerializer(payment)
return Response(status=status.HTTP_500_INTERNAL_ERROR) return Response(output_serializer.data)
except Exception as e:
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
### ###
# Admin views. # Admin views.