diff --git a/.rubocop_todo/style/inline_disable_annotation.yml b/.rubocop_todo/style/inline_disable_annotation.yml
index 09522a41e4af4ae3517434ba537d6c542c6e3fbe..03a466bbf704c0ad7d03dd596e51f9cfdc910265 100644
--- a/.rubocop_todo/style/inline_disable_annotation.yml
+++ b/.rubocop_todo/style/inline_disable_annotation.yml
@@ -1462,7 +1462,6 @@ Style/InlineDisableAnnotation:
     - 'ee/app/services/epics/update_dates_service.rb'
     - 'ee/app/services/epics/update_service.rb'
     - 'ee/app/services/geo/container_repository_sync_service.rb'
-    - 'ee/app/services/gitlab_subscriptions/add_on_purchases/self_managed/provision_code_suggestions_service.rb'
     - 'ee/app/services/gitlab_subscriptions/add_on_purchases/update_service.rb'
     - 'ee/app/services/gitlab_subscriptions/notify_seats_exceeded_batch_service.rb'
     - 'ee/app/services/gitlab_subscriptions/preview_billable_user_change_service.rb'
@@ -1909,7 +1908,6 @@ Style/InlineDisableAnnotation:
     - 'ee/spec/services/geo/framework_repository_sync_service_spec.rb'
     - 'ee/spec/services/geo/registry_consistency_service_spec.rb'
     - 'ee/spec/services/geo/registry_update_service_spec.rb'
-    - 'ee/spec/services/gitlab_subscriptions/add_on_purchases/self_managed/provision_code_suggestions_service_spec.rb'
     - 'ee/spec/services/gitlab_subscriptions/preview_billable_user_change_service_spec.rb'
     - 'ee/spec/services/merge_requests/update_blocks_service_spec.rb'
     - 'ee/spec/services/package_metadata/sync_service_spec.rb'
diff --git a/ee/app/models/gitlab_subscriptions/add_on_purchase.rb b/ee/app/models/gitlab_subscriptions/add_on_purchase.rb
index d4bea1fc3486cb10ddbe3c2288ef3e34e45a302f..7075970fece186d8f95026a2838b514f517c5377 100644
--- a/ee/app/models/gitlab_subscriptions/add_on_purchase.rb
+++ b/ee/app/models/gitlab_subscriptions/add_on_purchase.rb
@@ -46,6 +46,10 @@ class AddOnPurchase < ApplicationRecord
         .limit(limit)
     end
 
+    def self.find_by_namespace_and_add_on(namespace, add_on)
+      find_by(namespace: namespace, add_on: add_on)
+    end
+
     def self.next_candidate_requiring_assigned_users_refresh
       requiring_assigned_users_refresh(1)
         .order('last_assigned_users_refreshed_at ASC NULLS FIRST')
diff --git a/ee/app/services/gitlab_subscriptions/activate_service.rb b/ee/app/services/gitlab_subscriptions/activate_service.rb
index b578f9a3b8e8cf4784e2e5b24ed8b7d0d3852ebd..0c4c703bcdac5be7470ac192d6e3b379418a7811 100644
--- a/ee/app/services/gitlab_subscriptions/activate_service.rb
+++ b/ee/app/services/gitlab_subscriptions/activate_service.rb
@@ -72,7 +72,7 @@ def application_settings
     end
 
     def update_code_suggestions_add_on_purchase
-      ::GitlabSubscriptions::AddOnPurchases::SelfManaged::ProvisionCodeSuggestionsService.new.execute
+      ::GitlabSubscriptions::AddOnPurchases::SelfManaged::ProvisionServices::CodeSuggestions.new.execute
     end
   end
 end
diff --git a/ee/app/services/gitlab_subscriptions/add_on_purchases/self_managed/base_provision_service.rb b/ee/app/services/gitlab_subscriptions/add_on_purchases/self_managed/base_provision_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2c25da0f5865f18b339f60f1f3401b599a8a4998
--- /dev/null
+++ b/ee/app/services/gitlab_subscriptions/add_on_purchases/self_managed/base_provision_service.rb
@@ -0,0 +1,107 @@
+# frozen_string_literal: true
+
+module GitlabSubscriptions
+  module AddOnPurchases
+    module SelfManaged
+      class BaseProvisionService
+        include ::Gitlab::Utils::StrongMemoize
+
+        AddOnPurchaseSyncError = Class.new(StandardError)
+        MethodNotImplementedError = Class.new(StandardError)
+
+        def execute
+          result = license_has_add_on? ? create_or_update_add_on_purchase : expire_prior_add_on_purchase
+
+          unless result.success?
+            raise AddOnPurchaseSyncError, "Error syncing subscription add-on purchases. Message: #{result[:message]}"
+          end
+
+          result
+        rescue AddOnPurchaseSyncError => e
+          Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e)
+
+          ServiceResponse.error(message: e.message)
+        end
+
+        private
+
+        def license_has_add_on?
+          current_license&.online_cloud_license? && quantity > 0
+        end
+
+        def current_license
+          License.current
+        end
+        strong_memoize_attr :current_license
+
+        def license_restrictions
+          current_license&.license&.restrictions
+        end
+
+        def empty_success_response
+          ServiceResponse.success(payload: { add_on_purchase: nil })
+        end
+
+        def create_or_update_add_on_purchase
+          service_class = if add_on_purchase
+                            GitlabSubscriptions::AddOnPurchases::UpdateService
+                          else
+                            GitlabSubscriptions::AddOnPurchases::CreateService
+                          end
+
+          service_class.new(namespace, add_on, attributes).execute
+        end
+
+        def add_on_purchase
+          GitlabSubscriptions::AddOnPurchase.find_by_namespace_and_add_on(namespace, add_on)
+        end
+        strong_memoize_attr :add_on_purchase
+
+        def add_on
+          GitlabSubscriptions::AddOn.find_or_create_by_name(name)
+        end
+        strong_memoize_attr :add_on
+
+        def namespace
+          nil # self-managed is unrelated to namespaces
+        end
+
+        def attributes
+          {
+            add_on_purchase: add_on_purchase,
+            expires_on: expires_on,
+            purchase_xid: purchase_xid,
+            quantity: quantity
+          }
+        end
+
+        def expire_prior_add_on_purchase
+          return empty_success_response unless add_on_purchase
+
+          GitlabSubscriptions::AddOnPurchases::SelfManaged::ExpireService.new(add_on_purchase).execute
+        end
+
+        def purchase_xid
+          license_restrictions&.dig(:subscription_name)
+        end
+
+        def expires_on
+          current_license&.block_changes_at || current_license&.expires_at
+        end
+
+        def quantity
+          quantity_from_restrictions(license_restrictions) if license_restrictions
+        end
+        strong_memoize_attr :quantity
+
+        def name
+          raise MethodNotImplementedError
+        end
+
+        def quantity_from_restrictions(_)
+          raise MethodNotImplementedError
+        end
+      end
+    end
+  end
+end
diff --git a/ee/app/services/gitlab_subscriptions/add_on_purchases/self_managed/provision_code_suggestions_service.rb b/ee/app/services/gitlab_subscriptions/add_on_purchases/self_managed/provision_code_suggestions_service.rb
deleted file mode 100644
index 434743f523a5da313866555d529d606b567fd876..0000000000000000000000000000000000000000
--- a/ee/app/services/gitlab_subscriptions/add_on_purchases/self_managed/provision_code_suggestions_service.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-# frozen_string_literal: true
-
-module GitlabSubscriptions
-  module AddOnPurchases
-    module SelfManaged
-      class ProvisionCodeSuggestionsService
-        include ::Gitlab::Utils::StrongMemoize
-
-        AddOnPurchaseSyncError = Class.new(StandardError)
-
-        def execute
-          result = license_has_duo_pro? ? create_or_update_add_on_purchase : expire_prior_add_on_purchase
-
-          unless result.success?
-            raise AddOnPurchaseSyncError, "Error syncing subscription add-on purchases. Message: #{result[:message]}"
-          end
-
-          result
-        rescue AddOnPurchaseSyncError => e
-          Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e)
-
-          ServiceResponse.error(message: e.message)
-        end
-
-        private
-
-        def license_has_duo_pro?
-          current_license&.online_cloud_license? && license_restrictions[:code_suggestions_seat_count].to_i > 0
-        end
-
-        def current_license
-          License.current
-        end
-        strong_memoize_attr :current_license
-
-        def license_restrictions
-          current_license.license.restrictions
-        end
-        strong_memoize_attr :license_restrictions
-
-        def empty_success_response
-          ServiceResponse.success(payload: { add_on_purchase: nil })
-        end
-
-        def create_or_update_add_on_purchase
-          service_class = if gitlab_duo_pro_add_on_purchase
-                            GitlabSubscriptions::AddOnPurchases::UpdateService
-                          else
-                            GitlabSubscriptions::AddOnPurchases::CreateService
-                          end
-
-          service_class.new(namespace, gitlab_duo_pro_add_on, add_on_purchase_attributes).execute
-        end
-
-        # rubocop: disable CodeReuse/ActiveRecord
-        def gitlab_duo_pro_add_on_purchase
-          GitlabSubscriptions::AddOnPurchase.find_by(namespace: namespace, add_on: gitlab_duo_pro_add_on)
-        end
-        strong_memoize_attr :gitlab_duo_pro_add_on_purchase
-        # rubocop: enable CodeReuse/ActiveRecord
-
-        def gitlab_duo_pro_add_on
-          GitlabSubscriptions::AddOn.find_or_create_by_name(:code_suggestions)
-        end
-        strong_memoize_attr :gitlab_duo_pro_add_on
-
-        def namespace
-          nil
-        end
-
-        def add_on_purchase_attributes
-          {
-            quantity: license_restrictions[:code_suggestions_seat_count],
-            expires_on: current_license.block_changes_at || current_license.expires_at,
-            purchase_xid: license_restrictions[:subscription_name]
-          }.merge({ add_on_purchase: gitlab_duo_pro_add_on_purchase }.compact)
-        end
-
-        def expire_prior_add_on_purchase
-          return empty_success_response unless gitlab_duo_pro_add_on_purchase
-
-          GitlabSubscriptions::AddOnPurchases::SelfManaged::ExpireService.new(gitlab_duo_pro_add_on_purchase).execute
-        end
-      end
-    end
-  end
-end
diff --git a/ee/app/services/gitlab_subscriptions/add_on_purchases/self_managed/provision_services/code_suggestions.rb b/ee/app/services/gitlab_subscriptions/add_on_purchases/self_managed/provision_services/code_suggestions.rb
new file mode 100644
index 0000000000000000000000000000000000000000..91e1157d4d633ccd68aab5f32bd38ac68c7521b6
--- /dev/null
+++ b/ee/app/services/gitlab_subscriptions/add_on_purchases/self_managed/provision_services/code_suggestions.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module GitlabSubscriptions
+  module AddOnPurchases
+    module SelfManaged
+      module ProvisionServices
+        class CodeSuggestions < BaseProvisionService
+          private
+
+          def quantity_from_restrictions(restrictions)
+            restrictions[:code_suggestions_seat_count].to_i
+          end
+
+          def name
+            :code_suggestions
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/ee/app/services/licenses/destroy_service.rb b/ee/app/services/licenses/destroy_service.rb
index 356d1f431ad9ddf2dce0ebf2b7f1b13be1a2b72e..9f8d2c0add9937df3eb22eefb3222fe9b7fcd16f 100644
--- a/ee/app/services/licenses/destroy_service.rb
+++ b/ee/app/services/licenses/destroy_service.rb
@@ -23,7 +23,7 @@ def clear_future_subscriptions
     end
 
     def update_code_suggestions_add_on_purchase
-      ::GitlabSubscriptions::AddOnPurchases::SelfManaged::ProvisionCodeSuggestionsService.new.execute
+      ::GitlabSubscriptions::AddOnPurchases::SelfManaged::ProvisionServices::CodeSuggestions.new.execute
     end
   end
 end
diff --git a/ee/app/workers/sync_seat_link_request_worker.rb b/ee/app/workers/sync_seat_link_request_worker.rb
index d0ebecba5e9066b9602ae3cd9617f107b59fafbb..f88b71a88f5f609f80e5245cecb0ddf449ef0ddf 100644
--- a/ee/app/workers/sync_seat_link_request_worker.rb
+++ b/ee/app/workers/sync_seat_link_request_worker.rb
@@ -72,7 +72,7 @@ def request_error_message(response)
   end
 
   def update_code_suggestions_add_on_purchase
-    ::GitlabSubscriptions::AddOnPurchases::SelfManaged::ProvisionCodeSuggestionsService.new.execute
+    ::GitlabSubscriptions::AddOnPurchases::SelfManaged::ProvisionServices::CodeSuggestions.new.execute
   end
 
   def update_reconciliation!(response)
diff --git a/ee/spec/factories/gitlab_subscriptions/add_ons.rb b/ee/spec/factories/gitlab_subscriptions/add_ons.rb
index f079a92d7483d007107188b458cd5c656049b262..07c616743f286ae0388728ea440f0253a82f9f97 100644
--- a/ee/spec/factories/gitlab_subscriptions/add_ons.rb
+++ b/ee/spec/factories/gitlab_subscriptions/add_ons.rb
@@ -5,6 +5,11 @@
     name { GitlabSubscriptions::AddOn.names[:code_suggestions] }
     description { GitlabSubscriptions::AddOn.descriptions[:code_suggestions] }
 
+    trait :code_suggestions do
+      name { GitlabSubscriptions::AddOn.names[:code_suggestions] }
+      description { GitlabSubscriptions::AddOn.descriptions[:code_suggestions] }
+    end
+
     trait :gitlab_duo_pro do
       name { GitlabSubscriptions::AddOn.names[:code_suggestions] }
     end
diff --git a/ee/spec/models/gitlab_subscriptions/add_on_purchase_spec.rb b/ee/spec/models/gitlab_subscriptions/add_on_purchase_spec.rb
index f647897b2765fee9c3574e911575574204939c2c..97a0eeb09a9b58e369f7787a25a5a30f3cba8072 100644
--- a/ee/spec/models/gitlab_subscriptions/add_on_purchase_spec.rb
+++ b/ee/spec/models/gitlab_subscriptions/add_on_purchase_spec.rb
@@ -396,6 +396,26 @@
         expect(described_class.requiring_assigned_users_refresh(1).size).to eq 1
       end
     end
+
+    describe '.find_by_namespace_and_add_on' do
+      subject(:find_by_namespace_and_add_on) { described_class.find_by_namespace_and_add_on }
+
+      let(:namespace) { create(:group) }
+
+      let(:add_on_1) { create(:gitlab_subscription_add_on, :code_suggestions) }
+      let(:add_on_2) { create(:gitlab_subscription_add_on, :product_analytics) }
+
+      let!(:add_on_purchase_1) { create(:gitlab_subscription_add_on_purchase, namespace: nil, add_on: add_on_1) }
+      let!(:add_on_purchase_2) { create(:gitlab_subscription_add_on_purchase, namespace: namespace, add_on: add_on_1) }
+
+      let!(:add_on_purchase_3) { create(:gitlab_subscription_add_on_purchase, namespace: nil, add_on: add_on_2) }
+      let!(:add_on_purchase_4) { create(:gitlab_subscription_add_on_purchase, namespace: namespace, add_on: add_on_2) }
+
+      it 'filters by namespace and add-on' do
+        expect(described_class.find_by_namespace_and_add_on(nil, add_on_1)).to eq add_on_purchase_1
+        expect(described_class.find_by_namespace_and_add_on(namespace, add_on_1)).to eq add_on_purchase_2
+      end
+    end
   end
 
   describe '.uniq_add_on_names' do
diff --git a/ee/spec/services/gitlab_subscriptions/add_on_purchases/self_managed/base_provision_service_spec.rb b/ee/spec/services/gitlab_subscriptions/add_on_purchases/self_managed/base_provision_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a9a226931468676d48e86f635a880c8d96ec4662
--- /dev/null
+++ b/ee/spec/services/gitlab_subscriptions/add_on_purchases/self_managed/base_provision_service_spec.rb
@@ -0,0 +1,155 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSubscriptions::AddOnPurchases::SelfManaged::BaseProvisionService,
+  :aggregate_failures, feature_category: :plan_provisioning do
+  describe '#execute' do
+    it { expect { described_class.new.execute }.to raise_error described_class::MethodNotImplementedError }
+
+    context 'with child class insufficient implemented' do
+      let!(:current_license) { create_current_license(cloud_licensing_enabled: true) }
+
+      let(:provision_dummy_add_on_service_class) do
+        Class.new(described_class) do
+          def name
+            # One of the enums for name of GitlabSubscriptions::AddOn
+            :code_suggestions
+          end
+        end
+      end
+
+      specify do
+        expect { provision_dummy_add_on_service_class.new.execute }
+          .to raise_error described_class::MethodNotImplementedError
+      end
+    end
+
+    context 'with child class' do
+      subject(:result) { provision_dummy_add_on_service_class.new.execute }
+
+      let_it_be(:add_on) { create(:gitlab_subscription_add_on) }
+      let_it_be(:default_organization) { create(:organization, :default) }
+      let_it_be(:namespace) { nil }
+      let_it_be(:quantity) { 5 }
+      let_it_be(:subscription_name) { 'A-S00000002' }
+
+      let!(:current_license) do
+        create_current_license(
+          cloud_licensing_enabled: true,
+          restrictions: {
+            subscription_name: subscription_name
+          }
+        )
+      end
+
+      let(:provision_dummy_add_on_service_class) do
+        quantity_from_restrictions = quantity
+
+        Class.new(described_class) do
+          define_method :quantity_from_restrictions do |_|
+            quantity_from_restrictions
+          end
+
+          def name
+            # One of the enums for name of GitlabSubscriptions::AddOn
+            :code_suggestions
+          end
+        end
+      end
+
+      context 'without a current license', :without_license do
+        let!(:current_license) { nil }
+
+        it_behaves_like 'provision service expires add-on purchase'
+      end
+
+      context 'when current license is not a cloud license' do
+        let!(:current_license) do
+          create_current_license(
+            cloud_licensing_enabled: true,
+            offline_cloud_licensing_enabled: true
+          )
+        end
+
+        it_behaves_like 'provision service expires add-on purchase'
+      end
+
+      context 'when current license does not contain a code suggestions add-on purchase' do
+        let_it_be(:quantity) { 0 }
+
+        it_behaves_like 'provision service expires add-on purchase'
+      end
+
+      context 'when add-on record does not exist' do
+        before do
+          GitlabSubscriptions::AddOn.destroy_all # rubocop: disable Cop/DestroyAll -- clean-up
+        end
+
+        it 'creates the add-on record' do
+          expect { result }.to change { GitlabSubscriptions::AddOn.count }.by(1)
+        end
+      end
+
+      context 'when add-on purchase exists' do
+        let(:expiration_date) { Date.current + 3.months }
+        let!(:existing_add_on_purchase) do
+          create(
+            :gitlab_subscription_add_on_purchase,
+            namespace: namespace,
+            add_on: add_on,
+            expires_on: expiration_date
+          )
+        end
+
+        context 'when the update fails' do
+          it_behaves_like 'provision service handles error', GitlabSubscriptions::AddOnPurchases::UpdateService
+        end
+
+        context 'when existing add-on purchase is expired' do
+          let(:expiration_date) { Date.current - 3.months }
+
+          it_behaves_like 'provision service updates the existing add-on purchase'
+        end
+
+        it_behaves_like 'provision service updates the existing add-on purchase'
+      end
+
+      context 'when the creation fails' do
+        it_behaves_like 'provision service handles error', GitlabSubscriptions::AddOnPurchases::CreateService
+      end
+
+      context 'when the license has no block_changes_at set' do
+        let!(:current_license) do
+          create_current_license(
+            block_changes_at: nil,
+            cloud_licensing_enabled: true,
+            restrictions: {
+              code_suggestions_seat_count: quantity,
+              subscription_name: subscription_name
+            }
+          )
+        end
+
+        it 'uses expires_at from license' do
+          expect(GitlabSubscriptions::AddOnPurchases::CreateService).to receive(:new).with(
+            namespace,
+            add_on,
+            {
+              add_on_purchase: nil,
+              quantity: quantity,
+              expires_on: current_license.expires_at,
+              purchase_xid: subscription_name
+            }
+          ).and_call_original
+
+          expect(result[:add_on_purchase]).to have_attributes(
+            expires_on: current_license.expires_at
+          )
+        end
+      end
+
+      it_behaves_like 'provision service creates add-on purchase'
+    end
+  end
+end
diff --git a/ee/spec/services/gitlab_subscriptions/add_on_purchases/self_managed/provision_code_suggestions_service_spec.rb b/ee/spec/services/gitlab_subscriptions/add_on_purchases/self_managed/provision_code_suggestions_service_spec.rb
deleted file mode 100644
index 6b1c03181480c4d33d0abb1c76ee63cb139cc137..0000000000000000000000000000000000000000
--- a/ee/spec/services/gitlab_subscriptions/add_on_purchases/self_managed/provision_code_suggestions_service_spec.rb
+++ /dev/null
@@ -1,233 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe GitlabSubscriptions::AddOnPurchases::SelfManaged::ProvisionCodeSuggestionsService,
-  :aggregate_failures, feature_category: :plan_provisioning do
-  describe '#execute' do
-    let_it_be(:add_on) { create(:gitlab_subscription_add_on) }
-    let_it_be(:namespace) { nil }
-
-    let!(:current_license) do
-      create_current_license(
-        cloud_licensing_enabled: true,
-        restrictions: { code_suggestions_seat_count: purchased_add_on_quantity, subscription_name: subscription_name }
-      )
-    end
-
-    let_it_be(:default_organization) { create(:organization, :default) }
-
-    let(:purchased_add_on_quantity) { 5 }
-    let(:subscription_name) { 'A-S00000002' }
-
-    subject(:result) { described_class.new.execute }
-
-    shared_examples 'empty success response' do
-      it 'returns a success' do
-        expect(result[:status]).to eq(:success)
-        expect(result[:add_on_purchase]).to eq(nil)
-      end
-    end
-
-    shared_examples 'handle error' do |service_class|
-      it 'logs and returns an error' do
-        allow_next_instance_of(service_class) do |service|
-          allow(service).to receive(:execute).and_return(ServiceResponse.error(message: 'Something went wrong'))
-        end
-
-        expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
-        expect(result[:status]).to eq(:error)
-        expect(result[:message]).to eq('Error syncing subscription add-on purchases. Message: Something went wrong')
-      end
-    end
-
-    shared_examples 'expire add-on purchase' do
-      context 'with existing add-on purchase' do
-        let_it_be(:expiration_date) { Date.current + 3.months }
-        let_it_be(:existing_add_on_purchase) do
-          create(
-            :gitlab_subscription_add_on_purchase,
-            namespace: namespace,
-            add_on: add_on,
-            expires_on: expiration_date
-          )
-        end
-
-        it 'does not call any service to create or update an add-on purchase' do
-          expect(GitlabSubscriptions::AddOnPurchases::CreateService).not_to receive(:new)
-          expect(GitlabSubscriptions::AddOnPurchases::UpdateService).not_to receive(:new)
-
-          result
-        end
-
-        context 'when the expiration fails' do
-          it_behaves_like 'handle error', GitlabSubscriptions::AddOnPurchases::SelfManaged::ExpireService
-        end
-
-        it 'expires the existing add-on purchase' do
-          expect do
-            result
-            existing_add_on_purchase.reload
-          end.to change { existing_add_on_purchase.expires_on }.from(expiration_date).to(Date.yesterday)
-        end
-
-        it_behaves_like 'empty success response'
-      end
-
-      context 'without existing add-on purchase' do
-        it 'does not call any of the services to update an add-on purchase' do
-          expect(GitlabSubscriptions::AddOnPurchases::CreateService).not_to receive(:new)
-          expect(GitlabSubscriptions::AddOnPurchases::UpdateService).not_to receive(:new)
-          expect(GitlabSubscriptions::AddOnPurchases::SelfManaged::ExpireService).not_to receive(:new)
-
-          result
-        end
-
-        it_behaves_like 'empty success response'
-      end
-    end
-
-    context 'without a current license' do
-      let!(:current_license) { nil }
-
-      it_behaves_like 'expire add-on purchase'
-    end
-
-    context 'when current license is not a cloud license' do
-      let!(:current_license) do
-        create_current_license(cloud_licensing_enabled: true, offline_cloud_licensing_enabled: true)
-      end
-
-      it_behaves_like 'expire add-on purchase'
-    end
-
-    context 'when current license does not contain a code suggestions add-on purchase' do
-      let!(:current_license) do
-        create_current_license(cloud_licensing_enabled: true, restrictions: { subscription_name: subscription_name })
-      end
-
-      it_behaves_like 'expire add-on purchase'
-    end
-
-    context 'when add-on record does not exist' do
-      before do
-        GitlabSubscriptions::AddOn.destroy_all # rubocop: disable Cop/DestroyAll
-      end
-
-      it 'creates the add-on record' do
-        expect { result }.to change { GitlabSubscriptions::AddOn.count }.by(1)
-      end
-    end
-
-    context 'when add-on purchase exists' do
-      let(:expiration_date) { Date.current + 3.months }
-      let!(:existing_add_on_purchase) do
-        create(
-          :gitlab_subscription_add_on_purchase,
-          namespace: namespace,
-          add_on: add_on,
-          expires_on: expiration_date
-        )
-      end
-
-      shared_examples 'updates the existing add-on purchase' do
-        it 'updates the existing add-on purchase' do
-          expect(GitlabSubscriptions::AddOnPurchases::UpdateService).to receive(:new)
-            .with(
-              namespace,
-              add_on,
-              {
-                add_on_purchase: existing_add_on_purchase,
-                quantity: purchased_add_on_quantity,
-                expires_on: current_license.block_changes_at,
-                purchase_xid: subscription_name
-              }
-            ).and_call_original
-
-          expect { result }.not_to change { GitlabSubscriptions::AddOnPurchase.count }
-
-          expect(current_license.block_changes_at).to eq(current_license.expires_at + 14.days)
-          expect(result[:status]).to eq(:success)
-          expect(result[:add_on_purchase]).to have_attributes(
-            id: existing_add_on_purchase.id,
-            expires_on: current_license.block_changes_at,
-            quantity: purchased_add_on_quantity,
-            purchase_xid: subscription_name
-          )
-        end
-      end
-
-      context 'when the update fails' do
-        it_behaves_like 'handle error', GitlabSubscriptions::AddOnPurchases::UpdateService
-      end
-
-      context 'when existing add-on purchase is expired' do
-        let(:expiration_date) { Date.current - 3.months }
-
-        it_behaves_like 'updates the existing add-on purchase'
-      end
-
-      it_behaves_like 'updates the existing add-on purchase'
-    end
-
-    context 'when the creation fails' do
-      it_behaves_like 'handle error', GitlabSubscriptions::AddOnPurchases::CreateService
-    end
-
-    context 'when the license has no block_changes_at set' do
-      let!(:current_license) do
-        create_current_license(
-          block_changes_at: nil,
-          cloud_licensing_enabled: true,
-          restrictions: { code_suggestions_seat_count: purchased_add_on_quantity, subscription_name: subscription_name }
-        )
-      end
-
-      it 'creates a new add-on purchase' do
-        expect(GitlabSubscriptions::AddOnPurchases::CreateService).to receive(:new)
-          .with(
-            namespace,
-            add_on,
-            {
-              quantity: purchased_add_on_quantity,
-              expires_on: current_license.expires_at,
-              purchase_xid: subscription_name
-            }
-          ).and_call_original
-
-        expect { result }.to change { GitlabSubscriptions::AddOnPurchase.count }.by(1)
-
-        expect(current_license.block_changes_at).to eq(nil)
-        expect(result[:status]).to eq(:success)
-        expect(result[:add_on_purchase]).to have_attributes(
-          expires_on: current_license.expires_at,
-          quantity: purchased_add_on_quantity,
-          purchase_xid: subscription_name
-        )
-      end
-    end
-
-    it 'creates a new add-on purchase' do
-      expect(GitlabSubscriptions::AddOnPurchases::CreateService).to receive(:new)
-        .with(
-          namespace,
-          add_on,
-          {
-            quantity: purchased_add_on_quantity,
-            expires_on: current_license.block_changes_at,
-            purchase_xid: subscription_name
-          }
-        ).and_call_original
-
-      expect { result }.to change { GitlabSubscriptions::AddOnPurchase.count }.by(1)
-
-      expect(current_license.block_changes_at).to eq(current_license.expires_at + 14.days)
-      expect(result[:status]).to eq(:success)
-      expect(result[:add_on_purchase]).to have_attributes(
-        expires_on: current_license.block_changes_at,
-        quantity: purchased_add_on_quantity,
-        purchase_xid: subscription_name
-      )
-    end
-  end
-end
diff --git a/ee/spec/services/gitlab_subscriptions/add_on_purchases/self_managed/provision_services/code_suggestions_spec.rb b/ee/spec/services/gitlab_subscriptions/add_on_purchases/self_managed/provision_services/code_suggestions_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..563c8fa828f1412c2dbafc2d4701f2997f699482
--- /dev/null
+++ b/ee/spec/services/gitlab_subscriptions/add_on_purchases/self_managed/provision_services/code_suggestions_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSubscriptions::AddOnPurchases::SelfManaged::ProvisionServices::CodeSuggestions,
+  :aggregate_failures, feature_category: :plan_provisioning do
+  subject(:result) { described_class.new.execute }
+
+  describe '#execute', :freeze_time do
+    let_it_be(:add_on) { create(:gitlab_subscription_add_on, :code_suggestions) }
+    let_it_be(:default_organization) { create(:organization, :default) }
+    let_it_be(:namespace) { nil }
+    let_it_be(:subscription_name) { 'A-S00000002' }
+
+    let!(:current_license) do
+      create_current_license(
+        cloud_licensing_enabled: true,
+        restrictions: {
+          code_suggestions_seat_count: quantity,
+          subscription_name: subscription_name
+        }
+      )
+    end
+
+    context 'when current license has no code suggestions information' do
+      let!(:current_license) { create_current_license(cloud_licensing_enabled: true) }
+
+      it_behaves_like 'provision service expires add-on purchase'
+    end
+
+    context 'when current license has zero code suggestions seats purchased' do
+      let_it_be(:quantity) { 0 }
+
+      it_behaves_like 'provision service expires add-on purchase'
+    end
+
+    context 'when current license has code suggestions seats purchased' do
+      let_it_be(:quantity) { 1 }
+
+      it_behaves_like 'provision service creates add-on purchase'
+    end
+  end
+end
diff --git a/ee/spec/support/shared_examples/services/gitlab_subscriptions/add_on_purchases/self_managed/base_provision_service_shared_examples.rb b/ee/spec/support/shared_examples/services/gitlab_subscriptions/add_on_purchases/self_managed/base_provision_service_shared_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c4bc12794bba0f9480e6e74a3af23b01ecc593ad
--- /dev/null
+++ b/ee/spec/support/shared_examples/services/gitlab_subscriptions/add_on_purchases/self_managed/base_provision_service_shared_examples.rb
@@ -0,0 +1,129 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'call service to handle the provision of code suggestions' do
+  it 'calls the service to handle the provision of code suggestions' do
+    expect_next_instance_of(
+      GitlabSubscriptions::AddOnPurchases::SelfManaged::ProvisionServices::CodeSuggestions
+    ) do |service|
+      expect(service).to receive(:execute).and_call_original
+    end
+
+    subject
+  end
+end
+
+RSpec.shared_examples 'provision service have empty success response' do
+  it 'returns a success' do
+    expect(result[:status]).to eq(:success)
+    expect(result[:add_on_purchase]).to eq(nil)
+  end
+end
+
+RSpec.shared_examples 'provision service handles error' do |service_class|
+  it 'logs and returns an error' do
+    allow_next_instance_of(service_class) do |service|
+      allow(service).to receive(:execute).and_return(ServiceResponse.error(message: 'Something went wrong'))
+    end
+
+    expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
+    expect(result[:status]).to eq(:error)
+    expect(result[:message]).to eq('Error syncing subscription add-on purchases. Message: Something went wrong')
+  end
+end
+
+RSpec.shared_examples 'provision service expires add-on purchase' do
+  context 'with existing add-on purchase' do
+    let_it_be(:expiration_date) { Date.current + 3.months }
+    let_it_be(:existing_add_on_purchase) do
+      create(
+        :gitlab_subscription_add_on_purchase,
+        namespace: namespace,
+        add_on: add_on,
+        expires_on: expiration_date
+      )
+    end
+
+    it 'does not call any service to create or update an add-on purchase' do
+      expect(GitlabSubscriptions::AddOnPurchases::CreateService).not_to receive(:new)
+      expect(GitlabSubscriptions::AddOnPurchases::UpdateService).not_to receive(:new)
+
+      result
+    end
+
+    context 'when the expiration fails' do
+      it_behaves_like 'provision service handles error', GitlabSubscriptions::AddOnPurchases::SelfManaged::ExpireService
+    end
+
+    it 'expires the existing add-on purchase' do
+      expect do
+        result
+        existing_add_on_purchase.reload
+      end.to change { existing_add_on_purchase.expires_on }.from(expiration_date).to(Date.yesterday)
+    end
+
+    it_behaves_like 'provision service have empty success response'
+  end
+
+  context 'without existing add-on purchase' do
+    it 'does not call any of the services to update an add-on purchase' do
+      expect(GitlabSubscriptions::AddOnPurchases::CreateService).not_to receive(:new)
+      expect(GitlabSubscriptions::AddOnPurchases::UpdateService).not_to receive(:new)
+      expect(GitlabSubscriptions::AddOnPurchases::SelfManaged::ExpireService).not_to receive(:new)
+
+      result
+    end
+
+    it_behaves_like 'provision service have empty success response'
+  end
+end
+
+RSpec.shared_examples 'provision service updates the existing add-on purchase' do
+  it 'updates the existing add-on purchase' do
+    expect(GitlabSubscriptions::AddOnPurchases::UpdateService).to receive(:new)
+      .with(
+        namespace,
+        add_on,
+        {
+          add_on_purchase: existing_add_on_purchase,
+          quantity: quantity,
+          expires_on: current_license.block_changes_at,
+          purchase_xid: subscription_name
+        }
+      ).and_call_original
+
+    expect { result }.not_to change { GitlabSubscriptions::AddOnPurchase.count }
+
+    expect(current_license.block_changes_at).to eq(current_license.expires_at + 14.days)
+    expect(result[:status]).to eq(:success)
+    expect(result[:add_on_purchase]).to have_attributes(
+      id: existing_add_on_purchase.id,
+      expires_on: current_license.block_changes_at,
+      quantity: quantity,
+      purchase_xid: subscription_name
+    )
+  end
+end
+
+RSpec.shared_examples 'provision service creates add-on purchase' do
+  it 'creates a new add-on purchase' do
+    expect(GitlabSubscriptions::AddOnPurchases::CreateService).to receive(:new).with(
+      namespace,
+      add_on,
+      {
+        add_on_purchase: nil,
+        quantity: quantity,
+        expires_on: current_license.expires_at + 14.days,
+        purchase_xid: subscription_name
+      }
+    ).and_call_original
+
+    expect { result }.to change { GitlabSubscriptions::AddOnPurchase.count }.by(1)
+
+    expect(result[:status]).to eq(:success)
+    expect(result[:add_on_purchase]).to have_attributes(
+      expires_on: current_license.expires_at + 14.days,
+      quantity: quantity,
+      purchase_xid: subscription_name
+    )
+  end
+end
diff --git a/ee/spec/support/shared_examples/services/gitlab_subscriptions/add_on_purchases/self_managed/provision_code_suggestion_service_shared_examples.rb b/ee/spec/support/shared_examples/services/gitlab_subscriptions/add_on_purchases/self_managed/provision_code_suggestion_service_shared_examples.rb
deleted file mode 100644
index c30be87518796e932ff63cc0169d5efbc5896b50..0000000000000000000000000000000000000000
--- a/ee/spec/support/shared_examples/services/gitlab_subscriptions/add_on_purchases/self_managed/provision_code_suggestion_service_shared_examples.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'call service to handle the provision of code suggestions' do
-  it 'calls the service to handle the provision of code suggestions' do
-    expect_next_instance_of(
-      GitlabSubscriptions::AddOnPurchases::SelfManaged::ProvisionCodeSuggestionsService
-    ) do |service|
-      expect(service).to receive(:execute).and_call_original
-    end
-
-    subject
-  end
-end