Skip to content

[BUG]: Plans and taxes accept negative amounts while coupons and add-ons correctly reject them #5868

Description

@avisprince

Describe the bug
Lago's numericality validation is applied inconsistently across entity types. Coupons reject negative amount_cents with 422 (value_is_out_of_range), and add-ons do the same. But plans and taxes have no equivalent check — negative values are stored and flow into billing calculations. The coupon application override path also bypasses the validation that exists on coupon creation.

  1. Plans accept negative amountsamount_cents: -100 returns 200. Subscribed customer would receive credits instead of being charged.

  2. Taxes accept negative ratesrate: "-10.0" returns 200. A negative tax rate subtracts from invoice totals. A rate of -100 would zero out every invoice.

  3. Coupon application override bypasses validation — A valid 20% coupon applied with percentage_rate: "-50.0" override returns 200. The applied coupon surcharges instead of discounts. Also accepts "200.0" override.

  4. Plans accept negative trial periodstrial_period: -30 returns 200. No real-world meaning; could cause undefined behavior in billing period calculations.

  5. Coupon percentage rate can be negative at creationpercentage_rate: "-25.0" on a percentage coupon returns 200. Note: amount_cents on the same model correctly rejects negatives.

  6. Plans accept negative minimum commitment amountsminimum_commitment: {amount_cents: -1000} returns 200. Inverts the billing floor logic.

To Reproduce
Environment:

  • Lago API (getlago/api:v1.48.1)
  • PostgreSQL 15 with pg_partman
  • Redis
  • Env vars: LAGO_DISABLE_SEGMENT=true, LAGO_DISABLE_WALLET_REFRESH=true

Setup:

Organization with API key (via LAGO_CREATE_ORG=true), a customer.

Control — negative coupon amount correctly rejected:

POST /api/v1/coupons
Authorization: Bearer {api_key}
Content-Type: application/json

{
  "coupon": {
    "name": "Negative Amount Coupon",
    "code": "neg-amount-coupon",
    "coupon_type": "fixed_amount",
    "amount_cents": -500,
    "amount_currency": "USD",
    "frequency": "once",
    "expiration": "no_expiration",
    "reusable": true
  }
}

Returns 422 with value_is_out_of_range. Same for negative add-on amounts.

Bug 1 — negative plan amount:

POST /api/v1/plans
Authorization: Bearer {api_key}
Content-Type: application/json

{
  "plan": {
    "name": "Negative Plan",
    "code": "neg-plan-001",
    "interval": "monthly",
    "amount_cents": -100,
    "amount_currency": "USD",
    "pay_in_advance": false
  }
}

Returns 200.

Bug 2 — negative tax rate:

POST /api/v1/taxes
Authorization: Bearer {api_key}
Content-Type: application/json

{
  "tax": {
    "name": "Negative Tax",
    "code": "negative-tax",
    "rate": "-10.0"
  }
}

Returns 200.

Bug 3 — coupon override bypasses validation:

Create a valid 20% coupon, then apply with negative override:

POST /api/v1/applied_coupons
Authorization: Bearer {api_key}
Content-Type: application/json

{
  "applied_coupon": {
    "external_customer_id": "coupon-cust-001",
    "coupon_code": "override-pct-coupon",
    "percentage_rate": "-50.0"
  }
}

Returns 200. Also accepts "200.0".

Bug 4 — negative trial period:

POST /api/v1/plans
Authorization: Bearer {api_key}
Content-Type: application/json

{
  "plan": {
    "name": "Negative Trial Plan",
    "code": "neg-trial-plan",
    "interval": "monthly",
    "amount_cents": 5000,
    "amount_currency": "USD",
    "pay_in_advance": false,
    "trial_period": -30
  }
}

Returns 200.

Bug 5 — negative coupon percentage at creation:

POST /api/v1/coupons
Authorization: Bearer {api_key}
Content-Type: application/json

{
  "coupon": {
    "name": "Negative Pct Coupon",
    "code": "neg-pct-coupon",
    "coupon_type": "percentage",
    "percentage_rate": "-25.0",
    "frequency": "once",
    "expiration": "no_expiration",
    "reusable": true
  }
}

Returns 200. Note: amount_cents on the same model correctly rejects negatives — only percentage_rate is missing the check.

Bug 6 — negative minimum commitment:

POST /api/v1/plans
Authorization: Bearer {api_key}
Content-Type: application/json

{
  "plan": {
    "name": "Neg Commitment Plan",
    "code": "neg-commit-plan",
    "interval": "monthly",
    "amount_cents": 5000,
    "amount_currency": "USD",
    "pay_in_advance": false,
    "minimum_commitment": {
      "amount_cents": -1000,
      "invoice_display_name": "Negative Minimum"
    }
  }
}

Returns 200.

Reproducing with Dokkimi:

Dokkimi is an open source testing tool that stands up isolated Docker environments from declarative YAML definitions — services, databases, seed data, test steps, and assertions all in one file. The definition for this bug is in dokkimi/dokkimi-in-the-wild/.dokkimi/lagodokkimi run reproduces it from scratch.

Expected behavior
All financial fields should have consistent numericality validation:

  • Plans should reject negative amount_cents (like coupons and add-ons already do)
  • Tax rates should reject negative values
  • Coupon application overrides should validate percentage_rate the same way coupon creation validates amount_cents
  • Trial periods should reject negative values
  • Coupon percentage_rate should have the same greater_than: 0 check that amount_cents has
  • Minimum commitment amounts should reject negative values

Screenshots
N/A

Additional context

  • A plan with amount_cents: -100 generates invoices that credit the customer instead of charging them
  • A -10% tax rate reduces invoice totals; -100% zeros them out, bypassing Lago's credit notes system
  • A -50% coupon override surcharges the customer with no indication the "discount" is a price increase
  • Lago has a dedicated credit notes system for refunds and adjustments, making negative values on these fields unnecessary

Version

  • Lago: v1.48.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions