django-lark
Django integration for Lark billing.
Installation
pip install django-lark
Quick Start
- Add
django_larkto yourINSTALLED_APPS:
INSTALLED_APPS = [
...
'django_lark',
]
- Configure your Lark API key in
settings.py:
LARK_API_KEY = "lark_api_..."
Or set the LARK_API_KEY environment variable.
- 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
Release files for django-lark 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_lark-0.3.0.tar.gz | 25.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_lark-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 44.4 kB
Release files / django_lark-0.3.0.tar.gz
| Download URL | django_lark-0.3.0.tar.gz |
|---|---|
| Size | 25.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
181b3446bc989e80533d2ba3eafe7605fcb8121baf81bb507a4ab3e3c164ef6c
|
|
BLAKE2b-256 checksum How to use checksums |
676b7882a20574ffa19c52dc71e141a4e0fdeb7c94b432827b9e4c9a5ecef635
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 23, 2026.
Transparency logRelease files / django_lark-0.3.0-py3-none-any.whl
| Download URL | django_lark-0.3.0-py3-none-any.whl |
|---|---|
| Size | 19.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a6bd3d747063599527e4b584ad92c5288827ed830a5b91ac51693f9246889009
|
|
BLAKE2b-256 checksum How to use checksums |
836216811b70d997a99dc6089c69ea4492272a8a2fefe6decc0655da963909a1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 23, 2026.
Transparency log