Skip to main content

Entitlement

Source: src/data/entitlement/

Service

createEntitlementService / EntitlementService

Factory: createEntitlementService(deps: EntitlementServiceDeps) — returns EntitlementService.

Manages Discord entitlements and digital item grants for orders. Supports three Discord fulfillment types — role grants, emoji-role grants, and private channel permission overrides. Calls the Discord REST API to grant or revoke each type. Use DISCORD_API_BASE_URL env var to override the base URL (for testing).

MethodParametersReturnsDescription
generateEntitlementsForOrder(ctx: RequestContext, orderId: string)Promise<EntitlementRecord[]>Creates PENDING entitlement records for each product line item; idempotent
processEntitlements(ctx: RequestContext, orderId: string)Promise<void>Processes PENDING entitlements — grants Discord roles via REST; marks GRANTED or FAILED
revokeEntitlementsForOrder(ctx: RequestContext, orderId: string)Promise<void>Revokes GRANTED entitlements; removes Discord roles best-effort; marks REVOKED
retryEntitlement(ctx: RequestContext, entitlementId: string)Promise<EntitlementRecord>Retries a single FAILED entitlement; no-op if not FAILED

EntitlementServiceDeps

FieldTypeDescription
entitlementRepositoryEntitlementRepositoryPersistence layer
getOrder(ctx, orderId) => Promise<EntitlementOrder | null>Order lookup
getProduct(ctx, productId) => Promise<EntitlementProduct | null>Product lookup (reads grantedEntitlements; falls back to grantedRoleIds)
guildIdstringDiscord guild ID for role management
botTokenstringDiscord bot token

Event Subscriptions (not yet wired)

  • payment.confirmedgenerateEntitlementsForOrder + processEntitlements
  • order.refundedrevokeEntitlementsForOrder

Repository

EntitlementRepository

MethodParametersReturns
getById(ctx: RequestContext, id: string)Promise<EntitlementRecord | null>
listByTenant(ctx: RequestContext)Promise<EntitlementRecord[]>
listByOrder(ctx: RequestContext, orderId: string)Promise<EntitlementRecord[]>
create(ctx: RequestContext, record: Omit<EntitlementRecord, 'createdAt' | 'updatedAt'>)Promise<EntitlementRecord>
update(ctx: RequestContext, id: string, patch: Partial<...>)Promise<EntitlementRecord>
delete(ctx: RequestContext, id: string)Promise<void>

EntitlementRecord

FieldType
idstring
tenantIdstring
customerIdstring
orderIdstring
productIdstring
type'DISCORD_ROLE' | 'DISCORD_EMOJI' | 'CHANNEL_ACCESS' | 'DIGITAL_ITEM'
targetIdstring (optional — Discord role ID, channel ID, or item key)
status'PENDING' | 'GRANTED' | 'REVOKED' | 'FAILED'
grantedAtDate (optional)
revokedAtDate (optional)
failedReasonstring (optional)
createdAtDate
updatedAtDate

EntitlementType

ValueDiscord REST callAdmin label
DISCORD_ROLEPUT /guilds/{guildId}/members/{userId}/roles/{targetId}Discord Role
DISCORD_EMOJISame as DISCORD_ROLE — emoji access is role-gatedCustom Emoji Access
CHANNEL_ACCESSPUT /channels/{targetId}/permissions/{userId} with allow: "3072" (VIEW + SEND)Private Channel Access
DIGITAL_ITEMNo Discord REST callDigital Item

Validators

entitlementTypeSchema

Enum: DISCORD_ROLE, DISCORD_EMOJI, CHANNEL_ACCESS, DIGITAL_ITEM

entitlementStatusSchema

Enum: PENDING, GRANTED, REVOKED, FAILED

entitlementSchema

FieldTypeRequiredDefault
tenantIdstringYes
userIdstringYes
orderIdstringYes
productIdstringYes
typeentitlementTypeSchemaYes
roleIdstringNo
statusentitlementStatusSchemaNo'PENDING'
metadataRecord<string, unknown>No{}
grantedAtDate (coerced)No
createdAtDate (coerced)Yes
updatedAtDate (coerced)Yes

Inferred Types

  • Entitlementz.infer<typeof entitlementSchema>
  • EntitlementTypez.infer<typeof entitlementTypeSchema>
  • EntitlementStatusz.infer<typeof entitlementStatusSchema>