Move things around for readability in uncloud_pay models and serializer
This commit is contained in:
		
					parent
					
						
							
								4e51670a90
							
						
					
				
			
			
				commit
				
					
						5559d600c7
					
				
			
		
					 2 changed files with 141 additions and 83 deletions
				
			
		|  | @ -10,6 +10,7 @@ from calendar import monthrange | |||
| 
 | ||||
| import uuid | ||||
| 
 | ||||
| # Define DecimalField properties, used to represent amounts of money. | ||||
| AMOUNT_MAX_DIGITS=10 | ||||
| AMOUNT_DECIMALS=2 | ||||
| 
 | ||||
|  | @ -23,6 +24,70 @@ class RecurringPeriod(models.TextChoices): | |||
|     PER_HOUR   = 'HOUR', _('Per Hour') | ||||
|     PER_SECOND = 'SECOND', _('Per Second') | ||||
| 
 | ||||
| ### | ||||
| # Payments and Payment Methods. | ||||
| 
 | ||||
| class Payment(models.Model): | ||||
|     uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) | ||||
| 
 | ||||
|     owner = models.ForeignKey(get_user_model(), | ||||
|             on_delete=models.CASCADE) | ||||
| 
 | ||||
|     amount = models.DecimalField( | ||||
|             default=0.0, | ||||
|             max_digits=AMOUNT_MAX_DIGITS, | ||||
|             decimal_places=AMOUNT_DECIMALS, | ||||
|             validators=[MinValueValidator(0)]) | ||||
| 
 | ||||
|     source = models.CharField(max_length=256, | ||||
|                               choices = ( | ||||
|                                   ('wire', 'Wire Transfer'), | ||||
|                                   ('stripe', 'Stripe'), | ||||
|                                   ('voucher', 'Voucher'), | ||||
|                                   ('referral', 'Referral'), | ||||
|                                   ('unknown', 'Unknown') | ||||
|                               ), | ||||
|                               default='unknown') | ||||
|     timestamp = models.DateTimeField(editable=False, auto_now_add=True) | ||||
| 
 | ||||
| class PaymentMethod(models.Model): | ||||
|     uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) | ||||
|     owner = models.ForeignKey(get_user_model(), | ||||
|             on_delete=models.CASCADE, | ||||
|             editable=False) | ||||
|     source = models.CharField(max_length=256, | ||||
|             choices = ( | ||||
|                 ('stripe', 'Stripe'), | ||||
|                 ('unknown', 'Unknown'), | ||||
|                 ), | ||||
|             default='stripe') | ||||
|     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 stripe, see meooow-payv1/strip_utils.py | ||||
|                 payment = Payment(owner=self.owner, source=self.source, amount=amount) | ||||
|                 payment.save() # TODO: Check return status | ||||
| 
 | ||||
|                 return True | ||||
|             else: | ||||
|                 # We do not handle that source yet. | ||||
|                 return False | ||||
|         else: | ||||
|             return False | ||||
| 
 | ||||
|     class Meta: | ||||
|         unique_together = [['owner', 'primary']] | ||||
| 
 | ||||
| 
 | ||||
| ### | ||||
| # Bills & Payments. | ||||
| 
 | ||||
| class Bill(models.Model): | ||||
|     uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) | ||||
|     owner = models.ForeignKey(get_user_model(), | ||||
|  | @ -56,6 +121,10 @@ class Bill(models.Model): | |||
|         return self.ending_date < timezone.now() | ||||
| 
 | ||||
| class BillRecord(): | ||||
|     """ | ||||
|     Entry of a bill, dynamically generated from order records. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, bill, order_record): | ||||
|         self.bill = bill | ||||
|         self.order = order_record.order | ||||
|  | @ -114,6 +183,9 @@ class BillRecord(): | |||
|             raise Exception('Unsupported recurring period: {}.'. | ||||
|                     format(record.recurring_period)) | ||||
| 
 | ||||
| ### | ||||
| # Orders. | ||||
| 
 | ||||
| # /!\ BIG FAT WARNING /!\ # | ||||
| # | ||||
| # Order are assumed IMMUTABLE and used as SOURCE OF TRUST for generating | ||||
|  | @ -190,63 +262,12 @@ class OrderRecord(models.Model): | |||
|     def ending_date(self): | ||||
|         return self.order.ending_date | ||||
| 
 | ||||
| class PaymentMethod(models.Model): | ||||
|     uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) | ||||
|     owner = models.ForeignKey(get_user_model(), | ||||
|             on_delete=models.CASCADE, | ||||
|             editable=False) | ||||
|     source = models.CharField(max_length=256, | ||||
|             choices = ( | ||||
|                 ('stripe', 'Stripe'), | ||||
|                 ('unknown', 'Unknown'), | ||||
|                 ), | ||||
|             default='stripe') | ||||
|     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 stripe, see meooow-payv1/strip_utils.py | ||||
|                 payment = Payment(owner=self.owner, source=self.source, amount=amount) | ||||
|                 payment.save() # TODO: Check return status | ||||
| 
 | ||||
|                 return True | ||||
|             else: | ||||
|                 # We do not handle that source yet. | ||||
|                 return False | ||||
|         else: | ||||
|             return False | ||||
| 
 | ||||
|     class Meta: | ||||
|         unique_together = [['owner', 'primary']] | ||||
| 
 | ||||
| class Payment(models.Model): | ||||
|     uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) | ||||
| 
 | ||||
|     owner = models.ForeignKey(get_user_model(), | ||||
|             on_delete=models.CASCADE) | ||||
| 
 | ||||
|     amount = models.DecimalField( | ||||
|             default=0.0, | ||||
|             max_digits=AMOUNT_MAX_DIGITS, | ||||
|             decimal_places=AMOUNT_DECIMALS, | ||||
|             validators=[MinValueValidator(0)]) | ||||
| 
 | ||||
|     source = models.CharField(max_length=256, | ||||
|                               choices = ( | ||||
|                                   ('wire', 'Wire Transfer'), | ||||
|                                   ('stripe', 'Stripe'), | ||||
|                                   ('voucher', 'Voucher'), | ||||
|                                   ('referral', 'Referral'), | ||||
|                                   ('unknown', 'Unknown') | ||||
|                               ), | ||||
|                               default='unknown') | ||||
|     timestamp = models.DateTimeField(editable=False, auto_now_add=True) | ||||
| ### | ||||
| # Products | ||||
| 
 | ||||
| # Abstract (= no database representation) class used as parent for products | ||||
| # (e.g.  uncloud_vm.models.VMProduct). | ||||
| class Product(models.Model): | ||||
|     uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) | ||||
|     owner = models.ForeignKey(get_user_model(), | ||||
|  |  | |||
|  | @ -9,36 +9,62 @@ 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() | ||||
|     description = serializers.CharField() | ||||
|     recurring_period = serializers.CharField() | ||||
|     recurring_price = serializers.DecimalField(max_digits=10, decimal_places=2) | ||||
|     amount = serializers.DecimalField(max_digits=10, decimal_places=2) | ||||
| ### | ||||
| # Users. | ||||
| 
 | ||||
| class BillSerializer(serializers.ModelSerializer): | ||||
|     records = BillRecordSerializer(many=True, read_only=True) | ||||
| class UserSerializer(serializers.ModelSerializer): | ||||
|     class Meta: | ||||
|         model = Bill | ||||
|         fields = ['owner', 'total', 'due_date', 'creation_date', | ||||
|                 'starting_date', 'ending_date', 'records', 'final'] | ||||
|         model = get_user_model() | ||||
|         fields = ['username', 'email', 'balance'] | ||||
| 
 | ||||
|     # Display current 'balance' | ||||
|     balance = serializers.SerializerMethodField('get_balance') | ||||
|     def __sum_balance(self, entries): | ||||
|         return reduce(lambda acc, entry: acc + entry.amount, entries, 0) | ||||
| 
 | ||||
|     def get_balance(self, user): | ||||
|         return get_balance_for(user) | ||||
| 
 | ||||
| ### | ||||
| # Payments and Payment Methods. | ||||
| 
 | ||||
| class PaymentSerializer(serializers.ModelSerializer): | ||||
|     class Meta: | ||||
|         model = Payment | ||||
|         fields = ['owner', 'amount', 'source', 'timestamp'] | ||||
| 
 | ||||
| class PaymentMethodSerializer(serializers.ModelSerializer): | ||||
|     class Meta: | ||||
|         model = PaymentMethod | ||||
|         fields = ['source', 'description', 'primary'] | ||||
| 
 | ||||
| class CreditCardSerializer(serializers.Serializer): | ||||
|     number = serializers.IntegerField() | ||||
|     exp_month = serializers.IntegerField() | ||||
|     exp_year = serializers.IntegerField() | ||||
|     cvc = serializers.IntegerField() | ||||
| 
 | ||||
| class PaymentMethodSerializer(serializers.ModelSerializer): | ||||
| class CreatePaymentMethodSerializer(serializers.ModelSerializer): | ||||
|     credit_card = CreditCardSerializer() | ||||
| 
 | ||||
|     class Meta: | ||||
|         model = PaymentMethod | ||||
|         fields = ['source', 'description', 'primary'] | ||||
|         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'] | ||||
| class CreditCardSerializer(serializers.Serializer): | ||||
|     number = serializers.IntegerField() | ||||
|     exp_month = serializers.IntegerField() | ||||
|     exp_year = serializers.IntegerField() | ||||
|     cvc = serializers.IntegerField() | ||||
| 
 | ||||
| class CreatePaymentMethodSerializer(serializers.ModelSerializer): | ||||
|     credit_card = CreditCardSerializer() | ||||
|  | @ -58,15 +84,36 @@ class CreatePaymentMethodSerializer(serializers.ModelSerializer): | |||
|         validated_data['stripe_card_id'] = stripe_card['response_object']['id'] | ||||
|         payment_method = PaymentMethod.objects.create(**validated_data) | ||||
|         return payment_method | ||||
|         payment_method = PaymentMethod.objects.create(**validated_data) | ||||
|         return payment_method | ||||
| 
 | ||||
| class ProductSerializer(serializers.Serializer): | ||||
|     vms = VMProductSerializer(many=True, read_only=True) | ||||
| ### | ||||
| # Bills | ||||
| 
 | ||||
| # TODO: remove magic numbers for decimal fields | ||||
| class BillRecordSerializer(serializers.Serializer): | ||||
|     order = serializers.CharField() | ||||
|     description = serializers.CharField() | ||||
|     recurring_period = serializers.CharField() | ||||
|     recurring_price = serializers.DecimalField(max_digits=10, decimal_places=2) | ||||
|     amount = serializers.DecimalField(max_digits=10, decimal_places=2) | ||||
| 
 | ||||
| class BillSerializer(serializers.ModelSerializer): | ||||
|     records = BillRecordSerializer(many=True, read_only=True) | ||||
|     class Meta: | ||||
|         model = Bill | ||||
|         fields = ['owner', 'total', 'due_date', 'creation_date', | ||||
|                 'starting_date', 'ending_date', 'records', 'final'] | ||||
| 
 | ||||
| ### | ||||
| # Orders & Products. | ||||
| 
 | ||||
| class OrderRecordSerializer(serializers.ModelSerializer): | ||||
|     class Meta: | ||||
|         model = OrderRecord | ||||
|         fields  = ['setup_fee', 'recurring_price', 'description'] | ||||
| 
 | ||||
| 
 | ||||
| class OrderSerializer(serializers.ModelSerializer): | ||||
|     records = OrderRecordSerializer(many=True, read_only=True) | ||||
|     class Meta: | ||||
|  | @ -74,15 +121,5 @@ class OrderSerializer(serializers.ModelSerializer): | |||
|         fields = ['uuid', 'creation_date', 'starting_date', 'ending_date', | ||||
|                 'bill', 'recurring_period', 'records', 'recurring_price', 'setup_fee'] | ||||
| 
 | ||||
| class UserSerializer(serializers.ModelSerializer): | ||||
|     class Meta: | ||||
|         model = get_user_model() | ||||
|         fields = ['username', 'email', 'balance'] | ||||
| 
 | ||||
|     # Display current 'balance' | ||||
|     balance = serializers.SerializerMethodField('get_balance') | ||||
|     def __sum_balance(self, entries): | ||||
|         return reduce(lambda acc, entry: acc + entry.amount, entries, 0) | ||||
| 
 | ||||
|     def get_balance(self, user): | ||||
|         return get_balance_for(user) | ||||
| class ProductSerializer(serializers.Serializer): | ||||
|     vms = VMProductSerializer(many=True, read_only=True) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue