← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:remove-commercial-subscription-vouchers into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:remove-commercial-subscription-vouchers into launchpad:master.

Commit message:
Remove commercial subscription voucher system

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/377675

The ability to purchase commercial subscriptions using vouchers has been disabled on production since May 2018, so it's about time to remove the supporting code entirely.  Existing commercial subscriptions remain valid, and commercial admins can still grant them manually.

(This must not be merged until https://code.launchpad.net/~cjwatson/lp-production-configs/remove-commercial-subscription-vouchers/+merge/377674 has been merged.)
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:remove-commercial-subscription-vouchers into launchpad:master.
diff --git a/configs/development/launchpad-lazr.conf b/configs/development/launchpad-lazr.conf
index bc09133..1a53919 100644
--- a/configs/development/launchpad-lazr.conf
+++ b/configs/development/launchpad-lazr.conf
@@ -61,12 +61,6 @@ foreign_tree_store: file:///tmp/foreign-branches
 [codeimportdispatcher]
 forced_hostname: bazaar-importer
 
-[commercial]
-voucher_proxy_url: http://launchpad.test
-voucher_proxy_port: 2323
-voucher_proxy_timeout: 60000
-purchase_subscription_url: http://ubuntu.recycledmania.com/product_info.php?products_id=227
-
 [database]
 rw_main_master: dbname=launchpad_dev
 rw_main_slave:  dbname=launchpad_dev
diff --git a/configs/development/salesforce-configure-normal.zcml b/configs/development/salesforce-configure-normal.zcml
deleted file mode 100644
index 137ff07..0000000
--- a/configs/development/salesforce-configure-normal.zcml
+++ /dev/null
@@ -1,26 +0,0 @@
-<!-- Copyright 2009 Canonical Ltd.  This software is licensed under the
-     GNU Affero General Public License version 3 (see the file LICENSE).
--->
-
-<configure
-    xmlns="http://namespaces.zope.org/zope";
-    xmlns:browser="http://namespaces.zope.org/browser";
-    xmlns:i18n="http://namespaces.zope.org/i18n";
-    xmlns:zope="http://namespaces.zope.org/zope";
-    i18n_domain="launchpad">
-
-    <class class="lp.services.salesforce.proxy.SalesforceVoucherProxy">
-        <allow interface="lp.services.salesforce.interfaces.ISalesforceVoucherProxy" />
-    </class>
-
-    <class class="lp.services.salesforce.proxy.Voucher">
-        <allow attributes="voucher_id project status term_months __str__" />
-    </class>
-
-   <securedutility
-        class="lp.services.salesforce.tests.proxy.TestSalesforceVoucherProxy"
-        provides="lp.services.salesforce.interfaces.ISalesforceVoucherProxy">
-        <allow interface="lp.services.salesforce.interfaces.ISalesforceVoucherProxy" />
-    </securedutility>
-
-</configure>
diff --git a/configs/replicated-development/salesforce-configure-normal.zcml b/configs/replicated-development/salesforce-configure-normal.zcml
deleted file mode 120000
index eb52cfc..0000000
--- a/configs/replicated-development/salesforce-configure-normal.zcml
+++ /dev/null
@@ -1 +0,0 @@
-../development/salesforce-configure-normal.zcml
\ No newline at end of file
diff --git a/lib/lp/app/javascript/licence/tests/test_licence.html b/lib/lp/app/javascript/licence/tests/test_licence.html
index a8da28f..b070cd2 100644
--- a/lib/lp/app/javascript/licence/tests/test_licence.html
+++ b/lib/lp/app/javascript/licence/tests/test_licence.html
@@ -90,9 +90,7 @@ GNU Affero General Public License version 3 (see the file LICENSE).
             </div>
             </div>
             <div id="proprietary" class="hidden">
-              Commercial and proprietary projects do not qualify for free hosting;
-              therefore a subscription needs to be purchased in order to host this
-              project on Launchpad.
+              Commercial and proprietary projects do not qualify for free hosting.
             </div>
         </script>
     </body>
diff --git a/lib/lp/app/widgets/templates/license.pt b/lib/lp/app/widgets/templates/license.pt
index a88d647..53a9a55 100644
--- a/lib/lp/app/widgets/templates/license.pt
+++ b/lib/lp/app/widgets/templates/license.pt
@@ -75,31 +75,7 @@
        tal:attributes="href config/commercial/licensing_policy_url" class="sprite new-window">
       licensing policies</a>.
     <br/>
-    <div id="proprietary" class="hidden"
-         tal:condition="not: request/features/commercial_subscriptions.new.disabled">
-      Commercial and proprietary projects do not qualify for free hosting;
-      therefore a subscription needs to be purchased in order to host this
-      project on Launchpad.
-      <p>Here are the steps to obtain a commercial subscription:</p>
-      <ol>
-        <li>Purchase a Launchpad subscription in the
-          <a tal:define="config modules/lp.services.config/config"
-             tal:attributes="href config/commercial/purchase_subscription_url">
-            Canonical Shop</a>.
-        </li>
-        <li>You will receive an email that your order has been processed.</li>
-        <li>Then you may apply the purchased subscription to this project
-            on your
-          <a href="/people/+me/+vouchers"
-             tal:attributes="href string:${request/lp:person/fmt:url}/+vouchers">
-            voucher management page</a>.
-        </li>
-      </ol>
-      <a href="mailto:commercial@xxxxxxxxxxxxx";>Contact us</a>
-      for more information regarding commercial subscriptions.
-    </div>
-    <div id="proprietary" class="hidden"
-         tal:condition="request/features/commercial_subscriptions.new.disabled">
+    <div id="proprietary" class="hidden">
       Commercial and proprietary projects do not qualify for free hosting.
       If you have authorization from Canonical to use Launchpad's commercial
       features, then <a href="mailto:commercial@xxxxxxxxxxxxx";>contact us</a>
diff --git a/lib/lp/registry/browser/configure.zcml b/lib/lp/registry/browser/configure.zcml
index 66bb0fd..5ad858d 100644
--- a/lib/lp/registry/browser/configure.zcml
+++ b/lib/lp/registry/browser/configure.zcml
@@ -1107,13 +1107,6 @@
         template="../templates/person-portlet-related-projects.pt"
         />
     <browser:page
-        name="+vouchers"
-        for="lp.registry.interfaces.person.IPerson"
-        class="lp.registry.browser.person.PersonVouchersView"
-        permission="launchpad.Commercial"
-        template="../templates/person-vouchers.pt"
-        />
-    <browser:page
         name="+rdf"
         for="lp.registry.interfaces.person.IPerson"
         class="lp.registry.browser.person.PersonRdfView"
diff --git a/lib/lp/registry/browser/person.py b/lib/lp/registry/browser/person.py
index da99b67..b6273fd 100644
--- a/lib/lp/registry/browser/person.py
+++ b/lib/lp/registry/browser/person.py
@@ -39,7 +39,6 @@ __all__ = [
     'PersonSetContextMenu',
     'PersonSetNavigation',
     'PersonView',
-    'PersonVouchersView',
     'PPANavigationMenuMixIn',
     'RedirectToEditLanguagesView',
     'RestrictedMembershipsPersonView',
@@ -71,8 +70,6 @@ from zope.component import (
     getUtility,
     queryMultiAdapter,
     )
-from zope.error.interfaces import IErrorReportingUtility
-from zope.formlib import form
 from zope.formlib.form import FormFields
 from zope.formlib.widget import CustomWidgetFactory
 from zope.formlib.widgets import (
@@ -121,7 +118,6 @@ from lp.app.validators.email import valid_email
 from lp.app.validators.username import username_validator
 from lp.app.widgets.image import ImageChangeWidget
 from lp.app.widgets.itemswidgets import (
-    LaunchpadDropdownWidget,
     LaunchpadRadioWidget,
     LaunchpadRadioWidgetWithDescription,
     )
@@ -142,7 +138,6 @@ from lp.registry.browser.menu import (
     )
 from lp.registry.browser.teamjoin import TeamJoinMixin
 from lp.registry.enums import PersonVisibility
-from lp.registry.errors import VoucherAlreadyRedeemed
 from lp.registry.interfaces.codeofconduct import ISignedCodeOfConductSet
 from lp.registry.interfaces.distribution import IDistribution
 from lp.registry.interfaces.distributionsourcepackage import (
@@ -193,7 +188,6 @@ from lp.registry.model.person import get_recipients
 from lp.services.config import config
 from lp.services.database.decoratedresultset import DecoratedResultSet
 from lp.services.database.sqlbase import flush_database_updates
-from lp.services.features import getFeatureFlag
 from lp.services.feeds.browser import FeedsMixin
 from lp.services.geoip.interfaces import IRequestPreferredLanguages
 from lp.services.gpg.interfaces import (
@@ -227,11 +221,6 @@ from lp.services.propertycache import (
     cachedproperty,
     get_property_cache,
     )
-from lp.services.salesforce.interfaces import (
-    ISalesforceVoucherProxy,
-    REDEEMABLE_VOUCHER_STATUSES,
-    SalesforceVoucherProxyException,
-    )
 from lp.services.verification.interfaces.authtoken import LoginTokenType
 from lp.services.verification.interfaces.logintoken import ILoginTokenSet
 from lp.services.webapp import (
@@ -792,7 +781,6 @@ class PersonOverviewMenu(ApplicationMenu, PersonMenuMixin,
         'projects',
         'activate_ppa',
         'maintained',
-        'manage_vouchers',
         'owned_teams',
         'synchronised',
         'view_ppa_subscriptions',
@@ -820,16 +808,6 @@ class PersonOverviewMenu(ApplicationMenu, PersonMenuMixin,
         return Link(target, text, enabled=enabled, icon='info')
 
     @enabled_with_permission('launchpad.Edit')
-    def manage_vouchers(self):
-        target = '+vouchers'
-        text = 'Manage commercial subscriptions'
-        summary = 'Purchase and redeem commercial subscription vouchers'
-        return Link(
-            target, text, summary, icon='info',
-            enabled=not bool(
-                getFeatureFlag('commercial_subscriptions.new.disabled')))
-
-    @enabled_with_permission('launchpad.Edit')
     def editlanguages(self):
         target = '+editlanguages'
         text = 'Set preferred languages'
@@ -1352,126 +1330,6 @@ class PersonAccountAdministerView(LaunchpadFormView):
         self.context.setStatus(data['status'], self.user, data['comment'])
 
 
-class PersonVouchersView(LaunchpadFormView):
-    """Form for displaying and redeeming commercial subscription vouchers."""
-
-    custom_widget_voucher = LaunchpadDropdownWidget
-
-    @property
-    def page_title(self):
-        return 'Commercial subscription vouchers'
-
-    @property
-    def cancel_url(self):
-        """See `LaunchpadFormView`."""
-        return canonical_url(self.context)
-
-    def setUpFields(self):
-        """Set up the fields for this view."""
-
-        self.form_fields = []
-        self.form_fields = (self.createProjectField() +
-                            self.createVoucherField())
-
-    def createProjectField(self):
-        """Create the project field for selection commercial projects.
-
-        The vocabulary shows commercial projects owned by the current user.
-        """
-        field = FormFields(
-            Choice(__name__='project',
-                   title=_('Select the project you wish to subscribe'),
-                   description=_('Commercial projects you administer'),
-                   vocabulary="CommercialProjects",
-                   required=True),
-            render_context=self.render_context)
-        return field
-
-    def createVoucherField(self):
-        """Create voucher field.
-
-        Only redeemable vouchers owned by the user are shown.
-        """
-        terms = []
-        for voucher in self.redeemable_vouchers:
-            text = "%s (%d months)" % (
-                voucher.voucher_id, voucher.term_months)
-            terms.append(SimpleTerm(voucher, voucher.voucher_id, text))
-        voucher_vocabulary = SimpleVocabulary(terms)
-        field = FormFields(
-            Choice(__name__='voucher',
-                   title=_('Select a voucher'),
-                   description=_('Choose one of these redeemable vouchers'),
-                   vocabulary=voucher_vocabulary,
-                   required=True),
-            render_context=self.render_context)
-        return field
-
-    @cachedproperty
-    def show_voucher_selection(self):
-        return self.redeemable_vouchers or self.errors
-
-    @cachedproperty
-    def redeemable_vouchers(self):
-        """Get the list redeemable vouchers owned by the user."""
-        vouchers = [
-            voucher for voucher in
-            self.context.getRedeemableCommercialSubscriptionVouchers()]
-        return vouchers
-
-    def removeRedeemableVoucher(self, voucher):
-        """Remove the voucher from the cached list of redeemable vouchers.
-
-        Updated the voucher field and widget so that the form can be reused.
-        """
-        vouchers = get_property_cache(self).redeemable_vouchers
-        vouchers.remove(voucher)
-        # Setup the fields and widgets again, but withut the submitted data.
-        self.form_fields = (
-            self.createProjectField() + self.createVoucherField())
-        self.widgets = form.setUpWidgets(
-            self.form_fields.select('project', 'voucher'),
-            self.prefix, self.context, self.request,
-            data=self.initial_values, ignore_request=True)
-
-    @action(_("Redeem"), name="redeem")
-    def redeem_action(self, action, data):
-        salesforce_proxy = getUtility(ISalesforceVoucherProxy)
-        project = data['project']
-        voucher = data['voucher']
-
-        try:
-            # Perform a check that the submitted voucher id is valid.
-            check_voucher = salesforce_proxy.getVoucher(voucher.voucher_id)
-            if not check_voucher.status in REDEEMABLE_VOUCHER_STATUSES:
-                self.addError(
-                    _("Voucher %s has invalid status %s"
-                      % (check_voucher.voucher_id, check_voucher.status)))
-                return
-            # Redeem the voucher in Launchpad, marking the subscription as
-            # pending. Launchpad will honour the subscription but a job will
-            # still need to be run to notify Salesforce.
-            try:
-                project.redeemSubscriptionVoucher(
-                    voucher='pending-' + voucher.voucher_id,
-                    registrant=self.context,
-                    purchaser=self.context,
-                    subscription_months=voucher.term_months)
-            except VoucherAlreadyRedeemed as error:
-                self.setFieldError('voucher', _(error.message))
-                return
-            self.request.response.addInfoNotification(
-                _("Voucher redeemed successfully"))
-            self.removeRedeemableVoucher(voucher)
-        except SalesforceVoucherProxyException as error:
-            self.addError(
-                _("The voucher could not be redeemed at this time."))
-            # Log an OOPS report without raising an error.
-            info = (error.__class__, error, None)
-            globalErrorUtility = getUtility(IErrorReportingUtility)
-            globalErrorUtility.raising(info, self.request)
-
-
 class PersonLanguagesView(LaunchpadFormView):
     """Edit preferred languages for a person or team."""
     schema = Interface
diff --git a/lib/lp/registry/browser/product.py b/lib/lp/registry/browser/product.py
index ad188ec..bd398aa 100644
--- a/lib/lp/registry/browser/product.py
+++ b/lib/lp/registry/browser/product.py
@@ -1589,7 +1589,7 @@ class ProductReviewLicenseView(ReturnToReferrerMixin, ProductEditView,
                     'license_approved',
                     'Proprietary projects may not be manually '
                     'approved to use Launchpad.  Proprietary projects '
-                    'must use the commercial subscription voucher system '
+                    'must be granted a commercial subscription '
                     'to be allowed to use Launchpad.')
             else:
                 # An Other/Open Source licence was specified so it may be
diff --git a/lib/lp/registry/browser/tests/product-views.txt b/lib/lp/registry/browser/tests/product-views.txt
index d72e646..680c9f2 100644
--- a/lib/lp/registry/browser/tests/product-views.txt
+++ b/lib/lp/registry/browser/tests/product-views.txt
@@ -232,8 +232,8 @@ purchase a commercial subscription.
     ...     firefox, name='+review-license', form=form)
     >>> view.errors
     [u'Proprietary projects may not be manually approved to use Launchpad.
-       Proprietary projects must use the commercial subscription voucher
-       system to be allowed to use Launchpad.']
+       Proprietary projects must be granted a commercial subscription
+       to be allowed to use Launchpad.']
     >>> firefox.license_approved
     False
     >>> print firefox.reviewer_whiteboard
diff --git a/lib/lp/registry/browser/tests/test_commercialsubscription.py b/lib/lp/registry/browser/tests/test_commercialsubscription.py
deleted file mode 100644
index 791a569..0000000
--- a/lib/lp/registry/browser/tests/test_commercialsubscription.py
+++ /dev/null
@@ -1,180 +0,0 @@
-# Copyright 2012-2018 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Test views that manage commercial subscriptions."""
-
-__metaclass__ = type
-
-from lp.services.salesforce.interfaces import ISalesforceVoucherProxy
-from lp.services.salesforce.tests.proxy import TestSalesforceVoucherProxy
-from lp.services.webapp.publisher import canonical_url
-from lp.testing import (
-    FakeAdapterMixin,
-    login_celebrity,
-    login_person,
-    person_logged_in,
-    TestCaseWithFactory,
-    )
-from lp.testing.layers import DatabaseFunctionalLayer
-from lp.testing.pages import (
-    extract_text,
-    find_tags_by_class,
-    )
-from lp.testing.views import create_initialized_view
-
-
-class PersonVouchersViewTestCase(FakeAdapterMixin, TestCaseWithFactory):
-    layer = DatabaseFunctionalLayer
-
-    def makeVouchers(self, user, number, voucher_proxy=None):
-        if voucher_proxy is None:
-            voucher_proxy = TestSalesforceVoucherProxy()
-        self.registerUtility(voucher_proxy, ISalesforceVoucherProxy)
-        vouchers = []
-        for n in range(number):
-            vouchers.append(voucher_proxy.grantVoucher(user, user, user, 12))
-        return vouchers
-
-    def test_init_without_vouchers_or_projects(self):
-        # The view provides common view properties, but the form is disabled.
-        user = self.factory.makePerson()
-        self.factory.makeProduct(owner=user)
-        self.makeVouchers(user, 0)
-        user_url = canonical_url(user)
-        with person_logged_in(user):
-            view = create_initialized_view(user, '+vouchers')
-        self.assertEqual('Commercial subscription vouchers', view.page_title)
-        self.assertEqual(user_url, view.cancel_url)
-        self.assertIs(None, view.next_url)
-        self.assertEqual(0, len(view.redeemable_vouchers))
-
-    def assertFields(self, view):
-        self.assertEqual(1, len(view.redeemable_vouchers))
-        self.assertEqual(
-            ['project', 'voucher'], [f.__name__ for f in view.form_fields])
-
-    def test_init_with_vouchers_and_projects(self):
-        # The fields are setup when the user hase both vouchers and projects.
-        user = self.factory.makePerson()
-        login_person(user)
-        self.makeVouchers(user, 1)
-        self.factory.makeProduct(owner=user)
-        view = create_initialized_view(user, '+vouchers')
-        self.assertFields(view)
-
-    def test_init_with_commercial_admin_with_vouchers(self):
-        # The fields are setup if the commercial admin has vouchers.
-        commercial_admin = login_celebrity('commercial_admin')
-        self.makeVouchers(commercial_admin, 1)
-        view = create_initialized_view(commercial_admin, '+vouchers')
-        self.assertFields(view)
-
-    def test_with_commercial_admin_for_user_with_vouchers_and_projects(self):
-        # A commercial admin can see another user's vouchers.
-        user = self.factory.makePerson()
-        login_person(user)
-        self.makeVouchers(user, 1)
-        self.factory.makeProduct(owner=user)
-        login_celebrity('commercial_admin')
-        view = create_initialized_view(user, '+vouchers')
-        self.assertFields(view)
-
-    def assertRedeem(self, view, project, remaining=0):
-        self.assertEqual([], view.errors)
-        self.assertIsNot(None, project.commercial_subscription)
-        self.assertEqual(remaining, len(view.redeemable_vouchers))
-        self.assertEqual(
-            remaining, len(view.form_fields['voucher'].field.vocabulary))
-        self.assertEqual(
-            remaining, len(view.widgets['voucher'].vocabulary))
-
-    def makeForm(self, project, voucher_id):
-        return {
-            'field.project': project.name,
-            'field.voucher': voucher_id,
-            'field.actions.redeem': 'Redeem',
-            }
-
-    def test_redeem_with_commercial_admin_for_user(self):
-        # A commercial admin can redeem a voucher for a user.
-        project = self.factory.makeProduct()
-        user = project.owner
-        [voucher_id] = self.makeVouchers(user, 1)
-        form = self.makeForm(project, voucher_id)
-        login_celebrity('commercial_admin')
-        view = create_initialized_view(user, '+vouchers', form=form)
-        self.assertRedeem(view, project)
-
-    def test_redeem_with_commercial_admin(self):
-        # The fields are setup if the commercial admin has vouchers.
-        commercial_admin = login_celebrity('commercial_admin')
-        [voucher_id] = self.makeVouchers(commercial_admin, 1)
-        project = self.factory.makeProduct()
-        form = self.makeForm(project, voucher_id)
-        view = create_initialized_view(
-            commercial_admin, '+vouchers', form=form)
-        self.assertRedeem(view, project)
-
-    def test_redeem_twice_with_commercial_admin(self):
-        # The fields are setup if the commercial admin has vouchers.
-        commercial_admin = login_celebrity('commercial_admin')
-        voucher_proxy = TestSalesforceVoucherProxy()
-        voucher_id_1, voucher_id_2 = self.makeVouchers(
-            commercial_admin, 2, voucher_proxy)
-        project_1 = self.factory.makeProduct()
-        project_2 = self.factory.makeProduct()
-        form = self.makeForm(project_1, voucher_id_1)
-        view = create_initialized_view(
-            commercial_admin, '+vouchers', form=form)
-        self.assertRedeem(view, project_1, remaining=1)
-        # A job will notify Salesforce of the voucher redemption but here we
-        # will do it manually.
-        voucher_proxy.redeemVoucher(
-            voucher_id_1, commercial_admin, project_1)
-
-        form = self.makeForm(project_2, voucher_id_2)
-        view = create_initialized_view(
-            commercial_admin, '+vouchers', form=form)
-        self.assertRedeem(view, project_2)
-
-    def test_pending_vouchers_excluded(self):
-        # Vouchers pending redemption in Salesforce are not included in choice.
-        commercial_admin = login_celebrity('commercial_admin')
-        voucher_id_1, voucher_id_2 = self.makeVouchers(commercial_admin, 2)
-        project_1 = self.factory.makeProduct()
-        self.factory.makeCommercialSubscription(
-            project_1, False, 'pending-' + voucher_id_1)
-        view = create_initialized_view(commercial_admin, '+vouchers')
-        vouchers = list(view.widgets['voucher'].vocabulary)
-        # Only voucher2 in vocab since voucher1 is pending redemption.
-        self.assertEqual(1, len(vouchers))
-        self.assertEqual(voucher_id_2, vouchers[0].token)
-
-    def test_redeem_twice_causes_error(self):
-        # If a voucher is redeemed twice, the second attempt is rejected.
-        commercial_admin = login_celebrity('commercial_admin')
-        voucher_id_1, voucher_id_2 = self.makeVouchers(commercial_admin, 2)
-        project_1 = self.factory.makeProduct(name='p1')
-        project_2 = self.factory.makeProduct(name='p2')
-        url = canonical_url(commercial_admin, view_name='+vouchers')
-        browser = self.getUserBrowser(url, commercial_admin)
-        # A second browser opens the +vouchers page before the first browser
-        # attempts to redeem the voucher.
-        browser2 = self.getUserBrowser(url, commercial_admin)
-        browser.getControl(
-            'Select the project you wish to subscribe').value = 'p1'
-        browser.getControl(
-            'Select a voucher').getControl(voucher_id_1).selected = True
-        browser.getControl('Redeem').click()
-        with person_logged_in(commercial_admin):
-            self.assertIsNotNone(project_1.commercial_subscription)
-
-        browser2.getControl(
-            'Select the project you wish to subscribe').value = 'p2'
-        browser2.getControl(
-            'Select a voucher').getControl(voucher_id_1).selected = True
-        browser2.getControl('Redeem').click()
-        with person_logged_in(commercial_admin):
-            self.assertIsNone(project_2.commercial_subscription)
-        error_messages = find_tags_by_class(browser2.contents, 'message')
-        self.assertEqual(extract_text(error_messages[1]), 'Invalid value')
diff --git a/lib/lp/registry/configure.zcml b/lib/lp/registry/configure.zcml
index c5b2673..2d26209 100644
--- a/lib/lp/registry/configure.zcml
+++ b/lib/lp/registry/configure.zcml
@@ -1427,7 +1427,6 @@
                 owner
                 programminglang
                 projectgroup
-                redeemSubscriptionVoucher
                 releaseroot
                 screenshotsurl
                 sourceforgeproject
diff --git a/lib/lp/registry/doc/commercialsubscription.txt b/lib/lp/registry/doc/commercialsubscription.txt
index 0192b34..dffd2fa 100644
--- a/lib/lp/registry/doc/commercialsubscription.txt
+++ b/lib/lp/registry/doc/commercialsubscription.txt
@@ -21,25 +21,9 @@ indicated by 'None'.
     >>> print bzr.commercial_subscription
     None
 
-Redeem a voucher and check that the commercial_subscription
-attribute is correct.
+Make a commercial subscription.
 
-    >>> owner = bzr.owner
-    >>> owner.is_team
-    False
-    >>> bzr.redeemSubscriptionVoucher('asdf123', owner, owner, 12, 'notes')
-    >>> verifyObject(ICommercialSubscription, bzr.commercial_subscription)
-    True
-    >>> print bzr.commercial_subscription.product.name
-    bzr
-    >>> print bzr.commercial_subscription.sales_system_id
-    asdf123
-
-If a voucher is attempted to be redeemed twice, an error occurs.
-    >>> bzr.redeemSubscriptionVoucher('asdf123', owner, owner, 12)
-    Traceback (most recent call last):
-    ...
-    VoucherAlreadyRedeemed: Voucher asdf123 has already been redeemed for Bazaar
+    >>> _ = factory.makeCommercialSubscription(bzr)
 
 Commercial subscriptions have zope.Public permissions for reading.
 
@@ -60,7 +44,7 @@ regular users, and the project owner all are denied.
     >>> check_permission('launchpad.Commercial', bzr.commercial_subscription)
     False
 
-    >>> ignored = login_person(owner)
+    >>> ignored = login_person(bzr.owner)
     >>> check_permission('launchpad.Commercial', bzr.commercial_subscription)
     False
 
@@ -76,141 +60,6 @@ A member of the commercial admins team does have modification privileges.
     >>> check_permission('launchpad.Commercial', bzr.commercial_subscription)
     True
 
-
-The expiration date can be extended by redeeming another voucher.  In
-real life this would be done nearer to the expiration date.  For
-testing consistency, let's force the start and expiry dates to a known
-date and use a known time as the current time.
-
-    >>> from zope.security.proxy import removeSecurityProxy
-    >>> from datetime import date, datetime, timedelta
-    >>> from pytz import UTC
-    >>> login('no-priv@xxxxxxxxxxxxx')
-    >>> now = datetime(2008, 1, 10, tzinfo=UTC)
-    >>> subscription = removeSecurityProxy(bzr.commercial_subscription)
-    >>> subscription.date_starts = datetime(2008,1,15,tzinfo=UTC)
-    >>> subscription.date_expires = datetime(2009,1,15,tzinfo=UTC)
-    >>> old_date_expires = bzr.commercial_subscription.date_expires
-    >>> old_date_starts = bzr.commercial_subscription.date_starts
-    >>> bzr.redeemSubscriptionVoucher('foo456', owner, owner, 12,
-    ...     'notes', current_datetime=now)
-    >>> print bzr.commercial_subscription.sales_system_id
-    foo456
-
-The start date should not have changed.
-
-    >>> bzr.commercial_subscription.date_starts == old_date_starts
-    True
-
-The difference between the expiration dates is one year -- 365 days
-for 2009.
-
-    >>> expiry_diff = (bzr.commercial_subscription.date_expires -
-    ...                old_date_expires)
-    >>> expiry_diff == timedelta(365)
-    True
-
-A voucher can be redeemed for any number of months (as reported by the
-Salesforce proxy).  Test for a 6 month subscription.
-
-    >>> old_date_expires = bzr.commercial_subscription.date_expires
-    >>> old_date_starts = bzr.commercial_subscription.date_starts
-    >>> bzr.redeemSubscriptionVoucher('foo457', owner, owner, 6,
-    ...     'notes', current_datetime=now)
-    >>> print bzr.commercial_subscription.sales_system_id
-    foo457
-
-The start date did not change.
-
-    >>> bzr.commercial_subscription.date_starts == old_date_starts
-    True
-
-The expiration date has been increased by six months, from Jan-15-2010
-to Jul-15-2010.
-
-    >>> expiry_diff = (bzr.commercial_subscription.date_expires -
-    ...                old_date_expires)
-    >>> expiry_diff == (datetime(2010,7,15) - datetime(2010,1,15))
-    True
-
-Time calculations are sometimes prone to wrapping problems.  Let's
-ensure they wrap properly.
-
-End the subscription in November and add one month.
-
-    >>> subscription.date_expires = datetime(2009,11,15,tzinfo=UTC)
-    >>> old_date_expires = bzr.commercial_subscription.date_expires
-    >>> bzr.redeemSubscriptionVoucher('foo456', owner, owner, 1,
-    ...     'notes', current_datetime=now)
-    >>> print subscription.date_expires
-    2009-12-15 ...
-
-End the subscription in November and add two months.
-
-    >>> subscription.date_expires = datetime(2009,11,15,tzinfo=UTC)
-    >>> old_date_expires = bzr.commercial_subscription.date_expires
-    >>> bzr.redeemSubscriptionVoucher('foo1456', owner, owner, 2,
-    ...     'notes', current_datetime=now)
-    >>> print subscription.date_expires
-    2010-01-15...
-
-End the subscription in November and add 11 months.
-
-    >>> subscription.date_expires = datetime(2009,11,15,tzinfo=UTC)
-    >>> old_date_expires = bzr.commercial_subscription.date_expires
-    >>> bzr.redeemSubscriptionVoucher('foo2456', owner, owner, 11,
-    ...     'notes', current_datetime=now)
-    >>> print subscription.date_expires
-    2010-10-15...
-
-Ensure wrapping to the start of the next month works when the
-expiration date would normally be an invalid day of the month.  A
-subscription that expires on the October 31 and is extended by four
-months will be calculated as the last day of February, not the
-nonexistent February 31.
-
-    >>> subscription.date_expires = datetime(2009,10,31,tzinfo=UTC)
-    >>> old_date_expires = bzr.commercial_subscription.date_expires
-    >>> bzr.redeemSubscriptionVoucher('foo3456', owner, owner, 4,
-    ...     'notes', current_datetime=now)
-    >>> print subscription.date_expires
-    2010-02-28...
-
-And when the new date is in a leap year, the last day of February is
-the 29th.
-
-    >>> subscription.date_expires = datetime(2011,10,31,tzinfo=UTC)
-    >>> old_date_expires = bzr.commercial_subscription.date_expires
-    >>> bzr.redeemSubscriptionVoucher('foo4456', owner, owner, 4,
-    ...     'notes', current_datetime=now)
-    >>> print subscription.date_expires
-    2012-02-29...
-
-Extending a subscription after it has expired sets the date started to
-the current date.
-
-    >>> subscription.date_starts = datetime(2008,1,15,tzinfo=UTC)
-    >>> subscription.date_expires = datetime(2009,1,15,tzinfo=UTC)
-    >>> now = datetime(2010, 1, 1, tzinfo=UTC)
-    >>> bzr.redeemSubscriptionVoucher('foo5456', owner, owner, 12,
-    ...     'notes', current_datetime=now)
-    >>> print subscription.date_expires
-    2011-01-01...
-    >>> print subscription.date_starts
-    2010-01-01...
-
-If the original subscription has not expired the start date is not affected.
-
-    >>> subscription.date_starts = datetime(2008,1,15,tzinfo=UTC)
-    >>> subscription.date_expires = datetime(2009,1,15,tzinfo=UTC)
-    >>> now = datetime(2009, 1, 1, tzinfo=UTC)
-    >>> bzr.redeemSubscriptionVoucher('foo6456', owner, owner, 12,
-    ...     'notes', current_datetime=now)
-    >>> print subscription.date_expires
-    2010-01-15...
-    >>> print subscription.date_starts
-    2008-01-15...
-
 The commercial_subscription_is_due attribute is true if the licence
 does not qualify for free hosting and the commercial subscription
 is inactive or about to expire.  The is_permitted attribute is
@@ -221,9 +70,13 @@ or if the licence has been reviewed and been manually approved.
 
 The commercial subscription is about to expire here.
 
+    >>> from datetime import date, datetime, timedelta
+    >>> from pytz import UTC
+    >>> from zope.security.proxy import removeSecurityProxy
     >>> from lp.registry.interfaces.product import License
     >>> login('foo.bar@xxxxxxxxxxxxx')
     >>> bzr.licenses = [License.OTHER_PROPRIETARY]
+    >>> subscription = removeSecurityProxy(bzr.commercial_subscription)
     >>> subscription.date_expires = (
     ...     datetime.now(UTC) + timedelta(29))
     >>> bzr.qualifies_for_free_hosting
@@ -472,9 +325,8 @@ A reviewer can search for projects without a commercial subscription.
     ...     print product.name
     mega-money-maker
 
-You can search for products based on the date of that
-its commercial subscription was modified, which only
-happens when a voucher is redeemed.
+You can search for products based on the date when
+their commercial subscription was modified.
 
     >>> date_last_modified = bzr.commercial_subscription.date_last_modified
     >>> for product in product_set.forReview(
diff --git a/lib/lp/registry/doc/person-vouchers.txt b/lib/lp/registry/doc/person-vouchers.txt
deleted file mode 100644
index fec0136..0000000
--- a/lib/lp/registry/doc/person-vouchers.txt
+++ /dev/null
@@ -1,81 +0,0 @@
-Person Vouchers
-===============
-
-Users who own commercial projects with a proprietary licence can
-purchase vouchers from the Canonical Shop. The voucher is subsequently
-applied to one of their projects to initiate or renew their
-commerical-use subscription.
-
-    >>> from zope.component import getUtility
-    >>> from lp.registry.interfaces.person import IPersonSet
-    >>> person_set = getUtility(IPersonSet)
-    >>> cprov = person_set.getByName('cprov')
-
-    >>> from lp.services.salesforce.tests.proxy import (
-    ...     SalesforceXMLRPCTestTransport, TestSalesforceVoucherProxy)
-    >>> test_transport = SalesforceXMLRPCTestTransport()
-    >>> voucher_proxy = TestSalesforceVoucherProxy()
-
-A method 'getAllCommercialSubscriptionVouchers' returns a dictionary
-indexed by the valid voucher statuses with a list of the person's
-vouchers in the given state.
-
-    >>> def print_vouchers(voucher_dict):
-    ...     """Pretty print a dictionary of vouchers."""
-    ...     for voucher_type in sorted(voucher_dict):
-    ...         print voucher_type
-    ...         vouchers = voucher_dict[voucher_type]
-    ...         if len(vouchers) == 0:
-    ...             print "  ", None
-    ...         else:
-    ...             for voucher in voucher_dict[voucher_type]:
-    ...                 print "  ", voucher
-
-    >>> vouchers = cprov.getAllCommercialSubscriptionVouchers(
-    ...     voucher_proxy=voucher_proxy)
-    >>> print_vouchers(vouchers)
-    Redeemed
-      None
-    Reserved
-      LPCBS12-f78df324-0cc2-11dd-8b6b-000000000004,Reserved,12,unassigned
-      LPCBS12-f78df324-0cc2-11dd-8b6b-000000000005,Reserved,12,unassigned
-    Unredeemed
-      None
-
-Just the list of redeemable vouchers can be obtained using the
-getRedeemableCommercialSubscriptionVouchers.  All vouchers of status
-Unredeemed or Reserved are returned in a single list.
-
-    >>> vouchers = cprov.getRedeemableCommercialSubscriptionVouchers(
-    ...     voucher_proxy=voucher_proxy)
-    >>> for voucher in vouchers:
-    ...     print voucher
-      LPCBS12-f78df324-0cc2-11dd-8b6b-000000000004,Reserved,12,unassigned
-      LPCBS12-f78df324-0cc2-11dd-8b6b-000000000005,Reserved,12,unassigned
-
-When a voucher is redeemed its status changes.
-
-    >>> voucher = vouchers[0]
-    >>> product = factory.makeProduct(name='ubutini')
-    >>> result = voucher_proxy.redeemVoucher(
-    ...     voucher.voucher_id, cprov, product)
-    >>> print result
-    True
-
-    >>> vouchers = cprov.getAllCommercialSubscriptionVouchers(
-    ...     voucher_proxy=voucher_proxy)
-    >>> print_vouchers(vouchers)
-    Redeemed
-      LPCBS12-f78df324-0cc2-11dd-8b6b-000000000004,Redeemed,12,ubutini
-    Reserved
-      LPCBS12-f78df324-0cc2-11dd-8b6b-000000000005,Reserved,12,unassigned
-    Unredeemed
-      None
-
-The redeemed voucher is no longer listed in the list of redeemable ones.
-
-    >>> vouchers = cprov.getRedeemableCommercialSubscriptionVouchers(
-    ...     voucher_proxy=voucher_proxy)
-    >>> for voucher in vouchers:
-    ...     print voucher
-      LPCBS12-f78df324-0cc2-11dd-8b6b-000000000005,Reserved,12,unassigned
diff --git a/lib/lp/registry/errors.py b/lib/lp/registry/errors.py
index 0f52e34..077d3ad 100644
--- a/lib/lp/registry/errors.py
+++ b/lib/lp/registry/errors.py
@@ -33,7 +33,6 @@ __all__ = [
     'TeamMembershipPolicyError',
     'UserCannotChangeMembershipSilently',
     'UserCannotSubscribePerson',
-    'VoucherAlreadyRedeemed',
     ]
 
 import httplib
@@ -237,7 +236,3 @@ class InvalidMirrorReviewState(Exception):
 
 class CannotPackageProprietaryProduct(Exception):
     """Raised when a non-PUBLIC product's series is linked to a package."""
-
-
-class VoucherAlreadyRedeemed(Exception):
-    """Raised when a voucher is redeemed more than once."""
diff --git a/lib/lp/registry/interfaces/person.py b/lib/lp/registry/interfaces/person.py
index 2865d35..de0721a 100644
--- a/lib/lp/registry/interfaces/person.py
+++ b/lib/lp/registry/interfaces/person.py
@@ -1213,25 +1213,6 @@ class IPersonViewRestricted(IHasBranches, IHasSpecifications,
     def isAnyPillarOwner():
         """Is this person the owner of any pillar?"""
 
-    def getAllCommercialSubscriptionVouchers(voucher_proxy=None):
-        """Return all commercial subscription vouchers.
-
-        All of a `Person`s vouchers are returned, regardless of redemption
-        status.  Even vouchers marked inactive are returned.
-        The result is a dictionary, indexed by the three
-        voucher statuses.
-        :return: dict
-        """
-
-    def getRedeemableCommercialSubscriptionVouchers(voucher_proxy=None):
-        """Return the set of redeemable vouchers.
-
-        The `Person`s vouchers are returned if they are unredeemed and active.
-
-        The result is a list of vouchers.
-        :return: list
-        """
-
     def hasCurrentCommercialSubscription():
         """Return if the user has a current commercial subscription."""
 
diff --git a/lib/lp/registry/interfaces/product.py b/lib/lp/registry/interfaces/product.py
index 3e4cbfc..6b3df06 100644
--- a/lib/lp/registry/interfaces/product.py
+++ b/lib/lp/registry/interfaces/product.py
@@ -798,23 +798,6 @@ class IProductView(
                                 filter_statuses.
         """
 
-    def redeemSubscriptionVoucher(voucher, registrant, purchaser,
-                                  subscription_months, whiteboard=None,
-                                  current_datetime=None):
-        """Redeem a voucher and extend the subscription expiration date.
-
-        The voucher must have already been verified to be redeemable.
-        :param voucher: The voucher id as tracked in the external system.
-        :param registrant: Who is redeeming the voucher.
-        :param purchaser: Who purchased the voucher.  May not be known.
-        :param subscription_months: integer indicating the number of months
-            the voucher is for.
-        :param whiteboard: Notes for this activity.
-        :param current_datetime: Current time.  Will be datetime.now() if not
-            specified.
-        :return: None
-        """
-
     def getPackage(distroseries):
         """Return a package in that distroseries for this product."""
 
diff --git a/lib/lp/registry/javascript/tests/test_product_views.html b/lib/lp/registry/javascript/tests/test_product_views.html
index ffef064..34e5a11 100644
--- a/lib/lp/registry/javascript/tests/test_product_views.html
+++ b/lib/lp/registry/javascript/tests/test_product_views.html
@@ -578,30 +578,12 @@ GNU Affero General Public License version 3 (see the file LICENSE).
 
                             <div id="proprietary" class="lazr-closed" style=
                             "overflow: hidden; height: 0px;">
-                                Commercial and proprietary projects do not qualify for free
-                                hosting; therefore a subscription needs to be purchased in
-                                order to host this project on Launchpad.
-
-                                <p>Here are the steps to obtain a commercial
-                                subscription:</p>
-
-
-                                <ol>
-                                    <li>Purchase a Launchpad subscription in the <a href=
-                                    "http://ubuntu.recycledmania.com/product_info.php?products_id=227";>
-                                    Canonical Shop</a>.</li>
-
-
-                                    <li>You will receive an email that your order has been
-                                    processed.</li>
-
-
-                                    <li>Then you may apply the purchased subscription to this
-                                    project on your <a href="/~mark/+vouchers">voucher
-                                    management page</a>.</li>
-                                </ol>
-                                <a href="mailto:commercial@xxxxxxxxxxxxx";>Contact us</a> for
-                                more information regarding commercial subscriptions.
+                                Commercial and proprietary projects do not qualify for free hosting.
+                                If you have authorization from Canonical to use Launchpad's commercial
+                                features, then <a href="mailto:commercial@xxxxxxxxxxxxx";>contact us</a>
+                                to have that applied to this project.  Otherwise, you may only use
+                                Launchpad for software projects that comply with the licensing policies
+                                above.
                             </div>
                         </div>
                     </div>
diff --git a/lib/lp/registry/model/person.py b/lib/lp/registry/model/person.py
index 6e56208..ad5885b 100644
--- a/lib/lp/registry/model/person.py
+++ b/lib/lp/registry/model/person.py
@@ -298,11 +298,6 @@ from lp.services.propertycache import (
     cachedproperty,
     get_property_cache,
     )
-from lp.services.salesforce.interfaces import (
-    ISalesforceVoucherProxy,
-    REDEEMABLE_VOUCHER_STATUSES,
-    VOUCHER_STATUSES,
-    )
 from lp.services.searchbuilder import any
 from lp.services.statistics.interfaces.statistic import ILaunchpadStatisticSet
 from lp.services.verification.interfaces.authtoken import LoginTokenType
@@ -1140,46 +1135,6 @@ class Person(
         )
         return rs.one()
 
-    def getAllCommercialSubscriptionVouchers(self, voucher_proxy=None):
-        """See `IPerson`."""
-        if voucher_proxy is None:
-            voucher_proxy = getUtility(ISalesforceVoucherProxy)
-        commercial_vouchers = voucher_proxy.getAllVouchers(self)
-        vouchers = {}
-        for status in VOUCHER_STATUSES:
-            vouchers[status] = []
-        for voucher in commercial_vouchers:
-            assert voucher.status in VOUCHER_STATUSES, (
-                "Voucher %s has unrecognized status %s" %
-                (voucher.voucher_id, voucher.status))
-            vouchers[voucher.status].append(voucher)
-        return vouchers
-
-    def getRedeemableCommercialSubscriptionVouchers(self, voucher_proxy=None):
-        """See `IPerson`."""
-        # Circular imports.
-        from lp.registry.model.commercialsubscription import (
-            CommercialSubscription,
-            )
-        if voucher_proxy is None:
-            voucher_proxy = getUtility(ISalesforceVoucherProxy)
-        vouchers = voucher_proxy.getUnredeemedVouchers(self)
-        # Exclude pending vouchers being sent to Salesforce and vouchers which
-        # have already been redeemed.
-        voucher_ids = [unicode(voucher.voucher_id) for voucher in vouchers]
-        voucher_expr = (
-            "trim(leading 'pending-' "
-            "from CommercialSubscription.sales_system_id)")
-        already_redeemed = list(Store.of(self).using(CommercialSubscription)
-            .find(SQL(voucher_expr), SQL(voucher_expr).is_in(voucher_ids)))
-        redeemable_vouchers = [voucher for voucher in vouchers
-                               if voucher.voucher_id not in already_redeemed]
-        for voucher in redeemable_vouchers:
-            assert voucher.status in REDEEMABLE_VOUCHER_STATUSES, (
-                "Voucher %s has invalid status %s" %
-                (voucher.voucher_id, voucher.status))
-        return redeemable_vouchers
-
     def hasCurrentCommercialSubscription(self):
         """See `IPerson`."""
         # Circular imports.
diff --git a/lib/lp/registry/model/product.py b/lib/lp/registry/model/product.py
index 8b47075..a68b25b 100644
--- a/lib/lp/registry/model/product.py
+++ b/lib/lp/registry/model/product.py
@@ -12,7 +12,6 @@ __all__ = [
     ]
 
 
-import calendar
 import datetime
 import httplib
 import itertools
@@ -137,7 +136,6 @@ from lp.registry.errors import (
     CannotChangeInformationType,
     CommercialSubscribersOnly,
     ProprietaryProduct,
-    VoucherAlreadyRedeemed,
     )
 from lp.registry.interfaces.accesspolicy import (
     IAccessPolicyArtifactSource,
@@ -810,77 +808,6 @@ class Product(SQLBase, BugTargetBase, MakesAnnouncements,
         return (self.commercial_subscription
             and self.commercial_subscription.date_expires > now)
 
-    def redeemSubscriptionVoucher(self, voucher, registrant, purchaser,
-                                  subscription_months, whiteboard=None,
-                                  current_datetime=None):
-        """See `IProduct`."""
-
-        def add_months(start, num_months):
-            """Given a start date find the new date `num_months` later.
-
-            If the start date day is the last day of the month and the new
-            month does not have that many days, then the new date will be the
-            last day of the new month.  February is handled correctly too,
-            including leap years, where th 28th-31st maps to the 28th or
-            29th.
-            """
-            # The months are 1-indexed but the divmod calculation will only
-            # work if it is 0-indexed.  Subtract 1 from the months and then
-            # add it back to the new_month later.
-            years, new_month = divmod(start.month - 1 + num_months, 12)
-            new_month += 1
-            new_year = start.year + years
-            # If the day is not valid for the new month, make it the last day
-            # of that month, e.g. 20080131 + 1 month = 20080229.
-            weekday, days_in_month = calendar.monthrange(new_year, new_month)
-            new_day = min(days_in_month, start.day)
-            return start.replace(
-                year=new_year, month=new_month, day=new_day)
-
-        # The voucher may already have been redeemed or marked as redeemed
-        # pending notification being sent to Salesforce.
-        voucher_expr = (
-            "trim(leading 'pending-' "
-            "from CommercialSubscription.sales_system_id)")
-        already_redeemed = Store.of(self).find(
-            CommercialSubscription,
-            SQL(voucher_expr) == unicode(voucher)).any()
-        if already_redeemed:
-            raise VoucherAlreadyRedeemed(
-                "Voucher %s has already been redeemed for %s"
-                      % (voucher, already_redeemed.product.displayname))
-
-        if current_datetime is None:
-            current_datetime = datetime.datetime.now(pytz.timezone('UTC'))
-
-        if self.commercial_subscription is None:
-            date_starts = current_datetime
-            date_expires = add_months(date_starts, subscription_months)
-            subscription = CommercialSubscription(
-                product=self,
-                date_starts=date_starts,
-                date_expires=date_expires,
-                registrant=registrant,
-                purchaser=purchaser,
-                sales_system_id=voucher,
-                whiteboard=whiteboard)
-            get_property_cache(self).commercial_subscription = subscription
-        else:
-            if current_datetime <= self.commercial_subscription.date_expires:
-                # Extend current subscription.
-                self.commercial_subscription.date_expires = (
-                    add_months(self.commercial_subscription.date_expires,
-                               subscription_months))
-            else:
-                # Start the new subscription now and extend for the new
-                # period.
-                self.commercial_subscription.date_starts = current_datetime
-                self.commercial_subscription.date_expires = (
-                    add_months(current_datetime, subscription_months))
-            self.commercial_subscription.sales_system_id = voucher
-            self.commercial_subscription.registrant = registrant
-            self.commercial_subscription.purchaser = purchaser
-
     @property
     def qualifies_for_free_hosting(self):
         """See `IProduct`."""
diff --git a/lib/lp/registry/stories/product/xx-product-index.txt b/lib/lp/registry/stories/product/xx-product-index.txt
index 0b5b75b..fce4857 100644
--- a/lib/lp/registry/stories/product/xx-product-index.txt
+++ b/lib/lp/registry/stories/product/xx-product-index.txt
@@ -136,22 +136,6 @@ direct the owner to purchase a subscription.
     ...     'portlet-requires-subscription')
     <div...Purchasing a commercial subscription is required...</div>
 
-    >>> owner_browser.getLink('Manage commercial subscriptions').click()
-    >>> print owner_browser.title
-    Commercial subscription vouchers : Sample Person
-
-    >>> owner_browser.getLink('Canonical Shop')
-    <Link...url='http://ubuntu.recycledmania.com...'>
-
-The link can be disabled by a feature flag.
-
-    >>> from lp.services.features.testing import FeatureFixture
-    >>> with FeatureFixture({'commercial_subscriptions.new.disabled': 'true'}):
-    ...     owner_browser.getLink('Manage commercial subscriptions')
-    Traceback (most recent call last):
-    ...
-    LinkNotFoundError
-
 Any user can see that the project's licence is proprietary.
 
     >>> user_browser.open('http://launchpad.test/firefox')
@@ -197,25 +181,24 @@ category.
 Commercial Subscription Expiration
 ----------------------------------
 
-If the project has redeemed a voucher for a commercial subscription then
-the expiration date is shown to the project maintainers, Launchpad
-admins, and members of the Launchpad commercial team.
+If the project has been granted a commercial subscription then the
+expiration date is shown to the project maintainers, Launchpad admins,
+and members of the Launchpad commercial team.
 
-Redeem a voucher to enable the subscription.
+Enable the subscription.
 
-    >>> owner_browser = setupBrowser(auth='Basic bac@xxxxxxxxxxxxx:test')
-    >>> owner_browser.open('http://launchpad.test/people/+me/+vouchers')
-    >>> owner_browser.getControl(
-    ...     name='field.project').value = 'mega-money-maker'
-    >>> owner_browser.getControl(name='field.voucher').value = [
-    ...     'LPCBS12-f78df324-0cc2-11dd-8b6b-bac000000005']
-    >>> owner_browser.getControl('Redeem').click()
-    >>> print_feedback_messages(owner_browser.contents)
-    Voucher redeemed successfully
+    >>> from zope.component import getUtility
+    >>> from lp.registry.interfaces.product import IProductSet
+    >>> login(ANONYMOUS)
+    >>> mmm = getUtility(IProductSet).getByName('mega-money-maker')
+    >>> _ = login_person(mmm.owner)
+    >>> _ = factory.makeCommercialSubscription(mmm)
+    >>> logout()
 
  The owner will now see the expiration information on the project
  overview page.
 
+    >>> owner_browser = setupBrowser(auth='Basic bac@xxxxxxxxxxxxx:test')
     >>> owner_browser.open('http://launchpad.test/mega-money-maker')
     >>> print extract_text(find_tag_by_id(owner_browser.contents,
     ...                      'commercial_subscription'))
diff --git a/lib/lp/registry/stories/vouchers/xx-voucher-redemption.txt b/lib/lp/registry/stories/vouchers/xx-voucher-redemption.txt
deleted file mode 100644
index dfb017d..0000000
--- a/lib/lp/registry/stories/vouchers/xx-voucher-redemption.txt
+++ /dev/null
@@ -1,131 +0,0 @@
-Voucher Redemption
-==================
-
-For a project to use Launchpad it must either be released under an
-approved open source licence or the project administrators must buy a
-commercial subscription to use Launchpad.  Vouchers for commercial
-subscriptions are sold in the Canonical Store and the vouchers are
-then redeemed against a project in Launchpad to create or extend a
-commercial subscription.
-
-The voucher belongs to a user in Launchpad so the redemption function
-is related to a person.
-
-
-Accessing the voucher redemption page
--------------------------------------
-
-Mark is an administrator for at least one project that does not have a
-valid open source licence so he is displayed the voucher redemption
-page.
-
-    >>> browser = setupBrowser(auth='Basic mark@xxxxxxxxxxx:test')
-    >>> browser.open('http://launchpad.test/~mark')
-    >>> browser.getLink('Manage commercial subscriptions').click()
-    >>> main = find_main_content(browser.contents)
-    >>> print extract_text(main)
-    Purchase and Redeem Commercial Subscription Vouchers
-    ...Select the project you wish to subscribe...
-
-A user can access their voucher page but not someone else's.  Here
-no-priv tries to access '+vouchers' on another user and is not
-allowed.
-
-    >>> browser = setupBrowser(auth='Basic no-priv@xxxxxxxxxxxxx:test')
-    >>> browser.open('http://launchpad.test/~mark/+vouchers')
-    Traceback (most recent call last):
-    ...
-    Unauthorized: ...
-
-Unless, of course, that user is an administrator.  Then they can access
-the '+vouchers' page on any user.
-
-    >>> browser = setupBrowser(auth='Basic foo.bar@xxxxxxxxxxxxx:test')
-    >>> browser.open('http://launchpad.test/~mark/+vouchers')
-    >>> main = find_main_content(browser.contents)
-    >>> print extract_text(main)
-    Purchase and Redeem Commercial Subscription Vouchers
-    ...Select the project you wish to subscribe...
-
-Foo Bar has no vouchers.  Accessing the +vouchers page results in a message.
-
-    >>> browser.open('http://launchpad.test/~name12/+vouchers')
-    >>> main = find_main_content(browser.contents)
-    >>> print extract_text(main)
-    Purchase and Redeem Commercial Subscription Vouchers
-    ...You do not have any redeemable commercial subscription vouchers.
-    Here are the steps to obtain a commercial subscription:...
-
-
-Redeeming a voucher
-------------------
-
-Selecting a project the user owns and a valid voucher result in a
-successful voucher redemption.
-
-    >>> browser = setupBrowser(
-    ...     auth='Basic celso.providelo@xxxxxxxxxxxxx:test')
-    >>> browser.open('http://launchpad.test/~cprov/+vouchers')
-    >>> browser.getControl(name='field.project').value='aptoncd'
-    >>> browser.getControl(name='field.voucher').value = [
-    ...     'LPCBS12-f78df324-0cc2-11dd-8b6b-000000000004']
-    >>> browser.getControl('Redeem').click()
-    >>> print browser.url
-    http://launchpad.test/~cprov/+vouchers
-    >>> print_feedback_messages(browser.contents)
-    Voucher redeemed successfully
-
-
-Selecting a project that is not a commercial project owned by the user
-results in an error.
-
-    >>> browser.open('http://launchpad.test/~cprov/+vouchers')
-    >>> browser.getControl(name='field.project').value='bzr'
-    >>> browser.getControl(name='field.voucher').value = [
-    ...     'LPCBS12-f78df324-0cc2-11dd-8b6b-000000000005']
-    >>> browser.getControl('Redeem').click()
-    >>> print_feedback_messages(browser.contents)
-    There is 1 error.
-    Invalid value
-
-If, however, a person is a member of the Launchpad Commercial team they
-can apply a voucher to any project, not just those they administer.
-This ability is provided so that commercial admins in Launchpad can
-help projects with voucher issues, which is especially useful when
-setting up Canonical-internal projects.
-
-    >>> browser = setupBrowser(
-    ...     auth='Basic commercial-member@xxxxxxxxxxxxx:test')
-
-Show that the commercial member does not own or drive any projects by
-viewing their +related-projects page.
-
-    >>> login('foo.bar@xxxxxxxxxxxxx')
-    >>> from zope.component import getUtility
-    >>> from lp.app.interfaces.launchpad import ILaunchpadCelebrities
-    >>> from lp.registry.interfaces.person import IPersonSet
-    >>> commercial_member = getUtility(IPersonSet).getByEmail(
-    ...     'commercial-member@xxxxxxxxxxxxx')
-    >>> celebs = getUtility(ILaunchpadCelebrities)
-    >>> ignored = celebs.registry_experts.addMember(
-    ...     commercial_member, commercial_member)
-    >>> logout()
-
-    >>> browser.open(
-    ...     'http://launchpad.test/~commercial-member/+related-projects')
-    >>> main = find_main_content(browser.contents)
-    >>> print extract_text(main)
-    Related projects
-    ...
-    Commercial Member doesn't own or drive any projects.
-
-They can still redeem a voucher against a commercial project even though
-they are not responsible for it.
-
-    >>> browser.open('http://launchpad.test/~commercial-member/+vouchers')
-    >>> browser.getControl(name='field.project').value='mega-money-maker'
-    >>> browser.getControl(name='field.voucher').value = [
-    ...     'LPCBS12-f78df324-0cc2-11dd-8b6b-com000000001']
-    >>> browser.getControl('Redeem').click()
-    >>> print_feedback_messages(browser.contents)
-    Voucher redeemed successfully
diff --git a/lib/lp/registry/stories/webservice/xx-project-registry.txt b/lib/lp/registry/stories/webservice/xx-project-registry.txt
index 86f87ec..3694df6 100644
--- a/lib/lp/registry/stories/webservice/xx-project-registry.txt
+++ b/lib/lp/registry/stories/webservice/xx-project-registry.txt
@@ -1132,9 +1132,7 @@ through the API.
     >>> print mmm.commercial_subscription
     None
 
-    >>> owner = mmm.owner
-    >>> mmm.redeemSubscriptionVoucher('mmm_voucher', owner, owner, 12,
-    ...     'notes')
+    >>> _ = factory.makeCommercialSubscription(mmm)
     >>> print mmm.commercial_subscription.product.name
     mega-money-maker
 
diff --git a/lib/lp/registry/templates/person-index.pt b/lib/lp/registry/templates/person-index.pt
index 7c1dd87..4cfee46 100644
--- a/lib/lp/registry/templates/person-index.pt
+++ b/lib/lp/registry/templates/person-index.pt
@@ -82,10 +82,6 @@
         tal:define="link context/menu:overview/oauth_tokens"
         tal:condition="link/enabled"
         tal:content="structure link/fmt:link" />
-      <li
-        tal:define="link context/menu:overview/manage_vouchers"
-        tal:condition="link/enabled"
-        tal:content="structure link/fmt:link" />
     </ul>
 
     <div class="yui-g">
diff --git a/lib/lp/registry/templates/person-vouchers.pt b/lib/lp/registry/templates/person-vouchers.pt
deleted file mode 100644
index 73db207..0000000
--- a/lib/lp/registry/templates/person-vouchers.pt
+++ /dev/null
@@ -1,94 +0,0 @@
-<html
-  xmlns="http://www.w3.org/1999/xhtml";
-  xmlns:tal="http://xml.zope.org/namespaces/tal";
-  xmlns:metal="http://xml.zope.org/namespaces/metal";
-  xmlns:i18n="http://xml.zope.org/namespaces/i18n";
-  metal:use-macro="view/macro:page/main_only"
-  i18n:domain="launchpad"
->
-  <h1 metal:fill-slot="heading">
-    Purchase and Redeem Commercial Subscription Vouchers
-  </h1>
-
-  <div metal:fill-slot="main"
-     tal:define="
-       config modules/lp.services.config/config">
-
-    <p>
-      Any open source project may choose to purchase a commercial subscription
-      to enable additional features. Projects with proprietary
-      licences require a commercial subscription. The owners of commercial
-      projects can
-    </p>
-
-    <ul class="bulleted">
-      <li>
-        Configure project sharing policies to enable proprietary bugs,
-        branches, and blueprints which are only visible to trusted people.
-      </li>
-      <li>
-        Create private teams with private mailing lists and
-        private personal package archives.
-      </li>
-    </ul>
-
-    <p>
-      See <a href="https://help.launchpad.net/CommercialHosting";>Commercial
-      Hosting</a> for more details.
-    </p>
-
-    <tal:can-subscribe
-        condition="not: request/features/commercial_subscriptions.new.disabled">
-      <tal:no_vouchers condition="not: view/show_voucher_selection">
-        <p>
-          <strong>You do not have any redeemable commercial subscription vouchers.
-          Here are the steps to obtain a commercial subscription:</strong>
-        </p>
-
-        <ol>
-          <li>Purchase a Launchpad subscription from the
-            <a tal:define="config modules/lp.services.config/config"
-               tal:attributes="href config/commercial/purchase_subscription_url"
-               >Canonical Shop</a>.
-          </li>
-          <li>
-            You will receive an email that your order has been processed.
-          </li>
-          <li>
-            The shop will inform Launchpad of your purchase within 60 minutes.
-          </li>
-          <li>
-            Return to this page to choose the project the commercial
-            subscription is for.
-          </li>
-        </ol>
-      </tal:no_vouchers>
-
-      <tal:has_voucher condition="view/show_voucher_selection">
-        <div metal:use-macro="context/@@launchpad_form/form">
-          <metal:widgets fill-slot="widgets">
-            <table class="form">
-              <tal:widget define="widget nocall:view/widgets/project">
-                <metal:block use-macro="context/@@launchpad_form/widget_row" />
-              </tal:widget>
-              <tal:widget define="widget nocall:view/widgets/voucher">
-                <metal:block use-macro="context/@@launchpad_form/widget_row" />
-              </tal:widget>
-            </table>
-          </metal:widgets>
-        </div>
-      </tal:has_voucher>
-    </tal:can-subscribe>
-
-    <tal:cannot-subscribe
-        condition="request/features/commercial_subscriptions.new.disabled">
-      <p>
-        New commercial subscriptions are no longer available.
-        <a href="mailto:commercial@xxxxxxxxxxxxx";>Contact us</a> if you have
-        authorization from Canonical to use Launchpad's commercial features
-        and need this to be applied to your project, or if you have any
-        other questions about this.
-      </p>
-    </tal:cannot-subscribe>
-  </div>
-</html>
diff --git a/lib/lp/registry/templates/pillar-sharing.pt b/lib/lp/registry/templates/pillar-sharing.pt
index 7c0a33c..285f8df 100644
--- a/lib/lp/registry/templates/pillar-sharing.pt
+++ b/lib/lp/registry/templates/pillar-sharing.pt
@@ -36,13 +36,6 @@
     <p id='non-commercial-project-text'
        tal:condition="not: context/has_current_commercial_subscription|nothing">
       Open source projects have limited access to sharing features.
-      <tal:can-subscribe
-          condition="not: request/features/commercial_subscriptions.new.disabled">
-        You can
-        <a href="https://help.launchpad.net/CommercialHosting";>purchase a
-        commercial subscription</a> to enable more privacy options, such as
-        setting bugs and branches to be proprietary by default.
-      </tal:can-subscribe>
     </p>
     </tal:what-shared>
       <table>
diff --git a/lib/lp/registry/templates/product-portlet-requires-subscription.pt b/lib/lp/registry/templates/product-portlet-requires-subscription.pt
index 1589b3e..0bd703e 100644
--- a/lib/lp/registry/templates/product-portlet-requires-subscription.pt
+++ b/lib/lp/registry/templates/product-portlet-requires-subscription.pt
@@ -18,16 +18,6 @@
     <tal:date_expire
      tal:replace="context/commercial_subscription/date_expires/fmt:displaydate"
      />.</strong>
-    <tal:can-subscribe
-        condition="not: request/features/commercial_subscriptions.new.disabled">
-      <span
-          tal:define="user_menu view/user/menu:overview"
-          tal:condition="context/qualifies_for_free_hosting">
-        <br />Purchase a commercial subscription from your
-        <a tal:replace="structure user_menu/manage_vouchers/fmt:link" />
-        page.
-      </span>
-    </tal:can-subscribe>
   </p>
 
   <tal:proprietary condition="not: context/qualifies_for_free_hosting">
@@ -38,13 +28,7 @@
     </p>
 
     <ul class="bulleted" style="clear:left;">
-      <li tal:define="user_menu view/user/menu:overview"
-          tal:condition="not: request/features/commercial_subscriptions.new.disabled">
-        Purchase a commercial subscription from your
-        <a tal:replace="structure user_menu/manage_vouchers/fmt:link" />
-        page.
-      </li>
-      <li tal:condition="request/features/commercial_subscriptions.new.disabled">
+      <li>
         If you have authorization from Canonical to use Launchpad's
         commercial features, then
         <a href="mailto:commercial@xxxxxxxxxxxxx";>contact us</a>
diff --git a/lib/lp/registry/tests/test_product.py b/lib/lp/registry/tests/test_product.py
index c28eb8b..5cbb244 100644
--- a/lib/lp/registry/tests/test_product.py
+++ b/lib/lp/registry/tests/test_product.py
@@ -882,7 +882,7 @@ class TestProduct(TestCaseWithFactory):
             'past_sprints', 'personHasDriverRights',
             'primary_translatable', 'private_bugs',
             'programminglang', 'qualifies_for_free_hosting',
-            'recipes', 'redeemSubscriptionVoucher', 'registrant', 'releases',
+            'recipes', 'registrant', 'releases',
             'remote_product', 'removeCustomLanguageCode',
             'screenshotsurl',
             'searchFAQs', 'searchQuestions', 'security_contact',
@@ -934,7 +934,7 @@ class TestProduct(TestCaseWithFactory):
                 'license_info', 'licenses', 'logo', 'mugshot',
                 'official_answers', 'official_blueprints',
                 'official_codehosting', 'owner', 'private',
-                'programminglang', 'projectgroup', 'redeemSubscriptionVoucher',
+                'programminglang', 'projectgroup',
                 'releaseroot', 'screenshotsurl', 'sourceforgeproject',
                 'summary', 'uses_launchpad', 'wikiurl', 'vcs')),
             'launchpad.Moderate': set((
@@ -1488,27 +1488,6 @@ class ProductAttributeCacheTestCase(TestCaseWithFactory):
         self.assertEqual(self.product.licenses,
                          (License.ACADEMIC, License.AFFERO, License.MIT))
 
-    def testCommercialSubscriptionCache(self):
-        """commercial_subscription cache should not traverse transactions."""
-        self.assertEqual(self.product.commercial_subscription, None)
-        self.factory.makeCommercialSubscription(self.product)
-        self.assertEqual(self.product.commercial_subscription, None)
-        self.product.redeemSubscriptionVoucher(
-            'hello', self.product.owner, self.product.owner, 1)
-        self.assertEqual(
-            'hello', self.product.commercial_subscription.sales_system_id)
-        transaction.abort()
-        # Cache is cleared.
-        self.assertIs(None, self.product.commercial_subscription)
-
-        # Cache is cleared again.
-        transaction.abort()
-        self.factory.makeCommercialSubscription(self.product)
-        # Cache is cleared and it sees database changes that occur
-        # before the cache is populated.
-        self.assertEqual(
-            'new', self.product.commercial_subscription.sales_system_id)
-
 
 class ProductLicensingTestCase(TestCaseWithFactory):
     """Test the rules of licences and commercial subscriptions."""
diff --git a/lib/lp/scripts/garbo.py b/lib/lp/scripts/garbo.py
index 6f6ad6b..e5a9e07 100644
--- a/lib/lp/scripts/garbo.py
+++ b/lib/lp/scripts/garbo.py
@@ -36,7 +36,6 @@ from storm.expr import (
     And,
     In,
     Join,
-    Like,
     Max,
     Min,
     Or,
@@ -71,7 +70,6 @@ from lp.code.model.revision import (
     RevisionCache,
     )
 from lp.hardwaredb.model.hwdb import HWSubmission
-from lp.registry.model.commercialsubscription import CommercialSubscription
 from lp.registry.model.person import Person
 from lp.registry.model.product import Product
 from lp.registry.model.sourcepackagename import SourcePackageName
@@ -109,10 +107,6 @@ from lp.services.log.logger import PrefixFilter
 from lp.services.looptuner import TunableLoop
 from lp.services.openid.model.openidconsumer import OpenIDConsumerNonce
 from lp.services.propertycache import cachedproperty
-from lp.services.salesforce.interfaces import (
-    ISalesforceVoucherProxy,
-    SalesforceVoucherProxyException,
-    )
 from lp.services.scripts.base import (
     LaunchpadCronScript,
     LOCK_PATH,
@@ -438,58 +432,6 @@ class BugSummaryJournalRollup(TunableLoop):
         self.store.commit()
 
 
-class VoucherRedeemer(TunableLoop):
-    """Redeem pending sales vouchers with Salesforce."""
-    maximum_chunk_size = 5
-
-    voucher_expr = (
-        "trim(leading 'pending-' "
-        "from CommercialSubscription.sales_system_id)")
-
-    def __init__(self, log, abort_time=None):
-        super(VoucherRedeemer, self).__init__(log, abort_time)
-        self.store = IMasterStore(CommercialSubscription)
-
-    @cachedproperty
-    def _salesforce_proxy(self):
-        return getUtility(ISalesforceVoucherProxy)
-
-    @property
-    def _pending_subscriptions(self):
-        return self.store.find(
-            CommercialSubscription,
-            Like(CommercialSubscription.sales_system_id, 'pending-%')
-        )
-
-    def isDone(self):
-        return self._pending_subscriptions.count() == 0
-
-    def __call__(self, chunk_size):
-        successful_ids = []
-        for sub in self._pending_subscriptions[:chunk_size]:
-            sales_system_id = sub.sales_system_id[len('pending-'):]
-            try:
-                # The call to redeemVoucher returns True if it succeeds or it
-                # raises an exception.  Therefore the return value does not
-                # need to be checked.
-                self._salesforce_proxy.redeemVoucher(
-                    sales_system_id, sub.purchaser, sub.product)
-                successful_ids.append(unicode(sub.sales_system_id))
-            except SalesforceVoucherProxyException as error:
-                self.log.error(
-                    "Failed to redeem voucher %s: %s"
-                    % (sales_system_id, error.message))
-        # Update the successfully redeemed voucher ids to be not pending.
-        if successful_ids:
-            self.store.find(
-                CommercialSubscription,
-                CommercialSubscription.sales_system_id.is_in(successful_ids)
-            ).set(
-                CommercialSubscription.sales_system_id ==
-                SQL(self.voucher_expr))
-        transaction.commit()
-
-
 class PopulateDistributionSourcePackageCache(TunableLoop):
     """Populate the DistributionSourcePackageCache table.
 
@@ -1861,7 +1803,6 @@ class FrequentDatabaseGarbageCollector(BaseDatabaseGarbageCollector):
         OpenIDConsumerNoncePruner,
         PopulateDistributionSourcePackageCache,
         PopulateLatestPersonSourcePackageReleaseCache,
-        VoucherRedeemer,
         ]
     experimental_tunable_loops = []
 
diff --git a/lib/lp/scripts/tests/test_garbo.py b/lib/lp/scripts/tests/test_garbo.py
index e0cb87e..dfeeba8 100644
--- a/lib/lp/scripts/tests/test_garbo.py
+++ b/lib/lp/scripts/tests/test_garbo.py
@@ -21,7 +21,6 @@ from pytz import UTC
 from storm.exceptions import LostObjectError
 from storm.expr import (
     In,
-    Like,
     Min,
     Not,
     SQL,
@@ -112,8 +111,6 @@ from lp.services.job.model.job import Job
 from lp.services.librarian.model import TimeLimitedToken
 from lp.services.messages.model.message import Message
 from lp.services.openid.model.openidconsumer import OpenIDConsumerNonce
-from lp.services.salesforce.interfaces import ISalesforceVoucherProxy
-from lp.services.salesforce.tests.proxy import TestSalesforceVoucherProxy
 from lp.services.scripts.tests import run_script
 from lp.services.session.model import (
     SessionData,
@@ -1152,34 +1149,6 @@ class TestGarbo(FakeAdapterMixin, TestCaseWithFactory):
             "SELECT COUNT(*) FROM BugSummaryJournal").get_one()[0]
         self.assertThat(num_rows, Equals(0))
 
-    def test_VoucherRedeemer(self):
-        switch_dbuser('testadmin')
-
-        voucher_proxy = TestSalesforceVoucherProxy()
-        self.registerUtility(voucher_proxy, ISalesforceVoucherProxy)
-
-        # Mark has some unredeemed vouchers so set one of them as pending.
-        mark = getUtility(IPersonSet).getByName('mark')
-        voucher = voucher_proxy.getUnredeemedVouchers(mark)[0]
-        product = self.factory.makeProduct(owner=mark)
-        redeemed_id = voucher.voucher_id
-        self.factory.makeCommercialSubscription(
-            product, False, 'pending-%s' % redeemed_id)
-        transaction.commit()
-
-        self.runFrequently()
-
-        # There should now be 0 pending vouchers in Launchpad.
-        num_rows = IMasterStore(CommercialSubscription).find(
-            CommercialSubscription,
-            Like(CommercialSubscription.sales_system_id, 'pending-%')
-            ).count()
-        self.assertThat(num_rows, Equals(0))
-        # Salesforce should also now have redeemed the voucher.
-        unredeemed_ids = [
-            v.voucher_id for v in voucher_proxy.getUnredeemedVouchers(mark)]
-        self.assertNotIn(redeemed_id, unredeemed_ids)
-
     def test_UnusedPOTMsgSetPruner_removes_obsolete_message_sets(self):
         # UnusedPOTMsgSetPruner removes any POTMsgSet that are
         # participating in a POTemplate only as obsolete messages.
diff --git a/lib/lp/services/config/schema-lazr.conf b/lib/lp/services/config/schema-lazr.conf
index d14d216..d8c1204 100644
--- a/lib/lp/services/config/schema-lazr.conf
+++ b/lib/lp/services/config/schema-lazr.conf
@@ -488,23 +488,6 @@ working_directory_root: /var/tmp/codeimport/data
 
 
 [commercial]
-# URL for salesforce proxy.
-# datatype: string; a url
-voucher_proxy_url:
-
-# Port for salesforce proxy.
-# datatype: integer
-voucher_proxy_port:
-
-# Salesforce proxy timeout in milliseconds. Calls taking longer than this value
-# will be aborted.
-# datatype: integer
-voucher_proxy_timeout: 60000
-
-# URL for purchasing a commercial subscription.
-# datatype: string; a url
-purchase_subscription_url:
-
 # URL pointing to Ubuntu's licensing policy which Launchpad shares.
 # datatype: string; a url
 licensing_policy_url: https://help.launchpad.net/Legal/ProjectLicensing
diff --git a/lib/lp/services/configure.zcml b/lib/lp/services/configure.zcml
index 0585cd7..b85bfa5 100644
--- a/lib/lp/services/configure.zcml
+++ b/lib/lp/services/configure.zcml
@@ -24,7 +24,6 @@
   <include package=".oauth" />
   <include package=".openid" />
   <include package=".profile" />
-  <include package=".salesforce" />
   <include package=".scripts" />
   <include package=".session" />
   <include package=".sitesearch" />
diff --git a/lib/lp/services/salesforce/__init__.py b/lib/lp/services/salesforce/__init__.py
deleted file mode 100644
index 63b6c71..0000000
--- a/lib/lp/services/salesforce/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# Copyright 2010 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-# This file exists to make this directory a package.
diff --git a/lib/lp/services/salesforce/configure.zcml b/lib/lp/services/salesforce/configure.zcml
deleted file mode 100644
index aecebc8..0000000
--- a/lib/lp/services/salesforce/configure.zcml
+++ /dev/null
@@ -1,29 +0,0 @@
-<!-- Copyright 2010 Canonical Ltd.  This software is licensed under the
-     GNU Affero General Public License version 3 (see the file LICENSE).
--->
-
-<configure
-    xmlns="http://namespaces.zope.org/zope";
-    xmlns:browser="http://namespaces.zope.org/browser";
-    xmlns:i18n="http://namespaces.zope.org/i18n";
-    i18n_domain="launchpad">
-
-    <class
-        class="lp.services.salesforce.proxy.SalesforceVoucherProxy">
-        <allow
-            interface="lp.services.salesforce.interfaces.ISalesforceVoucherProxy"/>
-    </class>
-    <class
-        class="lp.services.salesforce.proxy.Voucher">
-        <allow
-            interface="lp.services.salesforce.interfaces.ISalesforceVoucher"/>
-    </class>
-    <securedutility
-        class="lp.services.salesforce.proxy.SalesforceVoucherProxy"
-        provides="lp.services.salesforce.interfaces.ISalesforceVoucherProxy">
-        <allow
-            interface="lp.services.salesforce.interfaces.ISalesforceVoucherProxy"/>
-    </securedutility>
-
-
-</configure>
diff --git a/lib/lp/services/salesforce/doc/voucher.txt b/lib/lp/services/salesforce/doc/voucher.txt
deleted file mode 100644
index bb0f46d..0000000
--- a/lib/lp/services/salesforce/doc/voucher.txt
+++ /dev/null
@@ -1,294 +0,0 @@
-= Subscription Vouchers =
-
-Subscription vouchers are items sold in the Canonical store.  When
-redeemed against a commercial project in Launchpad they give that
-project the right to use all of the services of Launchpad, just like
-open source projects.
-
-For testing purposes we use a custom XML-RPC transport which
-implements a mock proxy in the transport and avoids network traffic.
-
-    >>> from lp.services.salesforce.tests.proxy import (
-    ...     SalesforceXMLRPCTestTransport)
-    >>> import xmlrpclib
-    >>> test_transport = SalesforceXMLRPCTestTransport()
-    >>> server = xmlrpclib.ServerProxy("http://example.com";,
-    ...                                transport=test_transport)
-
-Let's create a convenience function for displaying our voucher lists,
-which are just dictionaries in the transport.
-
-    >>> def print_vouchers(vouchers):
-    ...     for voucher in vouchers:
-    ...         print "%s,%s" % (voucher['voucher_id'], voucher['status'])
-
-
-== Test the XMLRPC transport ==
-
-All of the sample voucher data in the transport can be shown.
-
-    >>> for voucher in test_transport.vouchers:
-    ...     print voucher
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000001,Reserved
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000002,Reserved
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000003,Reserved
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000004,Reserved
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000005,Reserved
-    LPCBS12-f78df324-0cc2-11dd-8b6b-bac000000001,Reserved
-    LPCBS12-f78df324-0cc2-11dd-8b6b-bac000000002,Reserved
-    LPCBS12-f78df324-0cc2-11dd-8b6b-bac000000003,Reserved
-    LPCBS12-f78df324-0cc2-11dd-8b6b-bac000000004,Reserved
-    LPCBS12-f78df324-0cc2-11dd-8b6b-bac000000005,Reserved
-    LPCBS12-f78df324-0cc2-11dd-8b6b-com000000001,Reserved
-    LPCBS12-f78df324-0cc2-11dd-8b6b-com000000002,Reserved
-    LPCBS12-f78df324-0cc2-11dd-8b6b-com000000003,Reserved
-    LPCBS12-f78df324-0cc2-11dd-8b6b-com000000004,Reserved
-    LPCBS12-f78df324-0cc2-11dd-8b6b-com000000005,Reserved
-
-We can retrieve all unredeemed vouchers for a person.
-
-    >>> from lp.registry.interfaces.person import (
-    ...     IPersonSet)
-    >>> from lp.registry.interfaces.product import (
-    ...     IProductSet)
-    >>> from zope.security.proxy import removeSecurityProxy
-    >>> mark = getUtility(IPersonSet).getByName('mark')
-    >>> mark_identifier = removeSecurityProxy(
-    ...     mark.account).openid_identifiers.any().identifier
-
-    >>> vouchers = server.getUnredeemedVouchers(mark_identifier)
-    >>> print_vouchers(vouchers)
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000001,Reserved
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000002,Reserved
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000003,Reserved
-
-A single voucher can be retrieved using the `getVoucher` method.
-Unlike the other retrieval methods this one does the lookup based on
-the voucher id, not the owner's id.
-
-    >>> voucher = server.getVoucher(
-    ...     'LPCBS12-f78df324-0cc2-11dd-8b6b-000000000001')
-    >>> print_vouchers([voucher])
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000001,Reserved
-
-Attempting to retrieve a non-existent voucher raises a `NotFound` fault.
-
-    >>> voucher = server.getVoucher('LPCBS12-nonexistent')
-    Traceback (most recent call last):
-      ...
-    Fault: <Fault NotFound: 'The voucher LPCBS12-nonexistent was not found.'>
-
-A single voucher can be redeemed, but must be associated with a project.
-
-    >>> firefox = getUtility(IProductSet).getByName('firefox')
-    >>> voucher = vouchers[0]['voucher_id']
-    >>> result = server.redeemVoucher(
-    ...     voucher, mark_identifier,
-    ...     firefox.id, firefox.display_name)
-    >>> print result
-    True
-    >>> vouchers = server.getUnredeemedVouchers(mark_identifier)
-    >>> print_vouchers(vouchers)
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000002,Reserved
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000003,Reserved
-
-The test transport can be forced to return a fault for testing.  The
-class attribute `forced_fault` can be set to a tuple of (method name,
-fault, message).  Once set, any call to the specified method will
-return the given fault.
-
-    >>> SalesforceXMLRPCTestTransport.forced_fault = (
-    ...     'getServerStatus', 'DeadServer', "He's dead, Jim.")
-    >>> status = server.getServerStatus()
-    Traceback (most recent call last):
-      ...
-    Fault: <Fault DeadServer: "He's dead, Jim.">
-
-The forced fault is set or `redeeemVoucher` and to return a `NotFound`
-fault.
-
-    >>> SalesforceXMLRPCTestTransport.forced_fault = (
-    ...     'redeemVoucher', 'NotFound','Something was not found.')
-    >>> voucher = vouchers[0]['voucher_id']
-    >>> result = server.redeemVoucher(
-    ...     voucher, mark_identifier,
-    ...     firefox.id, firefox.display_name)
-    Traceback (most recent call last):
-      ...
-    Fault: <Fault NotFound: 'Something was not found.'>
-
-Calls to other methods work as usual.
-
-    >>> status = server.getServerStatus()
-    >>> print status
-    Server is running normally
-
-Reset the class variable to disable forced faults.
-
-    >>> SalesforceXMLRPCTestTransport.forced_fault = None
-
-
-== Test the wrapper ==
-
-The interface `ISalesforceVoucherProxy` defines the interaction with
-the Salesforce proxy.
-
-    >>> from lp.services.salesforce.interfaces import (
-    ...     ISalesforceVoucher, ISalesforceVoucherProxy)
-    >>> from lp.testing import verifyObject
-    >>> voucher_proxy = getUtility(ISalesforceVoucherProxy)
-    >>> verifyObject(ISalesforceVoucherProxy, voucher_proxy)
-    True
-
-The status of the proxy server can be checked.
-
-    >>> status = voucher_proxy.getServerStatus()
-    >>> print status
-    Server is running normally
-
-All of the unredeemed vouchers for a Launchpad user can be retrieved.
-
-If the user has no matching vouchers an empty list is returned.
-
-    >>> foobar = getUtility(IPersonSet).getByName('name16')
-    >>> vouchers = voucher_proxy.getUnredeemedVouchers(foobar)
-    >>> len(vouchers)
-    0
-
-Similarly the call to get all of a user's vouchers returns the empty
-list when they have none.
-
-    >>> vouchers = voucher_proxy.getAllVouchers(foobar)
-    >>> len(vouchers)
-    0
-
-Get a single voucher by id.
-
-    >>> voucher = voucher_proxy.getVoucher(
-    ...     'LPCBS12-f78df324-0cc2-11dd-8b6b-000000000003')
-    >>> print voucher
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000003,Reserved,12,unassigned
-    >>> verifyObject(ISalesforceVoucher, voucher)
-    True
-
-Mark has some vouchers and they can be retrieved.
-
-    >>> vouchers = voucher_proxy.getUnredeemedVouchers(mark)
-    >>> for voucher in vouchers:
-    ...     print voucher
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000001,Reserved,12,unassigned
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000002,Reserved,12,unassigned
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000003,Reserved,12,unassigned
-
-Get a single voucher with an invalid id.
-
-    >>> voucher = voucher_proxy.getVoucher('LPCBS12-nonexistent')
-    Traceback (most recent call last):
-      ...
-    SVPNotFoundException: The voucher LPCBS12-nonexistent was not found.
-
-Redeem the first voucher for firefox.
-
-    >>> voucher_id = vouchers[0].voucher_id
-    >>> result = voucher_proxy.redeemVoucher(voucher_id, mark, firefox)
-    >>> print result
-    True
-    >>> vouchers = voucher_proxy.getUnredeemedVouchers(mark)
-    >>> for voucher in vouchers:
-    ...     print voucher
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000002,Reserved,12,unassigned
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000003,Reserved,12,unassigned
-
-If we get all of the vouchers the redeemed one is shown.
-
-    >>> vouchers = voucher_proxy.getAllVouchers(mark)
-    >>> for voucher in vouchers:
-    ...     print voucher
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000001,Redeemed,12,firefox
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000002,Reserved,12,unassigned
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000003,Reserved,12,unassigned
-
-Attempting to redeem an already redeemed voucher is unsuccessful.
-
-    >>> result = voucher_proxy.redeemVoucher(voucher_id, mark, firefox)
-    Traceback (most recent call last):
-      ...
-    SVPAlreadyRedeemedException: Voucher LPCBS12-f78df324-0cc2-11dd-8b6b-000000000001 is already redeemed
-
-Attempting to redeem a bogus voucher is also unsuccessful.
-
-    >>> voucher_id = "hownowbrowncow"
-    >>> result = voucher_proxy.redeemVoucher(voucher_id, mark, firefox)
-    Traceback (most recent call last):
-      ...
-    SVPNotFoundException: No such voucher hownowbrowncow
-
-A user cannot redeem a voucher if it is owned by someone else.
-
-    >>> cprov = getUtility(IPersonSet).getByName('cprov')
-    >>> vouchers = voucher_proxy.getUnredeemedVouchers(cprov)
-    >>> voucher = vouchers[0]
-    >>> result = voucher_proxy.redeemVoucher(voucher.voucher_id, mark, firefox)
-    Traceback (most recent call last):
-      ...
-    SVPNotAllowedException: Voucher is not owned by named user
-
-Using None for the project results in an error.
-
-    >>> result = voucher_proxy.redeemVoucher(voucher_id, mark, None)
-    Traceback (most recent call last):
-      ...
-    AttributeError:...
-
-Using None for the person results in an error.
-
-    >>> result = voucher_proxy.redeemVoucher(voucher_id, None, firefox)
-    Traceback (most recent call last):
-      ...
-    AttributeError:...
-
-If a project's name changes we can communicate that to Salesforce.
-
-    >>> from zope.security.proxy import removeSecurityProxy
-    >>> naked_firefox = removeSecurityProxy(firefox)
-    >>> print naked_firefox.display_name
-    Mozilla Firefox
-    >>> naked_firefox.display_name = 'Super Mozilla Firefox'
-    >>> print naked_firefox.display_name
-    Super Mozilla Firefox
-    >>> result = voucher_proxy.updateProjectName(naked_firefox)
-    >>> print result
-    1
-
-Attempting to rename a project that does not exist in the Salesforce
-voucher data results in an error.
-
-    >>> jokosher = getUtility(IProductSet).getByName('jokosher')
-    >>> result = voucher_proxy.updateProjectName(jokosher)
-    Traceback (most recent call last):
-      ...
-    SVPNotFoundException: No vouchers matching product id...
-
-A voucher can be created and assigned to a user.
-
-    >>> vouchers = voucher_proxy.getUnredeemedVouchers(cprov)
-    >>> for voucher in vouchers:
-    ...     print voucher
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000004,Reserved,12,unassigned
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000005,Reserved,12,unassigned
-
-    >>> voucher_id = voucher_proxy.grantVoucher(mark, mark, cprov, 12)
-    >>> print voucher_id
-    LPCBS12-f78df324-0cc2-11dd-0000-000000000001
-
-    >>> voucher_id = voucher_proxy.grantVoucher(mark, mark, cprov, 6)
-    >>> print voucher_id
-    LPCBS06-f78df324-0cc2-11dd-0000-000000000002
-
-    >>> vouchers = voucher_proxy.getUnredeemedVouchers(cprov)
-    >>> for voucher in vouchers:
-    ...     print voucher
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000004,Reserved,12,unassigned
-    LPCBS12-f78df324-0cc2-11dd-8b6b-000000000005,Reserved,12,unassigned
-    LPCBS12-f78df324-0cc2-11dd-0000-000000000001,Reserved,12,unassigned
-    LPCBS06-f78df324-0cc2-11dd-0000-000000000002,Reserved,6,unassigned
diff --git a/lib/lp/services/salesforce/interfaces.py b/lib/lp/services/salesforce/interfaces.py
deleted file mode 100644
index b03c98c..0000000
--- a/lib/lp/services/salesforce/interfaces.py
+++ /dev/null
@@ -1,131 +0,0 @@
-# Copyright 2010 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Interfaces related to Salesforce vouchers."""
-
-__metaclass__ = type
-
-__all__ = [
-    'ISalesforceVoucher',
-    'ISalesforceVoucherProxy',
-    'SalesforceVoucherProxyException',
-    'SFDCError',
-    'SVPAlreadyRedeemedException',
-    'SVPNotAllowedException',
-    'SVPNotFoundException',
-    'VOUCHER_STATUSES',
-    'REDEEMABLE_VOUCHER_STATUSES',
-    ]
-
-from zope.interface import Interface
-from zope.schema import (
-    Choice,
-    Int,
-    TextLine,
-    )
-
-from lp import _
-
-
-REDEEMABLE_VOUCHER_STATUSES = [
-    'Unredeemed',
-    'Reserved',
-    ]
-
-VOUCHER_STATUSES = REDEEMABLE_VOUCHER_STATUSES + ['Redeemed']
-
-
-class SalesforceVoucherProxyException(Exception):
-    """Exception raised on failed call to the SalesforceVoucherProxy."""
-
-
-class SFDCError(SalesforceVoucherProxyException):
-    """An exception was reported by salesforce.com."""
-
-
-class SVPNotFoundException(SalesforceVoucherProxyException):
-    """A named object was not found."""
-
-
-class SVPAlreadyRedeemedException(SalesforceVoucherProxyException):
-    """The voucher has already been redeemed."""
-
-
-class SVPNotAllowedException(SalesforceVoucherProxyException):
-    """The operation is not allowed by the current user."""
-
-
-class ISalesforceVoucherProxy(Interface):
-    """Wrapper class for voucher processing with Salesforce.
-
-    These vouchers are used to allow commercial projects to subscribe to
-    Launchpad.
-    """
-
-    def getUnredeemedVouchers(user):
-        """Get the unredeemed vouchers for the user."""
-
-    def getAllVouchers(user):
-        """Get all of the vouchers for the user."""
-
-    def getServerStatus():
-        """Get the server status."""
-
-    def getVoucher(voucher_id):
-        """Lookup a voucher."""
-
-    def redeemVoucher(voucher_id, user, project):
-        """Redeem a voucher.
-
-        :param voucher_id: string with the id of the voucher to be redeemed.
-        :param user: user who is redeeming the voucher.
-        :param project: project that is being subscribed.
-        :return: list with a boolean indicating status of redemption, and an
-            integer representing the number of months the subscription
-            allows.
-        """
-
-    def updateProjectName(project):
-        """Update the name of a project in Salesforce.
-
-        If a project changes its name it is updated in Salesforce.
-        :param project: the project to update
-        :return: integer representing the number of vouchers found for this
-            project which were updated.
-        """
-
-    def grantVoucher(admin, approver, recipient, term_months):
-        """An administrator can grant a voucher to a Launchpad user.
-
-        :param admin: the admin who is making the grant.
-        :param approver: the manager who approved the grant.
-        :param recipient: the user who is being given the voucher.
-        :param term_months: integer representing the number of months for the
-            voucher.
-        :return: the voucher id of the newly granted voucher.
-
-        This call assumes the admin and approver already exist in the
-        Salesforce database and can be looked up via their OpenID.  The
-        recipient may or may not exist, therefore basic information about the
-        recipient is sent in the call.
-        """
-
-
-class ISalesforceVoucher(Interface):
-    """Vouchers in Salesforce."""
-
-    voucher_id = TextLine(
-        title=_("Voucher ID"),
-        description=_("The id for the voucher."))
-    project = Choice(
-        title=_('Project'),
-        required=False,
-        vocabulary='Product',
-        description=_("The project the voucher is redeemed against."))
-    status = TextLine(
-        title=_("Status"),
-        description=_("The voucher's redemption status."))
-    term_months = Int(
-        title=_("Term in months"),
-        description=_("The voucher can be redeemed for a subscription "
-                      "for this number of months."))
diff --git a/lib/lp/services/salesforce/proxy.py b/lib/lp/services/salesforce/proxy.py
deleted file mode 100644
index 0e2803f..0000000
--- a/lib/lp/services/salesforce/proxy.py
+++ /dev/null
@@ -1,203 +0,0 @@
-# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Utilities for accessing the external Salesforce proxy."""
-
-__metaclass__ = type
-
-__all__ = [
-    'SalesforceVoucherProxy',
-    'SalesforceVoucherProxyException',
-    'Voucher',
-    ]
-
-import ssl
-from xmlrpclib import (
-    Fault,
-    ServerProxy,
-    )
-
-from zope.component import getUtility
-from zope.interface import implementer
-
-from lp.registry.interfaces.product import IProductSet
-from lp.services.config import config
-from lp.services.propertycache import cachedproperty
-from lp.services.salesforce.interfaces import (
-    ISalesforceVoucher,
-    ISalesforceVoucherProxy,
-    SalesforceVoucherProxyException,
-    SFDCError,
-    SVPAlreadyRedeemedException,
-    SVPNotAllowedException,
-    SVPNotFoundException,
-    )
-from lp.services.timeout import SafeTransportWithTimeout
-
-
-def fault_mapper(func):
-    """Decorator to catch Faults and map them to our exceptions."""
-
-    errorcode_map = dict(SFDCError=SFDCError,
-                         NotFound=SVPNotFoundException,
-                         AlreadyRedeemed=SVPAlreadyRedeemedException,
-                         NotAllowed=SVPNotAllowedException)
-
-    def decorator(*args, **kwargs):
-        try:
-            results = func(*args, **kwargs)
-        except Fault as fault:
-            exception = errorcode_map.get(fault.faultCode,
-                                          SalesforceVoucherProxyException)
-            raise exception(fault.faultString)
-        return results
-    return decorator
-
-
-@implementer(ISalesforceVoucher)
-class Voucher:
-    """A Commercial Subscription voucher."""
-
-    def __init__(self, values):
-        """Initialize using the values as returned from the SF proxy.
-        :param values['voucher_id']: voucher id.
-        :param values['status']: string representing the redemption status.
-        :param values['term']: integer representing number of months of
-            subscription the voucher enables.
-        :param values['project_id']: integer id for the project this voucher
-            has been redeemed  against.  If unredeemed this entry is absent.
-        """
-        self.voucher_id = values.get('voucher_id')
-        self.status = values.get('status')
-        self.term_months = values.get('term_months')
-        project_id = values.get('project_id')
-        if project_id is not None:
-            self.project = getUtility(IProductSet).get(project_id)
-        else:
-            self.project = None
-
-    def __str__(self):
-        if self.project is None:
-            project_name = "unassigned"
-        else:
-            project_name = self.project.name
-        return "%s,%s,%s,%s" % (self.voucher_id,
-                                self.status,
-                                self.term_months,
-                                project_name)
-
-
-@implementer(ISalesforceVoucherProxy)
-class SalesforceVoucherProxy:
-
-    def __init__(self):
-        # XXX cjwatson 2017-05-05: The proxy currently only has a
-        # self-signed certificate.  Until that's fixed, don't bother
-        # checking it.
-        context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
-        context.options |= ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3
-        self.xmlrpc_transport = SafeTransportWithTimeout(
-            timeout=config.commercial.voucher_proxy_timeout / 1000.0,
-            context=context)
-
-    @cachedproperty
-    def url(self):
-        """Get the proxy URL with port."""
-        return "%s:%d" % (config.commercial.voucher_proxy_url,
-                          config.commercial.voucher_proxy_port)
-
-    @property
-    def server(self):
-        """See `ISalesforceVoucherProxy`."""
-        # This is not a cachedproperty as each use needs a new proxy.
-        return ServerProxy(self.url,
-                           transport=self.xmlrpc_transport,
-                           allow_none=True)
-
-    def _getUserIdentifiers(self, user):
-        """Return the user's openid_identifier."""
-        from zope.security.proxy import removeSecurityProxy
-        return [
-            identifier.identifier for identifier
-                in removeSecurityProxy(user.account).openid_identifiers]
-
-    @fault_mapper
-    def getUnredeemedVouchers(self, user):
-        """See `ISalesforceVoucherProxy`."""
-        all_vouchers = []
-        for identifier in self._getUserIdentifiers(user):
-            vouchers = self.server.getUnredeemedVouchers(identifier)
-            if isinstance(vouchers, dict):
-                all_vouchers.append(vouchers)
-            else:
-                all_vouchers.extend(vouchers)
-        return [Voucher(voucher) for voucher in all_vouchers]
-
-    @fault_mapper
-    def getAllVouchers(self, user):
-        """See `ISalesforceVoucherProxy`."""
-        all_vouchers = []
-        for identifier in self._getUserIdentifiers(user):
-            vouchers = self.server.getAllVouchers(identifier)
-            if isinstance(vouchers, dict):
-                all_vouchers.append(vouchers)
-            else:
-                all_vouchers.extend(vouchers)
-        return [Voucher(voucher) for voucher in all_vouchers]
-
-    @fault_mapper
-    def getServerStatus(self):
-        """See `ISalesforceVoucherProxy`."""
-        status = self.server.getServerStatus()
-        return status
-
-    @fault_mapper
-    def getVoucher(self, voucher_id):
-        """See `ISalesforceVoucherProxy`."""
-        voucher = self.server.getVoucher(voucher_id)
-        if voucher is not None:
-            voucher = Voucher(voucher)
-        return voucher
-
-    @fault_mapper
-    def redeemVoucher(self, voucher_id, user, project):
-        """See `ISalesforceVoucherProxy`."""
-        for identifier in self._getUserIdentifiers(user):
-            vouchers = self.server.getAllVouchers(identifier)
-            if isinstance(vouchers, dict):
-                vouchers = [vouchers]
-            for voucher in vouchers:
-                if voucher['voucher_id'] == voucher_id:
-                    status = self.server.redeemVoucher(
-                        voucher_id, identifier,
-                        project.id, project.displayname)
-                    return status
-        # This will fail, but raise the expected exception.
-        return self.server.redeemVoucher(
-            voucher_id, identifier,
-            project.id, project.displayname)
-
-    @fault_mapper
-    def updateProjectName(self, project):
-        """See `ISalesforceVoucherProxy`."""
-        num_updated = self.server.updateProjectName(project.id,
-                                                    project.name)
-        return num_updated
-
-    @fault_mapper
-    def grantVoucher(self, admin, approver, recipient, term_months):
-        """See `ISalesforceVoucherProxy`."""
-        from zope.security.proxy import removeSecurityProxy
-        # Bypass zope's security because IEmailAddress.email is not public.
-        naked_email = removeSecurityProxy(recipient.preferredemail)
-        admin_identifier = removeSecurityProxy(
-            admin.account).openid_identifiers.any().identifier
-        approver_identifier = removeSecurityProxy(
-            approver.account).openid_identifiers.any().identifier
-        recipient_identifier = removeSecurityProxy(
-            recipient.account).openid_identifiers.any().identifier
-        voucher_id = self.server.grantVoucher(
-            admin_identifier, approver_identifier,
-            recipient_identifier, recipient.name,
-            naked_email.email, term_months)
-        return voucher_id
diff --git a/lib/lp/services/salesforce/tests/__init__.py b/lib/lp/services/salesforce/tests/__init__.py
deleted file mode 100644
index 63b6c71..0000000
--- a/lib/lp/services/salesforce/tests/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# Copyright 2010 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-# This file exists to make this directory a package.
diff --git a/lib/lp/services/salesforce/tests/proxy.py b/lib/lp/services/salesforce/tests/proxy.py
deleted file mode 100644
index 36da85e..0000000
--- a/lib/lp/services/salesforce/tests/proxy.py
+++ /dev/null
@@ -1,256 +0,0 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Helper classes for testing clients of the external Salesforce proxy."""
-
-
-__metaclass__ = type
-
-__all__ = [
-    'SalesforceXMLRPCTestTransport',
-    'TestSalesforceVoucherProxy',
-    ]
-
-
-import re
-from xmlrpclib import (
-    Fault,
-    loads,
-    Transport,
-    )
-
-from zope.interface import implementer
-
-from lp.services.salesforce.interfaces import ISalesforceVoucherProxy
-from lp.services.salesforce.proxy import SalesforceVoucherProxy
-
-
-TERM_RE = re.compile("^LPCBS(\d{2})-.*")
-
-
-def force_fault(func):
-    """Decorator to force a fault for testing.
-
-    If the property forced_fault is set and the function matches the specified
-    function name, then the fault is returned.  The property must be a tuple
-    of (method name, fault, message).
-    """
-    def decorator(self, *args, **kwargs):
-        if self.forced_fault is not None:
-            func_name, fault, message = self.forced_fault
-            if func_name == func.__name__:
-                raise Fault(fault, message)
-        return func(self, *args, **kwargs)
-    return decorator
-
-
-class Voucher:
-    """Test data for a single voucher."""
-    def __init__(self, voucher_id, owner):
-        self.voucher_id = voucher_id
-        self.owner = owner
-        self.status = 'Reserved'
-        self.project_id = None
-        self.project_name = None
-        self.term_months = self._getTermMonths()
-
-    def _getTermMonths(self):
-        """Pull the term in months from the voucher_id."""
-        match = TERM_RE.match(self.voucher_id)
-        if match is None:
-            raise Fault('GeneralError',
-                        'Invalid voucher id %s' % self.voucher_id)
-        num_months = int(match.group(1))
-        return num_months
-
-    def __str__(self):
-        return "%s,%s" % (self.voucher_id, self.status)
-
-    def asDict(self):
-        return dict(voucher_id=self.voucher_id,
-                    status=self.status,
-                    term_months=self.term_months,
-                    project_id=self.project_id)
-
-
-@implementer(ISalesforceVoucherProxy)
-class TestSalesforceVoucherProxy(SalesforceVoucherProxy):
-    """Test version of the SalesforceVoucherProxy using the test transport."""
-
-    def __init__(self):
-        self.xmlrpc_transport = SalesforceXMLRPCTestTransport()
-
-
-class SalesforceXMLRPCTestTransport(Transport):
-    """An XML-RPC test transport for the Salesforce proxy.
-
-    This transport contains a small amount of sample data and intercepts
-    requests that would normally be sent via XML-RPC but instead directly
-    provides responses based on the sample data.  This transport does not
-    simulate network errors or timeouts.
-    """
-
-    voucher_index = 0
-    voucher_prefix = 'LPCBS%02d-f78df324-0cc2-11dd-0000-%012d'
-    # The forced_fault is a tuple (method name, fault, message) or None.  See
-    # the decorator `force_fault` for details.
-    forced_fault = None
-
-    def __init__(self):
-        self.vouchers = [
-            # Test vouchers owned by mark.
-            Voucher('LPCBS12-f78df324-0cc2-11dd-8b6b-000000000001',
-                    'mark_oid'),
-            Voucher('LPCBS12-f78df324-0cc2-11dd-8b6b-000000000002',
-                    'mark_oid'),
-            Voucher('LPCBS12-f78df324-0cc2-11dd-8b6b-000000000003',
-                    'mark_oid'),
-            # Test vouchers owned by cprov.
-            Voucher('LPCBS12-f78df324-0cc2-11dd-8b6b-000000000004',
-                    'cprov_oid'),
-            Voucher('LPCBS12-f78df324-0cc2-11dd-8b6b-000000000005',
-                    'cprov_oid'),
-            # Test vouchers owned by bac.
-            Voucher('LPCBS12-f78df324-0cc2-11dd-8b6b-bac000000001',
-                    'mTmeENb'),
-            Voucher('LPCBS12-f78df324-0cc2-11dd-8b6b-bac000000002',
-                    'mTmeENb'),
-            Voucher('LPCBS12-f78df324-0cc2-11dd-8b6b-bac000000003',
-                    'mTmeENb'),
-            Voucher('LPCBS12-f78df324-0cc2-11dd-8b6b-bac000000004',
-                    'mTmeENb'),
-            Voucher('LPCBS12-f78df324-0cc2-11dd-8b6b-bac000000005',
-                    'mTmeENb'),
-            # Test vouchers owned by commercial-member.
-            Voucher('LPCBS12-f78df324-0cc2-11dd-8b6b-com000000001',
-                    'rPwGRk4'),
-            Voucher('LPCBS12-f78df324-0cc2-11dd-8b6b-com000000002',
-                    'rPwGRk4'),
-            Voucher('LPCBS12-f78df324-0cc2-11dd-8b6b-com000000003',
-                    'rPwGRk4'),
-            Voucher('LPCBS12-f78df324-0cc2-11dd-8b6b-com000000004',
-                    'rPwGRk4'),
-            Voucher('LPCBS12-f78df324-0cc2-11dd-8b6b-com000000005',
-                    'rPwGRk4'),
-            ]
-
-    def _createVoucher(self, owner_oid, term_months):
-        """Create a new voucher with the given term and owner."""
-        self.voucher_index += 1
-        voucher_id = self.voucher_prefix % (term_months, self.voucher_index)
-        voucher = Voucher(voucher_id, owner_oid)
-        self.vouchers.append(voucher)
-        return voucher
-
-    def _findVoucher(self, voucher_id):
-        """Find a voucher by id."""
-        for voucher in self.vouchers:
-            if voucher.voucher_id == voucher_id:
-                return voucher
-        return None
-
-    @force_fault
-    def getServerStatus(self):
-        """Get the server status.  If it responds it is healthy.
-
-        Included here for completeness though it is never called by
-        Launchpad.
-        """
-        return "Server is running normally"
-
-    @force_fault
-    def getUnredeemedVouchers(self, lp_openid):
-        """Return the list of unredeemed vouchers for a given id.
-
-        The returned value is a list of dictionaries, each having a 'voucher'
-        and 'status' keys.
-        """
-        vouchers = [voucher.asDict() for voucher in self.vouchers
-                    if (voucher.owner == lp_openid and
-                        voucher.status == 'Reserved')]
-        return vouchers
-
-    @force_fault
-    def getAllVouchers(self, lp_openid):
-        """Return the complete list of vouchers for a given id.
-
-        The returned value is a list of dictionaries, each having a 'voucher',
-        'status', and 'project_id' keys.
-        """
-        vouchers = [voucher.asDict() for voucher in self.vouchers
-                    if voucher.owner == lp_openid]
-        return vouchers
-
-    @force_fault
-    def getVoucher(self, voucher_id):
-        """Return the voucher."""
-
-        voucher = self._findVoucher(voucher_id)
-        if voucher is None:
-            raise Fault('NotFound',
-                        'The voucher %s was not found.' % voucher_id)
-        voucher = voucher.asDict()
-        return voucher
-
-    @force_fault
-    def redeemVoucher(self, voucher_id, lp_openid, lp_project_id,
-                      lp_project_name):
-        """Redeem the voucher.
-
-        :param voucher_id: string representing the unique voucher id.
-        :param lp_openid: string representing the Launchpad user's OpenID.
-        :param lp_project_id: Launchpad project id
-        :param lp_project_name: Launchpad project name
-        :return: Boolean representing the success or failure of the operation.
-        """
-        voucher = self._findVoucher(voucher_id)
-
-        if voucher is None:
-            raise Fault('NotFound', 'No such voucher %s' % voucher_id)
-        else:
-            if voucher.status != 'Reserved':
-                raise Fault('AlreadyRedeemed',
-                            'Voucher %s is already redeemed' % voucher_id)
-
-            if voucher.owner != lp_openid:
-                raise Fault('NotAllowed',
-                            'Voucher is not owned by named user')
-
-        voucher.status = 'Redeemed'
-        voucher.project_id = lp_project_id
-        voucher.project_name = lp_project_name
-        return [True]
-
-    @force_fault
-    def updateProjectName(self, lp_project_id, new_name):
-        """Set the project name for the given project id.
-
-        Returns the number of vouchers that were updated.
-        """
-        num_updated = 0
-        for voucher in self.vouchers:
-            if voucher.project_id == lp_project_id:
-                voucher.project_name = new_name
-                num_updated += 1
-        if num_updated == 0:
-            raise Fault('NotFound',
-                        'No vouchers matching product id %s' % lp_project_id)
-        return [num_updated]
-
-    @force_fault
-    def grantVoucher(self, admin_openid, approver_openid, recipient_openid,
-                     recipient_name, recipient_preferred_email, term_months):
-        """Grant a new voucher to the user."""
-        voucher = self._createVoucher(recipient_openid, term_months)
-        return voucher.voucher_id
-
-    def request(self, host, handler, request, verbose=None):
-        """Call the corresponding XML-RPC method.
-
-        The method name and arguments are extracted from `request`. The
-        method on this class with the same name as the XML-RPC method is
-        called, with the extracted arguments passed on to it.
-        """
-        args, method_name = loads(request)
-        method = getattr(self, method_name)
-        return method(*args)
diff --git a/lib/lp/services/salesforce/tests/test_doc.py b/lib/lp/services/salesforce/tests/test_doc.py
deleted file mode 100644
index b43a08b..0000000
--- a/lib/lp/services/salesforce/tests/test_doc.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 2010 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""
-Run the doctests.
-"""
-
-import os
-
-from lp.services.testing import build_test_suite
-from lp.testing.layers import DatabaseFunctionalLayer
-
-
-here = os.path.dirname(os.path.realpath(__file__))
-
-
-special = {}
-
-
-def test_suite():
-    suite = build_test_suite(here, special, layer=DatabaseFunctionalLayer)
-    return suite
diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
index a03bdfd..6489b57 100644
--- a/lib/lp/testing/factory.py
+++ b/lib/lp/testing/factory.py
@@ -4639,7 +4639,7 @@ class BareLaunchpadObjectFactory(ObjectFactory):
             expiry = datetime.now(pytz.UTC) - timedelta(days=1)
         else:
             expiry = datetime.now(pytz.UTC) + timedelta(days=30)
-        CommercialSubscription(
+        commercial_subscription = CommercialSubscription(
             product=product,
             date_starts=datetime.now(pytz.UTC) - timedelta(days=90),
             date_expires=expiry,
@@ -4647,12 +4647,14 @@ class BareLaunchpadObjectFactory(ObjectFactory):
             purchaser=product.owner,
             sales_system_id=voucher_id,
             whiteboard='')
+        del get_property_cache(product).commercial_subscription
+        return commercial_subscription
 
-    def grantCommercialSubscription(self, person, months=12):
+    def grantCommercialSubscription(self, person):
         """Give 'person' a commercial subscription."""
         product = self.makeProduct(owner=person)
-        product.redeemSubscriptionVoucher(
-            self.getUniqueString(), person, person, months)
+        self.makeCommercialSubscription(
+            product, voucher_id=self.getUniqueString())
 
     def makeLiveFS(self, registrant=None, owner=None, distroseries=None,
                    name=None, metadata=None, require_virtualized=True,
diff --git a/zcml/override-includes/salesforce-configure-testing.zcml b/zcml/override-includes/salesforce-configure-testing.zcml
deleted file mode 100644
index 52698d8..0000000
--- a/zcml/override-includes/salesforce-configure-testing.zcml
+++ /dev/null
@@ -1,26 +0,0 @@
-<!-- Copyright 2009 Canonical Ltd.  This software is licensed under the
-     GNU Affero General Public License version 3 (see the file LICENSE).
--->
-
-<configure
-    xmlns="http://namespaces.zope.org/zope";
-    xmlns:browser="http://namespaces.zope.org/browser";
-    xmlns:i18n="http://namespaces.zope.org/i18n";
-    xmlns:zope="http://namespaces.zope.org/zope";
-    i18n_domain="launchpad">
-
-    <class class="lp.services.salesforce.proxy.SalesforceVoucherProxy">
-        <allow interface="lp.services.salesforce.interfaces.ISalesforceVoucherProxy" />
-    </class>
-
-    <class class="lp.services.salesforce.proxy.Voucher">
-        <allow attributes="id voucher_id project status term_months __str__" />
-    </class>
-
-   <securedutility
-        class="lp.services.salesforce.tests.proxy.TestSalesforceVoucherProxy"
-        provides="lp.services.salesforce.interfaces.ISalesforceVoucherProxy">
-        <allow interface="lp.services.salesforce.interfaces.ISalesforceVoucherProxy" />
-    </securedutility>
-
-</configure>