diff --git a/enterprise/app/services/internal/accounts/marketing_attribution_service.rb b/enterprise/app/services/internal/accounts/marketing_attribution_service.rb index 08bb93823..a5300cf11 100644 --- a/enterprise/app/services/internal/accounts/marketing_attribution_service.rb +++ b/enterprise/app/services/internal/accounts/marketing_attribution_service.rb @@ -50,6 +50,7 @@ class Internal::Accounts::MarketingAttributionService 'stored_at' => Time.current.iso8601 }.compact ) + enqueue_signup_conversion end private @@ -79,4 +80,8 @@ class Internal::Accounts::MarketingAttributionService def internal_attributes_service @internal_attributes_service ||= Internal::Accounts::InternalAttributesService.new(account) end + + def enqueue_signup_conversion + Internal::Accounts::MarketingConversionTrackingJob.perform_later(account.id, 'cloud_signup', account.created_at) + end end diff --git a/enterprise/app/services/internal/accounts/marketing_conversion_tracking_service.rb b/enterprise/app/services/internal/accounts/marketing_conversion_tracking_service.rb index 41d3814dd..feb62811f 100644 --- a/enterprise/app/services/internal/accounts/marketing_conversion_tracking_service.rb +++ b/enterprise/app/services/internal/accounts/marketing_conversion_tracking_service.rb @@ -58,7 +58,7 @@ class Internal::Accounts::MarketingConversionTrackingService def conversion_payload payload = { transactionId: "#{event_name}-account-#{account.id}", - eventTimestamp: (occurred_at.present? ? Time.zone.parse(occurred_at.to_s) : Time.current).iso8601, + eventTimestamp: event_timestamp.iso8601, eventSource: 'WEB', adIdentifiers: click_attributes } @@ -78,6 +78,10 @@ class Internal::Accounts::MarketingConversionTrackingService end.to_h end + def event_timestamp + occurred_at || Time.current + end + def attribution marketing_attribution = account.internal_attributes['marketing_attribution'] || {} [marketing_attribution['last_touch'], marketing_attribution['first_touch']].find do |touch| diff --git a/spec/enterprise/controllers/api/v1/accounts_controller_spec.rb b/spec/enterprise/controllers/api/v1/accounts_controller_spec.rb index 00e50c0e8..94d2a2a51 100644 --- a/spec/enterprise/controllers/api/v1/accounts_controller_spec.rb +++ b/spec/enterprise/controllers/api/v1/accounts_controller_spec.rb @@ -28,19 +28,22 @@ RSpec.describe 'Enterprise Accounts API', type: :request do allow(AccountBuilder).to receive(:new).and_return(account_builder) allow(account_builder).to receive(:perform).and_return([user, account]) - with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do - post api_v1_accounts_url, - params: { - account_name: 'test', - email: email, - user: nil, - locale: nil, - user_full_name: user_full_name, - password: 'Password1!' - }, - headers: attribution_cookie_header, - as: :json - end + expect do + with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do + post api_v1_accounts_url, + params: { + account_name: 'test', + email: email, + user: nil, + locale: nil, + user_full_name: user_full_name, + password: 'Password1!' + }, + headers: attribution_cookie_header, + as: :json + end + end.to have_enqueued_job(Internal::Accounts::MarketingConversionTrackingJob) + .with(account.id, 'cloud_signup', account.created_at) attribution = account.reload.internal_attributes['marketing_attribution'] expect(attribution['captured_from']).to eq('cookie') @@ -51,13 +54,15 @@ RSpec.describe 'Enterprise Accounts API', type: :request do it 'does not record marketing attribution for authenticated add-workspace requests' do existing_user = create(:user, password: 'Password1!') - with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do - post api_v1_accounts_url, - params: { account_name: 'Second Account', email: existing_user.email, - user_full_name: existing_user.name, password: 'Password1!' }, - headers: existing_user.create_new_auth_token.merge(attribution_cookie_header), - as: :json - end + expect do + with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do + post api_v1_accounts_url, + params: { account_name: 'Second Account', email: existing_user.email, + user_full_name: existing_user.name, password: 'Password1!' }, + headers: existing_user.create_new_auth_token.merge(attribution_cookie_header), + as: :json + end + end.not_to have_enqueued_job(Internal::Accounts::MarketingConversionTrackingJob) account = Account.find(response.parsed_body.dig('data', 'account_id')) expect(account.internal_attributes).not_to include('marketing_attribution') diff --git a/spec/enterprise/services/internal/accounts/marketing_attribution_service_spec.rb b/spec/enterprise/services/internal/accounts/marketing_attribution_service_spec.rb index 0046908dc..50c6773b3 100644 --- a/spec/enterprise/services/internal/accounts/marketing_attribution_service_spec.rb +++ b/spec/enterprise/services/internal/accounts/marketing_attribution_service_spec.rb @@ -32,6 +32,15 @@ RSpec.describe Internal::Accounts::MarketingAttributionService do expect(attribution['last_touch']['source']).to eq('github') end + it 'enqueues signup conversion tracking after storing attribution' do + cookies[described_class::LAST_TOUCH_COOKIE] = encoded_cookie('source' => 'github') + + expect do + described_class.new(account: account, cookies: cookies).perform + end.to have_enqueued_job(Internal::Accounts::MarketingConversionTrackingJob) + .with(account.id, 'cloud_signup', account.created_at) + end + it 'does not store attribution outside Chatwoot Cloud' do allow(ChatwootApp).to receive(:chatwoot_cloud?).and_return(false) cookies[described_class::LAST_TOUCH_COOKIE] = encoded_cookie('source' => 'reddit') diff --git a/spec/enterprise/services/internal/accounts/marketing_conversion_tracking_service_spec.rb b/spec/enterprise/services/internal/accounts/marketing_conversion_tracking_service_spec.rb index f1b6578d6..624af0d7f 100644 --- a/spec/enterprise/services/internal/accounts/marketing_conversion_tracking_service_spec.rb +++ b/spec/enterprise/services/internal/accounts/marketing_conversion_tracking_service_spec.rb @@ -5,7 +5,7 @@ require 'rails_helper' RSpec.describe Internal::Accounts::MarketingConversionTrackingService do let(:account) { create(:account) } let(:event_name) { 'cloud_signup' } - let(:occurred_at) { '2026-06-23T10:30:00Z' } + let(:occurred_at) { Time.zone.parse('2026-06-23T10:30:00Z') } let(:private_key) { OpenSSL::PKey::RSA.new(2048).to_pem } let(:credentials) do instance_double(Google::Auth::ServiceAccountCredentials, fetch_access_token!: { 'access_token' => 'access-token' })