Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add subscription to Hubspot tracker #3676

Merged
merged 9 commits into from
Mar 27, 2024
29 changes: 26 additions & 3 deletions api/integrations/lead_tracking/hubspot/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import hubspot
from django.conf import settings
from hubspot.crm.companies import SimplePublicObjectInputForCreate
from hubspot.crm.companies import (
SimplePublicObjectInput,
SimplePublicObjectInputForCreate,
)
from hubspot.crm.contacts import BatchReadInputSimplePublicObjectId

from users.models import FFAdminUser
Expand Down Expand Up @@ -64,8 +67,15 @@ def create_contact(self, user: FFAdminUser, hubspot_company_id: str) -> dict:
)
return response.to_dict()

def create_company(self, name: str, organisation_id: int) -> dict:
properties = {"name": name, "orgid": str(organisation_id)}
def create_company(
self, name: str, active_subscription: str, organisation_id: int
) -> dict:
properties = {
"name": name,
"active_subscription": active_subscription,
"orgid": str(organisation_id),
}

simple_public_object_input_for_create = SimplePublicObjectInputForCreate(
properties=properties,
)
Expand All @@ -75,3 +85,16 @@ def create_company(self, name: str, organisation_id: int) -> dict:
)

return response.to_dict()

def update_company(self, active_subscription: str, hubspot_company_id: str) -> dict:
properties = {
"active_subscription": active_subscription,
}
simple_public_object_input = SimplePublicObjectInput(properties=properties)

response = self.client.crm.companies.basic_api.update(
company_id=hubspot_company_id,
simple_public_object_input=simple_public_object_input,
)

return response.to_dict()
28 changes: 27 additions & 1 deletion api/integrations/lead_tracking/hubspot/lead_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
from django.conf import settings

from integrations.lead_tracking.lead_tracking import LeadTracker
from organisations.models import HubspotOrganisation, Organisation
from organisations.models import (
HubspotOrganisation,
Organisation,
Subscription,
)
from users.models import FFAdminUser

from .client import HubspotClient
Expand Down Expand Up @@ -67,15 +71,37 @@ def get_or_create_organisation_hubspot_id(self, organisation: Organisation) -> s

response = self.client.create_company(
name=organisation.name,
active_subscription=organisation.subscription.plan,
organisation_id=organisation.id,
)

# Store the organisation data in the database since we are
# unable to look them up via a unique identifier.
HubspotOrganisation.objects.create(
organisation=organisation,
hubspot_id=response["id"],
)

return response["id"]

def update_company_active_subscription(
self, subscription: Subscription
) -> dict | None:
if not subscription.plan:
return

organisation = subscription.organisation

# Check if we're missing the associated hubspot id.
if not getattr(organisation, "hubspot_organisation", None):
return

response = self.client.update_company(
active_subscription=subscription.plan,
hubspot_company_id=organisation.hubspot_organisation.hubspot_id,
)

return response

def _get_client(self) -> HubspotClient:
return HubspotClient()
13 changes: 13 additions & 0 deletions api/integrations/lead_tracking/hubspot/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,16 @@ def track_hubspot_lead(user_id: int, organisation_id: int) -> None:

hubspot_lead_tracker = HubspotLeadTracker()
hubspot_lead_tracker.create_lead(user=user, organisation=organisation)


@register_task_handler()
def update_hubspot_active_subscription(subscription_id: int) -> None:
assert settings.ENABLE_HUBSPOT_LEAD_TRACKING

from organisations.models import Subscription

from .lead_tracker import HubspotLeadTracker

subscription = Subscription.objects.get(id=subscription_id)
hubspot_lead_tracker = HubspotLeadTracker()
hubspot_lead_tracker.update_company_active_subscription(subscription)
12 changes: 11 additions & 1 deletion api/organisations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
from simple_history.models import HistoricalRecords

from app.utils import is_enterprise, is_saas
from integrations.lead_tracking.hubspot.tasks import track_hubspot_lead
from integrations.lead_tracking.hubspot.tasks import (
track_hubspot_lead,
update_hubspot_active_subscription,
)
from organisations.chargebee import (
get_customer_id_from_subscription_id,
get_max_api_calls_for_plan,
Expand Down Expand Up @@ -251,6 +254,13 @@ def can_auto_upgrade_seats(self) -> bool:
def is_free_plan(self) -> bool:
return self.plan == FREE_PLAN_ID

@hook(AFTER_SAVE, when="plan", has_changed=True)
def update_hubspot_active_subscription(self):
if not settings.ENABLE_HUBSPOT_LEAD_TRACKING:
return

update_hubspot_active_subscription.delay(args=(self.id,))

@hook(AFTER_SAVE, when="cancellation_date", has_changed=True)
@hook(AFTER_SAVE, when="subscription_id", has_changed=True)
def update_mailer_lite_subscribers(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Organisation,
OrganisationRole,
)
from task_processor.task_run_method import TaskRunMethod
from users.models import FFAdminUser


Expand Down Expand Up @@ -89,7 +90,9 @@ def test_hubspot_with_new_contact_and_new_organisation(
organisation.refresh_from_db()
assert organisation.hubspot_organisation.hubspot_id == future_hubspot_id
mock_create_company.assert_called_once_with(
name=organisation.name, organisation_id=organisation.id
name=organisation.name,
active_subscription="free",
organisation_id=organisation.id,
)
mock_create_contact.assert_called_once_with(user, future_hubspot_id)
mock_get_contact.assert_called_once_with(user)
Expand Down Expand Up @@ -214,3 +217,65 @@ def test_hubspot_with_existing_contact_and_new_organisation(
# further hubspot resources.
mock_create_company.assert_not_called()
mock_create_contact.assert_not_called()


def test_update_company_active_subscription(
organisation: Organisation,
settings: SettingsWrapper,
mocker: MockerFixture,
) -> None:
settings.ENABLE_HUBSPOT_LEAD_TRACKING = True
settings.TASK_RUN_METHOD = TaskRunMethod.SYNCHRONOUSLY

mock_update_company = mocker.patch(
"integrations.lead_tracking.hubspot.client.HubspotClient.update_company"
)
hubspot_id = "12345"
# Create an existing hubspot organisation to mimic a previous
# successful API call.
HubspotOrganisation.objects.create(
organisation=organisation,
hubspot_id=hubspot_id,
)

assert organisation.subscription.plan == "free"

# When
organisation.subscription.plan = "scale-up-v2"
organisation.subscription.save()

# Then
mock_update_company.assert_called_once_with(
active_subscription=organisation.subscription.plan,
hubspot_company_id=hubspot_id,
)


def test_update_company_active_subscription_not_called(
organisation: Organisation,
settings: SettingsWrapper,
mocker: MockerFixture,
) -> None:
# Set to False to ensure update doesn't happen.
settings.ENABLE_HUBSPOT_LEAD_TRACKING = False
settings.TASK_RUN_METHOD = TaskRunMethod.SYNCHRONOUSLY

mock_update_company = mocker.patch(
"integrations.lead_tracking.hubspot.client.HubspotClient.update_company"
)
hubspot_id = "12345"
# Create an existing hubspot organisation to mimic a previous
# successful API call.
HubspotOrganisation.objects.create(
organisation=organisation,
hubspot_id=hubspot_id,
)

assert organisation.subscription.plan == "free"

# When
organisation.subscription.plan = "scale-up-v2"
organisation.subscription.save()

# Then
mock_update_company.assert_not_called()