In v3, fee tokens are held/tracked separately from the liquidity pool. This means that, unlike in v2, the invariant product does not change after a trade, as we see below.
Consider a specific pool with a given range-position, and suppose it has virtual reserves $x,y$ corresponding to assets X, Y respectively. Suppose these reserves initially satisfy the product-invariant $xy=L^2$. When a trader wants to put in $\Delta x$ amount of X tokens and receive $\Delta y$ Y tokens in exchange, here is how we can calculate $\Delta y$ they will receive, taking fees into account. Suppose the fee rate is $1 - \gamma$, e.g. $1 - \gamma = 0.003$.
Reduce the amount of X tokens converted, by the fee, so effectively they will convert $\gamma \Delta x$ of X
Plug this reduced amount into the invariant formula to get the corresponding $\Delta y$:
$$ (x + \gamma \Delta x)(y - \Delta y) = L^2 $$
Track the fee paid as $(1-\gamma)\Delta x$ amount of X tokens
Note that due to the non-fungibility of liquidity pools in v3, the fees are kept separate from the pool. Here are some relevant highlights from the v3 whitepaper: