Beispiel #1
0
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)
Beispiel #2
0
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})
    ctms.update(user_data, {"optin": True})
Beispiel #3
0
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
Beispiel #4
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
Beispiel #5
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)
        ctms.update(user_data, new_data)
    else:
        new_data.update({"email": email, "token": generate_token()})
        ctms_data = new_data.copy()
        ctms_contact = ctms.add(ctms_data)
        if ctms_contact:
            new_data["email_id"] = ctms_contact["email"]["email_id"]
        sfdc.add(new_data)
Beispiel #6
0
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})
            ctms.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
Beispiel #7
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
Beispiel #8
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
Beispiel #9
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