# Flutterwave Service - Implementation Guide

## Overview

This implementation provides a complete Flutterwave payment service for handling recurring and installment payments in the NexusMart e-commerce platform.

## Files Created

1. **`shop/flutterwave_service.py`** - Main service class (537 lines)
   - `FlutterwaveService` - Primary service class
   - Exception classes: `FlutterwaveServiceError`, `FlutterwaveTimeoutError`, `FlutterwaveNetworkError`

2. **`shop/services.py`** - Services package initialization
   - Imports and exports all service classes

3. **`requirements.txt`** - Updated with dependencies
   - `requests` - For HTTP API calls
   - `python-dateutil` - For date calculations

4. **`shop/tests.py`** - Comprehensive test suite
   - 12 test classes covering all service methods
   - Mock-based testing for API interactions

## Quick Start

### 1. Install Dependencies

```bash
pip install -r requirements.txt
```

### 2. Configure Environment Variables

Add to `.env` file:

```env
FLW_PUBLIC_KEY=pk_test_xxxxx          # Get from Flutterwave dashboard
FLW_SECRET_KEY=sk_test_xxxxx          # Get from Flutterwave dashboard
FLW_ENCRYPTION_KEY=FLWSECK_xxxxx      # Get from Flutterwave dashboard
```

### 3. Basic Usage

```python
from decimal import Decimal
from shop.flutterwave_service import FlutterwaveService

# Initialize service
service = FlutterwaveService()

# Initiate payment for card tokenization
result = service.initiate_payment(
    amount=Decimal("1000.00"),
    email="customer@example.com",
    phone="+234801234567",
    order_reference="ORD123456"
)

if result['status'] == 'success':
    # Redirect customer to authorization URL
    auth_url = result['authorization_url']
```

## API Methods

### 1. `initiate_payment()`

Starts the payment authorization process to enable card tokenization.

**Parameters:**
- `amount` (Decimal): Payment amount
- `email` (str): Customer email
- `phone` (str): Customer phone number
- `order_reference` (str): Unique order identifier
- `customer_name` (str, optional): Customer name
- `currency` (str, optional): Currency code (default: USD)

**Returns:**
```python
{
    'status': 'success' | 'failed',
    'authorization_url': str,
    'access_code': str,
    'transaction_ref': str,
    'message': str
}
```

### 2. `charge_recurring_card()`

Charges a previously authorized/saved card for installment payments.

**Parameters:**
- `amount` (Decimal): Amount to charge
- `email` (str): Customer email
- `token` (str): Saved card token from RecurringPaymentToken
- `order_reference` (str): Order ID
- `installment_num` (int): Which installment (1, 2, 3, etc.)
- `customer_name` (str, optional): Customer name
- `currency` (str, optional): Currency code

**Returns:**
```python
{
    'status': 'success' | 'failed',
    'transaction_ref': str,
    'message': str,
    'gateway_response': dict (optional)
}
```

### 3. `verify_transaction()`

Verifies if a transaction was actually successful.

**Parameters:**
- `reference` (str): Transaction reference ID

**Returns:**
```python
{
    'status': 'success' | 'failed' | 'pending',
    'verified': bool,
    'amount': str,
    'charge_amount': str,
    'message': str,
    'gateway_response': dict
}
```

### 4. `handle_failed_payment()`

Handles failed payments with automatic retry logic.

**Parameters:**
- `payment_obj` (ScheduledPayment): Payment model instance
- `error_msg` (str): Error message from failure
- `auto_retry` (bool, optional): Enable auto-retry (default: True)

**Returns:**
```python
{
    'status': 'scheduled_retry' | 'max_retries_exceeded',
    'retry_scheduled': bool,
    'next_retry_date': datetime | None,
    'message': str
}
```

**Retry Logic:**
- Max 3 attempts
- Progressive backoff: 5 minutes, 15 minutes, 1 hour
- Suspends installment schedule after max retries

### 5. `calculate_next_payment_date()`

Calculates the due date for the next payment.

**Parameters:**
- `start_date` (datetime): Initial payment date
- `payment_number` (int): Which payment (1st, 2nd, etc.)
- `interval` (str, optional): 'monthly', 'weekly', 'daily' (default: 'monthly')

**Returns:**
```python
datetime  # Next payment due date
```

## Integration with Models

The service integrates seamlessly with existing models:

### RecurringPaymentToken
```python
from shop.models import RecurringPaymentToken

token = RecurringPaymentToken.objects.create(
    user=user,
    flutterwave_token="flw_t_xxxx",
    card_last_four="4242",
    card_brand="Visa",
    authorization_url="https://checkout.flutterwave.com/v3/pay/xxxxx",
    is_authorized=True,
    gateway='flutterwave'
)
```

### InstallmentSchedule
```python
from shop.models import InstallmentSchedule
from dateutil.relativedelta import relativedelta

schedule = InstallmentSchedule.objects.create(
    order=order,
    payment_token=token,
    installment_amount=Decimal("250.00"),
    total_installments=4,
    start_date=timezone.now().date(),
    next_payment_date=(timezone.now() + relativedelta(months=1)).date(),
    final_payment_date=(timezone.now() + relativedelta(months=4)).date(),
    auto_retry=True,
    max_retries=3
)
```

### ScheduledPayment
```python
from shop.models import ScheduledPayment

payment = ScheduledPayment.objects.create(
    installment_schedule=schedule,
    order=order,
    amount=Decimal("250.00"),
    installment_number=1,
    scheduled_date=timezone.now().date(),
    due_date=(timezone.now() + relativedelta(months=1)).date()
)

# After successful charge
payment.mark_as_successful(
    transaction_ref=result['transaction_ref'],
    gateway_response=result.get('gateway_response')
)
```

## Complete Workflow Example

```python
from decimal import Decimal
from django.utils import timezone
from dateutil.relativedelta import relativedelta
from shop.models import Order, RecurringPaymentToken, InstallmentSchedule, ScheduledPayment
from shop.flutterwave_service import FlutterwaveService

service = FlutterwaveService()

# Step 1: Customer initiates installment payment
def create_installment_order(user, items, months=3):
    order = Order.objects.create(
        user=user,
        total=sum(item.price for item in items),
        phone=user.phone,
        payment_type='installment',
        total_installments=months
    )
    
    # Add items to order
    for item in items:
        OrderItem.objects.create(order=order, product=item, quantity=1)
    
    return order


# Step 2: Initiate first payment (for authorization)
def initiate_payment_flow(order):
    result = service.initiate_payment(
        amount=order.total / order.total_installments,
        email=order.user.email,
        phone=order.user.phone,
        order_reference=str(order.id),
        customer_name=order.user.get_full_name()
    )
    
    if result['status'] == 'success':
        return result['authorization_url']
    return None


# Step 3: Process authorization callback (webhook/redirect)
def process_authorization_callback(order, auth_code):
    # Get token from Flutterwave using auth_code
    # Save token for future charges
    token = RecurringPaymentToken.objects.create(
        user=order.user,
        flutterwave_token=auth_code,
        card_last_four="4242",
        card_brand="Visa",
        is_authorized=True,
        gateway='flutterwave'
    )
    
    # Create installment schedule
    installment_amount = order.total / Decimal(order.total_installments)
    schedule = InstallmentSchedule.objects.create(
        order=order,
        payment_token=token,
        installment_amount=installment_amount,
        total_installments=order.total_installments,
        next_payment_date=(timezone.now() + relativedelta(months=1)).date(),
        final_payment_date=(timezone.now() + relativedelta(months=order.total_installments)).date(),
        auto_retry=True,
        max_retries=3
    )
    
    # Create scheduled payment records for each installment
    for i in range(1, order.total_installments + 1):
        due_date = timezone.now().date() + relativedelta(months=i)
        ScheduledPayment.objects.create(
            installment_schedule=schedule,
            order=order,
            amount=installment_amount,
            installment_number=i,
            scheduled_date=timezone.now().date(),
            due_date=due_date
        )


# Step 4: Process scheduled payments (background task)
from celery import shared_task

@shared_task
def process_scheduled_payment(payment_id):
    payment = ScheduledPayment.objects.get(id=payment_id)
    schedule = payment.installment_schedule
    token = schedule.payment_token.flutterwave_token
    
    # Charge the card
    result = service.charge_recurring_card(
        amount=payment.amount,
        email=payment.order.user.email,
        token=token,
        order_reference=str(payment.order.id),
        installment_num=payment.installment_number
    )
    
    if result['status'] == 'success':
        # Mark as successful
        payment.mark_as_successful(
            transaction_ref=result['transaction_ref'],
            gateway_response=result.get('gateway_response')
        )
        
        # Update schedule
        schedule.mark_payment_complete()
        
        # If all payments done, mark order as paid
        if schedule.is_complete:
            payment.order.status = 'Paid'
            payment.order.save()
    else:
        # Handle failure with retry logic
        retry_result = service.handle_failed_payment(
            payment_obj=payment,
            error_msg=result['message'],
            auto_retry=True
        )
        
        if retry_result['retry_scheduled']:
            # Schedule retry
            process_scheduled_payment.apply_async(
                args=[payment.id],
                eta=retry_result['next_retry_date']
            )
```

## Error Handling

The service provides three exception types:

### FlutterwaveServiceError (Base)
General service errors - catch this for all service issues.

### FlutterwaveTimeoutError
API request timed out (30-second default).

### FlutterwaveNetworkError
Network connection failed.

All exceptions provide descriptive error messages.

## Testing

Run the test suite:

```bash
python manage.py test shop.tests.TestFlutterwaveServiceInit
python manage.py test shop.tests.TestInitiatePayment
python manage.py test shop.tests.TestChargeRecurringCard
python manage.py test shop.tests  # Run all tests
```

## Logging

The service logs all operations to Django's logger:

```python
import logging
logger = logging.getLogger('shop.flutterwave_service')
```

Configure in Django settings:
```python
LOGGING = {
    'loggers': {
        'shop.flutterwave_service': {
            'level': 'INFO',
            'handlers': ['console', 'file'],
        }
    }
}
```

## Production Deployment

1. **Update credentials to production keys:**
   ```env
   FLW_PUBLIC_KEY=pk_live_xxxxx
   FLW_SECRET_KEY=sk_live_xxxxx
   FLW_ENCRYPTION_KEY=FLWSECK_live_xxxxx
   ```

2. **Enable comprehensive logging**

3. **Set up Celery for background processing**

4. **Configure Flutterwave webhooks** for payment notifications

5. **Monitor retry queue** for failed payments needing intervention

6. **Set up alerts** for suspended installment schedules

## Features

✓ Card tokenization for recurring charges
✓ Automatic retry logic with progressive backoff
✓ Transaction verification
✓ Comprehensive error handling
✓ Network timeout protection
✓ Detailed logging
✓ Graceful error recovery
✓ Full test coverage
✓ Type hints for better IDE support
✓ Decimal precision for financial calculations

## Security Notes

- API credentials loaded from environment variables only
- No credentials stored in code
- HTTPS enforced for all Flutterwave API calls
- Transaction references use cryptographic UUIDs
- Proper error messages (no sensitive data exposed)
- Models support encrypted token storage (future enhancement)

## Support

For issues or questions:
1. Check the comprehensive logging output
2. Review test cases for usage examples
3. Verify environment variables are set correctly
4. Check Flutterwave API documentation at https://developer.flutterwave.com
