# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 import hashlib import secrets from odoo import api, fields, models class FusionBillingService(models.Model): """A source app that pushes billing data (NexaCloud / NexaDesk / NexaMaps). The bearer API key is shown ONCE on generation and stored only as a SHA-256 hash. This record is the auth + routing boundary for the inbound API and the target for outbound webhooks. See spec §5.1 / §7 / §8. """ _name = "fusion.billing.service" _description = "Fusion Billing — Source Service" _order = "name" name = fields.Char(required=True) code = fields.Char( required=True, index=True, help="Stable code the app identifies itself with, e.g. nexacloud / nexadesk / nexamaps.", ) active = fields.Boolean(default=True) api_key_hash = fields.Char( string="API Key (SHA-256)", help="Hash of the bearer key. The raw key is displayed once at generation time.", ) webhook_url = fields.Char(help="Endpoint this app exposes to receive billing webhooks.") webhook_secret = fields.Char(help="Shared secret for HMAC-SHA256 webhook signatures.") account_link_ids = fields.One2many( "fusion.billing.account.link", "service_id", string="Customer Links", ) account_link_count = fields.Integer(compute="_compute_account_link_count") _code_uniq = models.Constraint("unique(code)", "Service code must be unique.") @api.depends("account_link_ids") def _compute_account_link_count(self): for rec in self: rec.account_link_count = len(rec.account_link_ids) def action_generate_api_key(self): """Generate a fresh bearer key, store only its hash, return the raw key. TODO(spec §7): surface the raw key once in the UI (wizard/notification). """ self.ensure_one() raw = secrets.token_urlsafe(32) self.api_key_hash = hashlib.sha256(raw.encode()).hexdigest() return raw