Shop It Docs
Developer Resourcescare-package

Care Package Redemption Requests

Redemption request state machine, feature quota enforcement, usage tracking, and admin status transitions.

Care Package Redemption Requests

1. Redemption Request State Machine

2. VALID_STATUS_TRANSITIONS

Defined in care-package-redemption-admin.service.ts:

const VALID_STATUS_TRANSITIONS: Record<CarePackageRedemptionStatus, CarePackageRedemptionStatus[]> = {
  pending:     ["accepted", "rejected", "in_progress"],
  in_progress: ["completed", "rejected"],
  accepted:    ["in_progress", "completed"],
  completed:   [],
  rejected:    [],
  cancelled:   [],
};

Table form:

FromAllowed transitions
pendingaccepted, rejected, in_progress
in_progresscompleted, rejected
acceptedin_progress, completed
completednone (terminal)
rejectednone (terminal)
cancellednone (terminal; customer-only from pending)

3. Feature Quota Enforcement (Raise-Time)

CarePackageRedemptionCustomerService.raiseRequest() checks at request creation time:

  1. Subscription must be active.
  2. Feature must exist in carePackageSnapshot.features[] with isEnabled = true.
  3. If feature.isUnlimited = true: skip quota check.
  4. If feature.isUnlimited = false:
    • Load carePackageFeatureUsage row for (subscriptionId, featureType).
    • If usage.usedCount >= feature.usageCount: throw CARE_PACKAGE_REDEMPTION_QUOTA_EXCEEDED.
    • If no usage row exists: usedCount is treated as 0 (row created on first accepted transition).

4. Feature Usage Tracking

Table: care_package_feature_usage

  • subscriptionId (FK), featureType (enum) — unique pair
  • usedCount — incremented by 1 on each accepted admin transition (not on raise)

Increment path — admin accepted transition in CarePackageRedemptionAdminService.updateStatus():

DB transaction:
  UPDATE care_package_redemption_requests SET status = 'accepted' WHERE id = :id
  UPDATE care_package_feature_usage
    SET used_count = used_count + 1, updated_at = now()
    WHERE subscription_id = :subscriptionId AND feature_type = :featureType

The usage row must exist (created at subscription creation or first use). The transaction ensures atomicity: if usage increment fails, request status is not updated.

5. Terminal Status Handling

When status becomes completed or rejected:

  • resolvedAt is set to now() on the redemption request row.
  • No feature usage adjustment for rejected (usage was already incremented on accepted; no rollback).

When status advances without accept (pending → in_progress):

  • Feature usage is NOT incremented at in_progress — only at accepted.

6. Customer Cancel

CarePackageRedemptionCustomerService.cancel():

  • Guard: if (request.status !== "pending") → throw CARE_PACKAGE_REDEMPTION_CANCEL_NOT_PENDING
  • Sets: status = "cancelled", resolvedAt = now(), updatedAt = now()
  • Does not adjust feature usage (none was allocated on pending).

7. Ownership Verification

Customer findOwnedRequest():

  • First checks request exists via subscriptionId — if not found: CARE_PACKAGE_REDEMPTION_NOT_FOUND.
  • Then checks request.customerId !== customerId — if mismatch: CARE_PACKAGE_REDEMPTION_NOT_OWNED.

Note: Unlike subscription ownership (which uses a single WHERE clause), redemption uses a two-step check because the request has no direct customerId + id compound index — only subscriptionId is indexed.

8. QA Checklist

  • Raise validates active subscription, enabled feature, quota.
  • isUnlimited = true features bypass quota.
  • Admin accepted transition increments usedCount in a single DB transaction.
  • completed and rejected transitions set resolvedAt.
  • Customer cancel only works on pending status.
  • VALID_STATUS_TRANSITIONS blocks all invalid transitions with CARE_PACKAGE_REDEMPTION_INVALID_STATUS_TRANSITION.