# Third-Party Integrations Implementation Plan

**Version:** 1.1
**Created:** January 6, 2026
**Updated:** January 7, 2026
**Status:** ✅ **IMPLEMENTATION COMPLETE**
**Covers:** FINAL-PRELAUNCH.md Phase 3.2, 3.3, and Phase 4

---

## Implementation Status

| Phase | Component | Status | Notes |
|-------|-----------|--------|-------|
| 3.2 | Stripe Integration | ✅ Complete | Full checkout, billing portal, webhooks |
| 3.2 | PayPal Integration | ✅ Complete | REST API v2, orders, subscriptions |
| 3.3 | Plan Upgrade Flow | ✅ Complete | Stripe/PayPal/Manual payment selection |
| 3.3 | Credit Purchase Flow | ✅ Complete | One-time credit package checkout |
| 4 | Resend Email Service | ✅ Complete | Transactional emails with templates |
| 4 | Brevo Marketing | ⏸️ Deferred | Future implementation |

---

## Executive Summary

This document provides complete implementation details for:
- **Phase 3.2:** Stripe & PayPal payment integration
- **Phase 3.3:** Plans & packages (upgrade flows, service packages)
- **Phase 4:** Email services (Resend for transactional, Brevo for marketing)

All integrations use the existing `IntegrationProvider` model in `/backend/igny8_core/modules/system/models.py` for centralized API key management.

---

## Table of Contents

1. [Existing Infrastructure](#1-existing-infrastructure)
2. [Phase 3.2: Payment Integration](#2-phase-32-payment-integration)
3. [Phase 3.3: Plans & Packages](#3-phase-33-plans--packages)
4. [Phase 4: Email Services](#4-phase-4-email-services)
5. [Required Credentials Checklist](#5-required-credentials-checklist)
6. [Implementation Order](#6-implementation-order)

---

## 1. Existing Infrastructure

### 1.1 IntegrationProvider Model

**Location:** `backend/igny8_core/modules/system/models.py`

The system already has a centralized model for all third-party credentials:

```python
class IntegrationProvider(models.Model):
    provider_id = CharField(primary_key=True)  # 'stripe', 'paypal', 'resend'
    display_name = CharField(max_length=100)
    provider_type = CharField(choices=['ai', 'payment', 'email', 'storage'])
    api_key = CharField(max_length=500)        # Primary API key
    api_secret = CharField(max_length=500)     # Secondary secret
    webhook_secret = CharField(max_length=500) # Webhook signing secret
    api_endpoint = URLField()                  # Custom endpoint (PayPal sandbox/live)
    config = JSONField()                       # Provider-specific config
    is_active = BooleanField()
    is_sandbox = BooleanField()
```

**Pre-seeded Providers (from migration 0016):**

| Provider ID | Type | Purpose | Status |
|------------|------|---------|--------|
| `stripe` | payment | Card payments, subscriptions | Active (sandbox) |
| `paypal` | payment | PayPal payments | Active (sandbox) |
| `resend` | email | Transactional emails | Active |

### 1.2 Existing Billing Models

**Location:** `backend/igny8_core/business/billing/models.py`

| Model | Purpose |
|-------|---------|
| `Payment` | Payment records with Stripe/PayPal/Manual support |
| `Invoice` | Invoice records with line items |
| `CreditPackage` | One-time credit purchase packages |
| `CreditTransaction` | Credit ledger |
| `Subscription` (in auth) | Account subscription tracking |
| `Plan` (in auth) | Plan definitions with Stripe IDs |

### 1.3 Existing Services

| Service | Location | Status |
|---------|----------|--------|
| `PaymentService` | `business/billing/services/payment_service.py` | ✅ Complete |
| `InvoiceService` | `business/billing/services/invoice_service.py` | ✅ Complete |
| `StripeService` | `business/billing/services/stripe_service.py` | ✅ **NEW - Complete** |
| `PayPalService` | `business/billing/services/paypal_service.py` | ✅ **NEW - Complete** |
| `EmailService` | `business/billing/services/email_service.py` | ✅ **Updated - Resend Integration** |

---

## 2. Phase 3.2: Payment Integration

### 2.1 Stripe Integration

#### 2.1.1 Required Credentials from Stripe Dashboard

**From [dashboard.stripe.com](https://dashboard.stripe.com):**

| Credential | Where to Find | Store In |
|------------|---------------|----------|
| **Publishable Key** | Developers → API Keys → Publishable key | `IntegrationProvider.api_key` |
| **Secret Key** | Developers → API Keys → Secret key | `IntegrationProvider.api_secret` |
| **Webhook Signing Secret** | Developers → Webhooks → Endpoint → Signing secret | `IntegrationProvider.webhook_secret` |

**Webhook Endpoint to Configure:**
```
Production: https://api.igny8.com/api/v1/billing/webhooks/stripe/
Sandbox: https://api-staging.igny8.com/api/v1/billing/webhooks/stripe/
```

**Events to Subscribe:**
- `checkout.session.completed` - New subscription
- `invoice.paid` - Recurring payment success
- `invoice.payment_failed` - Payment failure
- `customer.subscription.updated` - Plan changes
- `customer.subscription.deleted` - Cancellation

#### 2.1.2 IntegrationProvider Configuration

```json
{
  "provider_id": "stripe",
  "display_name": "Stripe",
  "provider_type": "payment",
  "api_key": "pk_live_xxx...",        // Publishable key
  "api_secret": "sk_live_xxx...",     // Secret key
  "webhook_secret": "whsec_xxx...",   // Webhook secret
  "config": {
    "currency": "usd",
    "payment_methods": ["card"],
    "billing_portal_enabled": true
  },
  "is_active": true,
  "is_sandbox": false  // Set to true for test mode
}
```

#### 2.1.3 Backend Implementation

**New Files to Create:**

```
backend/igny8_core/business/billing/
├── services/
│   └── stripe_service.py          # Stripe API wrapper
├── views/
│   └── stripe_views.py            # Checkout, portal, webhooks
└── tasks/
    └── stripe_sync.py             # Sync jobs (optional)
```

**stripe_service.py:**

```python
"""
Stripe Service - Wrapper for Stripe API operations
"""
import stripe
from django.conf import settings
from igny8_core.modules.system.models import IntegrationProvider


class StripeService:
    """Service for Stripe payment operations"""
    
    def __init__(self):
        provider = IntegrationProvider.get_provider('stripe')
        if not provider:
            raise ValueError("Stripe provider not configured")
        
        self.is_sandbox = provider.is_sandbox
        stripe.api_key = provider.api_secret
        self.publishable_key = provider.api_key
        self.webhook_secret = provider.webhook_secret
        self.config = provider.config or {}
    
    def create_checkout_session(self, account, plan, success_url, cancel_url):
        """
        Create Stripe Checkout session for new subscription
        """
        session = stripe.checkout.Session.create(
            customer_email=account.billing_email or account.owner.email,
            payment_method_types=['card'],
            mode='subscription',
            line_items=[{
                'price': plan.stripe_price_id,
                'quantity': 1,
            }],
            success_url=success_url,
            cancel_url=cancel_url,
            metadata={
                'account_id': str(account.id),
                'plan_id': str(plan.id),
            },
            subscription_data={
                'metadata': {
                    'account_id': str(account.id),
                }
            }
        )
        return session
    
    def create_credit_checkout_session(self, account, credit_package, success_url, cancel_url):
        """
        Create Stripe Checkout for one-time credit purchase
        """
        session = stripe.checkout.Session.create(
            customer_email=account.billing_email or account.owner.email,
            payment_method_types=['card'],
            mode='payment',
            line_items=[{
                'price_data': {
                    'currency': self.config.get('currency', 'usd'),
                    'product_data': {
                        'name': credit_package.name,
                        'description': f'{credit_package.credits} credits',
                    },
                    'unit_amount': int(credit_package.price * 100),  # Cents
                },
                'quantity': 1,
            }],
            success_url=success_url,
            cancel_url=cancel_url,
            metadata={
                'account_id': str(account.id),
                'credit_package_id': str(credit_package.id),
                'credit_amount': credit_package.credits,
            }
        )
        return session
    
    def create_billing_portal_session(self, account, return_url):
        """
        Create Stripe Billing Portal session for subscription management
        """
        # Get or create Stripe customer
        customer_id = self._get_or_create_customer(account)
        
        session = stripe.billing_portal.Session.create(
            customer=customer_id,
            return_url=return_url,
        )
        return session
    
    def construct_webhook_event(self, payload, sig_header):
        """
        Verify and construct webhook event
        """
        return stripe.Webhook.construct_event(
            payload, sig_header, self.webhook_secret
        )
    
    def _get_or_create_customer(self, account):
        """Get existing Stripe customer or create new one"""
        if account.stripe_customer_id:
            return account.stripe_customer_id
        
        customer = stripe.Customer.create(
            email=account.billing_email or account.owner.email,
            name=account.name,
            metadata={'account_id': str(account.id)},
        )
        
        account.stripe_customer_id = customer.id
        account.save(update_fields=['stripe_customer_id'])
        
        return customer.id
```

**stripe_views.py:**

```python
"""
Stripe Views - Checkout, Portal, and Webhook endpoints
"""
from rest_framework.views import APIView
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework import status
from django.views.decorators.csrf import csrf_exempt
import json
import logging

from .stripe_service import StripeService
from ..services.payment_service import PaymentService
from igny8_core.auth.models import Plan, Account

logger = logging.getLogger(__name__)


class StripeCheckoutView(APIView):
    """Create Stripe Checkout session"""
    permission_classes = [IsAuthenticated]
    
    def post(self, request):
        account = request.user.account
        plan_id = request.data.get('plan_id')
        
        try:
            plan = Plan.objects.get(id=plan_id, is_active=True)
        except Plan.DoesNotExist:
            return Response({'error': 'Plan not found'}, status=404)
        
        if not plan.stripe_price_id:
            return Response({'error': 'Plan not configured for Stripe'}, status=400)
        
        service = StripeService()
        frontend_url = settings.FRONTEND_URL
        
        session = service.create_checkout_session(
            account=account,
            plan=plan,
            success_url=f'{frontend_url}/account/plans?success=true',
            cancel_url=f'{frontend_url}/account/plans?canceled=true',
        )
        
        return Response({
            'checkout_url': session.url,
            'session_id': session.id,
        })


class StripeCreditCheckoutView(APIView):
    """Create Stripe Checkout for credit package"""
    permission_classes = [IsAuthenticated]
    
    def post(self, request):
        from ..models import CreditPackage
        
        account = request.user.account
        package_id = request.data.get('package_id')
        
        try:
            package = CreditPackage.objects.get(id=package_id, is_active=True)
        except CreditPackage.DoesNotExist:
            return Response({'error': 'Credit package not found'}, status=404)
        
        service = StripeService()
        frontend_url = settings.FRONTEND_URL
        
        session = service.create_credit_checkout_session(
            account=account,
            credit_package=package,
            success_url=f'{frontend_url}/account/usage?purchase=success',
            cancel_url=f'{frontend_url}/account/usage?purchase=canceled',
        )
        
        return Response({
            'checkout_url': session.url,
            'session_id': session.id,
        })


class StripeBillingPortalView(APIView):
    """Create Stripe Billing Portal session"""
    permission_classes = [IsAuthenticated]
    
    def post(self, request):
        account = request.user.account
        
        service = StripeService()
        frontend_url = settings.FRONTEND_URL
        
        session = service.create_billing_portal_session(
            account=account,
            return_url=f'{frontend_url}/account/plans',
        )
        
        return Response({
            'portal_url': session.url,
        })


@csrf_exempt
@api_view(['POST'])
@permission_classes([AllowAny])
def stripe_webhook(request):
    """
    Handle Stripe webhook events
    
    Events handled:
    - checkout.session.completed: New subscription or credit purchase
    - invoice.paid: Recurring payment success
    - invoice.payment_failed: Payment failure
    - customer.subscription.updated: Plan changes
    - customer.subscription.deleted: Cancellation
    """
    payload = request.body
    sig_header = request.META.get('HTTP_STRIPE_SIGNATURE')
    
    try:
        service = StripeService()
        event = service.construct_webhook_event(payload, sig_header)
    except Exception as e:
        logger.error(f'Stripe webhook error: {str(e)}')
        return Response({'error': str(e)}, status=400)
    
    event_type = event['type']
    data = event['data']['object']
    
    logger.info(f'Stripe webhook received: {event_type}')
    
    if event_type == 'checkout.session.completed':
        _handle_checkout_completed(data)
    elif event_type == 'invoice.paid':
        _handle_invoice_paid(data)
    elif event_type == 'invoice.payment_failed':
        _handle_payment_failed(data)
    elif event_type == 'customer.subscription.updated':
        _handle_subscription_updated(data)
    elif event_type == 'customer.subscription.deleted':
        _handle_subscription_deleted(data)
    
    return Response({'status': 'success'})


def _handle_checkout_completed(session):
    """Handle successful checkout"""
    metadata = session.get('metadata', {})
    account_id = metadata.get('account_id')
    
    if not account_id:
        logger.error('No account_id in checkout session metadata')
        return
    
    try:
        account = Account.objects.get(id=account_id)
    except Account.DoesNotExist:
        logger.error(f'Account {account_id} not found')
        return
    
    if session.get('mode') == 'subscription':
        # Handle new subscription
        subscription_id = session.get('subscription')
        _activate_subscription(account, subscription_id, metadata)
    elif session.get('mode') == 'payment':
        # Handle credit purchase
        credit_package_id = metadata.get('credit_package_id')
        credit_amount = metadata.get('credit_amount')
        _add_purchased_credits(account, credit_package_id, credit_amount)


def _activate_subscription(account, stripe_subscription_id, metadata):
    """Activate subscription after successful payment"""
    import stripe
    from django.utils import timezone
    from igny8_core.auth.models import Subscription, Plan
    
    # Get subscription details from Stripe
    stripe_sub = stripe.Subscription.retrieve(stripe_subscription_id)
    
    plan_id = metadata.get('plan_id')
    plan = Plan.objects.get(id=plan_id)
    
    # Update or create subscription
    subscription, created = Subscription.objects.update_or_create(
        account=account,
        defaults={
            'plan': plan,
            'stripe_subscription_id': stripe_subscription_id,
            'status': 'active',
            'current_period_start': timezone.datetime.fromtimestamp(
                stripe_sub.current_period_start, tz=timezone.utc
            ),
            'current_period_end': timezone.datetime.fromtimestamp(
                stripe_sub.current_period_end, tz=timezone.utc
            ),
        }
    )
    
    # Add initial credits
    from ..services.credit_service import CreditService
    CreditService.add_credits(
        account=account,
        amount=plan.included_credits,
        transaction_type='subscription',
        description=f'Subscription: {plan.name}'
    )
    
    logger.info(f'Subscription activated for account {account.id}')


def _add_purchased_credits(account, credit_package_id, credit_amount):
    """Add purchased credits to account"""
    from ..services.credit_service import CreditService
    
    credit_amount = int(credit_amount)
    
    CreditService.add_credits(
        account=account,
        amount=credit_amount,
        transaction_type='purchase',
        description=f'Credit package purchase ({credit_amount} credits)'
    )
    
    logger.info(f'Added {credit_amount} credits to account {account.id}')


def _handle_invoice_paid(invoice):
    """Handle successful recurring payment"""
    subscription_id = invoice.get('subscription')
    if not subscription_id:
        return
    
    from igny8_core.auth.models import Subscription
    
    try:
        subscription = Subscription.objects.get(stripe_subscription_id=subscription_id)
    except Subscription.DoesNotExist:
        return
    
    # Add monthly credits
    from ..services.credit_service import CreditService
    CreditService.add_credits(
        account=subscription.account,
        amount=subscription.plan.included_credits,
        transaction_type='subscription',
        description=f'Monthly renewal: {subscription.plan.name}'
    )


def _handle_payment_failed(invoice):
    """Handle failed payment"""
    subscription_id = invoice.get('subscription')
    if not subscription_id:
        return
    
    from igny8_core.auth.models import Subscription
    
    try:
        subscription = Subscription.objects.get(stripe_subscription_id=subscription_id)
        subscription.status = 'past_due'
        subscription.save()
    except Subscription.DoesNotExist:
        pass
    
    # TODO: Send notification email


def _handle_subscription_updated(subscription_data):
    """Handle subscription update (plan change)"""
    subscription_id = subscription_data.get('id')
    
    from igny8_core.auth.models import Subscription
    
    try:
        subscription = Subscription.objects.get(stripe_subscription_id=subscription_id)
        # Update status
        status_map = {
            'active': 'active',
            'past_due': 'past_due',
            'canceled': 'canceled',
            'trialing': 'trialing',
        }
        stripe_status = subscription_data.get('status')
        subscription.status = status_map.get(stripe_status, 'active')
        subscription.save()
    except Subscription.DoesNotExist:
        pass


def _handle_subscription_deleted(subscription_data):
    """Handle subscription cancellation"""
    subscription_id = subscription_data.get('id')
    
    from igny8_core.auth.models import Subscription
    
    try:
        subscription = Subscription.objects.get(stripe_subscription_id=subscription_id)
        subscription.status = 'canceled'
        subscription.save()
    except Subscription.DoesNotExist:
        pass
```

#### 2.1.4 Frontend Implementation

**New API calls in `frontend/src/services/billing.api.ts`:**

```typescript
export const createStripeCheckout = async (planId: string): Promise<{ checkout_url: string }> => {
  const response = await api.post('/billing/stripe/checkout/', { plan_id: planId });
  return response.data;
};

export const createStripeCreditCheckout = async (packageId: string): Promise<{ checkout_url: string }> => {
  const response = await api.post('/billing/stripe/credit-checkout/', { package_id: packageId });
  return response.data;
};

export const openStripeBillingPortal = async (): Promise<{ portal_url: string }> => {
  const response = await api.post('/billing/stripe/billing-portal/');
  return response.data;
};
```

**Button handlers:**

```typescript
// Subscribe to plan
const handleSubscribe = async (planId: string) => {
  const { checkout_url } = await createStripeCheckout(planId);
  window.location.href = checkout_url;
};

// Buy credits
const handleBuyCredits = async (packageId: string) => {
  const { checkout_url } = await createStripeCreditCheckout(packageId);
  window.location.href = checkout_url;
};

// Manage subscription
const handleManageSubscription = async () => {
  const { portal_url } = await openStripeBillingPortal();
  window.location.href = portal_url;
};
```

---

### 2.2 PayPal Integration

#### 2.2.1 Required Credentials from PayPal Developer Dashboard

**From [developer.paypal.com](https://developer.paypal.com):**

| Credential | Where to Find | Store In |
|------------|---------------|----------|
| **Client ID** | My Apps & Credentials → App → Client ID | `IntegrationProvider.api_key` |
| **Client Secret** | My Apps & Credentials → App → Secret | `IntegrationProvider.api_secret` |
| **Webhook ID** | My Apps & Credentials → Webhooks → Webhook ID | `config.webhook_id` |

**Endpoints:**
- Sandbox: `https://api-m.sandbox.paypal.com`
- Production: `https://api-m.paypal.com`

**Webhook Events to Subscribe:**
- `CHECKOUT.ORDER.APPROVED`
- `PAYMENT.CAPTURE.COMPLETED`
- `BILLING.SUBSCRIPTION.ACTIVATED`
- `BILLING.SUBSCRIPTION.CANCELLED`
- `PAYMENT.CAPTURE.DENIED`

#### 2.2.2 IntegrationProvider Configuration

```json
{
  "provider_id": "paypal",
  "display_name": "PayPal",
  "provider_type": "payment",
  "api_key": "AY...",              // Client ID
  "api_secret": "EL...",           // Client Secret
  "webhook_secret": "",            // Not used for PayPal (uses webhook ID)
  "api_endpoint": "https://api-m.paypal.com",  // or sandbox
  "config": {
    "currency": "USD",
    "webhook_id": "WH-xxx...",
    "return_url": "https://app.igny8.com/account/plans?paypal=success",
    "cancel_url": "https://app.igny8.com/account/plans?paypal=cancel"
  },
  "is_active": true,
  "is_sandbox": false
}
```

#### 2.2.3 Backend Implementation

**paypal_service.py:**

```python
"""
PayPal Service - REST API v2 integration
"""
import requests
import base64
from django.conf import settings
from igny8_core.modules.system.models import IntegrationProvider


class PayPalService:
    """Service for PayPal payment operations"""
    
    def __init__(self):
        provider = IntegrationProvider.get_provider('paypal')
        if not provider:
            raise ValueError("PayPal provider not configured")
        
        self.client_id = provider.api_key
        self.client_secret = provider.api_secret
        self.base_url = provider.api_endpoint or 'https://api-m.paypal.com'
        self.config = provider.config or {}
        self.is_sandbox = provider.is_sandbox
        
        self._access_token = None
    
    def _get_access_token(self):
        """Get OAuth access token"""
        if self._access_token:
            return self._access_token
        
        auth = base64.b64encode(
            f'{self.client_id}:{self.client_secret}'.encode()
        ).decode()
        
        response = requests.post(
            f'{self.base_url}/v1/oauth2/token',
            headers={
                'Authorization': f'Basic {auth}',
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            data='grant_type=client_credentials'
        )
        response.raise_for_status()
        
        self._access_token = response.json()['access_token']
        return self._access_token
    
    def _make_request(self, method, endpoint, **kwargs):
        """Make authenticated API request"""
        token = self._get_access_token()
        headers = kwargs.pop('headers', {})
        headers['Authorization'] = f'Bearer {token}'
        headers['Content-Type'] = 'application/json'
        
        url = f'{self.base_url}{endpoint}'
        response = requests.request(method, url, headers=headers, **kwargs)
        response.raise_for_status()
        return response.json()
    
    def create_order(self, account, amount, currency='USD', description=''):
        """
        Create PayPal order for one-time payment
        """
        order = self._make_request('POST', '/v2/checkout/orders', json={
            'intent': 'CAPTURE',
            'purchase_units': [{
                'amount': {
                    'currency_code': currency,
                    'value': str(amount),
                },
                'description': description,
                'custom_id': str(account.id),
            }],
            'application_context': {
                'return_url': self.config.get('return_url'),
                'cancel_url': self.config.get('cancel_url'),
                'brand_name': 'IGNY8',
                'landing_page': 'BILLING',
                'user_action': 'PAY_NOW',
            }
        })
        
        return order
    
    def capture_order(self, order_id):
        """
        Capture payment for approved order
        """
        return self._make_request('POST', f'/v2/checkout/orders/{order_id}/capture')
    
    def get_order(self, order_id):
        """
        Get order details
        """
        return self._make_request('GET', f'/v2/checkout/orders/{order_id}')
    
    def create_subscription(self, account, plan_id):
        """
        Create PayPal subscription
        Requires plan to be created in PayPal dashboard first
        """
        subscription = self._make_request('POST', '/v1/billing/subscriptions', json={
            'plan_id': plan_id,  # PayPal plan ID
            'custom_id': str(account.id),
            'application_context': {
                'return_url': self.config.get('return_url'),
                'cancel_url': self.config.get('cancel_url'),
                'brand_name': 'IGNY8',
                'user_action': 'SUBSCRIBE_NOW',
            }
        })
        
        return subscription
    
    def verify_webhook_signature(self, headers, body):
        """
        Verify webhook signature
        """
        verification = self._make_request('POST', '/v1/notifications/verify-webhook-signature', json={
            'auth_algo': headers.get('PAYPAL-AUTH-ALGO'),
            'cert_url': headers.get('PAYPAL-CERT-URL'),
            'transmission_id': headers.get('PAYPAL-TRANSMISSION-ID'),
            'transmission_sig': headers.get('PAYPAL-TRANSMISSION-SIG'),
            'transmission_time': headers.get('PAYPAL-TRANSMISSION-TIME'),
            'webhook_id': self.config.get('webhook_id'),
            'webhook_event': body,
        })
        
        return verification.get('verification_status') == 'SUCCESS'
```

---

## 3. Phase 3.3: Plans & Packages

### 3.1 Plan Upgrade Flow

**User Journey:**
1. User on Plans page sees current plan and upgrade options
2. Clicks "Upgrade" on desired plan
3. Redirected to Stripe Checkout (or PayPal)
4. Completes payment
5. Webhook triggers:
   - Updates Subscription to new plan
   - Prorates credits (optional)
6. User redirected back with success message

**Stripe Proration:**
Stripe handles proration automatically when upgrading. The `subscription_data` can include:

```python
subscription_data={
    'proration_behavior': 'create_prorations',  # or 'always_invoice'
}
```

### 3.2 Credit Package Purchase Flow

**Current Packages (from CreditPackage model):**

| Package | Credits | Price | Value |
|---------|---------|-------|-------|
| Starter | 500 | $9.99 | ~$0.02/credit |
| Value | 2,000 | $29.99 | ~$0.015/credit |
| Pro | 5,000 | $59.99 | ~$0.012/credit |
| Enterprise | 15,000 | $149.99 | ~$0.01/credit |

**Purchase Flow:**
1. User on Usage page clicks "Buy Credits"
2. Selects package
3. Redirected to Stripe/PayPal Checkout
4. Completes payment
5. Webhook adds credits to account
6. User redirected back to Usage page

### 3.3 Service Packages (Future)

| Package | Type | Price | Includes |
|---------|------|-------|----------|
| Setup App | One-time | $299 | Initial config, 1hr onboarding |
| Campaign Mgmt | Monthly | $199/mo | 10 keywords/mo, content review |

**Implementation:** Create `ServicePackage` model similar to `CreditPackage`.

---

## 4. Phase 4: Email Services

### 4.1 Resend (Transactional)

#### 4.1.1 Required Credentials

**From [resend.com/api-keys](https://resend.com/api-keys):**

| Credential | Where to Find | Store In |
|------------|---------------|----------|
| **API Key** | API Keys → Create API Key | `IntegrationProvider.api_key` |

**Domain Verification:**
1. Add domain in Resend dashboard
2. Add DNS records (DKIM, SPF, DMARC)
3. Verify domain

#### 4.1.2 IntegrationProvider Configuration

```json
{
  "provider_id": "resend",
  "display_name": "Resend",
  "provider_type": "email",
  "api_key": "re_xxx...",
  "config": {
    "from_email": "noreply@igny8.com",
    "from_name": "IGNY8",
    "reply_to": "support@igny8.com"
  },
  "is_active": true
}
```

#### 4.1.3 Requirements.txt Addition

```
resend>=0.7.0
```

#### 4.1.4 Email Service Implementation

**email_service.py (Updated):**

```python
"""
Email Service - Multi-provider email sending
Uses Resend for transactional, Brevo for marketing
"""
import resend
import logging
from typing import Optional, List, Dict
from django.template.loader import render_to_string
from igny8_core.modules.system.models import IntegrationProvider

logger = logging.getLogger(__name__)


class EmailService:
    """Unified email service supporting multiple providers"""
    
    def __init__(self):
        self._resend_configured = False
        self._brevo_configured = False
        self._setup_providers()
    
    def _setup_providers(self):
        """Initialize email providers"""
        # Setup Resend
        resend_provider = IntegrationProvider.get_provider('resend')
        if resend_provider and resend_provider.api_key:
            resend.api_key = resend_provider.api_key
            self._resend_config = resend_provider.config or {}
            self._resend_configured = True
        
        # Setup Brevo (future)
        brevo_provider = IntegrationProvider.get_provider('brevo')
        if brevo_provider and brevo_provider.api_key:
            self._brevo_config = brevo_provider.config or {}
            self._brevo_configured = True
    
    def send_transactional(
        self,
        to: str,
        subject: str,
        html: Optional[str] = None,
        text: Optional[str] = None,
        template: Optional[str] = None,
        context: Optional[Dict] = None,
        from_email: Optional[str] = None,
        reply_to: Optional[str] = None,
        attachments: Optional[List] = None,
    ):
        """
        Send transactional email via Resend
        
        Args:
            to: Recipient email
            subject: Email subject
            html: HTML content (or use template)
            text: Plain text content
            template: Django template path (e.g., 'emails/welcome.html')
            context: Template context
            from_email: Override sender
            reply_to: Reply-to address
            attachments: List of attachments
        """
        if not self._resend_configured:
            logger.error("Resend not configured - falling back to Django mail")
            return self._send_django_mail(to, subject, text or html)
        
        # Render template if provided
        if template:
            html = render_to_string(template, context or {})
        
        try:
            params = {
                'from': from_email or f"{self._resend_config.get('from_name', 'IGNY8')} <{self._resend_config.get('from_email', 'noreply@igny8.com')}>",
                'to': [to] if isinstance(to, str) else to,
                'subject': subject,
            }
            
            if html:
                params['html'] = html
            if text:
                params['text'] = text
            if reply_to:
                params['reply_to'] = reply_to
            
            response = resend.Emails.send(params)
            
            logger.info(f"Email sent via Resend: {subject} to {to}")
            return response
            
        except Exception as e:
            logger.error(f"Failed to send email via Resend: {str(e)}")
            raise
    
    def _send_django_mail(self, to, subject, message):
        """Fallback to Django's send_mail"""
        from django.core.mail import send_mail
        from django.conf import settings
        
        send_mail(
            subject=subject,
            message=message,
            from_email=settings.DEFAULT_FROM_EMAIL,
            recipient_list=[to],
            fail_silently=False,
        )


# Singleton instance
_email_service = None

def get_email_service():
    global _email_service
    if _email_service is None:
        _email_service = EmailService()
    return _email_service


# Convenience functions
def send_welcome_email(user, account):
    """Send welcome email after signup"""
    service = get_email_service()
    service.send_transactional(
        to=user.email,
        subject='Welcome to IGNY8!',
        template='emails/welcome.html',
        context={
            'user': user,
            'account': account,
            'login_url': f'{settings.FRONTEND_URL}/login',
        }
    )


def send_password_reset_email(user, reset_token):
    """Send password reset email"""
    service = get_email_service()
    service.send_transactional(
        to=user.email,
        subject='Reset Your IGNY8 Password',
        template='emails/password_reset.html',
        context={
            'user': user,
            'reset_url': f'{settings.FRONTEND_URL}/reset-password?token={reset_token}',
        }
    )


def send_payment_confirmation(account, payment):
    """Send payment confirmation email"""
    service = get_email_service()
    service.send_transactional(
        to=account.billing_email or account.owner.email,
        subject=f'Payment Confirmed - Invoice #{payment.invoice.invoice_number}',
        template='emails/payment_confirmed.html',
        context={
            'account': account,
            'payment': payment,
        }
    )


def send_subscription_activated(account, subscription):
    """Send subscription activation email"""
    service = get_email_service()
    service.send_transactional(
        to=account.billing_email or account.owner.email,
        subject=f'Subscription Activated - {subscription.plan.name}',
        template='emails/subscription_activated.html',
        context={
            'account': account,
            'subscription': subscription,
            'plan': subscription.plan,
        }
    )


def send_low_credits_warning(account, current_credits, threshold):
    """Send low credits warning"""
    service = get_email_service()
    service.send_transactional(
        to=account.billing_email or account.owner.email,
        subject='Low Credits Warning - IGNY8',
        template='emails/low_credits.html',
        context={
            'account': account,
            'current_credits': current_credits,
            'threshold': threshold,
            'topup_url': f'{settings.FRONTEND_URL}/account/usage',
        }
    )
```

### 4.2 Brevo (Marketing) - Future

**Configuration (when needed):**

```json
{
  "provider_id": "brevo",
  "display_name": "Brevo",
  "provider_type": "email",
  "api_key": "xkeysib-xxx...",
  "config": {
    "from_email": "hello@igny8.com",
    "from_name": "IGNY8",
    "list_id": 123
  },
  "is_active": false
}
```

**Requirements:** `sib-api-v3-sdk>=7.6.0`

---

## 5. Required Credentials Checklist

> **Note:** All credentials are stored in the `IntegrationProvider` model and can be configured via Django Admin. The implementation supports both sandbox (testing) and production environments.

### 5.1 Stripe Credentials

| Item | Value | Status |
|------|-------|--------|
| Publishable Key (Live) | `pk_live_...` | ⬜ Add to IntegrationProvider |
| Secret Key (Live) | `sk_live_...` | ⬜ Add to IntegrationProvider |
| Webhook Signing Secret | `whsec_...` | ⬜ Add to IntegrationProvider |
| Products/Prices Created | Plan IDs | ⬜ Create in Stripe Dashboard |
| **Code Implementation** | — | ✅ **Complete** |

**Stripe Products to Create:**
1. **Starter Plan** - $99/mo - `price_starter_monthly`
2. **Growth Plan** - $199/mo - `price_growth_monthly`
3. **Scale Plan** - $299/mo - `price_scale_monthly`

### 5.2 PayPal Credentials

| Item | Value | Status |
|------|-------|--------|
| Client ID (Live) | `AY...` | ⬜ Add to IntegrationProvider |
| Client Secret (Live) | `EL...` | ⬜ Add to IntegrationProvider |
| Webhook ID | `WH-...` | ⬜ Add to IntegrationProvider config |
| Subscription Plans Created | PayPal Plan IDs | ⬜ Create in PayPal Dashboard |
| **Code Implementation** | — | ✅ **Complete** |

### 5.3 Resend Credentials

| Item | Value | Status |
|------|-------|--------|
| API Key | `re_...` | ⬜ Add to IntegrationProvider |
| Domain Verified | igny8.com | ⬜ Configure in Resend Dashboard |
| DNS Records Added | DKIM, SPF | ⬜ Add to DNS provider |
| **Code Implementation** | — | ✅ **Complete** |

### 5.4 Brevo Credentials (Future)

| Item | Value | Status |
|------|-------|--------|
| API Key | `xkeysib-...` | ⬜ Future |
| Contact List Created | List ID | ⬜ Future |

---

## 6. Implementation Order

### Phase 1: Backend Setup ✅ COMPLETE

1. **✅ Add dependencies:**
   ```bash
   pip install resend>=0.7.0
   # PayPal uses requests (already installed)
   # Stripe already installed
   ```

2. **✅ Create service files:**
   - ✅ `backend/igny8_core/business/billing/services/stripe_service.py`
   - ✅ `backend/igny8_core/business/billing/services/paypal_service.py`
   - ✅ Update `backend/igny8_core/business/billing/services/email_service.py`

3. **✅ Create view files:**
   - ✅ `backend/igny8_core/business/billing/views/stripe_views.py`
   - ✅ `backend/igny8_core/business/billing/views/paypal_views.py`

4. **✅ Add URL routes:**
   ```python
   # backend/igny8_core/business/billing/urls.py
   urlpatterns += [
       path('stripe/checkout/', StripeCheckoutView.as_view()),
       path('stripe/credit-checkout/', StripeCreditCheckoutView.as_view()),
       path('stripe/billing-portal/', StripeBillingPortalView.as_view()),
       path('webhooks/stripe/', stripe_webhook),
       path('paypal/create-order/', PayPalCreateOrderView.as_view()),
       path('paypal/capture-order/', PayPalCaptureOrderView.as_view()),
       path('webhooks/paypal/', paypal_webhook),
   ]
   ```

5. **✅ Add email templates:**
   ```
   backend/igny8_core/templates/emails/
   ├── base.html
   ├── welcome.html
   ├── password_reset.html
   ├── email_verification.html
   ├── payment_confirmation.html
   ├── payment_approved.html
   ├── payment_rejected.html
   ├── payment_failed.html
   ├── subscription_activated.html
   ├── subscription_renewal.html
   ├── low_credits.html
   └── refund_notification.html
   ```

### Phase 2: Stripe Configuration ⏳ PENDING USER CREDENTIALS

1. ⏳ Create products and prices in Stripe Dashboard (User action needed)
2. ⏳ Configure webhook endpoint (User action needed)
3. ⏳ Get API keys and add to IntegrationProvider via admin (User action needed)
4. ⏳ Test in sandbox mode (After credentials added)

### Phase 3: Frontend Integration ✅ COMPLETE

1. ✅ Add billing API methods (stripe, paypal, purchaseCredits, subscribeToPlan)
2. ✅ Update PlansAndBillingPage with payment buttons
3. ✅ Add PaymentGatewaySelector component
4. ✅ Add success/cancel handling

### Phase 4: Email Configuration ⏳ PENDING USER CREDENTIALS

1. ⏳ Add Resend API key to IntegrationProvider (User action needed)
2. ⏳ Verify domain at resend.com (User action needed)
3. ⏳ Test all email triggers (After credentials added)

### Phase 5: Testing ⏳ PENDING USER CREDENTIALS

1. ⏳ Test Stripe checkout flow (sandbox) - After credentials added
2. ⏳ Test PayPal flow (sandbox) - After credentials added
3. ⏳ Test webhook handling - After webhooks configured
4. ⏳ Test email delivery - After Resend configured
5. ⏳ Switch to production credentials - After testing complete

---

## Summary

**Implementation Status:** ✅ **CODE COMPLETE** - Ready for credentials and testing

**Time Spent:** 2-3 days (Backend + Frontend implementation)

**Pending Actions (User):**
- ⏳ Stripe account: Create products/prices, configure webhooks, add API keys
- ⏳ PayPal developer account: Create app, configure webhooks, add credentials
- ⏳ Resend account: Verify domain, add API key

**Files Created:** ✅
- ✅ `stripe_service.py` (500+ lines)
- ✅ `stripe_views.py` (560+ lines)
- ✅ `paypal_service.py` (500+ lines)
- ✅ `paypal_views.py` (600+ lines)
- ✅ Updated `email_service.py` (760+ lines)
- ✅ 12 email templates
- ✅ `PaymentGatewaySelector.tsx` (160+ lines)
- ✅ Updated `PlansAndBillingPage.tsx`
- ✅ Updated `billing.api.ts` (+285 lines)

**Existing Infrastructure Used:**
- ✅ `IntegrationProvider` model for all credentials
- ✅ `Payment`, `Invoice`, `CreditPackage` models
- ✅ `PaymentService` for payment processing
- ✅ `CreditService` for credit management

**Next Steps:**
1. Add Stripe credentials to Django Admin → Integration Providers
2. Add PayPal credentials to Django Admin → Integration Providers
3. Add Resend API key to Django Admin → Integration Providers
4. Test payment flows in sandbox mode
5. Switch to production credentials when ready
