Supporting Person Accounts in Your ISV Solution - Part 2

Jun 27, 2018

by Jaswinder Rattanpal

This is the final blog in the series on how to support Person Accounts in your ISV Solution. In this blog, we will talk about how to create a managed package that will support Person Accounts in Financial Services Cloud, and that can be installed in any org, whether Person Accounts have been enabled or not.

The first part of my blog series explained person account-related considerations for managed packages, and a design pattern for ensuring that a managed package can be deployed to an org, even if person accounts have not been enabled (i.e. an org that is using the default business accounts). Now lets see how that design pattern is implemented in practice.

Scenario
Consider an external system with customer birthdates. We need to make an on-demand API call to get an individual's birthdate from that system, and then save the result in the Salesforce field “Account.PersonBirthDate.” We will ensure that code is written in a way that doesn't require Person Accounts to have been enabled in advance.

For this use case, we will create a Lightning component and embed it on the Account record page's sidebar. This component will display a button if the record is a PersonAccount. When the button is pressed, and if the record is a Person Account, then information will be fetched with an API callout and the record updated.

With that, let's get started.

Development
The following Apex class will return a birthdate.
public with sharing class AccountAPI {
    @AuraEnabled
    public static boolean isFSCPersonAccountEnabled(){
        //Check if FSC Person Account is enabled
        //Requires install of FSC
        FinServ__UsePersonAccount__c  fscpa = FinServ__UsePersonAccount__c.getInstance('Use Person Account');
        return (fscpa != null)?fscpa.FinServ__Enable__c:false;
    } 
    @AuraEnabled
    public static APIWrapper fetchAccountInfo(Id AccountId){
        APIWrapper apiw = new APIWrapper();
            
        
        //Confirm that ID is correct
        AccountId = String.escapeSingleQuotes(AccountId);
        String query = 'Select Id, PersonBirthDate from Account where Id=\'' + AccountId + '\'';
        try{
            sObject a = Database.query(query);
            //Make PRETEND API call
            //FetchAccountInfo(AccountId)
            apiw.BirthDate = date.newInstance(1990, 11, 21); 
        }catch(Exception e){
            
        }
        
        return apiw;
    } 
    //Wrapper class to return Data
    public class APIWrapper{
        @AuraEnabled
        public Date BirthDate;
    }
}

Lightning component to use above Apex class, fetch BirthDate and save record.
<aura:component controller="AccountAPI" implements="flexipage:availableForRecordHome,force:hasRecordId" access="global" >
    <aura:attribute name="account" type="Object"/>
    <aura:attribute name="simpleAccount" type="Object"/>
    <aura:attribute name="accountError" type="String"/>
    <force:recordData aura:id="accountRecordLoader"
        recordId="{!v.recordId}"
        layoutType="FULL"
        targetRecord="{!v.account}"
        targetFields="{!v.simpleAccount}"
        targetError="{!v.accountError}"
        mode="EDIT"
    />
    
    <aura:attribute name="isFSCPersonAccountEnabled" type="boolean" default="false" />
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    <!-- If no loading errors then proceed -->
    <aura:if isTrue="{!empty(v.accountError)}">
        <aura:if isTrue="{!v.isFSCPersonAccountEnabled}">
            <aura:if isTrue="{!v.simpleAccount.IsPersonAccount}">
                <lightning:button label="Fetch &amp; Save Data" onclick="{!c.fetchAndSave}" class="slds-m-top_medium" />
                <aura:set attribute="else">
                    This is not a Person Account 
                </aura:set>
            </aura:if>
            <aura:set attribute="else">
                This is not a Person Account 
            </aura:set>
        </aura:if>
    </aura:if>
</aura:component>
({
    doInit: function(component, event, helper) {
        //Check if FSC Person account is enabled from custom settings
        var action = component.get("c.isFSCPersonAccountEnabled");
        action.setCallback(this, function(response){
            if(component.isValid() && response !== null && response.getState() == 'SUCCESS'){
                //Store if FSC Person Account is Enabled
                component.set("v.isFSCPersonAccountEnabled", response.getReturnValue());
            }
        });
        
        $A.enqueueAction(action);
    },
    fetchAndSave: function(component, event, helper){
        //Fetch BirthDate via API to update the record
        var action = component.get("c.fetchAccountInfo");
        var acc = component.get('v.simpleAccount');
        action.setParams({"AccountId": acc.Id});

        action.setCallback(this, function(response){
            if(component.isValid() && response !== null && response.getState() == 'SUCCESS'){
                var obj = response.getReturnValue();
                component.set("v.simpleAccount.PersonBirthdate ", obj.BirthDate);
                component.find("accountRecordLoader").saveRecord(function(saveResult) {
                    if (saveResult.state === "SUCCESS" || saveResult.state === "DRAFT") {
                       var toastEvent = $A.get("e.force:showToast");
                        toastEvent.setParams({
                            title: "Success!",
                            message: "BirthDate Updated!",
                            type: "success"
                        });
                        toastEvent.fire();
                    }
                    else if (saveResult.state === "INCOMPLETE") {
                        console.log("User is offline, device doesn't support drafts.");
                    }
                    else if (saveResult.state === "ERROR") {
                        console.log('Problem saving contact, error: ' +
                                     JSON.stringify(saveResult.error));
                    }
                    else {
                        console.log('Unknown problem, state: ' + saveResult.state +
                                    ', error: ' + JSON.stringify(saveResult.error));
                    }
                })
            }
        });
        
        $A.enqueueAction(action);
    }
})

Packaging
The next step, after development, is to package the solution. Packaging follows the same standardized process that you know and love. I have packaged the above code and installed it in orgs with and without Person Accounts enabled. There were no installation errors and the code worked without any issues.

You can test it by installing this package from https://login.salesforce.com/packaging/installPackage.apexp?p0=04t1I000003cqto

Security Review
The standard Security Review Process should be followed. Make sure to add CRUD/FLS checks (at the very least) in the code above. As it is, the package above will fail Security Review because I haven't implemented any CRUD/FLS checks when running the query.

Conclusion
With this, you should be able to developer and package your functionality to support Person and Business Account in the same package.

Resources #blog #news