feat(billing): add missing payment methods and configurations
- Added migration to include global payment method configurations for Stripe and PayPal (both disabled). - Ensured existing payment methods like bank transfer and manual payment are correctly configured. - Added database constraints and indexes for improved data integrity in billing models. - Introduced foreign key relationship between CreditTransaction and Payment models. - Added webhook configuration fields to PaymentMethodConfig for future payment gateway integrations. - Updated SignUpFormUnified component to handle payment method selection based on user country and plan. - Implemented PaymentHistory component to display user's payment history with status indicators.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
"""
|
||||
Serializers for Billing Models
|
||||
"""
|
||||
from typing import Any, Dict, Optional
|
||||
from decimal import Decimal
|
||||
from rest_framework import serializers
|
||||
from .models import CreditTransaction, CreditUsageLog
|
||||
from igny8_core.auth.models import Account
|
||||
@@ -8,7 +10,11 @@ from igny8_core.business.billing.models import PaymentMethodConfig, Payment
|
||||
|
||||
|
||||
class CreditTransactionSerializer(serializers.ModelSerializer):
|
||||
transaction_type_display = serializers.CharField(source='get_transaction_type_display', read_only=True)
|
||||
"""Serializer for credit transactions"""
|
||||
transaction_type_display: serializers.CharField = serializers.CharField(
|
||||
source='get_transaction_type_display',
|
||||
read_only=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = CreditTransaction
|
||||
@@ -20,7 +26,11 @@ class CreditTransactionSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
class CreditUsageLogSerializer(serializers.ModelSerializer):
|
||||
operation_type_display = serializers.CharField(source='get_operation_type_display', read_only=True)
|
||||
"""Serializer for credit usage logs"""
|
||||
operation_type_display: serializers.CharField = serializers.CharField(
|
||||
source='get_operation_type_display',
|
||||
read_only=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = CreditUsageLog
|
||||
@@ -34,24 +44,27 @@ class CreditUsageLogSerializer(serializers.ModelSerializer):
|
||||
|
||||
class CreditBalanceSerializer(serializers.Serializer):
|
||||
"""Serializer for credit balance response"""
|
||||
credits = serializers.IntegerField()
|
||||
plan_credits_per_month = serializers.IntegerField()
|
||||
credits_used_this_month = serializers.IntegerField()
|
||||
credits_remaining = serializers.IntegerField()
|
||||
credits: serializers.IntegerField = serializers.IntegerField()
|
||||
plan_credits_per_month: serializers.IntegerField = serializers.IntegerField()
|
||||
credits_used_this_month: serializers.IntegerField = serializers.IntegerField()
|
||||
credits_remaining: serializers.IntegerField = serializers.IntegerField()
|
||||
|
||||
|
||||
class UsageSummarySerializer(serializers.Serializer):
|
||||
"""Serializer for usage summary response"""
|
||||
period = serializers.DictField()
|
||||
total_credits_used = serializers.IntegerField()
|
||||
total_cost_usd = serializers.DecimalField(max_digits=10, decimal_places=2)
|
||||
by_operation = serializers.DictField()
|
||||
by_model = serializers.DictField()
|
||||
period: serializers.DictField = serializers.DictField()
|
||||
total_credits_used: serializers.IntegerField = serializers.IntegerField()
|
||||
total_cost_usd: serializers.DecimalField = serializers.DecimalField(max_digits=10, decimal_places=2)
|
||||
by_operation: serializers.DictField = serializers.DictField()
|
||||
by_model: serializers.DictField = serializers.DictField()
|
||||
|
||||
|
||||
class PaymentMethodConfigSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for payment method configuration"""
|
||||
payment_method_display = serializers.CharField(source='get_payment_method_display', read_only=True)
|
||||
payment_method_display: serializers.CharField = serializers.CharField(
|
||||
source='get_payment_method_display',
|
||||
read_only=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = PaymentMethodConfig
|
||||
@@ -66,43 +79,66 @@ class PaymentMethodConfigSerializer(serializers.ModelSerializer):
|
||||
|
||||
class PaymentConfirmationSerializer(serializers.Serializer):
|
||||
"""Serializer for manual payment confirmation"""
|
||||
invoice_id = serializers.IntegerField(required=True)
|
||||
payment_method = serializers.ChoiceField(
|
||||
invoice_id: serializers.IntegerField = serializers.IntegerField(required=True)
|
||||
payment_method: serializers.ChoiceField = serializers.ChoiceField(
|
||||
choices=['bank_transfer', 'local_wallet'],
|
||||
required=True
|
||||
)
|
||||
manual_reference = serializers.CharField(
|
||||
manual_reference: serializers.CharField = serializers.CharField(
|
||||
required=True,
|
||||
max_length=255,
|
||||
help_text="Transaction reference number"
|
||||
)
|
||||
manual_notes = serializers.CharField(
|
||||
manual_notes: serializers.CharField = serializers.CharField(
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
help_text="Additional notes about the payment"
|
||||
)
|
||||
amount = serializers.DecimalField(
|
||||
amount: serializers.DecimalField = serializers.DecimalField(
|
||||
max_digits=10,
|
||||
decimal_places=2,
|
||||
required=True
|
||||
)
|
||||
proof_url = serializers.URLField(
|
||||
proof_url: serializers.URLField = serializers.URLField(
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
help_text="URL to receipt/proof of payment"
|
||||
)
|
||||
|
||||
def validate_proof_url(self, value: Optional[str]) -> Optional[str]:
|
||||
"""Validate proof_url is a valid URL format"""
|
||||
if value and not value.strip():
|
||||
raise serializers.ValidationError("Proof URL cannot be empty if provided")
|
||||
if value:
|
||||
# Additional validation: must be http or https
|
||||
if not value.startswith(('http://', 'https://')):
|
||||
raise serializers.ValidationError("Proof URL must start with http:// or https://")
|
||||
return value
|
||||
|
||||
def validate_amount(self, value: Optional[Decimal]) -> Decimal:
|
||||
"""Validate amount has max 2 decimal places"""
|
||||
if value is None:
|
||||
raise serializers.ValidationError("Amount is required")
|
||||
if value <= 0:
|
||||
raise serializers.ValidationError("Amount must be greater than 0")
|
||||
# Check decimal precision (max 2 decimal places)
|
||||
if value.as_tuple().exponent < -2:
|
||||
raise serializers.ValidationError("Amount can have maximum 2 decimal places")
|
||||
return value
|
||||
|
||||
|
||||
class LimitCardSerializer(serializers.Serializer):
|
||||
"""Serializer for individual limit card"""
|
||||
title = serializers.CharField()
|
||||
limit = serializers.IntegerField()
|
||||
used = serializers.IntegerField()
|
||||
available = serializers.IntegerField()
|
||||
unit = serializers.CharField()
|
||||
category = serializers.CharField()
|
||||
percentage = serializers.FloatField()
|
||||
title: serializers.CharField = serializers.CharField()
|
||||
limit: serializers.IntegerField = serializers.IntegerField()
|
||||
used: serializers.IntegerField = serializers.IntegerField()
|
||||
available: serializers.IntegerField = serializers.IntegerField()
|
||||
unit: serializers.CharField = serializers.CharField()
|
||||
category: serializers.CharField = serializers.CharField()
|
||||
percentage: serializers.FloatField = serializers.FloatField()
|
||||
|
||||
|
||||
class UsageLimitsSerializer(serializers.Serializer):
|
||||
"""Serializer for usage limits response"""
|
||||
limits = LimitCardSerializer(many=True)
|
||||
limits: LimitCardSerializer = LimitCardSerializer(many=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user