def sfdc_add_update(update_data, user_data=None): # for use with maintenance mode only # TODO remove after maintenance is over and queue is processed if user_data: sfdc.update(user_data, update_data) ctms.update(user_data, update_data) else: ctms_data = update_data.copy() ctms_contact = ctms.add(ctms_data) if ctms_contact: update_data["email_id"] = ctms_contact["email"]["email_id"] try: sfdc.add(update_data) except sfapi.SalesforceMalformedRequest as e: # noqa # possibly a duplicate email. try the update below. user_data = get_user_data(email=update_data["email"], extra_fields=["id"]) if user_data: # we have a user, delete generated token and email_id # and continue with an update update_data.pop("token", None) update_data.pop("email_id", None) sfdc.update(user_data, update_data) ctms.update(user_data, update_data) else: # still no user, try the add one more time ctms_contact = ctms.add(update_data) if ctms_contact: update_data["email_id"] = ctms_contact["email"]["email_id"] sfdc.add(update_data)
def confirm_user(token): """ Confirm any pending subscriptions for the user with this token. If any of the subscribed newsletters have welcome messages, send them. :param token: User's token :param user_data: Dictionary with user's data from Exact Target, as returned by get_user_data(), or None if that wasn't available when this was called. :raises: BasketError for fatal errors, NewsletterException for retryable errors. """ get_lock(token) user_data = get_user_data(token=token) if user_data is None: statsd.incr('news.tasks.confirm_user.confirm_user_not_found') return if user_data['optin']: # already confirmed return if not ('email' in user_data and user_data['email']): raise BasketError('token has no email in ET') sfdc.update(user_data, {'optin': True})
def fxa_email_changed(data): ts = data["ts"] fxa_id = data["uid"] email = data["email"] cache_key = "fxa_email_changed:%s" % fxa_id prev_ts = float(cache.get(cache_key, 0)) if prev_ts and prev_ts > ts: # message older than our last update for this UID return # Update SFDC user_data = get_user_data(fxa_id=fxa_id, extra_fields=["id"]) if user_data: sfdc.update(user_data, {"fxa_primary_email": email}) else: # FxA record not found, try email user_data = get_user_data(email=email, extra_fields=["id"]) if user_data: sfdc.update(user_data, {"fxa_id": fxa_id, "fxa_primary_email": email}) else: # No matching record for Email or FxA ID. Create one. sfdc.add({"email": email, "fxa_id": fxa_id, "fxa_primary_email": email}) statsd.incr("news.tasks.fxa_email_changed.user_not_found") sfmc.upsert_row("FXA_EmailUpdated", {"FXA_ID": fxa_id, "NewEmailAddress": email}) cache.set(cache_key, ts, 7200) # 2 hr
def process_subhub_event_customer_created(data): """ Event name: customer.created Creates or updates a SFDC customer when a new payment processor/Stripe customer is created """ statsd.incr('news.tasks.process_subhub_event.customer_created') first, last = split_name(data['name']) contact_data = {'fxa_id': data['user_id'], 'payee_id': data['customer_id']} user_data = get_user_data(email=data['email']) # if user was found in sfdc, see if we should update their name(s) if user_data: # if current last name is '_', update it if user_data['last_name'] == '_': contact_data['last_name'] = last # if current last name is blank/Null, update it if not user_data['first_name']: contact_data['first_name'] = first sfdc.update(user_data, contact_data) statsd.incr('news.tasks.process_subhub_event.customer_created.updated') # if no user was found, create new user in sfdc else: contact_data['email'] = data['email'] contact_data['first_name'] = first contact_data['last_name'] = last # create the user in sfdc statsd.incr('news.tasks.process_subhub_event.customer_created.created') sfdc.add(contact_data)
def update_user_meta(token, data): """Update a user's metadata, not newsletters""" sfdc.update({"token": token}, data) try: ctms.update_by_alt_id("token", token, data) except CTMSNotFoundByAltIDError: if not settings.SFDC_ENABLED: raise
def update_user_meta(token, data): """Update a user's metadata, not newsletters""" sfdc.update({"token": token}, data) try: ctms.update_by_alt_id("token", token, data) except CTMSNotFoundByAltIDError: # TODO: raise this exception when CTMS is primary data source pass
def update_custom_unsub(token, reason): """Record a user's custom unsubscribe reason.""" get_lock(token) try: sfdc.update({'token': token}, {'reason': reason}) except sfapi.SalesforceMalformedRequest: # likely the record can't be found. nothing to do. pass
def process_petition_signature(data): """ Add petition signature to SFDC """ data = data['form'] get_lock(data['email']) # tells the backend to leave the "subscriber" flag alone contact_data = {'_set_subscriber': False} contact_data.update( {k: data[k] for k in PETITION_CONTACT_FIELDS if data.get(k)}) user_data = get_user_data(email=data['email'], extra_fields=['id']) if user_data: 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') if data.get('email_subscription', False): upsert_user.delay( SUBSCRIBE, { 'token': user_data['token'], 'lang': data.get('lang', 'en-US'), 'newsletters': 'mozilla-foundation', 'source_url': data['source_url'], }) campaign_member = { 'CampaignId': data['campaign_id'], 'ContactId': user_data['id'], 'Full_URL__c': data['source_url'], 'Status': 'Signed', } comments = data.get('comments') if comments: campaign_member['Petition_Comments__c'] = comments[:500] metadata = data.get('metadata') if metadata: campaign_member['Petition_Flex__c'] = json.dumps(metadata)[:500] try: sfdc.campaign_member.create(campaign_member) 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 amo_check_user_for_deletion(user_id): """If a user has no addons their AMO info should be removed""" addons = sfdc.sf.query( sfapi.format_soql( "SELECT Id FROM DevAddOn__c WHERE AMO_Contact_ID__c = {contact_id} LIMIT 1", contact_id=user_id, ), ) if not addons["records"]: sfdc.update({"id": user_id}, {"amo_id": None, "amo_user": False})
def process_petition_signature(data): """ Add petition signature to SFDC """ data = data['form'] get_lock(data['email']) # tells the backend to leave the "subscriber" flag alone contact_data = {'_set_subscriber': False} contact_data.update({k: data[k] for k in PETITION_CONTACT_FIELDS if data.get(k)}) user_data = get_user_data(email=data['email'], extra_fields=['id']) if user_data: 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') if data.get('email_subscription', False): upsert_user.delay(SUBSCRIBE, { 'token': user_data['token'], 'lang': 'en-US', 'newsletters': 'mozilla-foundation', 'source_url': data['source_url'], }) campaign_member = { 'CampaignId': data['campaign_id'], 'ContactId': user_data['id'], 'Full_URL__c': data['source_url'], 'Status': 'Signed', } comments = data.get('comments') if comments: campaign_member['Petition_Comments__c'] = comments[:500] metadata = data.get('metadata') if metadata: campaign_member['Petition_Flex__c'] = json.dumps(metadata)[:500] try: sfdc.campaign_member.create(campaign_member) 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 update_custom_unsub(token, reason): """Record a user's custom unsubscribe reason.""" get_lock(token) try: sfdc.update({"token": token}, {"reason": reason}) except sfapi.SalesforceMalformedRequest: # likely the record can't be found. nothing to do. return try: ctms.update_by_alt_id("token", token, {"reason": reason}) except CTMSNotFoundByAltIDError: # No record found for that token, nothing to do. pass
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 update_custom_unsub(token, reason): """Record a user's custom unsubscribe reason.""" get_lock(token) try: sfdc.update({'token': token}, {'reason': reason}) except sfapi.SalesforceMalformedRequest: # likely the record can't be found. try the DoI DE. user = get_sfmc_doi_user(token) if user and user.get('email'): get_lock(user['email']) user['reason'] = reason try: sfdc.add(user) except sfapi.SalesforceMalformedRequest: # probably already know the email address sfdc.update({'email': user['email']}, user)
def record_common_voice_update(data): # do not change the sent data in place. A retry will use the changed data. dcopy = data.copy() email = dcopy.pop("email") user_data = get_user_data(email=email, extra_fields=["id"]) new_data = { "source_url": "https://voice.mozilla.org", "newsletters": [settings.COMMON_VOICE_NEWSLETTER], } for k, v in dcopy.items(): new_data["cv_" + k] = v if user_data: sfdc.update(user_data, new_data) else: new_data.update({"email": email, "token": generate_token()}) sfdc.add(new_data)
def upsert_amo_user_data(data, user_sync=False): """ Update AMO user data in the SFDC contact, or create a contact. Return the Contact data (the contact ID at a minimum). :param data: dict of amo user data :param user_sync: bool True if this is a User Sync request :return: dict of SFDC contact data """ data = data.copy() fxa_id = data.pop("fxa_id", None) amo_id = data.pop("id", None) user = None # records can come in with no "id" or "fxa_id" field if amo_id: user = get_user_data(amo_id=amo_id, extra_fields=["id", "amo_id", "fxa_id"]) if not user and fxa_id: # Try to find user with fxa_id user = get_user_data(fxa_id=fxa_id, extra_fields=["id", "amo_id", "fxa_id"]) if not user: # Cannot find user with FxA ID or AMO ID, ignore the update return None if user_sync and not user["amo_id"]: # do not update user as AMO User unless it comes from an AddonSync return None amo_deleted = data.pop("deleted", False) amo_data = {f"amo_{k}": v for k, v in data.items() if v} amo_data["amo_id"] = amo_id amo_data["amo_deleted"] = amo_deleted if not user_sync: # only ensure this is true if this is from an addon sync amo_data["amo_user"] = True if amo_deleted or fxa_id is None: amo_data["amo_id"] = None sfdc.update(user, amo_data) ctms.update(user, amo_data) return user
def record_common_voice_goals(data): email = data.pop('email') user_data = get_user_data(email=email, extra_fields=['id']) new_data = { 'source_url': 'https://voice.mozilla.org', 'newsletters': [settings.COMMON_VOICE_NEWSLETTER], } for k, v in data.items(): new_data['cv_' + k] = v if user_data: sfdc.update(user_data, new_data) else: new_data.update({ 'email': email, 'token': generate_token(), }) sfdc.add(new_data)
def sfdc_add_update(update_data, user_data=None): # for use with maintenance mode only # TODO remove after maintenance is over and queue is processed if user_data: sfdc.update(user_data, update_data) else: try: sfdc.add(update_data) except sfapi.SalesforceMalformedRequest as e: # noqa # possibly a duplicate email. try the update below. user_data = get_user_data(email=update_data['email'], extra_fields=['id']) if user_data: # we have a user, delete generated token # and continue with an update update_data.pop('token', None) sfdc.update(user_data, update_data) else: # still no user, try the add one more time sfdc.add(update_data)
def fxa_email_changed(data): ts = data["ts"] fxa_id = data["uid"] email = data["email"] cache_key = "fxa_email_changed:%s" % fxa_id prev_ts = float(cache.get(cache_key, 0)) if prev_ts and prev_ts > ts: # message older than our last update for this UID return # Update SFDC / CTMS user_data = get_user_data(fxa_id=fxa_id, extra_fields=["id"]) if user_data: sfdc.update(user_data, {"fxa_primary_email": email}) ctms.update(user_data, {"fxa_primary_email": email}) else: # FxA record not found, try email user_data = get_user_data(email=email, extra_fields=["id"]) if user_data: sfdc.update(user_data, { "fxa_id": fxa_id, "fxa_primary_email": email }) ctms.update(user_data, { "fxa_id": fxa_id, "fxa_primary_email": email }) else: # No matching record for Email or FxA ID. Create one. data = { "email": email, "token": generate_token(), "fxa_id": fxa_id, "fxa_primary_email": email, } ctms_data = data.copy() contact = ctms.add(ctms_data) if contact: data["email_id"] = contact["email"]["email_id"] sfdc.add(data) statsd.incr("news.tasks.fxa_email_changed.user_not_found") cache.set(cache_key, ts, 7200) # 2 hr
def record_common_voice_goals(data): # do not change the sent data in place. A retry will use the changed data. dcopy = data.copy() email = dcopy.pop('email') user_data = get_user_data(email=email, extra_fields=['id']) new_data = { 'source_url': 'https://voice.mozilla.org', 'newsletters': [settings.COMMON_VOICE_NEWSLETTER], } for k, v in dcopy.items(): new_data['cv_' + k] = v if user_data: sfdc.update(user_data, new_data) else: new_data.update({ 'email': email, 'token': generate_token(), }) sfdc.add(new_data)
def confirm_user(token): """ Confirm any pending subscriptions for the user with this token. If any of the subscribed newsletters have welcome messages, send them. :param token: User's token :param user_data: Dictionary with user's data from Exact Target, as returned by get_user_data(), or None if that wasn't available when this was called. :raises: BasketError for fatal errors, NewsletterException for retryable errors. """ get_lock(token) user_data = get_user_data(token=token) if user_data is None: user = get_sfmc_doi_user(token) if user and user.get('email'): get_lock(user['email']) user['optin'] = True try: sfdc.add(user) except sfapi.SalesforceMalformedRequest: # probably already know the email address sfdc.update({'email': user['email']}, user) statsd.incr('news.tasks.confirm_user.moved_from_sfmc') else: statsd.incr('news.tasks.confirm_user.confirm_user_not_found') return if user_data['optin']: # already confirmed return if not ('email' in user_data and user_data['email']): raise BasketError('token has no email in ET') sfdc.update(user_data, {'optin': True})
def get_fxa_user_data(fxa_id, email): """Return a user data dict, just like `get_user_data` below, but ensure we have a good FxA contact First look for a user by FxA ID. If we get a user, and the email matches what was passed in, return it. If the email doesn't match, set the first user's FxA_ID to "DUPE:<fxa_id>" so that we don't run into dupe issues, and set "fxa_deleted" to True. Then look up a user with the email address and return that or None. """ user_data = None # try getting user data with the fxa_id first user_data_fxa = get_user_data(fxa_id=fxa_id, extra_fields=["id"]) if user_data_fxa: user_data = user_data_fxa # If email doesn't match, update FxA primary email field with the new email. if user_data_fxa["email"] != email: sfdc.update(user_data_fxa, {"fxa_primary_email": email}) # if we still don't have user data try again with email this time if not user_data: user_data = get_user_data(email=email, extra_fields=["id"]) return user_data
def upsert_amo_user_data(data): """ Update AMO user data in the SFDC contact, or create a contact. Return the Contact data (the contact ID at a minimum). :param data: dict of amo user data :return: dict of SFDC contact data """ fxa_id = data.pop("fxa_id") amo_id = data.pop("id") user = get_user_data(amo_id=amo_id, extra_fields=["id", "amo_id", "fxa_id"]) if not user: # Try to find user with fxa_id user = get_user_data(fxa_id=fxa_id, extra_fields=["id", "amo_id", "fxa_id"]) if not user: # Cannot find user with FxA ID or AMO ID, ignore the update return None amo_deleted = data.pop("deleted", False) amo_data = {f"amo_{k}": v for k, v in data.items() if v} amo_data["amo_user"] = True amo_data["amo_id"] = amo_id amo_data["amo_deleted"] = amo_deleted if amo_deleted: if fxa_id is None: # Deleted user, we don't want do keep linking that AMO account with # that FxA account in Salesforce, they might re-create a new AMO # account from their previous FxA account. amo_data["amo_id"] = None else: if fxa_id is None: # User takeover protection, we don't want do keep linking that AMO # account with that FxA account in Salesforce amo_data["amo_id"] = None sfdc.update(user, amo_data) return user
def upsert_amo_user_data(data): """ Update AMO user data in the SFDC contact, or create a contact. Return the Contact data (the contact ID at a minimum). :param data: dict of amo user data :return: dict of SFDC contact data """ email = data.pop('email') amo_id = data.pop('id') amo_deleted = data.pop('deleted', False) amo_data = {f'amo_{k}': v for k, v in data.items() if v} amo_data['amo_user'] = not amo_deleted user = get_user_data(amo_id=amo_id, extra_fields=['id', 'amo_id']) if user: sfdc.update(user, amo_data) return user # include the ID in update or add since we couldn't find # the user with this ID above amo_data['amo_id'] = amo_id user = get_user_data(email=email, extra_fields=['id']) if user: sfdc.update(user, amo_data) # need amo_id for linking addons and authors user['amo_id'] = amo_id return user amo_data['email'] = email amo_data['source_url'] = 'https://addons.mozilla.org/' # returns only the new user ID in a dict, but that will work # when passed to e.g. `sfdc.update()` user = sfdc.add(amo_data) # need amo_id for linking addons and authors user['amo_id'] = amo_id return user
def process_petition_signature(data): """ Add petition signature to CTMS / SFDC If SFDC is enabled, a campaign member record is created. """ data = data["form"] get_lock(data["email"]) # tells the backend to leave the "subscriber" flag alone contact_data = { "_set_subscriber": False, # SFDC: leave the "subscriber" flag alone "mofo_relevant": True, # CTMS: set contact as MoFo relevant } contact_data.update({k: data[k] for k in PETITION_CONTACT_FIELDS if data.get(k)}) user_data = get_user_data(email=data["email"], extra_fields=["id"]) if user_data: 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"] ctms_data = contact_data.copy() contact_data["record_type"] = settings.DONATE_CONTACT_RECORD_TYPE del ctms_data["_set_subscriber"] ctms_data["mofo_relevant"] = True contact = ctms.add(ctms_data) if contact: contact_data["email_id"] = contact["email"]["email_id"] 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") if data.get("email_subscription", False): upsert_user.delay( SUBSCRIBE, { "token": user_data["token"], "lang": data.get("lang", "en-US"), "newsletters": "mozilla-foundation", "source_url": data["source_url"], }, ) if not settings.SFDC_ENABLED: return campaign_id = data["campaign_id"] # Fix a specific issue with a specific campaign where the ID was entered without # the leading 7 if len(campaign_id) == 17 and not campaign_id.startswith("7"): campaign_id = f"7{campaign_id}" campaign_member = { "CampaignId": campaign_id, "ContactId": user_data["id"], "Full_URL__c": data["source_url"], "Status": "Signed", } comments = data.get("comments") if comments: campaign_member["Petition_Comments__c"] = comments[:500] metadata = data.get("metadata") if metadata: campaign_member["Petition_Flex__c"] = json.dumps(metadata)[:500] try: sfdc.campaign_member.create(campaign_member) 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
def fxa_delete(data): sfmc.upsert_row("FXA_Deleted", {"FXA_ID": data["uid"]}) user_data = get_user_data(fxa_id=data["uid"], extra_fields=["id"]) if user_data: sfdc.update(user_data, {"fxa_deleted": True})
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 upsert_contact(api_call_type, data, user_data): """ Update or insert (upsert) a contact record in SFDC @param int api_call_type: What kind of API call it was. Could be SUBSCRIBE, UNSUBSCRIBE, or SET. @param dict data: POST data from the form submission @param dict user_data: existing contact data from SFDC @return: token, created """ update_data = data.copy() forced_optin = data.pop('optin', False) if 'format' in data: update_data['format'] = 'T' if data['format'].upper().startswith( 'T') else 'H' newsletters = parse_newsletters_csv(data.get('newsletters')) if user_data: cur_newsletters = user_data.get('newsletters', None) else: cur_newsletters = None # check for and remove transactional newsletters if api_call_type == SUBSCRIBE: all_transactionals = set(get_transactional_message_ids()) newsletters_set = set(newsletters) transactionals = newsletters_set & all_transactionals if transactionals: newsletters = list(newsletters_set - transactionals) send_transactional_messages(update_data, user_data, list(transactionals)) if not newsletters: # no regular newsletters return None, None # Set the newsletter flags in the record by comparing to their # current subscriptions. update_data['newsletters'] = parse_newsletters(api_call_type, newsletters, cur_newsletters) if api_call_type != UNSUBSCRIBE: # Are they subscribing to any newsletters that don't require confirmation? # When including any newsletter that does not # require confirmation, user gets a pass on confirming and goes straight # to confirmed. to_subscribe = [ nl for nl, sub in update_data['newsletters'].items() if sub ] if to_subscribe and not (forced_optin or (user_data and user_data.get('optin'))): exempt_from_confirmation = Newsletter.objects \ .filter(slug__in=to_subscribe, requires_double_optin=False) \ .exists() if exempt_from_confirmation: update_data['optin'] = True # record source URL nl_map = newsletter_map() source_url = update_data.get('source_url') email = update_data.get('email') if not email: email = user_data.get('email') if user_data else None if email: # send all newsletters whether already subscribed or not # bug 1308971 # if api_call_type == SET this is pref center, so only send new subscriptions nl_list = newsletters if api_call_type == SUBSCRIBE else to_subscribe for nlid in nl_list: if nlid in nl_map: record_source_url.delay(email, source_url, nl_map[nlid]) if user_data is None: # no user found. create new one. update_data['token'] = generate_token() if settings.MAINTENANCE_MODE: sfdc_add_update.delay(update_data) else: # don't catch exceptions here. SalesforceError subclasses will retry. sfdc.add(update_data) return update_data['token'], True if forced_optin and not user_data.get('optin'): update_data['optin'] = True # they opted out of email before, but are subscribing again # clear the optout flag if api_call_type != UNSUBSCRIBE and user_data.get('optout'): update_data['optout'] = False # update record if user_data and user_data.get('token'): token = user_data['token'] else: token = update_data['token'] = generate_token() if settings.MAINTENANCE_MODE: sfdc_add_update.delay(update_data, user_data) else: sfdc.update(user_data, update_data) return token, False
def update_user_meta(token, data): """Update a user's metadata, not newsletters""" sfdc.update({'token': token}, data)
def fxa_delete(data): sfmc.upsert_row('FXA_Deleted', {'FXA_ID': data['uid']}) user_data = get_user_data(fxa_id=data['uid'], extra_fields=['id']) if user_data: sfdc.update(user_data, {'fxa_deleted': True})
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 upsert_contact(api_call_type, data, user_data): """ Update or insert (upsert) a contact record in SFDC @param int api_call_type: What kind of API call it was. Could be SUBSCRIBE, UNSUBSCRIBE, or SET. @param dict data: POST data from the form submission @param dict user_data: existing contact data from SFDC @return: token, created """ update_data = data.copy() forced_optin = data.pop('optin', False) if 'format' in data: update_data['format'] = 'T' if data['format'].upper().startswith('T') else 'H' newsletters = parse_newsletters_csv(data.get('newsletters')) if user_data: cur_newsletters = user_data.get('newsletters', None) else: cur_newsletters = None # check for and remove transactional newsletters if api_call_type == SUBSCRIBE: all_transactionals = set(get_transactional_message_ids()) newsletters_set = set(newsletters) transactionals = newsletters_set & all_transactionals if transactionals: newsletters = list(newsletters_set - transactionals) send_transactional_messages(update_data, user_data, list(transactionals)) if not newsletters: # no regular newsletters return None, None # Set the newsletter flags in the record by comparing to their # current subscriptions. update_data['newsletters'] = parse_newsletters(api_call_type, newsletters, cur_newsletters) if api_call_type != UNSUBSCRIBE: # Are they subscribing to any newsletters that don't require confirmation? # When including any newsletter that does not # require confirmation, user gets a pass on confirming and goes straight # to confirmed. to_subscribe = [nl for nl, sub in update_data['newsletters'].iteritems() if sub] if to_subscribe and not (forced_optin or (user_data and user_data.get('optin'))): exempt_from_confirmation = Newsletter.objects \ .filter(slug__in=to_subscribe, requires_double_optin=False) \ .exists() if exempt_from_confirmation: update_data['optin'] = True # record source URL nl_map = newsletter_map() source_url = update_data.get('source_url') email = update_data.get('email') if not email: email = user_data.get('email') if user_data else None if email: # send all newsletters whether already subscribed or not # bug 1308971 # if api_call_type == SET this is pref center, so only send new subscriptions nl_list = newsletters if api_call_type == SUBSCRIBE else to_subscribe for nlid in nl_list: if nlid in nl_map: record_source_url.delay(email, source_url, nl_map[nlid]) if user_data is None: # no user found. create new one. update_data['token'] = generate_token() if settings.MAINTENANCE_MODE: sfdc_add_update.delay(update_data) else: # don't catch exceptions here. SalesforceError subclasses will retry. sfdc.add(update_data) return update_data['token'], True if forced_optin and not user_data.get('optin'): update_data['optin'] = True # they opted out of email before, but are subscribing again # clear the optout flag if api_call_type != UNSUBSCRIBE and user_data.get('optout'): update_data['optout'] = False # update record if user_data and user_data.get('token'): token = user_data['token'] else: token = update_data['token'] = generate_token() if settings.MAINTENANCE_MODE: sfdc_add_update.delay(update_data, user_data) else: sfdc.update(user_data, update_data) return token, False
def upsert_contact(api_call_type, data, user_data): """ Update or insert (upsert) a contact record in SFDC @param int api_call_type: What kind of API call it was. Could be SUBSCRIBE, UNSUBSCRIBE, or SET. @param dict data: POST data from the form submission @param dict user_data: existing contact data from SFDC @return: token, created """ update_data = data.copy() forced_optin = data.pop("optin", False) if "format" in data: update_data["format"] = "T" if data["format"].upper().startswith("T") else "H" newsletters = parse_newsletters_csv(data.get("newsletters")) if user_data: cur_newsletters = user_data.get("newsletters", None) else: cur_newsletters = None # check for and remove transactional newsletters if api_call_type == SUBSCRIBE: all_transactionals = set(get_transactional_message_ids()) newsletters_set = set(newsletters) transactionals = newsletters_set & all_transactionals if transactionals: newsletters = list(newsletters_set - transactionals) send_acoustic_tx_messages( data["email"], data.get("lang", "en-US"), list(transactionals), ) if not newsletters: # no regular newsletters return None, None # Set the newsletter flags in the record by comparing to their # current subscriptions. update_data["newsletters"] = parse_newsletters( api_call_type, newsletters, cur_newsletters, ) send_confirm = False if api_call_type != UNSUBSCRIBE: # Check for newsletter-specific user updates to_subscribe_slugs = [ nl for nl, sub in update_data["newsletters"].items() if sub ] check_optin = not (forced_optin or (user_data and user_data.get("optin"))) check_mofo = not (user_data and user_data.get("mofo_relevant")) if to_subscribe_slugs and (check_optin or check_mofo): to_subscribe = Newsletter.objects.filter(slug__in=to_subscribe_slugs) # Are they subscribing to any newsletters that require confirmation? # If none require confirmation, user goes straight to confirmed (optin) # Otherwise, prepare to send a fx or moz confirmation if check_optin: exempt_from_confirmation = any( [not o.requires_double_optin for o in to_subscribe], ) if exempt_from_confirmation: update_data["optin"] = True else: send_fx_confirm = all([o.firefox_confirm for o in to_subscribe]) send_confirm = "fx" if send_fx_confirm else "moz" # Update a user to MoFo-relevant if they subscribed to a MoFo newsletters if check_mofo: if any([ns.is_mofo for ns in to_subscribe]): update_data["mofo_relevant"] = True if user_data is None: # no user found. create new one. token = update_data["token"] = generate_token() if settings.MAINTENANCE_MODE: sfdc_add_update.delay(update_data) else: ctms_data = update_data.copy() ctms_contact = ctms.add(ctms_data) if ctms_contact: # Successfully added to CTMS, send email_id to SFDC update_data["email_id"] = ctms_contact["email"]["email_id"] # don't catch exceptions here. SalesforceError subclasses will retry. sfdc.add(update_data) if send_confirm and settings.SEND_CONFIRM_MESSAGES: send_confirm_message.delay( data["email"], token, data.get("lang", "en-US"), send_confirm, ) return token, True if forced_optin and not user_data.get("optin"): update_data["optin"] = True # they opted out of email before, but are subscribing again # clear the optout flag if api_call_type != UNSUBSCRIBE and user_data.get("optout"): update_data["optout"] = False # update record if user_data and user_data.get("token"): token = user_data["token"] else: token = update_data["token"] = generate_token() if settings.MAINTENANCE_MODE: sfdc_add_update.delay(update_data, user_data) else: sfdc.update(user_data, update_data) ctms.update(user_data, update_data) if send_confirm and settings.SEND_CONFIRM_MESSAGES: send_confirm_message.delay( user_data["email"], token, update_data.get("lang", user_data.get("lang", "en-US")), send_confirm, ) return token, False
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