# Signals The `subscriptions` package dispatches custom Django signals for every major lifecycle event. Connect to these signals to add custom behavior without modifying the package. ## Connecting to Signals ```python # your_app/signals.py from subscriptions.signals import subscription_activated, payment_success def on_subscription_activated(sender, instance, **kwargs): """Called when a subscription becomes active.""" print(f"Subscription activated: {instance.user} - {instance.plan.name}") def on_payment_success(sender, instance, **kwargs): """Called when a payment is successfully settled.""" print(f"Payment settled: {instance.order_id} - {instance.gross_amount}") subscription_activated.connect(on_subscription_activated) payment_success.connect(on_payment_success) ``` Or using the `@receiver` decorator: ```python from django.dispatch import receiver from subscriptions.signals import subscription_created @receiver(subscription_created) def handle_new_subscription(sender, instance, **kwargs): """Send welcome email when subscription is created.""" send_welcome_email(instance.user) ``` ## Signal Reference ### Subscription Lifecycle | Signal | Sent When | `instance` Type | |--------|-----------|----------------| | `subscription_created` | New subscription saved | `Subscription` | | `subscription_activated` | Status changes to `active` | `Subscription` | | `subscription_cancelled` | Status changes to `cancelled` | `Subscription` | | `subscription_paused` | Status changes to `paused` | `Subscription` | | `subscription_resumed` | *(dispatched via `subscription_activated`)* | `Subscription` | | `subscription_expired` | Status changes to `expired` | `Subscription` | | `subscription_renewed` | *(dispatched manually when renewing)* | `Subscription` | | `subscription_plan_changed` | *(available for custom use)* | `Subscription` | ### Payment Lifecycle | Signal | Sent When | `instance` Type | |--------|-----------|----------------| | `payment_created` | New payment saved | `Payment` | | `payment_success` | Payment is settled (`is_paid=True`) | `Payment` | | `payment_failed` | Status is `deny`, `cancel`, `expire`, or `failure` | `Payment` | | `payment_refunded` | Status is `refund` or `partial_refund` | `Payment` | ### Invoice Lifecycle | Signal | Sent When | `instance` Type | |--------|-----------|----------------| | `invoice_created` | New invoice saved | `Invoice` | | `invoice_paid` | Status changes to `paid` | `Invoice` | | `invoice_overdue` | Status changes to `overdue` | `Invoice` | | `invoice_voided` | Status changes to `void` | `Invoice` | ### Webhook | Signal | Sent When | `instance` Type | |--------|-----------|----------------| | `webhook_received` | New webhook log created | `WebhookLog` | | `webhook_processed` | Webhook log status becomes `processed` | `WebhookLog` | ### Wallet | Signal | Sent When | `instance` Type | |--------|-----------|----------------| | `topup_completed` | Top-up status changes to `success` | `TopUp` | | `wallet_credited` | Wallet transaction created with positive amount | `WalletTransaction` | | `wallet_debited` | Wallet transaction created with negative amount | `WalletTransaction` | ## How Signals Are Dispatched Signals are dispatched via `post_save` handlers registered in `subscriptions/signals.py`: - **Subscription**: On `post_save`, checks if `status` is in `update_fields` and dispatches the corresponding signal - **Payment**: On `post_save`, checks `is_paid` or failure states - **Invoice**: On `post_save`, checks status for `paid`, `overdue`, `void` - **WebhookLog**: On `post_save`, dispatches `webhook_received` on create and `webhook_processed` on status update - **TopUp**: On `post_save`, dispatches `topup_completed` when status is `success` - **WalletTransaction**: On `post_save`, dispatches `wallet_credited` or `wallet_debited` based on amount sign ## Example Use Cases ### Send Slack notification on payment failure ```python @receiver(payment_failed) def notify_slack_on_failure(sender, instance, **kwargs): slack_webhook(f"Payment failed: {instance.order_id} ({instance.gross_amount} {instance.currency})") ``` ### Log subscription changes to analytics ```python @receiver(subscription_activated) def track_activation(sender, instance, **kwargs): analytics.track(instance.user.id, "subscription_activated", { "plan": instance.plan.name, "payment_type": instance.payment_type, }) ``` ### Auto-provision resources on activation ```python @receiver(subscription_activated) def provision_resources(sender, instance, **kwargs): features = instance.plan.features create_user_workspace(instance.user, max_projects=features.get("max_projects", 1)) ```