billing and paymetn methods
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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']
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user