Promotion Management Endpoints Index

Promotion Management

GET /api/promotions

List all promotions with optional filtering (requires administrator access)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...

Query Parameters:

  • sort - JSON array with field name and direction ["fieldName","ASC|DESC"]
    Example: sort=["name","ASC"] - Sort by name in ascending order
  • page - Page number (1-based)
    Example: page=1 - Get the first page
  • perPage - Number of items per page
    Example: perPage=10 - Show 10 items per page
  • filter - JSON object with field name/value pairs for filtering {"fieldName1":"value1","fieldName2":value2}
    Example: filter={"name":"Summer"} - Filter promotions with name containing "Summer"

    Special filter: Global Search
    Using the key q in the filter will search across all text columns:
    Example: filter={"q":"summer","active":true} - Find active promotions with "summer" in any text field
  • active - Filter by active status (true/false)
  • syncWithStripe - Whether to sync with Stripe before fetching data (default: true)

Example Requests:

GET /api/promotions?page=1&perPage=10&sort=["name","ASC"]&filter={"active":true}&syncWithStripe=true
GET /api/promotions?page=1&perPage=20&filter={"q":"summer"}

Response (200 OK):

{
    "data": [
        {
            "id": 1,
            "name": "Summer Sale",
            "description": "20% off summer products",
            "stripePromotionCodeId": "promo_1NcL5GJ8oAJtHC9rnvmaJ4Hl",
            "code": "SUMMER20",
            "couponId": 1,
            "maxRedemptions": 100,
            "redemptionCount": 45,
            "validUntil": "2023-09-30T23:59:59Z",
            "isActive": true,
            "createdAt": "2023-07-15T14:25:00Z",
            "updatedAt": null
        }
        // Additional promotion objects...
    ],
    "total": 2
}

Headers:

Content-Range: promotions 0-1/2
Accept-Range: promotions
Access-Control-Expose-Headers: Content-Range
X-Total-Count: 2

Error Responses:

{
    "message": "Administrator access required"
}

Notes:

  • The syncWithStripe parameter ensures promotion data is synchronized with Stripe before filtering
  • The response includes pagination metadata in headers
  • Search functionality looks across name, description, code, and Stripe promotion code ID fields
  • Only administrators can access this endpoint
GET /api/promotions/applied

List all applied promotions with filtering, sorting, and pagination (requires administrator access)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...

Query Parameters:

  • sort - JSON array with field name and direction ["fieldName","ASC|DESC"]
    Allowed sort fields: applied_at, promotion_name, promotion_code, username, email, plan_name
    Example: sort=["applied_at","DESC"] - Sort by application date descending
  • page - Page number (1-based)
    Example: page=1 - Get the first page
  • perPage - Number of items per page
    Example: perPage=10 - Show 10 items per page
  • filter - JSON object with field name/value pairs for filtering
    Example: filter={"userId":42,"startDate":"2023-06-01","endDate":"2023-08-31"}

    Available filters:
    • q - Global search across promotion names, codes, usernames, and emails
    • userId - Filter by specific user ID
    • promotionId - Filter by specific promotion ID
    • subscriptionId - Filter by specific subscription ID
    • startDate - Filter by applied date (YYYY-MM-DD format)
    • endDate - Filter by applied date (YYYY-MM-DD format)

Example Requests:

GET /api/promotions/applied?page=1&perPage=10&sort=["applied_at","DESC"]&filter={"userId":42}
GET /api/promotions/applied?page=1&perPage=20&filter={"q":"summer","startDate":"2023-06-01","endDate":"2023-08-31"}

Response (200 OK):

{
    "data": [
        {
            "id": 1,
            "appliedAt": "2023-07-15T14:25:00Z",
            "promotionId": 5,
            "promotionName": "Summer Sale",
            "promotionCode": "SUMMER20",
            "couponId": 1,
            "couponType": "DISC",
            "couponTypeName": "Discount Percentage",
            "percentOff": 20,
            "trialDays": null,
            "subscriptionId": 18,
            "planName": "Premium Monthly",
            "planInterval": "month",
            "planAmount": 2999,
            "planCurrency": "usd",
            "userId": 42,
            "username": "johndoe",
            "email": "john@example.com"
        },
        {
            "id": 2,
            "appliedAt": "2023-07-10T09:15:00Z",
            "promotionId": 6,
            "promotionName": "Early Adopter",
            "promotionCode": "EARLY25",
            "couponId": 2,
            "couponType": "DISC",
            "couponTypeName": "Discount Percentage",
            "percentOff": 25,
            "trialDays": null,
            "subscriptionId": 24,
            "planName": "Pro Annual",
            "planInterval": "year",
            "planAmount": 29999,
            "planCurrency": "usd",
            "userId": 37,
            "username": "janesmith",
            "email": "jane@example.com"
        }
    ],
    "total": 42
}

Headers:

Content-Range: applied-promotions 0-1/42
Accept-Range: applied-promotions
Access-Control-Expose-Headers: Content-Range
X-Total-Count: 42

Error Responses:

{
    "message": "Administrator access required"
}

{
    "message": "Invalid date format in filter. Use YYYY-MM-DD format."
}

Notes:

  • This endpoint provides comprehensive details about applied promotions, including information about the user, promotion, coupon, and subscription
  • The default sorting is by applied_at in descending order (newest first)
  • Date filters accept dates in ISO format (YYYY-MM-DD)
  • Text search looks for matches in promotion names, promotion codes, usernames, and email addresses
  • Only administrators can access this endpoint
  • Plan amounts are in cents (e.g., 2999 = $29.99)
GET /api/promotions/:id

Get details of a specific promotion by ID (requires administrator access)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...

Query Parameters:

  • includeCoupons - Whether to include associated coupon details (true/false)

Response (200 OK):

{
    "id": 1,
    "name": "Summer Sale 2023",
    "description": "Special discounts for summer products",
    "stripePromotionCodeId": "promo_1NcL5GJ8oAJtHC9rnvmaJ4Hl",
    "code": "SUMMER20",
    "couponId": 1,
    "maxRedemptions": 100,
    "redemptionCount": 45,
    "validUntil": "2023-09-30T23:59:59Z",
    "isActive": true,
    "createdAt": "2023-05-15T00:00:00Z",
    "updatedAt": null,
    "coupon": {
        "id": 1,
        "stripeCouponId": "Q9Wpvk4o",
        "name": "Welcome Discount",
        "couponTypeCode": "DISC",
        "couponTypeName": "Discount Percentage",
        "discountPercentage": 20,
        "trialDays": null,
        "couponDurationTypeCode": "ONCE",
        "couponDurationTypeName": "Once",
        "durationInMonths": null,
        "isActive": true,
        "createdAt": "2023-01-15T08:30:00Z",
        "updatedAt": null
    }
}

Error Responses:

{
    "message": "Administrator access required"
}

{
    "message": "Invalid promotion ID"
}

{
    "message": "Promotion not found"
}

Notes:

  • This endpoint syncs the specific promotion with Stripe before returning the data
  • The coupon field is only included when includeCoupons=true
GET /api/promotions/:id/coupons

Get coupons associated with a promotion (requires administrator access)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...

Response (200 OK):

[
    {
        "id": 1,
        "stripeCouponId": "Q9Wpvk4o",
        "name": "Welcome Discount",
        "couponTypeCode": "DISC",
        "couponTypeName": "Discount Percentage",
        "discountPercentage": 20,
        "trialDays": null,
        "couponDurationTypeCode": "ONCE",
        "couponDurationTypeName": "Once",
        "durationInMonths": null,
        "isActive": true,
        "createdAt": "2023-01-15T08:30:00Z",
        "updatedAt": null,
        "timesRedeemed": 45
    }
]

Error Responses:

{
    "message": "Administrator access required"
}

{
    "message": "Invalid promotion ID"
}

{
    "message": "Promotion not found"
}

Notes:

  • This endpoint syncs the promotion with Stripe before returning coupon data
  • Each promotion is typically associated with one coupon
  • The timesRedeemed field comes from Stripe data
GET /api/promotions/validate/:code

Validate a promotion code without applying it (requires authentication)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...

Response (200 OK) - Valid Promotion:

{
    "valid": true,
    "promotion": {
        "id": 1,
        "name": "Summer Sale",
        "description": "20% off summer products",
        "code": "SUMMER20",
        "coupon": {
            "type": "DISC",
            "name": "Summer Discount",
            "discountPercentage": 20,
            "trialDays": null
        }
    }
}

Response (200 OK) - Invalid Promotion:

{
    "valid": false,
    "message": "Promotion code not found"
}

Error Responses:

{
    "message": "Authentication required"
}

{
    "message": "Promotion code is required"
}

{
    "message": "Promotion code is inactive"
}

{
    "message": "Promotion code has expired"
}

{
    "message": "Maximum redemptions reached for this promotion code"
}

Notes:

  • This endpoint syncs the promotion with Stripe before validation
  • Validates against both local database and Stripe data
  • Does not apply the promotion, only validates its eligibility
POST /api/promotions

Create a new promotion code for an existing coupon (requires administrator access and CSRF token)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...
x-csrf-token: a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3

Request Body:

{
    "name": "Holiday Special",
    "description": "Special promotions for the holiday season",
    "code": "HOLIDAY25",
    "couponId": 1,
    "maxRedemptions": 500,
    "validUntil": "2023-12-31T23:59:59Z",
    "isActive": true
}

Response (201 Created):

{
    "message": "Promotion created successfully",
    "promotion": {
        "id": 3,
        "name": "Holiday Special",
        "description": "Special promotions for the holiday season",
        "stripePromotionCodeId": "promo_1NcL5GJ8oAJtHC9rnvmaJ4Hl",
        "code": "HOLIDAY25",
        "couponId": 1,
        "maxRedemptions": 500,
        "redemptionCount": 0,
        "validUntil": "2023-12-31T23:59:59Z",
        "isActive": true,
        "createdAt": "2023-11-01T00:00:00Z",
        "updatedAt": null
    }
}

Error Responses:

{
    "message": "Administrator access required"
}

{
    "message": "Missing required fields: name and description are required"
}

{
    "message": "Promotion code already exists"
}

{
    "message": "Coupon not found"
}

{
    "message": "Failed to create promotion",
    "error": "Error creating promotion in Stripe"
}

Notes:

  • The promotion is created in both Stripe and the local database
  • The couponId must reference an existing coupon
  • The code must be unique across all promotions
POST /api/promotions/apply

Apply a promotion code to the user's subscription (requires authentication and CSRF token)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...
x-csrf-token: a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3

Request Body:

{
    "code": "SUMMER20"
}

Response (200 OK) - Percentage Discount:

{
    "message": "Promotion code applied successfully",
    "discount": {
        "type": "percentage",
        "percentOff": 20,
        "amountOff": null,
        "currency": null
    }
}

Response (200 OK) - Amount Discount:

{
    "message": "Promotion code applied successfully",
    "discount": {
        "type": "amount",
        "percentOff": null,
        "amountOff": 500,
        "currency": "usd"
    }
}

Error Responses:

{
    "message": "Authentication required"
}

{
    "message": "Promotion code is required"
}

{
    "message": "Promotion code not found"
}

{
    "message": "Promotion code is inactive"
}

{
    "message": "Promotion code has expired"
}

{
    "message": "Maximum redemptions reached for this promotion code"
}

{
    "message": "You must have an active subscription to apply a promotion code"
}

{
    "message": "Your subscription already has a promotion code applied"
}

Notes:

  • User must have an active subscription to apply promotions
  • Only one promotion can be active per subscription
  • The promotion is applied immediately to the Stripe subscription
  • Amount discounts are in cents (e.g., 500 = $5.00)
PUT /api/promotions/:id

Update a promotion's details (requires administrator access and CSRF token)

Note: Only certain fields can be updated. The code, couponId, and stripePromotionCodeId cannot be changed once created.

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...
x-csrf-token: a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3

Request Body:

{
    "name": "Summer Sale 2023 Extended",
    "description": "Extended summer discounts for clearance",
    "isActive": true
}

Response (200 OK):

{
    "message": "Promotion updated successfully",
    "promotion": {
        "id": 1,
        "name": "Summer Sale 2023 Extended",
        "description": "Extended summer discounts for clearance",
        "stripePromotionCodeId": "promo_1NcL5GJ8oAJtHC9rnvmaJ4Hl",
        "code": "SUMMER20",
        "couponId": 1,
        "maxRedemptions": 200,
        "redemptionCount": 45,
        "validUntil": "2023-09-30T23:59:59Z",
        "isActive": true,
        "createdAt": "2023-05-15T00:00:00Z",
        "updatedAt": "2023-09-01T00:00:00Z"
    }
}

Error Responses:

{
    "message": "Administrator access required"
}

{
    "message": "Invalid promotion ID"
}

{
    "message": "No update parameters provided"
}

{
    "message": "Promotion not found"
}

{
    "message": "Failed to update promotion",
    "error": "Error updating promotion in Stripe"
}

Notes:

  • Updates are synchronized with Stripe before and after the operation
  • Only name, description, maxRedemptions, and isActive can be updated
  • maxRedemptions is updated only in the local database, not sent to Stripe
  • isActive updates are sent to Stripe as the active field
DELETE /api/promotions/:id

Delete a promotion (requires administrator access and CSRF token)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...
x-csrf-token: a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3

Response (200 OK):

{
    "message": "Promotion deleted successfully"
}

Error Responses:

{
    "message": "Administrator access required"
}

{
    "message": "Invalid promotion ID"
}

{
    "message": "Promotion not found"
}

{
    "message": "Cannot delete promotion with associated coupons. Remove all coupons first."
}

Notes:

  • Deletion is synchronized with Stripe - the promotion is removed from both systems
  • This is a permanent operation and cannot be undone
  • The promotion is synced with Stripe before deletion to ensure data consistency