Skip to main content

Django integration for Lark billing

Project description

django-lark

Django integration for Lark billing.

Installation

pip install django-lark

Quick Start

  1. Add django_lark to your INSTALLED_APPS:
INSTALLED_APPS = [
    ...
    'django_lark',
]
  1. Configure your Lark API key in settings.py:
LARK_API_KEY = "lark_api_..."

Or set the LARK_API_KEY environment variable.

  1. Include the URLs (optional, for customer portal):
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:

# 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

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

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

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

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

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

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)

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

Usage Reporting

Record usage events for metered/usage-based billing:

import uuid
from django_lark.utils import record_usage, record_usage_for_user

# Record usage for a user (uses their external_id)
record_usage_for_user(
    user,
    event_name="api_calls",
    data={"count": 100},
    idempotency_key=str(uuid.uuid4()),
)

# Record usage with explicit subject_id
record_usage(
    subject_id="user_123",
    event_name="compute_hours",
    data={"hours": "2.5", "instance_type": "gpu"},
    idempotency_key=f"job_{job_id}",
)

# With timestamp (for backdated events)
from datetime import datetime, timezone

record_usage_for_user(
    user,
    event_name="storage_gb",
    data={"amount": "50.5"},
    idempotency_key=f"storage_{user.pk}_{date}",
    timestamp=datetime.now(timezone.utc),
)

The idempotency_key ensures the same event isn't processed multiple times. Use a unique, deterministic key based on the event (e.g., f"job_{job_id}" or f"{user_id}_{event}_{timestamp}").

View Decorators

from django_lark.decorators import subscription_required, usage_within_limits, track_usage

@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')

# Block access if user has exceeded included usage limits
@usage_within_limits()
def metered_api(request):
    return JsonResponse({"result": "ok"})

@usage_within_limits(redirect_url='/upgrade/')
def limited_api(request):
    return JsonResponse({"result": "ok"})

# Track usage automatically on successful responses
@track_usage("api_calls")
def my_api_view(request):
    return JsonResponse({"result": "ok"})

# With custom usage data
@track_usage("api_calls", data={"endpoint": "weather_reports"})
def weather_reports_api(request):
    return JsonResponse({"weather_reports": []})

# Dynamic data based on request/response
@track_usage(
    "tokens_used",
    data=lambda req, res, *a, **kw: {"input_tokens": getattr(res, "input_tokens")}
)
def llm_api(request):
    return JsonResponse({"response": "Hello!"})

# Custom idempotency key
@track_usage(
    "file_uploads",
    idempotency_key=lambda req, res, *a, **kw: f"upload_{req.POST['file_id']}"
)
def upload_file(request):
    return JsonResponse({"status": "uploaded"})

# Combine decorators: require subscription, check usage limits, then track usage
@subscription_required(rate_card_ids=['rc_pro'])
@usage_within_limits(redirect_url='/upgrade/')
@track_usage("premium_api_calls")
def premium_api(request):
    return JsonResponse({"premium": True})

Template Tags

{% 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:

<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:

{# 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:

{# 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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_lark-0.3.0.tar.gz (25.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_lark-0.3.0-py3-none-any.whl (19.1 kB view details)

Uploaded Python 3

File details

Details for the file django_lark-0.3.0.tar.gz.

File metadata

  • Download URL: django_lark-0.3.0.tar.gz
  • Upload date:
  • Size: 25.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for django_lark-0.3.0.tar.gz
Algorithm Hash digest
SHA256 181b3446bc989e80533d2ba3eafe7605fcb8121baf81bb507a4ab3e3c164ef6c
MD5 739d1ab7ca7606ced8dee4b7137a782d
BLAKE2b-256 676b7882a20574ffa19c52dc71e141a4e0fdeb7c94b432827b9e4c9a5ecef635

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_lark-0.3.0.tar.gz:

Publisher: publish.yml on uselark/django-lark

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_lark-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: django_lark-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 19.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for django_lark-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a6bd3d747063599527e4b584ad92c5288827ed830a5b91ac51693f9246889009
MD5 55e45dc9564fddd53af9e823fc266c92
BLAKE2b-256 836216811b70d997a99dc6089c69ea4492272a8a2fefe6decc0655da963909a1

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_lark-0.3.0-py3-none-any.whl:

Publisher: publish.yml on uselark/django-lark

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page