fix(billing): resolve code-review findings (authz, cross-billing, validation, webhook integrity)
- C1/H4: rating cron only rates subs on the charge's own plan_id
- C1: _fc_rate_usage skips creating a line when amount is 0 (still updates existing)
- C2/C4: /usage authorizes each event (exists + is_subscription + linked customer)
- C3: API handlers validate input and return 4xx-shaped errors instead of raising;
controller maps status=='error' to HTTP 400
- H1: cron uses real billing window [last_invoice_date or start_date, next_invoice_date)
- H2: _aggregate uses half-open window anchored on period_start
- H3: idempotency scoped to (subscription_id, metric_id, idempotency_key)
- H5: webhook stores canonical body, signs+POSTs it verbatim, adds X-Fusion-Event-Id,
caps backoff at 2**min(attempts,10)
- H6: SSRF guard rejects non-https / localhost / private / link-local webhook_url
- M7: charge_model reduced to standard/package (dropped unimplemented graduated/volume)
- L1: currency_id required on charge + reconciliation
- L2: charge price non-negative + unit_batch positive DB constraints
Adds 17 regression tests (suite 22 -> 39, all green via fcb_test_on_trial.sh).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,13 +10,19 @@ class SaleOrder(models.Model):
|
||||
def _fc_rate_usage(self, charge, period_start, period_end):
|
||||
"""Aggregate this subscription's usage for `charge`'s metric in the period,
|
||||
compute the overage amount, and upsert a matching overage order line.
|
||||
Returns the amount."""
|
||||
Returns the amount.
|
||||
|
||||
A zero amount never *creates* a new line (no $0.00 overage clutter); if a
|
||||
line already exists it is still updated so a dropped-to-zero overage clears.
|
||||
"""
|
||||
self.ensure_one()
|
||||
Usage = self.env['fusion.billing.usage']
|
||||
total = Usage._aggregate(self, charge.metric_id, period_start, period_end)
|
||||
_overage, amount = charge._compute_billable(total)
|
||||
if charge.product_id:
|
||||
line = self.order_line.filtered(lambda l: l.product_id == charge.product_id)
|
||||
if not line and amount == 0:
|
||||
return amount
|
||||
vals = {'product_uom_qty': 1, 'price_unit': amount}
|
||||
if line:
|
||||
line.write(vals)
|
||||
|
||||
Reference in New Issue
Block a user