# django-lark

Django integration for [Lark billing](https://uselark.ai).

## Installation

```bash
pip install django-lark
```

## Quick Start

1. Add `django_lark` to your `INSTALLED_APPS`:

```python
INSTALLED_APPS = [
    ...
    'django_lark',
]
```

2. Configure your Lark API key in `settings.py`:

```python
LARK_API_KEY = "lark_api_..."
```

Or set the `LARK_API_KEY` environment variable.

3. Include the URLs (optional, for customer portal):

```python
urlpatterns = [
    ...
    path('billing/', include('django_lark.urls')),
]
```

## Configuration

All settings are prefixed with `LARK_`:

| Setting | Default | Description |
|---------|---------|-------------|
| `LARK_API_KEY` | Required | Your Lark API key |
| `LARK_BASE_URL` | `https://api.uselark.ai` | Lark API base URL |
| `LARK_TIMEOUT` | `60.0` | Request timeout in seconds |
| `LARK_MAX_RETRIES` | `2` | Max retry attempts |
| `LARK_USER_SUBJECT_FIELD` | `email` | User field to use as external_id |
| `LARK_AUTO_CREATE_SUBJECTS` | `False` | Auto-create Lark subjects for users |

## How It Works

django-lark uses Lark's `external_id` feature to identify users without maintaining a local database mapping. When you create a subject in Lark with an `external_id`, you can use that same `external_id` in any API call that accepts a `subject_id`.

By default, django-lark uses the user's email as the `external_id`. You can customize this with the `LARK_USER_SUBJECT_FIELD` setting:

```python
# Use user's primary key
LARK_USER_SUBJECT_FIELD = "id"  # Results in "django_user_{pk}"

# Use email (default)
LARK_USER_SUBJECT_FIELD = "email"

# Use a custom field
LARK_USER_SUBJECT_FIELD = "uuid"

# Use a callable for full control
LARK_USER_SUBJECT_FIELD = lambda user: f"myapp_{user.organization_id}_{user.id}"
```

## Usage

### Client Access

```python
from django_lark import get_lark_client, get_async_lark_client

# Sync
client = get_lark_client()
subjects = client.subjects.list()

# Async
async def my_view(request):
    client = get_async_lark_client()
    subjects = await client.subjects.list()
```

### Get External ID for a User

```python
from django_lark.utils import get_external_id_for_user

external_id = get_external_id_for_user(user)
# Use this external_id in any Lark API call
```

### Create Lark Subjects for Users

```python
from django_lark.utils import get_or_create_subject_for_user

# Get or create a Lark subject for a Django user
subject, created = get_or_create_subject_for_user(user)
```

### Check Subscription Status

```python
from django_lark.utils import get_billing_state_for_user

billing_state = get_billing_state_for_user(user)
if billing_state.has_active_subscription:
    print("User has active subscription!")
```

### Create Subscriptions

```python
from django_lark.utils import create_subscription_for_user

# Create a subscription and redirect to checkout
response = create_subscription_for_user(
    user,
    rate_card_id="rc_pro",
    success_url="https://example.com/welcome/",
    cancelled_url="https://example.com/pricing/",
)

# Check if checkout is required
if response.result.result_type == "requires_action":
    return redirect(response.result.action.checkout_url)
else:
    # Subscription created directly (e.g., free plan or payment method on file)
    subscription = response.result.subscription

# With fixed rate quantities (e.g., seat-based pricing)
subscription = create_subscription_for_user(
    user,
    rate_card_id="rc_team",
    fixed_rate_quantities={"seats": 5},
    success_url="https://example.com/welcome/",
)

# With price multipliers (e.g., discounts)
subscription = create_subscription_for_user(
    user,
    rate_card_id="rc_pro",
    rate_price_multipliers={"seats": 0.8},  # 20% discount
    success_url="https://example.com/welcome/",
)
```

### Cancel Subscriptions

```python
from django_lark.utils import cancel_subscription, cancel_subscription_for_user

# Cancel by subscription ID (no ownership check)
cancelled = cancel_subscription("sub_abc123")

# Cancel with ownership verification (recommended)
cancelled = cancel_subscription_for_user(user, "sub_abc123")
```

### Change Rate Card (Upgrade/Downgrade)

```python
from django_lark.utils import change_subscription_rate_card, change_subscription_rate_card_for_user

# Change rate card by subscription ID (no ownership check)
response = change_subscription_rate_card(
    "sub_abc123",
    rate_card_id="rc_enterprise",
    success_url="https://example.com/upgraded/",
    cancelled_url="https://example.com/plans/",
    upgrade_behavior="prorate",  # or "rate_difference"
)

# Change with ownership verification (recommended)
response = change_subscription_rate_card_for_user(
    user,
    "sub_abc123",
    rate_card_id="rc_enterprise",
    success_url="https://example.com/upgraded/",
)

# Check if checkout is required (e.g., for payment)
if response.result.type == "requires_action":
    return redirect(response.result.action.checkout_url)
else:
    # Rate card changed directly
    subscription = response.result.subscription
```

The `upgrade_behavior` parameter controls how charges are calculated:
- `"prorate"` - Customer is charged for the prorated difference based on remaining time
- `"rate_difference"` - Customer is charged the full difference between rate cards

### View Decorators

```python
from django_lark.decorators import subscription_required

@subscription_required()
def premium_view(request):
    return render(request, 'premium.html')

@subscription_required(rate_card_ids=['rc_pro', 'rc_enterprise'])
def pro_view(request):
    return render(request, 'pro.html')

@subscription_required(redirect_url='/pricing/')
def feature_view(request):
    return render(request, 'feature.html')
```

### Template Tags

```django
{% load lark_tags %}

{% has_active_subscription as is_subscribed %}
{% if is_subscribed %}
    <p>Welcome, premium member!</p>
{% else %}
    <a href="/pricing/">Upgrade now</a>
{% endif %}

{% has_subscription_to_rate_card "rc_pro" as has_pro %}
{% if has_pro %}
    <p>Pro features unlocked!</p>
{% endif %}

{% get_subscriptions as subscriptions %}
{% for sub in subscriptions %}
    <p>{{ sub.rate_card_id }} -
       <span class="badge {{ sub.status|lark_subscription_status_badge }}">
           {{ sub.status }}
       </span>
    </p>
{% endfor %}

{% get_lark_external_id as external_id %}
```

### Customer Portal

Redirect users to the Lark customer portal:

```django
<a href="{% url 'django_lark:customer_portal' %}?return_url={{ request.path }}">
    Manage Subscription
</a>
```

### Checkout

Redirect users to Lark checkout to subscribe to a rate card:

```django
{# Link-based checkout #}
<a href="{% url 'django_lark:checkout' %}?rate_card_id=rc_pro&success_url=/welcome/">
    Subscribe to Pro
</a>

{# Form-based checkout #}
<form method="post" action="{% url 'django_lark:checkout' %}">
    {% csrf_token %}
    <input type="hidden" name="rate_card_id" value="rc_pro">
    <input type="hidden" name="success_url" value="/welcome/">
    <input type="hidden" name="cancelled_url" value="/pricing/">
    <button type="submit">Subscribe</button>
</form>
```

### Change Rate Card

Upgrade or downgrade a user's subscription to a different rate card:

```django
{# Link-based upgrade #}
<a href="{% url 'django_lark:change_rate_card' %}?subscription_id={{ subscription.id }}&rate_card_id=rc_enterprise&success_url=/upgraded/">
    Upgrade to Enterprise
</a>

{# Form-based upgrade with proration #}
<form method="post" action="{% url 'django_lark:change_rate_card' %}">
    {% csrf_token %}
    <input type="hidden" name="subscription_id" value="{{ subscription.id }}">
    <input type="hidden" name="rate_card_id" value="rc_enterprise">
    <input type="hidden" name="success_url" value="/upgraded/">
    <input type="hidden" name="cancelled_url" value="/plans/">
    <input type="hidden" name="upgrade_behavior" value="prorate">
    <button type="submit">Upgrade Plan</button>
</form>
```

## License

MIT
