Entitlement Model
How ShipNext represents DURATION, QUOTA, user_entitlements, and quota_transactions for subscriptions and credits.
This page builds on SaaS Model. ShipNext models "whether a user can use a resource" as an entitlement.
Current entitlement types:
| Type | Meaning | Typical use |
|---|---|---|
DURATION | Unlimited usage while valid, usually balance = -1 | Unlimited downloads or feature access during subscription |
QUOTA | Numeric balance that can be consumed | AI credits, generation count |
Business types live in src/modules/billing/types.ts. Database tables are defined in the SQLite and PostgreSQL payment schemas.
Configuration
Plan entitlements are configured in src/modules/billing/config/plan.ts under prices[].entitlements:
{
entitlementType: EntitlementType.Quota,
balance: 500,
priority: 10,
isRollOver: false,
resourceType: EntitlementResourceType.Credit,
refreshInterval: RefreshInterval.Monthly,
}Built-in resource types:
export enum EntitlementResourceType {
Storage = "STORAGE",
Credit = "CREDIT",
}To add a resource such as IMAGE_GENERATION, extend the enum and implement grant, consume, and display logic.
user_entitlements
Stores the user's currently available entitlements.
| Field | Description |
|---|---|
user_id | Owner user |
type | DURATION or QUOTA |
resource_type | Resource such as CREDIT or STORAGE |
balance | Remaining balance; DURATION usually uses -1 |
priority | Lower numbers are consumed first |
valid_from | Start timestamp |
valid_until | End timestamp; null means no expiration |
source | SUBSCRIPTION, TOPUP, or LIFETIME |
source_id | Subscription ID, order ID, or other source ID |
quota_transactions
Audit log for quota changes.
| action | Meaning |
|---|---|
BUY_TOPUP | Purchased additional quota |
SUB_GRANT | Subscription grant or renewal |
CONSUME | Quota consumed |
REFUND | Quota returned |
EXPIRE | Quota expired or reset |
Status can be PENDING, SUCCESS, or FAILED. Long-running jobs should reserve quota first, then commit or roll back depending on the result.
Grant flow
Webhook -> processPaymentWebhook
-> payment_subscriptions / payment_orders
-> grantSubscriptionEntitlement / handleSubscriptionRenewal
-> user_entitlements + quota_transactionsRules:
- New subscription: create entitlements from the current price.
- Renewal: if
isRollOver = true, add balance and extend validity; otherwise reset to the new cycle amount. - Upgrade: update by resource under the same
SUBSCRIPTION/sourceIdto avoid duplicate grants in the same cycle. - One-time purchase: use
TOPUPsource, usually without expiration unless your product defines one.
Consume flow
Call prepareQuota(userId, resourceType, amount):
- Check for a valid
DURATIONentitlement. If present, allow usage without writing quota deduction. - Query valid
QUOTAentitlements bypriority ASCand expiration. - If balance is enough, write
PENDINGtransactions and reserve balance. - On success, call
commitQuota(transactionId). - On failure, call
rollbackQuota(transactionId).
This is useful for AI generation, rendering, or batch jobs that may fail after work starts.
Common models
Subscription + quota
The built-in starter/pro examples grant CREDIT on each cycle.
Subscription + unlimited usage
Use EntitlementType.Duration with balance = -1.
One-time purchase + quota
Use a BillingModel.OneTime price and grant source = "TOPUP" when payment succeeds.
Storage quota
plan.storageLimit displays storage capacity in Dashboard. API enforcement depends on EntitlementResourceType.Storage. If only storageLimit is configured, users may see a limit while the upload API does not enforce it.
Checklist
- Provider Price IDs match
plan.ts. - Every paid price has expected entitlements.
- Successful subscription creates
user_entitlements. - Renewal rollover/reset behavior matches product rules.
- Consuming features use
prepare -> commit / rollback. - Displayed limits and enforced API limits come from aligned rules.