[SalesForce] How to setup custom Self-Registration on Salesforce Community

We've build a Salesforce Community and would like to enable Self-Registration. We want to customize this experience so that the user is associated with a Contact and Account that already exists in Salesforce.

Following the help documents on Salesforce, I enabled Self-Registration. We followed this link to the "self-registration Apex controller" and modified the code to locate a matching Contact and Account.

enter image description here

However, we constantly get portal account must have a role error when running the code in the Community (tests all pass without error):

There was an error in registering a user in site My_Community. The error message is: portal account owner must have a role

The default account I setup on in Community Registration setting as an owner with a Role, and the user I'm trying to register has a matching contact whose account has an owner with an assigned Role.

I've found this Stack Exchange question with the same error message, but the user references something called LightningSelfRegisterController, which I cannot find in my org.

The link "self-registration Apex controller" on the Community Admin page takes you to the CommunitiesSelfRegController class. I assume that the out-of-the-box Register page calls that controller, but I'm certain this controller is ever being called. I modified the registerUser() method like so:

public PageReference registerUser() {

       // it's okay if password is null - we'll send the user a random password in that case
    if (!isValidPassword()) {
        ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.ERROR, Label.site.passwords_dont_match);
        ApexPages.addMessage(msg);
        return null;
    }    

    // lookup contact based on email address
    // if there's more than one, get the oldest record
    Contact cnt = [select Id, AccountId from Contact where AccountId <> null and Email = :email order by CreatedDate limit 1];

    // if contact not found, show error and return null
    if(cnt == null) {
        ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, CONTACT_NOT_FOUND));
        return null;
    }

    String profileId = [select Id from Profile where Name = :COMMUNITY_PROFILE_NAME].Id;
    // never used
    // String roleEnum =  null;
    String accountId = cnt.AccountId; 

    String userName = email;

    User u = new User();
    u.Username = userName;
    u.Email = email;
    u.FirstName = firstName;
    u.LastName = lastName;
    u.CommunityNickname = communityNickname;
    u.ProfileId = profileId;
    u.ContactId = cnt.Id;

    String userId;

    try {
        userId = Site.createExternalUser(u, accountId, password);
    } catch(Site.ExternalUserCreateException ex) {
        List<String> errors = ex.getDisplayMessages();
        for (String error : errors)  {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, error));
        }

        // This message is used for debugging. Do not display this in the UI to the end user.
        // It has the information around why the user creation failed.
        System.debug(ex.getMessage());
    }

    if (userId != null) { 
        if (password != null && password.length() > 1) {
            return Site.login(userName, password, ApexPages.currentPage().getParameters().get('startURL'));
        }
        else {
            PageReference page = System.Page.CommunitiesSelfRegConfirm;
            page.setRedirect(true);
            return page;
        }
    }
    return null;
}

My questions are:

  1. Am I on the right path here to enable self-registration with custom contact and account setup?
  2. Do you see any problems with my code or settings?
  3. Any idea why I would the portal account owner must have a role error when running this in the Community (all my tests pass)?

Appreciate your help.

Best Answer

I answered that question to which you refer. Here's my answer from other there..

The Guest user assigned to new accounts created by community self-registration has no role. I solved this problem by adding logic to the Insert Before trigger on the Account object that reassigns accounts owned by this user to a "real" user. You may want to come up with a more elegant method that doesn't embed record Id's, but this should work.

trigger AccountTrigger on Account ( before insert ) {
    if (Trigger.isBefore && Trigger.isInsert) 
        AccountTriggerHandler.beforeInsert(Trigger.New);
}

public without sharing class AccountTriggerHandler {

    public static void beforeInsert (List<Account> accounts) {
        system.debug('AccountTriggerHandler.beforeInsert accounts: ' + accounts);
        Id guestId = '0056A00000zzzzzzzz'; // the Id of the Guest User in the community
        Id defaultOwnerId = '0056A00000zzzzzzzz'; // the Id of a "real" user
        for (Account account : accounts) {
            if (account.OwnerId == guestId ) {
                account.OwnerId = defaultOwnerId;
            }
        }
    }
}

I believe the real answer to your quest is to create a multi-step VisualForce wizard that creates the account/contact records in one method, and then creates the user you're looking for in the next step. When you get the second step, you should be able to add that contact to a community because you've set Account.OwnerId to an owner with a role in the Account Trigger.