billing and paymetn methods

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-07 04:28:46 +00:00
parent 31c06d032c
commit 4e764e208d
11 changed files with 1010 additions and 349 deletions

View File

@@ -40,8 +40,6 @@ class Igny8AdminSite(admin.AdminSite):
('billing', 'Invoice'),
('billing', 'Payment'),
('billing', 'CreditPackage'),
('billing', 'PaymentMethodConfig'),
('billing', 'AccountPaymentMethod'),
('billing', 'CreditCostConfig'),
],
},
@@ -107,6 +105,12 @@ class Igny8AdminSite(admin.AdminSite):
('automation', 'AutomationRun'),
],
},
'Payments': {
'models': [
('billing', 'PaymentMethodConfig'),
('billing', 'AccountPaymentMethod'),
],
},
'Integrations & Sync': {
'models': [
('integration', 'SiteIntegration'),
@@ -170,6 +174,7 @@ class Igny8AdminSite(admin.AdminSite):
'Writer Module',
'Thinker Module',
'System Configuration',
'Payments',
'Integrations & Sync',
'Publishing',
'Optimization',

View File

@@ -545,6 +545,179 @@ class AdminBillingViewSet(viewsets.ViewSet):
status=status.HTTP_400_BAD_REQUEST
)
@action(detail=False, methods=['get', 'post'])
def payment_method_configs(self, request):
"""List/create payment method configs (country-level)"""
error = self._require_admin(request)
if error:
return error
class PMConfigSerializer(serializers.ModelSerializer):
class Meta:
model = PaymentMethodConfig
fields = [
'id',
'country_code',
'payment_method',
'display_name',
'is_enabled',
'instructions',
'sort_order',
'created_at',
'updated_at',
]
read_only_fields = ['id', 'created_at', 'updated_at']
if request.method.lower() == 'post':
serializer = PMConfigSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
obj = serializer.save()
return Response(PMConfigSerializer(obj).data, status=status.HTTP_201_CREATED)
qs = PaymentMethodConfig.objects.all().order_by('country_code', 'sort_order', 'payment_method')
country = request.query_params.get('country_code')
method = request.query_params.get('payment_method')
if country:
qs = qs.filter(country_code=country)
if method:
qs = qs.filter(payment_method=method)
data = PMConfigSerializer(qs, many=True).data
return Response({'results': data, 'count': len(data)})
@action(detail=True, methods=['get', 'patch', 'put', 'delete'], url_path='payment_method_config')
@extend_schema(tags=['Admin Billing'])
def payment_method_config(self, request, pk=None):
"""Retrieve/update/delete a payment method config"""
error = self._require_admin(request)
if error:
return error
obj = get_object_or_404(PaymentMethodConfig, id=pk)
class PMConfigSerializer(serializers.ModelSerializer):
class Meta:
model = PaymentMethodConfig
fields = [
'id',
'country_code',
'payment_method',
'display_name',
'is_enabled',
'instructions',
'sort_order',
'created_at',
'updated_at',
]
read_only_fields = ['id', 'created_at', 'updated_at']
if request.method.lower() == 'get':
return Response(PMConfigSerializer(obj).data)
if request.method.lower() in ['patch', 'put']:
partial = request.method.lower() == 'patch'
serializer = PMConfigSerializer(obj, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
obj = serializer.save()
return Response(PMConfigSerializer(obj).data)
# delete
obj.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=False, methods=['get', 'post'])
@extend_schema(tags=['Admin Billing'])
def account_payment_methods(self, request):
"""List/create account payment methods (admin)"""
error = self._require_admin(request)
if error:
return error
class AccountPMSerializer(serializers.ModelSerializer):
class Meta:
model = AccountPaymentMethod
fields = [
'id',
'account',
'type',
'display_name',
'is_default',
'is_enabled',
'is_verified',
'country_code',
'instructions',
'metadata',
'created_at',
'updated_at',
]
read_only_fields = ['id', 'is_verified', 'created_at', 'updated_at']
if request.method.lower() == 'post':
serializer = AccountPMSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
obj = serializer.save()
return Response(AccountPMSerializer(obj).data, status=status.HTTP_201_CREATED)
qs = AccountPaymentMethod.objects.select_related('account').order_by('account_id', '-is_default', 'display_name')
account_id = request.query_params.get('account_id')
if account_id:
qs = qs.filter(account_id=account_id)
data = AccountPMSerializer(qs, many=True).data
return Response({'results': data, 'count': len(data)})
@action(detail=True, methods=['get', 'patch', 'put', 'delete'], url_path='account_payment_method')
@extend_schema(tags=['Admin Billing'])
def account_payment_method(self, request, pk=None):
"""Retrieve/update/delete an account payment method (admin)"""
error = self._require_admin(request)
if error:
return error
obj = get_object_or_404(AccountPaymentMethod, id=pk)
class AccountPMSerializer(serializers.ModelSerializer):
class Meta:
model = AccountPaymentMethod
fields = [
'id',
'account',
'type',
'display_name',
'is_default',
'is_enabled',
'is_verified',
'country_code',
'instructions',
'metadata',
'created_at',
'updated_at',
]
read_only_fields = ['id', 'is_verified', 'created_at', 'updated_at']
if request.method.lower() == 'get':
return Response(AccountPMSerializer(obj).data)
if request.method.lower() in ['patch', 'put']:
partial = request.method.lower() == 'patch'
serializer = AccountPMSerializer(obj, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
obj = serializer.save()
if serializer.validated_data.get('is_default'):
AccountPaymentMethod.objects.filter(account=obj.account).exclude(id=obj.id).update(is_default=False)
return Response(AccountPMSerializer(obj).data)
obj.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=True, methods=['post'], url_path='account_payment_method/set_default')
@extend_schema(tags=['Admin Billing'])
def set_default_account_payment_method(self, request, pk=None):
"""Set default account payment method (admin)"""
error = self._require_admin(request)
if error:
return error
obj = get_object_or_404(AccountPaymentMethod, id=pk)
AccountPaymentMethod.objects.filter(account=obj.account).update(is_default=False)
obj.is_default = True
obj.save(update_fields=['is_default'])
return Response({'message': 'Default payment method updated', 'id': obj.id})
@action(detail=False, methods=['get'])
def stats(self, request):
"""System billing stats"""

View File

@@ -94,9 +94,10 @@ class CreditPackageAdmin(admin.ModelAdmin):
@admin.register(PaymentMethodConfig)
class PaymentMethodConfigAdmin(admin.ModelAdmin):
list_display = ['country_code', 'payment_method', 'is_enabled', 'display_name', 'sort_order']
list_display = ['country_code', 'payment_method', 'display_name', 'is_enabled', 'sort_order', 'updated_at']
list_filter = ['payment_method', 'is_enabled', 'country_code']
search_fields = ['country_code', 'display_name', 'payment_method']
list_editable = ['is_enabled', 'sort_order']
readonly_fields = ['created_at', 'updated_at']

View File

@@ -19,6 +19,21 @@ urlpatterns = [
path('billing/pending_payments/', BillingAdminViewSet.as_view({'get': 'pending_payments'}), name='admin-billing-pending-payments'),
path('billing/<int:pk>/approve_payment/', BillingAdminViewSet.as_view({'post': 'approve_payment'}), name='admin-billing-approve-payment'),
path('billing/<int:pk>/reject_payment/', BillingAdminViewSet.as_view({'post': 'reject_payment'}), name='admin-billing-reject-payment'),
path('billing/payment-method-configs/', BillingAdminViewSet.as_view({'get': 'payment_method_configs', 'post': 'payment_method_configs'}), name='admin-billing-payment-method-configs'),
path('billing/payment-method-configs/<int:pk>/', BillingAdminViewSet.as_view({
'get': 'payment_method_config',
'patch': 'payment_method_config',
'put': 'payment_method_config',
'delete': 'payment_method_config',
}), name='admin-billing-payment-method-config'),
path('billing/account-payment-methods/', BillingAdminViewSet.as_view({'get': 'account_payment_methods', 'post': 'account_payment_methods'}), name='admin-billing-account-payment-methods'),
path('billing/account-payment-methods/<int:pk>/', BillingAdminViewSet.as_view({
'get': 'account_payment_method',
'patch': 'account_payment_method',
'put': 'account_payment_method',
'delete': 'account_payment_method',
}), name='admin-billing-account-payment-method'),
path('billing/account-payment-methods/<int:pk>/set_default/', BillingAdminViewSet.as_view({'post': 'set_default_account_payment_method'}), name='admin-billing-account-payment-method-set-default'),
]
urlpatterns += router.urls

View File

@@ -1,75 +0,0 @@
"""
Seed credit packages for testing
"""
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from igny8_core.business.billing.models import CreditPackage
from decimal import Decimal
def seed_credit_packages():
"""Create default credit packages"""
packages = [
{
'name': 'Starter Pack',
'slug': 'starter-pack',
'credits': 1000,
'price': Decimal('9.99'),
'discount_percentage': 0,
'description': 'Perfect for trying out the platform',
'sort_order': 1,
'is_featured': False
},
{
'name': 'Professional Pack',
'slug': 'professional-pack',
'credits': 5000,
'price': Decimal('39.99'),
'discount_percentage': 20,
'description': 'Best for growing teams',
'sort_order': 2,
'is_featured': True
},
{
'name': 'Business Pack',
'slug': 'business-pack',
'credits': 15000,
'price': Decimal('99.99'),
'discount_percentage': 30,
'description': 'Ideal for established businesses',
'sort_order': 3,
'is_featured': False
},
{
'name': 'Enterprise Pack',
'slug': 'enterprise-pack',
'credits': 50000,
'price': Decimal('299.99'),
'discount_percentage': 40,
'description': 'Maximum value for high-volume users',
'sort_order': 4,
'is_featured': True
}
]
created_count = 0
for pkg_data in packages:
pkg, created = CreditPackage.objects.get_or_create(
slug=pkg_data['slug'],
defaults=pkg_data
)
if created:
created_count += 1
print(f"✅ Created: {pkg.name} - {pkg.credits:,} credits for ${pkg.price}")
else:
print(f"⏭️ Exists: {pkg.name}")
print(f"\n✅ Seeded {created_count} new credit packages")
print(f"📊 Total active packages: {CreditPackage.objects.filter(is_active=True).count()}")
if __name__ == '__main__':
seed_credit_packages()

View File

@@ -1,125 +0,0 @@
"""
Seed payment method configurations
"""
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from igny8_core.business.billing.models import PaymentMethodConfig
def seed_payment_configs():
"""Create payment method configurations for various countries"""
configs = [
# United States - Stripe and PayPal only
{
'country_code': 'US',
'payment_method': 'stripe',
'is_enabled': True,
'display_name': 'Credit/Debit Card',
'instructions': 'Pay securely with your credit or debit card via Stripe',
'sort_order': 1
},
{
'country_code': 'US',
'payment_method': 'paypal',
'is_enabled': True,
'display_name': 'PayPal',
'instructions': 'Pay with your PayPal account',
'sort_order': 2
},
# India - All methods including manual
{
'country_code': 'IN',
'payment_method': 'stripe',
'is_enabled': True,
'display_name': 'Credit/Debit Card',
'instructions': 'Pay securely with your credit or debit card',
'sort_order': 1
},
{
'country_code': 'IN',
'payment_method': 'paypal',
'is_enabled': True,
'display_name': 'PayPal',
'instructions': 'Pay with your PayPal account',
'sort_order': 2
},
{
'country_code': 'IN',
'payment_method': 'bank_transfer',
'is_enabled': True,
'display_name': 'Bank Transfer (NEFT/IMPS/RTGS)',
'instructions': 'Transfer funds to our bank account. Payment will be verified within 1-2 business days.',
'bank_name': 'HDFC Bank',
'account_number': 'XXXXXXXXXXXXX',
'routing_number': 'HDFC0000XXX',
'swift_code': 'HDFCINBB',
'sort_order': 3
},
{
'country_code': 'IN',
'payment_method': 'local_wallet',
'is_enabled': True,
'display_name': 'UPI / Digital Wallet',
'instructions': 'Pay via Paytm, PhonePe, Google Pay, or other UPI apps. Upload payment screenshot for verification.',
'wallet_type': 'UPI',
'wallet_id': 'igny8@paytm',
'sort_order': 4
},
# United Kingdom - Stripe, PayPal, Bank Transfer
{
'country_code': 'GB',
'payment_method': 'stripe',
'is_enabled': True,
'display_name': 'Credit/Debit Card',
'instructions': 'Pay securely with your credit or debit card',
'sort_order': 1
},
{
'country_code': 'GB',
'payment_method': 'paypal',
'is_enabled': True,
'display_name': 'PayPal',
'instructions': 'Pay with your PayPal account',
'sort_order': 2
},
{
'country_code': 'GB',
'payment_method': 'bank_transfer',
'is_enabled': True,
'display_name': 'Bank Transfer (BACS/Faster Payments)',
'instructions': 'Transfer funds to our UK bank account.',
'bank_name': 'Barclays Bank',
'account_number': 'XXXXXXXX',
'routing_number': 'XX-XX-XX',
'swift_code': 'BARCGB22',
'sort_order': 3
},
]
created_count = 0
updated_count = 0
for config_data in configs:
config, created = PaymentMethodConfig.objects.update_or_create(
country_code=config_data['country_code'],
payment_method=config_data['payment_method'],
defaults={k: v for k, v in config_data.items() if k not in ['country_code', 'payment_method']}
)
if created:
created_count += 1
print(f"✅ Created: {config.country_code} - {config.get_payment_method_display()}")
else:
updated_count += 1
print(f"🔄 Updated: {config.country_code} - {config.get_payment_method_display()}")
print(f"\n✅ Created {created_count} configurations")
print(f"🔄 Updated {updated_count} configurations")
print(f"📊 Total active: {PaymentMethodConfig.objects.filter(is_enabled=True).count()}")
if __name__ == '__main__':
seed_payment_configs()