Repository: mozmeao/basket

Issue:
SubHub > Salesforce integration to accept subscription upgrades and downgrades
Update basket to accept new event types - `customer.subscription.upgrade` and `customer.subscription.downgrade`.  Mappings can be found here:  https://docs.google.com/spreadsheets/d/1z6aXj_9GUpl5g_hR2k_SmjY5tyhxvnoHGOr8e-cbo18/edit#gid=1497563275 and here
https://docs.google.com/spreadsheets/d/1z6aXj_9GUpl5g_hR2k_SmjY5tyhxvnoHGOr8e-cbo18/edit#gid=813168994

@marty331 is making the changes on Subhub



Discussion:
A few questions from the doc:

1. The new Opportunity fields seem to be all lower case when all of the other fields have been title case. I find naming consistency to be very helpful normally. Is this an issue with the doc or have we decided to go another way with naming?
2. It says the value for `StageName` will differ based on the event but I don't see any info about what those will be yet.
3. There is no Opportunity field specified for the `proration_amount` value. Should I ignore that or is a new field forthcoming?

I've got a good start on the bits not mentioned above. Once we get answers to these questions I should be able to quickly get a PR filed.

Resolving patch:
diff --git a/basket/news/tasks.py b/basket/news/tasks.py
--- a/basket/news/tasks.py
+++ b/basket/news/tasks.py
@@ -845,6 +845,39 @@ def process_subhub_event_subscription_reactivated(data):
     })
 
 
+@et_task
+def process_subhub_event_subscription_updated(data):
+    statsd.incr('news.tasks.process_subhub_event.subscription_updated')
+    user_data = get_user_data(payee_id=data['customer_id'],
+                              extra_fields=['id'])
+    if not user_data:
+        statsd.incr('news.tasks.process_subhub_event.subscription_updated.user_not_found')
+        raise RetryTask('Could not find user. Try again.')
+
+    direction = 'Down' if data['event_type'].endswith('downgrade') else 'Up'
+    stage_name = f'Subscription {direction}grade'
+    sfdc.opportunity.create({
+        'Amount': cents_to_dollars(data['plan_amount_new']),
+        'Plan_Amount_Old__c': cents_to_dollars(data['plan_amount_old']),
+        'Billing_Cycle_End__c': iso_format_unix_timestamp(data['current_period_end']),
+        'CloseDate': iso_format_unix_timestamp(data.get('close_date', time())),
+        'Donation_Contact__c': user_data['id'],
+        'Event_Id__c': data['event_id'],
+        'Event_Name__c': data['event_type'],
+        'Invoice_Number__c': data['invoice_number'],
+        'Name': 'Subscription Services',
+        'Payment_Interval__c': data['interval'],
+        'Payment_Source__c': 'Stripe',
+        'PMT_Invoice_ID__c': data['invoice_id'],
+        'PMT_Subscription_ID__c': data['subscription_id'],
+        'Proration_Amount__c': data['proration_amount'],
+        'RecordTypeId': settings.SUBHUB_OPP_RECORD_TYPE,
+        'Service_Plan__c': data['nickname_new'],
+        'Nickname_Old__c': data['nickname_old'],
+        'StageName': stage_name,
+    })
+
+
 SUB_STAGE_NAMES = {
     'customer.subscription_cancelled': 'Subscription Canceled',
     'customer.deleted': 'Account Deleted',
diff --git a/basket/news/views.py b/basket/news/views.py
--- a/basket/news/views.py
+++ b/basket/news/views.py
@@ -44,6 +44,7 @@
     process_subhub_event_subscription_cancel,
     process_subhub_event_subscription_charge,
     process_subhub_event_subscription_reactivated,
+    process_subhub_event_subscription_updated,
 )
 from basket.news.utils import (
     SET,
@@ -84,6 +85,8 @@
     'customer.subscription.created': process_subhub_event_subscription_charge,  # subscriptioin creations & recurring charges
     'customer.subscription_cancelled': process_subhub_event_subscription_cancel,
     'customer.subscription.reactivated': process_subhub_event_subscription_reactivated,
+    'customer.subscription.upgrade': process_subhub_event_subscription_updated,
+    'customer.subscription.downgrade': process_subhub_event_subscription_updated,
     'customer.deleted': process_subhub_event_subscription_cancel,
     'invoice.payment_failed': process_subhub_event_payment_failed,
 }


Test patch:
diff --git a/basket/news/tests/test_tasks.py b/basket/news/tests/test_tasks.py
--- a/basket/news/tests/test_tasks.py
+++ b/basket/news/tests/test_tasks.py
@@ -36,6 +36,7 @@
     process_subhub_event_payment_failed,
     process_subhub_event_subscription_cancel,
     process_subhub_event_subscription_charge,
+    process_subhub_event_subscription_updated,
     record_common_voice_goals,
     RECOVERY_MESSAGE_ID,
     SUBSCRIBE,
@@ -278,6 +279,80 @@ def test_charge_disputed(self, sfdc_mock):
         })
 
 
+@override_settings(TASK_LOCKING_ENABLE=False)
+@patch('basket.news.tasks.get_user_data')
+@patch('basket.news.tasks.sfdc')
+class SubHubEventSubUpdatedTests(TestCase):
+    def _get_data(self, direction='up'):
+        return {
+            'event_id': 'the-event-id',
+            'event_type': f'customer.subscription.{direction}grade',
+            'customer_id': 'cus_1234',
+            'plan_amount_new': '1000',
+            'plan_amount_old': '100',
+            'current_period_end': '1566305505',
+            'close_date': '1566305509',
+            'interval': 'monthly',
+            'invoice_number': 'abc123',
+            'invoice_id': 'inv_abc123',
+            'proration_amount': '5',
+            'subscription_id': 'sub_123',
+            'charge': '8675309',
+            'nickname_old': 'bowling',
+            'nickname_new': 'abide',
+        }
+
+    def test_upgrade(self, sfdc_mock, gud_mock):
+        gud_mock.return_value = {'id': '1234'}
+        data = self._get_data()
+        process_subhub_event_subscription_updated(data)
+        sfdc_mock.opportunity.create.assert_called_with({
+            'Amount': 10.0,
+            'Plan_Amount_Old__c': 1.0,
+            'Billing_Cycle_End__c': '2019-08-20T12:51:45',
+            'CloseDate': '2019-08-20T12:51:49',
+            'Donation_Contact__c': '1234',
+            'Event_Id__c': 'the-event-id',
+            'Event_Name__c': 'customer.subscription.upgrade',
+            'Invoice_Number__c': data['invoice_number'],
+            'Name': 'Subscription Services',
+            'Payment_Interval__c': data['interval'],
+            'Payment_Source__c': 'Stripe',
+            'PMT_Invoice_ID__c': data['invoice_id'],
+            'PMT_Subscription_ID__c': data['subscription_id'],
+            'Proration_Amount__c': data['proration_amount'],
+            'RecordTypeId': settings.SUBHUB_OPP_RECORD_TYPE,
+            'Service_Plan__c': data['nickname_new'],
+            'Nickname_Old__c': data['nickname_old'],
+            'StageName': 'Subscription Upgrade',
+        })
+
+    def test_downgrade(self, sfdc_mock, gud_mock):
+        gud_mock.return_value = {'id': '1234'}
+        data = self._get_data('down')
+        process_subhub_event_subscription_updated(data)
+        sfdc_mock.opportunity.create.assert_called_with({
+            'Amount': 10.0,
+            'Plan_Amount_Old__c': 1.0,
+            'Billing_Cycle_End__c': '2019-08-20T12:51:45',
+            'CloseDate': '2019-08-20T12:51:49',
+            'Donation_Contact__c': '1234',
+            'Event_Id__c': 'the-event-id',
+            'Event_Name__c': 'customer.subscription.downgrade',
+            'Invoice_Number__c': data['invoice_number'],
+            'Name': 'Subscription Services',
+            'Payment_Interval__c': data['interval'],
+            'Payment_Source__c': 'Stripe',
+            'PMT_Invoice_ID__c': data['invoice_id'],
+            'PMT_Subscription_ID__c': data['subscription_id'],
+            'Proration_Amount__c': data['proration_amount'],
+            'RecordTypeId': settings.SUBHUB_OPP_RECORD_TYPE,
+            'Service_Plan__c': data['nickname_new'],
+            'Nickname_Old__c': data['nickname_old'],
+            'StageName': 'Subscription Downgrade',
+        })
+
+
 @override_settings(TASK_LOCKING_ENABLE=False)
 @patch('basket.news.tasks.get_user_data')
 @patch('basket.news.tasks.sfdc')