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​

  • Discount — z.infer<typeof discountSchema>
  • DiscountCreateInput — z.infer<typeof discountCreateInputSchema>
  • DiscountPatchInput — z.infer<typeof discountPatchInputSchema>
  • DiscountEligibilityInput — z.infer<typeof discountEligibilityInputSchema>
  • DiscountType — z.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