feat(billing): inbound API handlers (customer/usage/catalog)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -62,3 +62,34 @@ class FusionBillingService(models.Model):
|
|||||||
return self.browse()
|
return self.browse()
|
||||||
key_hash = hashlib.sha256(raw_key.encode()).hexdigest()
|
key_hash = hashlib.sha256(raw_key.encode()).hexdigest()
|
||||||
return self.search([('api_key_hash', '=', key_hash), ('active', '=', True)], limit=1)
|
return self.search([('api_key_hash', '=', key_hash), ('active', '=', True)], limit=1)
|
||||||
|
|
||||||
|
def _api_upsert_customer(self, payload):
|
||||||
|
self.ensure_one()
|
||||||
|
ext = payload.get('external_id')
|
||||||
|
if not ext:
|
||||||
|
return {'status': 'error', 'error': 'external_id required'}
|
||||||
|
link = self.env['fusion.billing.account.link']._resolve_or_create_partner(
|
||||||
|
self, ext, name=payload.get('name'), email=payload.get('email'))
|
||||||
|
return {'status': 'ok', 'partner_id': link.partner_id.id, 'external_id': ext}
|
||||||
|
|
||||||
|
def _api_record_usage(self, payload):
|
||||||
|
self.ensure_one()
|
||||||
|
events = payload.get('events') or []
|
||||||
|
Usage = self.env['fusion.billing.usage']
|
||||||
|
accepted = 0
|
||||||
|
for ev in events:
|
||||||
|
sub = self.env['sale.order'].browse(int(ev['subscription_external_id']))
|
||||||
|
Usage._record_usage(
|
||||||
|
sub, ev['metric_code'], float(ev['quantity']),
|
||||||
|
ev['period_start'], ev['period_end'], idem=ev.get('idempotency_key'))
|
||||||
|
accepted += 1
|
||||||
|
return {'status': 'ok', 'accepted': accepted}
|
||||||
|
|
||||||
|
def _api_catalog(self):
|
||||||
|
self.ensure_one()
|
||||||
|
charges = self.env['fusion.billing.charge'].search([('active', '=', True)])
|
||||||
|
return {'status': 'ok', 'charges': [{
|
||||||
|
'plan_code': c.plan_code, 'metric': c.metric_id.code,
|
||||||
|
'included_quota': c.included_quota, 'price_per_unit': c.price_per_unit,
|
||||||
|
'unit_batch': c.unit_batch, 'charge_model': c.charge_model,
|
||||||
|
} for c in charges]}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
from . import test_identity
|
from . import test_identity
|
||||||
from . import test_charge
|
from . import test_charge
|
||||||
from . import test_usage
|
from . import test_usage
|
||||||
|
from . import test_api
|
||||||
|
|||||||
47
fusion_centralize_billing/tests/test_api.py
Normal file
47
fusion_centralize_billing/tests/test_api.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo.tests.common import TransactionCase, tagged
|
||||||
|
|
||||||
|
|
||||||
|
@tagged('post_install', '-at_install')
|
||||||
|
class TestApiHandlers(TransactionCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.service = self.env['fusion.billing.service'].sudo().create(
|
||||||
|
{'name': 'NexaMaps', 'code': 'nexamaps'})
|
||||||
|
self.env['fusion.billing.metric'].sudo().create(
|
||||||
|
{'name': 'API Calls', 'code': 'api_calls', 'aggregation': 'sum'})
|
||||||
|
self.plan = self.env['sale.subscription.plan'].sudo().create(
|
||||||
|
{'name': 'Monthly', 'billing_period_value': 1, 'billing_period_unit': 'month'})
|
||||||
|
|
||||||
|
def test_api_upsert_customer(self):
|
||||||
|
res = self.service._api_upsert_customer(
|
||||||
|
{'external_id': 'client-9', 'name': 'Globex', 'email': 'billing@globex.test'})
|
||||||
|
self.assertEqual(res['status'], 'ok')
|
||||||
|
link = self.env['fusion.billing.account.link'].search(
|
||||||
|
[('service_id', '=', self.service.id), ('external_id', '=', 'client-9')])
|
||||||
|
self.assertEqual(link.partner_id.name, 'Globex')
|
||||||
|
|
||||||
|
def test_api_record_usage_batch(self):
|
||||||
|
self.service._api_upsert_customer({'external_id': 'client-9', 'name': 'Globex'})
|
||||||
|
partner = self.env['fusion.billing.account.link'].search(
|
||||||
|
[('external_id', '=', 'client-9')]).partner_id
|
||||||
|
sub = self.env['sale.order'].sudo().create(
|
||||||
|
{'partner_id': partner.id, 'is_subscription': True, 'plan_id': self.plan.id})
|
||||||
|
res = self.service._api_record_usage({'events': [{
|
||||||
|
'subscription_external_id': str(sub.id), 'metric_code': 'api_calls',
|
||||||
|
'quantity': 1234.0, 'period_start': '2026-05-01', 'period_end': '2026-06-01',
|
||||||
|
'idempotency_key': 'maps:client-9:2026-05-01',
|
||||||
|
}]})
|
||||||
|
self.assertEqual(res['accepted'], 1)
|
||||||
|
usage = self.env['fusion.billing.usage'].search([('subscription_id', '=', sub.id)])
|
||||||
|
self.assertEqual(usage.quantity, 1234.0)
|
||||||
|
|
||||||
|
def test_api_catalog_lists_active_charges(self):
|
||||||
|
self.env['fusion.billing.charge'].sudo().create({
|
||||||
|
'name': 'Maps overage', 'plan_code': 'maps-business',
|
||||||
|
'metric_id': self.env['fusion.billing.metric'].search([('code', '=', 'api_calls')]).id,
|
||||||
|
'included_quota': 5_000_000.0, 'price_per_unit': 0.10, 'unit_batch': 1000.0})
|
||||||
|
cat = self.service._api_catalog()
|
||||||
|
codes = [c['plan_code'] for c in cat['charges']]
|
||||||
|
self.assertIn('maps-business', codes)
|
||||||
Reference in New Issue
Block a user