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:
@@ -125,29 +125,136 @@ class PaymentAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
"""
|
||||
Override save_model to set approved_by when status changes to succeeded.
|
||||
The Payment.save() method will handle all the cascade updates automatically.
|
||||
Override save_model to trigger approval workflow when status changes to succeeded.
|
||||
This ensures manual status changes in admin also activate accounts and add credits.
|
||||
"""
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
from igny8_core.business.billing.services.credit_service import CreditService
|
||||
from igny8_core.auth.models import Subscription
|
||||
|
||||
# Check if status changed to 'succeeded'
|
||||
status_changed_to_succeeded = False
|
||||
if change and 'status' in form.changed_data:
|
||||
if obj.status == 'succeeded' and form.initial.get('status') != 'succeeded':
|
||||
status_changed_to_succeeded = True
|
||||
elif not change and obj.status == 'succeeded':
|
||||
status_changed_to_succeeded = True
|
||||
|
||||
# Save the payment first
|
||||
if obj.status == 'succeeded' and not obj.approved_by:
|
||||
obj.approved_by = request.user
|
||||
if not obj.approved_at:
|
||||
obj.approved_at = timezone.now()
|
||||
if not obj.processed_at:
|
||||
obj.processed_at = timezone.now()
|
||||
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
# If status changed to succeeded, trigger the full approval workflow
|
||||
if status_changed_to_succeeded:
|
||||
try:
|
||||
with transaction.atomic():
|
||||
invoice = obj.invoice
|
||||
account = obj.account
|
||||
|
||||
# Get subscription from invoice or account
|
||||
subscription = None
|
||||
if invoice and hasattr(invoice, 'subscription') and invoice.subscription:
|
||||
subscription = invoice.subscription
|
||||
elif account and hasattr(account, 'subscription'):
|
||||
try:
|
||||
subscription = account.subscription
|
||||
except Subscription.DoesNotExist:
|
||||
pass
|
||||
|
||||
# Update Invoice
|
||||
if invoice and invoice.status != 'paid':
|
||||
invoice.status = 'paid'
|
||||
invoice.paid_at = timezone.now()
|
||||
invoice.save()
|
||||
|
||||
# Update Subscription
|
||||
if subscription and subscription.status != 'active':
|
||||
subscription.status = 'active'
|
||||
subscription.external_payment_id = obj.manual_reference
|
||||
subscription.save()
|
||||
|
||||
# Update Account
|
||||
if account.status != 'active':
|
||||
account.status = 'active'
|
||||
account.save()
|
||||
|
||||
# Add Credits (check if not already added)
|
||||
from igny8_core.business.billing.models import CreditTransaction
|
||||
existing_credit = CreditTransaction.objects.filter(
|
||||
account=account,
|
||||
metadata__payment_id=obj.id
|
||||
).exists()
|
||||
|
||||
if not existing_credit:
|
||||
credits_to_add = 0
|
||||
plan_name = ''
|
||||
|
||||
if subscription and subscription.plan:
|
||||
credits_to_add = subscription.plan.included_credits
|
||||
plan_name = subscription.plan.name
|
||||
elif account and account.plan:
|
||||
credits_to_add = account.plan.included_credits
|
||||
plan_name = account.plan.name
|
||||
|
||||
if credits_to_add > 0:
|
||||
CreditService.add_credits(
|
||||
account=account,
|
||||
amount=credits_to_add,
|
||||
transaction_type='subscription',
|
||||
description=f'{plan_name} - Invoice {invoice.invoice_number}',
|
||||
metadata={
|
||||
'subscription_id': subscription.id if subscription else None,
|
||||
'invoice_id': invoice.id,
|
||||
'payment_id': obj.id,
|
||||
'approved_by': request.user.email
|
||||
}
|
||||
)
|
||||
self.message_user(
|
||||
request,
|
||||
f'✓ Payment approved: Account activated, {credits_to_add} credits added',
|
||||
level='SUCCESS'
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
self.message_user(
|
||||
request,
|
||||
f'✗ Payment saved but workflow failed: {str(e)}',
|
||||
level='ERROR'
|
||||
)
|
||||
|
||||
def approve_payments(self, request, queryset):
|
||||
"""Approve selected manual payments"""
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
from igny8_core.business.billing.services.credit_service import CreditService
|
||||
from igny8_core.auth.models import Subscription
|
||||
|
||||
count = 0
|
||||
successful = []
|
||||
errors = []
|
||||
|
||||
for payment in queryset.filter(status='pending_approval'):
|
||||
try:
|
||||
with transaction.atomic():
|
||||
invoice = payment.invoice
|
||||
subscription = invoice.subscription if hasattr(invoice, 'subscription') else None
|
||||
account = payment.account
|
||||
|
||||
# Get subscription from invoice or account
|
||||
subscription = None
|
||||
if invoice and hasattr(invoice, 'subscription') and invoice.subscription:
|
||||
subscription = invoice.subscription
|
||||
elif account and hasattr(account, 'subscription'):
|
||||
try:
|
||||
subscription = account.subscription
|
||||
except Subscription.DoesNotExist:
|
||||
pass
|
||||
|
||||
# Update Payment
|
||||
payment.status = 'succeeded'
|
||||
payment.approved_by = request.user
|
||||
@@ -172,10 +279,12 @@ class PaymentAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
account.save()
|
||||
|
||||
# Add Credits
|
||||
if subscription and subscription.plan:
|
||||
credits_added = 0
|
||||
if subscription and subscription.plan and subscription.plan.included_credits > 0:
|
||||
credits_added = subscription.plan.included_credits
|
||||
CreditService.add_credits(
|
||||
account=account,
|
||||
amount=subscription.plan.included_credits,
|
||||
amount=credits_added,
|
||||
transaction_type='subscription',
|
||||
description=f'{subscription.plan.name} - Invoice {invoice.invoice_number}',
|
||||
metadata={
|
||||
@@ -185,17 +294,38 @@ class PaymentAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
'approved_by': request.user.email
|
||||
}
|
||||
)
|
||||
elif account and account.plan and account.plan.included_credits > 0:
|
||||
credits_added = account.plan.included_credits
|
||||
CreditService.add_credits(
|
||||
account=account,
|
||||
amount=credits_added,
|
||||
transaction_type='subscription',
|
||||
description=f'{account.plan.name} - Invoice {invoice.invoice_number}',
|
||||
metadata={
|
||||
'invoice_id': invoice.id,
|
||||
'payment_id': payment.id,
|
||||
'approved_by': request.user.email
|
||||
}
|
||||
)
|
||||
|
||||
count += 1
|
||||
successful.append(f'Payment #{payment.id} - {account.name} - Invoice {invoice.invoice_number} - {credits_added} credits')
|
||||
|
||||
except Exception as e:
|
||||
errors.append(f'Payment {payment.id}: {str(e)}')
|
||||
errors.append(f'Payment #{payment.id}: {str(e)}')
|
||||
|
||||
if count:
|
||||
self.message_user(request, f'Successfully approved {count} payment(s)')
|
||||
# Detailed success message
|
||||
if successful:
|
||||
self.message_user(request, f'✓ Successfully approved {len(successful)} payment(s):', level='SUCCESS')
|
||||
for msg in successful[:10]: # Show first 10
|
||||
self.message_user(request, f' • {msg}', level='SUCCESS')
|
||||
if len(successful) > 10:
|
||||
self.message_user(request, f' ... and {len(successful) - 10} more', level='SUCCESS')
|
||||
|
||||
# Detailed error messages
|
||||
if errors:
|
||||
self.message_user(request, f'✗ Failed to approve {len(errors)} payment(s):', level='ERROR')
|
||||
for error in errors:
|
||||
self.message_user(request, error, level='ERROR')
|
||||
self.message_user(request, f' • {error}', level='ERROR')
|
||||
|
||||
approve_payments.short_description = 'Approve selected manual payments'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user