Saltar al contenido principal

Discount Scope Rules

Nota: este documento describe las reglas de alcance de un descuento (a qué parte del carrito aplica). Para tipos de descuento, combinación (cumulative) y cupones generales multi-evento, ver la guía de organizador en Descuentos y códigos.
Discounts support two explicit application scopes:
  1. Total purchase
    • Applies to registration rates, products, and custom amount fields.
    • Service fees (organizer / platform commission) are never discounted and are always calculated on the pre-discount item base (including any configured minimum, e.g. 3 EUR).
    • If the discount amount exceeds the sum of cart items, the surplus is not applied against the service fee.
  2. Rates only
    • Applies exclusively to registration rates.
    • Products, booking add-ons, and custom amount inputs remain untouched.
    • Organizer fees are not affected.
Only a code explicitly flagged as free (is_free_code) waives the service fee. A 100% (or higher) percentage discount that is not flagged as free still zeroes its discounted base but leaves the service fee untouched, exactly like any other percentage (e.g. 25%). A free code is configured with four independent categories: registration rate, store products, hotel/Booking, and additional services. At least one category is always selected; unselected categories remain payable. The service fee is waived by the free-code flag itself, independently of these four cart categories. The scope selector is independent of the free-code flag. Free codes no longer force a whole-order or rates-only scope; their category checkboxes determine what is waived. If neither flag is selected (legacy data), the behaviour matches the previous implementation: the discount targets the sum of cart items but not organizer fees. When creating or editing a normal discount, the UI enforces mutual exclusivity between the two options and defaults to Rates only for new entries.

Interaction with the unreconciled-subtotal payment guard

Order creation is protected by Order::hasUnreconciledSubtotal(), which blocks the payment step (Redsys and Stripe alike) if a non-group, non-rate-change order has exactly one order item with qty == 1 and subtotal exceeds that item’s line_amount by more than 0.02. A valid discount can only ever lower the subtotal relative to the line amount, never raise it, so an order with a correctly applied discount will never trip this guard. If a payment fails with this guard’s error while a discount is applied, the discount calculation itself is very unlikely to be the cause — check for a duplicated or stale subtotal on the order instead.