Example #1
0
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
Example #2
0
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
Example #3
0
 def test_user_not_in_sf(self, sfdc_mock):
     """A user not found in SFDC should produce an error response."""
     sfdc_mock.get.side_effect = NewsletterException('DANGER!')
     token = generate_token()
     resp = self.client.get('/news/user/{}/'.format(token))
     self.assertEqual(resp.status_code, 400)
     resp_data = json.loads(resp.content)
     self.assertDictEqual(resp_data, {
         'status': 'error',
         'desc': 'DANGER!',
         'code': errors.BASKET_UNKNOWN_ERROR,
     })
Example #4
0
 def setUp(self):
     self.token = generate_token()
     self.email = '*****@*****.**'
     # User data in format that get_user_data() returns it
     self.get_user_data = {
         'email': self.email,
         'token': self.token,
         'format': 'H',
         'country': 'us',
         'lang': 'en',
         'newsletters': ['slug'],
         'status': 'ok',
     }
Example #5
0
 def setUp(self):
     self.token = generate_token()
     self.email = '*****@*****.**'
     # User data in format that get_user_data() returns it
     self.get_user_data = {
         'email': self.email,
         'token': self.token,
         'format': 'H',
         'country': 'us',
         'lang': 'en',
         'newsletters': ['slug'],
         'status': 'ok',
     }
Example #6
0
 def setUp(self):
     self.token = generate_token()
     self.email = "*****@*****.**"
     # User data in format that get_user_data() returns it
     self.get_user_data = {
         "email": self.email,
         "token": self.token,
         "format": "H",
         "country": "us",
         "lang": "en",
         "newsletters": ["slug"],
         "status": "ok",
     }
Example #7
0
 def test_user_not_in_sf(self, sfdc_mock):
     """A user not found in SFDC should produce an error response."""
     sfdc_mock.get.side_effect = NewsletterException('DANGER!')
     token = generate_token()
     resp = self.client.get('/news/user/{}/'.format(token))
     self.assertEqual(resp.status_code, 400)
     resp_data = json.loads(resp.content)
     self.assertDictEqual(
         resp_data, {
             'status': 'error',
             'desc': 'DANGER!',
             'code': errors.BASKET_UNKNOWN_ERROR,
         })
Example #8
0
 def test_user_not_in_sf(self, sfdc_mock):
     """A user not found in SFDC should produce an error response."""
     sfdc_mock.get.side_effect = NewsletterException("DANGER!")
     token = generate_token()
     resp = self.client.get("/news/user/{}/".format(token))
     self.assertEqual(resp.status_code, 400)
     resp_data = json.loads(resp.content)
     self.assertDictEqual(
         resp_data,
         {
             "status": "error",
             "desc": "DANGER!",
             "code": errors.BASKET_UNKNOWN_ERROR
         },
     )
Example #9
0
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)
Example #10
0
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)
Example #11
0
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
Example #12
0
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)
Example #13
0
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
Example #14
0
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
Example #15
0
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
Example #16
0
 def test_valid_tokens(self):
     self.assertTrue(views.is_token('abcdef-abcdef-abcdef-deadbeef-123456'))
     self.assertTrue(views.is_token(utils.generate_token()))
Example #17
0
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
Example #18
0
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
Example #19
0
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
Example #20
0
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
Example #21
0
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
Example #22
0
 def test_valid_tokens(self):
     self.assertTrue(views.is_token('abcdef-abcdef-abcdef-deadbeef-123456'))
     self.assertTrue(views.is_token(utils.generate_token()))