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.
| Method | Parameters | Returns | Description |
|---|---|---|---|
| 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
| Field | Type |
|---|---|
| code | string |
| items | DiscountEvaluationItem[] |
| customerId | string (optional) |
| roleIds | string[] (optional) |
| now | Date (optional) |
DiscountEvaluationResult
| Field | Type |
|---|---|
| discountId | string |
| discountCode | string (optional) |
| subtotalCents | number |
| discountAmountCents | number |
| totalCents | number |
Repository
DiscountRepository
| Method | Parameters | Returns |
|---|---|---|
| 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— includesdiscount: DiscountRecordalready_consumed— includesdiscount: DiscountRecordlimit_reachednot_found
Validators
discountTypeSchema
Enum: PERCENT, AMOUNT
discountSchema
| Field | Type | Required | Default |
|---|---|---|---|
| tenantId | string | Yes | — |
| code | string | No | — |
| type | discountTypeSchema | Yes | — |
| percent | number (1–100) | No | — |
| amountCents | number (int, ≥ 0) | No | — |
| scope.appliesToAll | boolean | No | true |
| scope.productIds | string[] | No | [] |
| scope.roleIds | string[] | No | [] |
| scope.customerIds | string[] | No | [] |
| lifecycle.isActive | boolean | No | true |
| lifecycle.startsAt | Date | No | — |
| lifecycle.endsAt | Date | No | — |
| lifecycle.usageLimit | number (int, positive) | No | — |
| lifecycle.usageCount | number (int, ≥ 0) | No | 0 |
| lifecycle.consumedOrderIds | string[] | No | [] |
| createdAt | Date (coerced) | Yes | — |
| updatedAt | Date (coerced) | Yes | — |
Refinements:
percentis required whentypeisPERCENTamountCentsis required whentypeisAMOUNTendsAtmust be afterstartsAt
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
| Field | Type | Required | Default |
|---|---|---|---|
| code | string | Yes | — |
| items | Array<{ productId, quantity, unitPriceCents }> | Yes | — |
| customerId | string | No | — |
| roleIds | string[] | No | [] |
| now | Date | No | — |
discountErrorResponseSchema
| Field | Type | Required |
|---|---|---|
| code | string | Yes |
| message | string | Yes |
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)
| Method | Parameters | Returns |
|---|---|---|
| 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
| Field | Type |
|---|---|
| id | string |
| tenantId | string |
| discountId | string |
| customerId | string |
| rule | 'one_time' | 'next_n' | 'forever' |
| totalAllowed | number | null |
| usedCount | number |
| expiresAt | Date | null |
| createdAt | Date |
| updatedAt | Date |