BIO.RE
Referral

Apply Coupon

Authenticated. Validates a coupon code against expiry, applicability, min purchase, total / per-user usage caps under a SELECT FOR UPDATE row lock. Returns the calculated discount.

POST /api/v1/referral/coupon/apply โ€” ๐Ÿ”‘ Bearer ยท Rate limit: 10 req / minute ยท Kill-switched

Validates a coupon code against the active rules + the user's prior usage, calculates the discount with Decimal arithmetic (no float rounding), and records a CouponUsage row โ€” all under a SELECT ... FOR UPDATE row lock so concurrent applications can't both pass the maxUses check.

TOCTOU-safe. The full validation chain (active flag, time window, applicability, min purchase, total uses, per-user uses) plus the CouponUsage insert run inside one transaction with the coupon row locked via SELECT ... FOR UPDATE. Two concurrent requests for the last available slot can't both succeed.

Discount is calculated, not stored as money. The endpoint returns the discount value (a number) โ€” the actual money side-effect happens in the calling flow (e.g. wallet load, message send) which deducts this amount from the transaction price. The server records the usage; the caller is responsible for actually applying the discount to their domain transaction.

Request

Body โ€” ApplyCouponDto

FieldTypeRequiredValidationNotes
codestringโœ“IsString()The coupon code to apply (case-sensitive โ€” server does NOT uppercase before lookup)
transactionAmountnumberโœ“IsNumber(), Min(1)The amount the coupon is being applied against, in dollars (NOT cents). Used for minPurchase check + percentage calculation.
appliesTostringโœ“IsString()What domain the coupon is being applied to (e.g. message). Server matches against Coupon.appliesTo ('BOTH' accepts any).
HeaderRequiredNotes
Authorization: Bearer <accessToken>โœ“JWT from POST /auth/login

Response

200 OK โ€” ApiResponseOf<ApplyCouponResponseDto>

{
  "success": true,
  "data": {
    "discount": 2.50
  }
}
FieldTypeNotes
discountnumberCalculated discount in dollars (already capped at transactionAmount โ€” won't exceed the input). 2 decimal places.

Errors

HTTPcode / i18nKeyPayloadReason
400referral.coupon.invalidโ€”Code unknown OR Coupon.active = false
400referral.coupon.not_activeโ€”Coupon.startsAt is in the future
400referral.coupon.expiredโ€”Coupon.endsAt is in the past
400referral.coupon.not_applicableโ€”Coupon.appliesTo !== 'BOTH' AND !== submitted appliesTo
400referral.coupon.min_purchase{ min }transactionAmount < Coupon.minPurchase
400referral.coupon.usage_limitโ€”Total CouponUsage count >= Coupon.maxUses
400referral.coupon.already_usedโ€”This user's CouponUsage count >= Coupon.perUserLimit (default 1)
400(DTO validation)โ€”Missing fields / transactionAmount < 1
401(guard)โ€”Missing / invalid bearer token
429(throttle)โ€”Rate limit exceeded (10 req/min)
503features.referral_disabledโ€”Admin kill switch REFERRAL is active

Side effects

Inside one transaction (timeout: 10s):

  1. Lock the coupon row โ€” SELECT ... FOR UPDATE on Coupon by code. Missing OR active = false โ†’ throw invalid.
  2. Time window โ€” reject if startsAt > now (not_active) OR endsAt < now (expired).
  3. Applicability โ€” reject if coupon.appliesTo !== 'BOTH' && coupon.appliesTo !== submitted appliesTo.
  4. Minimum purchase โ€” reject if coupon.minPurchase set AND transactionAmount < minPurchase. Error carries { min } payload.
  5. Total usage cap โ€” couponUsage.count({ where: { couponId } }); if >= maxUses, throw usage_limit.
  6. Per-user cap โ€” couponUsage.count({ where: { couponId, userId } }); if >= perUserLimit (default 1), throw already_used.
  7. Discount calculation (Prisma.Decimal arithmetic):
    • FIXED type โ†’ discount = coupon.value.
    • PERCENTAGE type โ†’ discount = transactionAmount * (coupon.value / 100).
    • Cap at transaction amount โ€” discount = min(discount, transactionAmount).
    • Round to 2 decimal places.
  8. Record usage โ€” couponUsage.create({ id, couponId, userId, amount: discount }).

After the transaction commits, return { discount: discountDec.toNumber() }. Audit log: [coupon] Applied <code>: $<amount> off for user <userId>.

Code samples

curl -X POST https://api.bio.re/api/v1/referral/coupon/apply \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "code": "WELCOME20",
    "transactionAmount": 10,
    "appliesTo": "message"
  }'
type ApplyCouponInput = {
  code: string;
  transactionAmount: number;
  appliesTo: string;
};

type ApplyCouponResult = {
  discount: number;
};

async function applyCoupon(accessToken: string, input: ApplyCouponInput): Promise<ApplyCouponResult> {
  const res = await fetch('https://api.bio.re/api/v1/referral/coupon/apply', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(input),
  });
  const json = await res.json();
  if (!res.ok || !json.success) {
    throw Object.assign(new Error(json?.error?.message ?? 'Coupon apply failed'), {
      code: json?.error?.code,
      min: json?.error?.min, // present on 'min_purchase'
    });
  }
  return json.data;
}
import { useMutation } from '@tanstack/react-query';

export function useApplyCoupon() {
  return useMutation({
    mutationFn: async (input: ApplyCouponInput) => {
      const res = await fetch('/api/v1/referral/coupon/apply', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(input),
      });
      const json = await res.json();
      if (!res.ok || !json.success) {
        throw Object.assign(new Error(json?.error?.message ?? 'Coupon apply failed'), {
          code: json?.error?.code,
          i18nKey: json?.error?.i18nKey,
          min: json?.error?.min,
        });
      }
      return json.data as ApplyCouponResult;
    },
  });
}

Try it

POST
/api/v1/referral/coupon/apply
AuthorizationBearer <token>

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

curl -X POST "https://loading/api/v1/referral/coupon/apply" \  -H "Content-Type: application/json" \  -d '{    "code": "WELCOME20",    "transactionAmount": 10,    "appliesTo": "message"  }'
{
  "success": true,
  "data": {
    "discount": 10
  }
}
{
  "success": false,
  "error": {
    "code": "AUTH_UNAUTHORIZED",
    "message": "Invalid credentials",
    "i18nKey": "auth.login.invalid_credentials",
    "i18nVars": {
      "field": "email"
    },
    "details": [
      {
        "message": "email must be an email"
      }
    ],
    "correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}
{
  "success": false,
  "error": {
    "code": "AUTH_UNAUTHORIZED",
    "message": "Invalid credentials",
    "i18nKey": "auth.login.invalid_credentials",
    "i18nVars": {
      "field": "email"
    },
    "details": [
      {
        "message": "email must be an email"
      }
    ],
    "correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}
{
  "success": false,
  "error": {
    "code": "AUTH_UNAUTHORIZED",
    "message": "Invalid credentials",
    "i18nKey": "auth.login.invalid_credentials",
    "i18nVars": {
      "field": "email"
    },
    "details": [
      {
        "message": "email must be an email"
      }
    ],
    "correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}

Source

SourcePathLines
Controllerapps/api-core/src/modules/referral/referral.controller.ts66โ€“77 (applyCoupon)
DTO (request)apps/api-core/src/modules/referral/dto/index.ts5โ€“14 (ApplyCouponDto)
DTO (response)apps/api-core/src/modules/referral/dto/referral-response.dto.ts382โ€“385 (ApplyCouponResponseDto)
Serviceapps/api-core/src/modules/referral/referral.service.ts392โ€“442 (applyCoupon โ€” TOCTOU-safe transaction with row lock)
Prisma modelspackages/prisma/prisma/schema.prismaCoupon (code unique, type enum FIXED/PERCENTAGE, appliesTo, maxUses, perUserLimit, minPurchase, time window), CouponUsage (records each use), enum CouponType

On this page