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, })
def process_subhub_event_subscription_cancel(data): """ Event name: customer.subscription_cancelled or customer.deleted """ statsd.incr('news.tasks.process_subhub_event.subscription_cancel') 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_cancel.user_not_found' ) raise RetryTask('Could not find user. Try again.') nickname = data['nickname'] if isinstance(nickname, list): nickname = nickname[0] sfdc.opportunity.create({ 'Amount': cents_to_dollars(data['plan_amount']), 'Billing_Cycle_End__c': iso_format_unix_timestamp(data['current_period_end']), 'Billing_Cycle_Start__c': iso_format_unix_timestamp(data['current_period_start']), 'CloseDate': iso_format_unix_timestamp(data.get('cancel_at', time())), 'Donation_Contact__c': user_data['id'], 'Event_Id__c': data['event_id'], 'Event_Name__c': data['event_type'], 'Name': 'Subscription Services', 'Payment_Source__c': 'Stripe', 'PMT_Subscription_ID__c': data['subscription_id'], 'RecordTypeId': settings.SUBHUB_OPP_RECORD_TYPE, 'Service_Plan__c': nickname, 'StageName': SUB_STAGE_NAMES[data['event_type']], }) if data['event_type'] == 'customer.deleted': sfdc.update(user_data, {'fxa_deleted': True})
def process_subhub_event_subscription_reactivated(data): statsd.incr('news.tasks.process_subhub_event.subscription_reactivated') 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_reactivated.user_not_found' ) raise RetryTask('Could not find user. Try again.') nickname = data['nickname'] if isinstance(nickname, list): nickname = nickname[0] sfdc.opportunity.create({ 'Amount': cents_to_dollars(data['plan_amount']), 'Billing_Cycle_End__c': iso_format_unix_timestamp(data['current_period_end']), 'CloseDate': iso_format_unix_timestamp(data.get('close_date', time())), 'Credit_Card_Type__c': data['brand'], 'Last_4_Digits__c': data['last4'], 'Donation_Contact__c': user_data['id'], 'Event_Id__c': data['event_id'], 'Event_Name__c': data['event_type'], 'Name': 'Subscription Services', 'Payment_Source__c': 'Stripe', 'PMT_Subscription_ID__c': data['subscription_id'], 'RecordTypeId': settings.SUBHUB_OPP_RECORD_TYPE, 'Service_Plan__c': nickname, 'StageName': 'Reactivation', })
def process_subhub_event_payment_failed(data): """ Event name: invoice.payment_failed """ statsd.incr('news.tasks.process_subhub_event.payment_failed') user_data = get_user_data(payee_id=data['customer_id'], extra_fields=['id']) # the only user identifiable information available is the payment # processor/Stripe ID, so if the user wasn't found by that, there's really # nothing to be done here but retry. if not user_data: statsd.incr( 'news.tasks.process_subhub_event.payment_failed.user_not_found') raise RetryTask('Could not find user. Try again.') nickname = data['nickname'] if isinstance(nickname, list): nickname = nickname[0] sfdc.opportunity.create({ 'Amount': cents_to_dollars(data['amount_due']), 'CloseDate': iso_format_unix_timestamp(data['created']), 'Donation_Contact__c': user_data['id'], 'Event_Id__c': data['event_id'], 'Event_Name__c': data['event_type'], 'Name': 'Subscription Services', 'PMT_Subscription_ID__c': data['subscription_id'], 'PMT_Transaction_ID__c': data['charge_id'], 'Payment_Source__c': 'Stripe', 'RecordTypeId': settings.SUBHUB_OPP_RECORD_TYPE, 'Service_Plan__c': nickname, 'StageName': 'Payment Failed', 'currency__c': data['currency'], })
def fxa_verified(data): """Add new FxA users to SFDC""" # if we're not using the sandbox ignore testing domains if email_is_testing(data["email"]): return lang = get_best_language(get_accept_languages(data.get("locale"))) if not lang or lang not in newsletter_languages(): lang = "other" email = data["email"] fxa_id = data["uid"] create_date = data.get("createDate", data.get("ts")) newsletters = data.get("newsletters") metrics = data.get("metricsContext", {}) new_data = { "email": email, "source_url": fxa_source_url(metrics), "country": data.get("countryCode", ""), "fxa_lang": data.get("locale"), "fxa_service": data.get("service", ""), "fxa_id": fxa_id, "optin": True, "format": "H", } if create_date: new_data["fxa_create_date"] = iso_format_unix_timestamp(create_date) newsletters = newsletters or [] newsletters.append(settings.FXA_REGISTER_NEWSLETTER) new_data["newsletters"] = newsletters user_data = get_fxa_user_data(fxa_id, email) # don't overwrite the user's language if already set if not (user_data and user_data.get("lang")): new_data["lang"] = lang upsert_contact(SUBSCRIBE, new_data, user_data)
def process_subhub_event_subscription_charge(data): """ Event names: customer.subscription.created, customer.recurring_charge This method handles both new and recurring charges. Each of the handled events contains the same payload data. The only variation below is in regards to Initial_Purchase__c, which will be True for the `customer.subscription.created` event, and False for the `customer.recurring_charge` event. """ statsd.incr('news.tasks.process_subhub_event.subscription_charge') 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_charge.user_not_found' ) raise RetryTask('Could not find user. Try again.') nickname = data['nickname'] if isinstance(nickname, list): nickname = nickname[0] # if a customer re-instates service after a cancellation, the record needs to be updated oppy_data = { 'Amount': cents_to_dollars(data['plan_amount']), 'Billing_Cycle_End__c': iso_format_unix_timestamp(data['current_period_end']), 'Billing_Cycle_Start__c': iso_format_unix_timestamp(data['current_period_start']), 'CloseDate': iso_format_unix_timestamp(data['created']), 'Credit_Card_Type__c': data['brand'], 'currency__c': data['currency'], 'Donation_Contact__c': user_data['id'], 'Event_Id__c': data['event_id'], 'Event_Name__c': data['event_type'], 'Initial_Purchase__c': data['event_type'] == 'customer.subscription.created', 'Invoice_Number__c': data['invoice_number'], 'Last_4_Digits__c': data['last4'], 'Name': 'Subscription Services', 'Next_Invoice_Date__c': iso_format_unix_timestamp(data['next_invoice_date']), 'Payment_Source__c': 'Stripe', 'PMT_Subscription_ID__c': data['subscription_id'], 'PMT_Transaction_ID__c': data['charge'], 'RecordTypeId': settings.SUBHUB_OPP_RECORD_TYPE, 'Service_Plan__c': nickname, 'StageName': 'Closed Won', } if 'proration_amount' in data: oppy_data['Proration_Amount__c'] = cents_to_dollars( data['proration_amount']) if 'total_amount' in data: oppy_data['Total_Amount__c'] = cents_to_dollars(data['total_amount']) sfdc.opportunity.upsert(f'PMT_Invoice_ID__c/{data["invoice_id"]}', oppy_data)
def process_donation(data): get_lock(data['email']) # tells the backend to leave the "subscriber" flag alone contact_data = {'_set_subscriber': False} # do "or ''" because data can contain None values first_name = (data.get('first_name') or '').strip() last_name = (data.get('last_name') or '').strip() if first_name and last_name: contact_data['first_name'] = first_name contact_data['last_name'] = last_name elif first_name: contact_data['first_name'] = first_name elif last_name: names = data['last_name'].rsplit(None, 1) if len(names) == 2: first, last = names else: first, last = '', names[0] if first: contact_data['first_name'] = first if last: contact_data['last_name'] = last user_data = get_user_data(email=data['email'], extra_fields=['id']) if user_data: if contact_data and ( ('first_name' in contact_data and contact_data['first_name'] != user_data['first_name']) or ('last_name' in contact_data and contact_data['last_name'] != user_data['last_name'])): sfdc.update(user_data, contact_data) else: contact_data['token'] = generate_token() contact_data['email'] = data['email'] contact_data['record_type'] = settings.DONATE_CONTACT_RECORD_TYPE # returns a dict with the new ID but no other user data, but that's enough here user_data = sfdc.add(contact_data) if not user_data.get('id'): # retry here to make sure we associate the donation data with the proper account raise RetryTask('User not yet available') # add opportunity donation = { 'RecordTypeId': settings.DONATE_OPP_RECORD_TYPE, 'Name': 'Foundation Donation', 'Donation_Contact__c': user_data['id'], 'StageName': 'Closed Won', 'Amount': float(data['donation_amount']), 'Currency__c': data['currency'].upper(), 'Payment_Source__c': data['service'], 'PMT_Transaction_ID__c': data['transaction_id'], 'Payment_Type__c': 'Recurring' if data['recurring'] else 'One-Time', } # this is a unix timestamp in ms since epoc timestamp = data.get('created') if timestamp: donation['CloseDate'] = iso_format_unix_timestamp(timestamp) for dest_name, source_name in DONATION_NEW_FIELDS.items(): if source_name in data: donation[dest_name] = data[source_name] for dest_name, source_name in DONATION_OPTIONAL_FIELDS.items(): if data.get(source_name): # truncate at 2000 chars as that's the max for # a SFDC text field. We may do more granular # truncation per field in future. donation[dest_name] = data[source_name][:2000] try: sfdc.opportunity.create(donation) except sfapi.SalesforceMalformedRequest as e: if e.content and e.content[0].get('errorCode') == 'DUPLICATE_VALUE': # already in the system, ignore pass else: raise
def process_donation(data): get_lock(data["email"]) contact_data = { "_set_subscriber": False, # SFDC, leave "subscriber" flag alone "mofo_relevant": True, # CTMS, set a MoFo relevant contact } # do "or ''" because data can contain None values first_name = (data.get("first_name") or "").strip() last_name = (data.get("last_name") or "").strip() if first_name and last_name: contact_data["first_name"] = first_name contact_data["last_name"] = last_name elif first_name: contact_data["first_name"] = first_name elif last_name: names = data["last_name"].rsplit(None, 1) if len(names) == 2: first, last = names else: first, last = "", names[0] if first: contact_data["first_name"] = first if last: contact_data["last_name"] = last user_data = get_user_data(email=data["email"], extra_fields=["id"]) if user_data: if contact_data and ( ( "first_name" in contact_data and contact_data["first_name"] != user_data["first_name"] ) or ( "last_name" in contact_data and contact_data["last_name"] != user_data["last_name"] ) ): sfdc.update(user_data, contact_data) ctms_data = contact_data.copy() del ctms_data["_set_subscriber"] ctms.update(user_data, ctms_data) else: contact_data["token"] = generate_token() contact_data["email"] = data["email"] contact_data["record_type"] = settings.DONATE_CONTACT_RECORD_TYPE ctms_data = contact_data.copy() del ctms_data["_set_subscriber"] del ctms_data["record_type"] contact = ctms.add(ctms_data) if contact: contact_data["email_id"] = contact["email"]["email_id"] if not settings.SFDC_ENABLED: return # returns a dict with the new ID but no other user data, but that's enough here user_data = sfdc.add(contact_data) if not user_data.get("id"): # retry here to make sure we associate the donation data with the proper account raise RetryTask("User not yet available") if not settings.SFDC_ENABLED: return # add opportunity donation = { "RecordTypeId": settings.DONATE_OPP_RECORD_TYPE, "Name": "Foundation Donation", "Donation_Contact__c": user_data["id"], "StageName": "Closed Won", "Amount": float(data["donation_amount"]), "Currency__c": data["currency"].upper(), "Payment_Source__c": data["service"], "PMT_Transaction_ID__c": data["transaction_id"], "Payment_Type__c": "Recurring" if data["recurring"] else "One-Time", } # https://github.com/mozmeao/basket/issues/364 if "campaign_id" in data: donation["CampaignId"] = data["campaign_id"] # this is a unix timestamp in ms since epoc timestamp = data.get("created") if timestamp: donation["CloseDate"] = iso_format_unix_timestamp(timestamp) for dest_name, source_name in DONATION_NEW_FIELDS.items(): if source_name in data: donation[dest_name] = data[source_name] for dest_name, source_name in DONATION_OPTIONAL_FIELDS.items(): if data.get(source_name): # truncate at 2000 chars as that's the max for # a SFDC text field. We may do more granular # truncation per field in future. donation[dest_name] = data[source_name][:2000] try: sfdc.opportunity.create(donation) except sfapi.SalesforceMalformedRequest as e: if e.content and e.content[0].get("errorCode") == "DUPLICATE_VALUE": # already in the system, ignore pass else: raise
def process_donation(data): if 'data' in data: # here for old messages # TODO remove in a subsequent deployment data = data['data'] get_lock(data['email']) # tells the backend to leave the "subscriber" flag alone contact_data = {'_set_subscriber': False} first_name = data.get('first_name', '').strip() last_name = data.get('last_name', '').strip() if first_name and last_name: contact_data['first_name'] = first_name contact_data['last_name'] = last_name elif first_name: contact_data['first_name'] = first_name elif last_name: names = data['last_name'].rsplit(None, 1) if len(names) == 2: first, last = names else: first, last = '', names[0] if first: contact_data['first_name'] = first if last: contact_data['last_name'] = last user_data = get_user_data(email=data['email'], extra_fields=['id']) if user_data: if contact_data and ( ('first_name' in contact_data and contact_data['first_name'] != user_data['first_name']) or ('last_name' in contact_data and contact_data['last_name'] != user_data['last_name'])): sfdc.update(user_data, contact_data) else: contact_data['token'] = generate_token() contact_data['email'] = data['email'] contact_data['record_type'] = settings.DONATE_CONTACT_RECORD_TYPE sfdc.add(contact_data) # fetch again to get ID user_data = get_user_data(email=data.get('email'), extra_fields=['id']) if not user_data: # retry here to make sure we associate the donation data with the proper account raise RetryTask('User not yet available') # add opportunity donation = { 'RecordTypeId': settings.DONATE_OPP_RECORD_TYPE, 'Name': 'Foundation Donation', 'Donation_Contact__c': user_data['id'], 'StageName': 'Closed Won', 'Amount': float(data['donation_amount']), 'Currency__c': data['currency'].upper(), 'Payment_Source__c': data['service'], 'PMT_Transaction_ID__c': data['transaction_id'], 'Payment_Type__c': 'Recurring' if data['recurring'] else 'One-Time', } # this is a unix timestamp in ms since epoc timestamp = data.get('created') if timestamp: donation['CloseDate'] = iso_format_unix_timestamp(timestamp) for dest_name, source_name in DONATION_OPTIONAL_FIELDS.items(): if source_name in data: donation[dest_name] = data[source_name] try: sfdc.opportunity.create(donation) except sfapi.SalesforceMalformedRequest as e: if e.content and e.content[0].get('errorCode') == 'DUPLICATE_VALUE': # already in the system, ignore pass else: raise
def process_donation(data): get_lock(data['email']) # tells the backend to leave the "subscriber" flag alone contact_data = {'_set_subscriber': False} # do "or ''" because data can contain None values first_name = (data.get('first_name') or '').strip() last_name = (data.get('last_name') or '').strip() if first_name and last_name: contact_data['first_name'] = first_name contact_data['last_name'] = last_name elif first_name: contact_data['first_name'] = first_name elif last_name: names = data['last_name'].rsplit(None, 1) if len(names) == 2: first, last = names else: first, last = '', names[0] if first: contact_data['first_name'] = first if last: contact_data['last_name'] = last user_data = get_user_data(email=data['email'], extra_fields=['id']) if user_data: if contact_data and ( ('first_name' in contact_data and contact_data['first_name'] != user_data['first_name']) or ('last_name' in contact_data and contact_data['last_name'] != user_data['last_name'])): sfdc.update(user_data, contact_data) else: contact_data['token'] = generate_token() contact_data['email'] = data['email'] contact_data['record_type'] = settings.DONATE_CONTACT_RECORD_TYPE sfdc.add(contact_data) # fetch again to get ID user_data = get_user_data(email=data.get('email'), extra_fields=['id']) if not user_data: # retry here to make sure we associate the donation data with the proper account raise RetryTask('User not yet available') # add opportunity donation = { 'RecordTypeId': settings.DONATE_OPP_RECORD_TYPE, 'Name': 'Foundation Donation', 'Donation_Contact__c': user_data['id'], 'StageName': 'Closed Won', 'Amount': float(data['donation_amount']), 'Currency__c': data['currency'].upper(), 'Payment_Source__c': data['service'], 'PMT_Transaction_ID__c': data['transaction_id'], 'Payment_Type__c': 'Recurring' if data['recurring'] else 'One-Time', } # this is a unix timestamp in ms since epoc timestamp = data.get('created') if timestamp: donation['CloseDate'] = iso_format_unix_timestamp(timestamp) for dest_name, source_name in DONATION_NEW_FIELDS.items(): if source_name in data: donation[dest_name] = data[source_name] for dest_name, source_name in DONATION_OPTIONAL_FIELDS.items(): if source_name in data: # truncate at 2000 chars as that's the max for # a SFDC text field. We may do more granular # truncation per field in future. donation[dest_name] = data[source_name][:2000] try: sfdc.opportunity.create(donation) except sfapi.SalesforceMalformedRequest as e: if e.content and e.content[0].get('errorCode') == 'DUPLICATE_VALUE': # already in the system, ignore pass else: raise