Skip to main content

Discount

Source: src/data/discount/

Service

createDiscountService / DiscountService

Factory: createDiscountService(repository: DiscountRepository) — returns DiscountService.

Owns discount lifecycle and deterministic price calculations. Framework-agnostic; talks to persistence through DiscountRepository.

MethodParametersReturnsDescription
getById(ctx: RequestContext, id: string)Promise<DiscountRecord | null>Returns a discount by id
listByTenant(ctx: RequestContext)Promise<DiscountRecord[]>Lists all discounts for the tenant
create(ctx: RequestContext, input: DiscountCreateInput)Promise<DiscountRecord>Creates a new discount; normalizes code to uppercase
update(ctx: RequestContext, id: string, patch: DiscountPatchInput)Promise<DiscountRecord>Updates a discount; normalizes code to uppercase
remove(ctx: RequestContext, id: string)Promise<void>Deletes a discount by id
evaluateEligibility(ctx: RequestContext, input: DiscountEvaluationInput)Promise<DiscountEvaluationResult>Evaluates discount eligibility, enforcing lifecycle, scope, and usage-limit rules; computes final pricing
consumeUsage(ctx: RequestContext, input: { discountId: string, orderId: string })Promise<DiscountUsageConsumeResult>Records usage of a discount for an order
resolveBestForCustomer(ctx: RequestContext, input: ResolveBestForCustomerInput)Promise<ResolveBestForCustomerResult>Finds the highest-value eligible discount for a customer from assignments + global pool
consumeAssignmentUsage(ctx: RequestContext, assignmentId: string)Promise<void>Increments usedCount on an assignment; no-op for forever rule
assignToCustomer(ctx: RequestContext, input: DiscountAssignmentCreateInput)Promise<DiscountAssignment>Creates or upserts a discount assignment for a customer (idempotent)
revokeAssignment(ctx: RequestContext, assignmentId: string)Promise<DiscountAssignment | null>Soft-revokes an assignment by setting expiresAt to the past; returns null if not found

DiscountEvaluationInput

FieldType
codestring
itemsDiscountEvaluationItem[]
customerIdstring (optional)
roleIdsstring[] (optional)
nowDate (optional)

DiscountEvaluationResult

FieldType
discountIdstring
discountCodestring (optional)
subtotalCentsnumber
discountAmountCentsnumber
totalCentsnumber

Repository

DiscountRepository

MethodParametersReturns
getById(ctx: RequestContext, id: string)Promise<DiscountRecord | null>
findByCode(ctx: RequestContext, code: string)Promise<DiscountRecord | null>
listByTenant(ctx: RequestContext)Promise<DiscountRecord[]>
create(ctx: RequestContext, input: DiscountCreateInput)Promise<DiscountRecord>
update(ctx: RequestContext, id: string, patch: DiscountPatchInput)Promise<DiscountRecord>
delete(ctx: RequestContext, id: string)Promise<void>
consumeUsage(ctx: RequestContext, input: DiscountUsageConsumeInput)Promise<DiscountUsageConsumeResult>
findActiveGlobalDiscounts(ctx: RequestContext)Promise<DiscountRecord[]> — all active, non-expired, globally-scoped discounts within the usage limit

DiscountUsageConsumeResult

Union type with status:

  • consumed — includes discount: DiscountRecord
  • already_consumed — includes discount: DiscountRecord
  • limit_reached
  • not_found

Validators

discountTypeSchema

Enum: PERCENT, AMOUNT

discountSchema

FieldTypeRequiredDefault
tenantIdstringYes
codestringNo
typediscountTypeSchemaYes
percentnumber (1–100)No
amountCentsnumber (int, ≥ 0)No
scope.appliesToAllbooleanNotrue
scope.productIdsstring[]No[]
scope.roleIdsstring[]No[]
scope.customerIdsstring[]No[]
lifecycle.isActivebooleanNotrue
lifecycle.startsAtDateNo
lifecycle.endsAtDateNo
lifecycle.usageLimitnumber (int, positive)No
lifecycle.usageCountnumber (int, ≥ 0)No0
lifecycle.consumedOrderIdsstring[]No[]
createdAtDate (coerced)Yes
updatedAtDate (coerced)Yes

Refinements:

  • percent is required when type is PERCENT
  • amountCents is required when type is AMOUNT
  • endsAt must be after startsAt

discountCreateInputSchema

Same as discountSchema but omits tenantId (from base), subtotalCents/totalCents, and lifecycle.usageCount/lifecycle.consumedOrderIds. Lifecycle is optional.

discountPatchInputSchema

All fields from discountSchema except tenantId, made partial. Includes same refinements.

discountEligibilityInputSchema

FieldTypeRequiredDefault
codestringYes
itemsArray<{ productId, quantity, unitPriceCents }>Yes
customerIdstringNo
roleIdsstring[]No[]
nowDateNo

discountErrorResponseSchema

FieldTypeRequired
codestringYes
messagestringYes

Inferred Types

  • Discountz.infer<typeof discountSchema>
  • DiscountCreateInputz.infer<typeof discountCreateInputSchema>
  • DiscountPatchInputz.infer<typeof discountPatchInputSchema>
  • DiscountEligibilityInputz.infer<typeof discountEligibilityInputSchema>
  • DiscountTypez.infer<typeof discountTypeSchema>

Assignment Repository

Additional DiscountRepository Methods (Assignments)

MethodParametersReturns
findAssignmentsForCustomer(ctx, customerId)Promise<DiscountAssignment[]>
createAssignment(ctx, input: DiscountAssignmentCreateInput)Promise<DiscountAssignment>
incrementAssignmentUsage(ctx, assignmentId)Promise<DiscountAssignment>
findAssignment(ctx, assignmentId)Promise<DiscountAssignment | null>
updateAssignment(ctx, assignmentId, patch)Promise<DiscountAssignment>
findActiveGlobalDiscounts(ctx)Promise<DiscountRecord[]> — active, non-expired, scope.appliesToAll=true discounts within usage limit

DiscountAssignment

FieldType
idstring
tenantIdstring
discountIdstring
customerIdstring
rule'one_time' | 'next_n' | 'forever'
totalAllowednumber | null
usedCountnumber
expiresAtDate | null
createdAtDate
updatedAtDate