# Membership coupon architecture

## Current data foundation

`coupons` is the authoritative, admin-controlled Coupon domain record. It stores a unique uppercase `code`, nullable plain-text `description`, `discount_type`, `discount_value`, `applicable_product`, `first_purchase_only`, optional validity timestamps, optional usage limits, and `is_active`.

`discount_type` is a `CouponDiscountType` enum. For `percentage`, `discount_value` is stored in basis points (for example, 20% is `2000`). For `fixed`, it is stored as an integer currency minor-unit amount. No floating-point discount values are used.

`applicable_product` is a `CouponApplicableProduct` enum and currently only supports `membership`. Coupons do not apply to Lead Credit or any other product.

`first_purchase_only` means the coupon is intended only for a first paid Membership purchase. It does not override other eligibility rules. Validity timestamps and nullable positive usage limits define future eligibility concepts; null validity bounds mean immediately valid/no expiry, and null limits mean unlimited.

## Redemption foundation

`coupon_redemptions` is the durable record of successful coupon consumption. It links a coupon and user to the fulfilled Membership order, records `redeemed_at`, and prevents multiple redemptions for one Membership order. Its indexes support total and per-user usage checks.

## Renewal commission rule

A Partner renewal commission uses the post-coupon payable amount. Checkout integration, trusted order snapshots, redemption creation, and the buyer coupon UI are implemented.

## Admin management

Admins can list, search, create, edit, activate, and deactivate Membership coupons. The listing exposes only coupon configuration and an aggregate successful redemption count; it does not expose redemption user data. Coupons are paginated and ordered newest first.

The Admin form accepts a human percentage with up to two decimal places and converts it exactly to basis points (`12.5` becomes `1250`), or accepts a MYR fixed amount and converts it exactly to minor units (`20.00` becomes `2000`). Coupon codes are trimmed and uppercased on creation, must contain only `A-Z`, `0-9`, `-`, or `_`, and are immutable after creation. Coupons are never hard-deleted; Admins deactivate them instead, preserving historical redemption meaning.

Coupon configuration changes affect future eligibility only; previously-created MembershipOrder snapshots remain historically authoritative.

## Pure Membership validation engine

`MembershipCouponService` normalizes the submitted code, looks up the Coupon, and returns an immutable `MembershipCouponResult`; it never creates or updates records. It evaluates coupon existence, active state, Membership product scope, validity dates, the persisted referral rule, `first_purchase_only`, durable redemption limits, and finally a positive Membership base amount. Invalid results contain a centralized `MembershipCouponInvalidReason` and always return a zero discount.

Usage is derived exclusively from `coupon_redemptions`: total limits count every redemption for the coupon and per-user limits count redemptions for that coupon/user pair. Pending checkout attempts and Membership orders are not usage authority.

At successful local Membership fulfilment, `CouponRedemptionService` creates the redemption in the same database transaction as the order, Membership activation, and commission work. It locks the Coupon row, counts existing redemptions, verifies only total/per-user capacity, then inserts the row. The immutable order snapshot remains authoritative for the Coupon identity and economics: later deactivation, expiry, or discount edits do not invalidate a previously-created order. Capacity exhaustion raises a controlled exception before activation or payout, rolling the local fulfilment transaction back. This also covers locally fulfilled zero-payable orders; repeated callbacks cannot create another redemption.

Percentage discounts use integer floor arithmetic: `intdiv(base_amount_minor * basis_points, 10000)`. Fixed discounts use their stored minor-unit amount and are capped at the Membership base amount, so the calculated discount cannot make a payable amount negative. `first_purchase_only` accepts only the caller-provided `first_paid_membership` context; ordinary coupons may also apply to `renewal`.

For a first paid Membership, a clean registration (`referred_by_user_id` is null) may use an otherwise eligible coupon. Any persisted referral (`referred_by_user_id` is not null) makes that first-paid purchase ineligible, whether the direct referrer is a Partner or a non-Partner. This is historical attribution only: current Partner status, hierarchy, affiliate code validity, and commission records are not inspected. Referral history does not block renewal coupons. This rule is evaluated before `first_purchase_only`, so a referred first-paid buyer receives `referred_first_paid_membership`.

## MembershipOrder coupon snapshots

`membership_orders` can now snapshot a trusted valid `MembershipCouponResult`: nullable `coupon_id`, `coupon_code`, `coupon_discount_type`, `coupon_discount_value`, and `coupon_discount_minor` (zero when no coupon is applied). The order stores the code, type, and original configured value for historical clarity even if the Coupon is later edited, deactivated, or no longer available through its relationship.

`coupon_discount_value` follows Coupon storage semantics: percentage coupons use basis points and fixed coupons use minor units. `coupon_discount_minor` is the exact calculated discount for that order and is never recalculated; for example, a fixed RM150 coupon retains `coupon_discount_value = 15000` while its applied amount may be capped at `coupon_discount_minor = 10000` for a RM100 Membership.

Only the server-side snapshot service accepts a valid typed result. Browser/request coupon fields are not authoritative. The snapshot foundation itself does not change amounts for callers that do not explicitly opt into clean first-paid coupon checkout.

## Clean first-paid checkout integration

Coupon checkout is available only for a clean first paid Membership: the server requires `first_paid_membership` and `referred_by_user_id` to be null. The buyer may preview a code through a safe endpoint that returns only the normalized code, base amount, coupon discount, and payable amount. CHIP and E-Wallet revalidate the submitted code immediately before creating the order; browser-provided price, context, discount, and coupon identifiers are ignored.

For an eligible clean purchase, the order snapshots the coupon and calculates `base_amount_minor - coupon_discount_minor` as `amount_payable_minor`, `amount_minor`, and `takafulhub_net_minor`. Positive payables charge that stored amount through CHIP or E-Wallet. A zero-payable 100% coupon is fulfilled locally without creating a zero-value CHIP purchase. Coupon redemption is never consumed by preview, order creation, or payment-attempt creation; it is consumed only in the successful local fulfilment transaction.

Referred first-paid buyers see a neutral unavailable message and server-side coupon requests are rejected. Renewal coupons are available to otherwise eligible buyers regardless of referral history, except that `first_purchase_only` coupons remain invalid. One order accepts at most one coupon; removing it before checkout simply submits no coupon code and restores the normal price.

## Renewal coupon economics

For a renewal Coupon, the order snapshots economics in this exact order: base Membership price, coupon discount, post-coupon payable amount, Partner renewal commission, then TakafulHub net. When a top Partner exists, the renewal commission is `intdiv(amount_payable_minor * partner_renewal_commission_bps, 10000)`, never a percentage of the original base amount. For example, RM100 minus a RM20 Coupon is RM80 payable; at a 20% renewal rate, the Partner commission is RM16 and net is RM64.

The top-Partner hierarchy rule is unchanged. The stored `partner_renewal_commission_minor` is the historical liability that fulfilment pays; settings, Coupon configuration, Partner status, and hierarchy changes are not re-evaluated. A 100% or fixed-capped renewal Coupon produces zero payable, zero commission, and zero net, and uses local zero-payment fulfilment with no provider purchase or Partner payout. Successful fulfilment consumes exactly one Coupon redemption, subject to the final usage-capacity check.
