AppFrontier

Documentation Chargent Configuration

Unsolicited Custom Landing with
Payment Request


Overview

This document will outline how to build a Force.com Site page that collects information from a visitor. Based on the information collected the visitor will be associated with an existing contact or a new contact will be created.

Once the information is collected, an Opportunity will be created, associated to the contact’s account, and a payment request will be generated. The visitor will then be directed to the payment request link generated to collect payment.

This code can be further customized to allow the visitor to pick products or other data attributes that can be used to create the Opportunity. The breadth of the site is only limited by your imagination and skill level.

This document will serve as a starting point on getting the landing page in front of the payment request and the code required to generate the records necessary to create and redirect to the payment request page once the prior actions are completed


Prerequisites

  1. You should install Chargent using the link found at https://www.appfrontier.com/chargent-installation.html.
  2. You must be on the Sites edition or higher
  3. Create a Force.com site to host the pages


Active Site Home Page

This will be the starting point / landing page for the visitor. It will collect some basic information and can be customized as you see fit. The key part of the page is the action parameter as it is used by the controller to control the flow of the entire process

In this example we simply collect the Visitor's Name, Email, and phone number:

<!-- The Action parameter is what will direct us to the correct page -->
<apex:page id="Example_Landing_Page" controller="Example_Landing_Controller" action="{!checkForReq}">
 
    <apex:pageMessages id="msgs"/>
    <apex:form>
 
        <apex:pageblock>
            <apex:pageBlockButtons location="bottom">
                <apex:commandButton value="Submit" action="{!checkForExistingContact}" reRender="msgs"/>
            </apex:pageBlockButtons>
            <apex:pageBlockSection columns="2" title="Your Details">
                <apex:pageBlockSectionItem>
                    <apex:outputLabel value="First Name"/>
                    <apex:inputText id="first-name" value="{!visitorDetails.firstName}"/>
                </apex:pageBlockSectionItem>
                <apex:pageBlockSectionItem>
                    <apex:outputLabel value="Last Name"/>
                    <apex:inputText id="last-name" value="{!visitorDetails.lastName}"/>
                </apex:pageBlockSectionItem>
                <apex:pageBlockSectionItem>
                    <apex:outputLabel value="Email"/>
                    <apex:inputText id="email-address" html-type="email" value="{!visitorDetails.visitorEmail}"/>
                </apex:pageBlockSectionItem>
                <apex:pageBlockSectionItem>
                    <apex:outputLabel value="Phone"/>
                    <apex:inputText id="visitor-phone" html-type="tel" value="{!visitorDetails.visitorPhone}"/>
                </apex:pageBlockSectionItem>
 
            </apex:pageBlockSection>
        </apex:pageblock>
    </apex:form>
 
</apex:page>


Payment Page

This is the page that we are directed to after the contact is created / updated and the Opportunity and payment request are created against said contact. This is the page that will actually take the payment instrument information and then pass control to either the success or fail page.

We do not use the action parameter to control the flow here. Instead, the page was loaded with the prid URL parameter and that is passed to the success or fail page. If the payment is successful the success page picks up the action again using the prid to control the flow

<apex:page id="Example_Payment" controller="Example_Landing_Controller" showHeader="false" sideBar="false">
    <apex:outputPanel id="header" layout="block">
        <!-- YOUR HEADER IMAGE HERE -->
    </apex:outputPanel>
 
 
    <apex:outputPanel id="content" layout="block">
 
        <!-- The Success will pass the pid to close the opportunity and display the message
                The fail and cancel will simply take to the appropriate pages with no controller
                -->
        <ChargentSFA:SitePaymentComponent showAddress="false" fail="/Example_Fail" success="/Example_Success?pid={!$CurrentPage.Parameters.pid}&prid={!$CurrentPage.Parameters.prid}" cancel="/Example_Cancel"/>
 
    </apex:outPutPanel>
 
 
    <apex:outputPanel id="footer" layout="block">
        <!-- This is a Footer block. Paste your APEX or HTML code here. -->
 
        <!-- You may use <style> tag for your styles. -->
        <!-- CSS style example: -->
 
        <style type="text/css">
            .footer {
                background-color: whitesmoke;
                text-align: center;
                color: black;
                font-size: 14px;
                height: 130px;
                bottom:0;
                margin-top: 1.5em;
                width: 100%;
                border-top: 1px solid lightgray;
                position: fixed;
            }
 
            .footertext {
                line-height: 50px;
            }
 
            .maillink, .maillink:hover {
                color: red;
                text-decoration: none;
            }
 
            .band {
                /*height: 80px;*/
                height: 100%;
                background-color: gray;
            }
        </style>
 
 
        <apex:outputPanel layout="block" styleClass="footer">
            <apex:outputText value="Example Company  | 870 Market Street, Suite 809 | San Francisco, CA 94102  |  USA  |  " styleClass="footertext"/>
            <apex:outputLink value="http://www.example.com" >www.example.com</apex:outputLink>
            <apex:outputText value="  |  " styleClass="footertext"/>
            <apex:outputLink value="mailto:me@example.com" styleClass="maillink">me@example.com</apex:outputLink>
            <apex:outputPanel layout="block" styleClass="band" />
        </apex:outputPanel>
 
 
    </apex:outputPanel>
 
</apex:page>

The key part of this page is the ChargentSFA:SitePaymentComponent and the properties set on that component. Notice how we grab the prid parameter and pass it on to the success page


Success Page

This is the page we will be take to when the payment is successful. It uses the action parameter to call the controller method which will see that the prid parameter is present and close the opportunity.

Note: It is important that the req parameter is not included in the URL for this page. If it is we will be taken back to the payment page which at this point has been consumed.

<!-- The action parameter will perform the Closing of the opportunity since the success URL contains the prid parameter -->
<apex:page id="Example_Success" controller="Example_Landing_Controller" showHeader="false" sideBar="false" action="{!checkForReq}">
 
 
    <apex:outputPanel id="header" layout="block">
        <!-- This is a Header block. Paste your APEX or HTML code here. -->
 
    </apex:outputPanel>
 
 
    <apex:outPutPanel layout="block">
 
        <div>
            <h1>Thanks for your payment!</h1>
            <p>We Appreciate It!</p>
        </div>
 
    </apex:outPutPanel>
 
    <apex:outputPanel id="footer" layout="block">
        <!-- This is a Footer block. Paste your APEX or HTML code here. -->
 
        <!-- You may use <style> tag for your styles. -->
        <!-- CSS style example: -->
 
        <style type="text/css">
            .footer {
                background-color: whitesmoke;
                text-align: center;
                color: black;
                font-size: 14px;
                height: 130px;
                bottom:0;
                margin-top: 1.5em;
                width: 100%;
                border-top: 1px solid lightgray;
                position: absolute;
            }
 
            .footertext {
                line-height: 50px;
            }
 
            .maillink, .maillink:hover {
                color: red;
                text-decoration: none;
            }
 
            .band {
                /*height: 80px;*/
                height: 100%;
                background-color: gray;
            }
        </style>
 
 
        <apex:outputPanel layout="block" styleClass="footer">
            <apex:outputText value="Example Company  | 870 Market Street, Suite 809 | San Francisco, CA 94102  |  USA  |  " styleClass="footertext"/>
            <apex:outputLink value="http://www.example.com" >www.example.com</apex:outputLink>
            <apex:outputText value="  |  " styleClass="footertext"/>
            <apex:outputLink value="mailto:me@example.com" styleClass="maillink">me@example.com</apex:outputLink>
            <apex:outputPanel layout="block" styleClass="band" />
        </apex:outputPanel>
 
 
    </apex:outputPanel>
 
</apex:page>


Cancel / Fail Pages

Both of these pages are the same except for the message displayed and the name of the page of course.

In the code below pick / replace text within { } and remove the { } to save each page

<apex:page id="Example_{Fail | Cancel}"> <!-- No controller needed here unless you want to do something that requires it -->
 
    <apex:outputPanel id="header" layout="block">
        <!-- This is a Header block. Paste your APEX or HTML code here. -->
 
    </apex:outputPanel>
 
    <apex:outputPanel id="content" layout="block">
        <!-- Paste your Fail Page APEX or HTML code here -->
        <apex:pageBlock >
            <apex:pageMessage summary="{YOUR MESSAGE HERE}" severity="error" strength="3" />
        </apex:pageBlock>
    </apex:outputPanel>
 
    <apex:outputPanel id="footer" layout="block">
        <!-- This is a Footer block. Paste your APEX or HTML code here. -->
 
        <!-- You may use <style> tag for your styles. -->
        <!-- CSS style example: -->
 
        <style type="text/css">
            .footer {
                background-color: whitesmoke;
                text-align: center;
                color: black;
                font-size: 14px;
                height: 130px;
                bottom:0;
                margin-top: 1.5em;
                width: 100%;
                border-top: 1px solid lightgray;
                position: absolute;
            }
 
            .footertext {
                line-height: 50px;
            }
 
            .maillink, .maillink:hover {
                color: red;
                text-decoration: none;
            }
 
            .band {
                /*height: 80px;*/
                height: 100%;
                background-color: gray;
            }
        </style>
 
 
        <apex:outputPanel layout="block" styleClass="footer">
            <apex:outputText value="Example Company  | 870 Market Street, Suite 809 | San Francisco, CA 94102  |  USA  |  " styleClass="footertext"/>
            <apex:outputLink value="http://www.example.com" >www.example.com</apex:outputLink>
            <apex:outputText value="  |  " styleClass="footertext"/>
            <apex:outputLink value="mailto:me@example.com" styleClass="maillink">me@example.com</apex:outputLink>
            <apex:outputPanel layout="block" styleClass="band" />
        </apex:outputPanel>
 
 
    </apex:outputPanel>
 
 
</apex:page>


Controller for the Force.com Site

Below is the apex controller that the Landing and success pages use. It handles all of the logic to control the flow or the site, create / update the account and contact, and create the opportunity and payment request.

The code has been commented so you can see what is being done. Most of it can be customized, however, pay attention to anything that adds or removes a parameter from a pagereference or redirects to a specific page. This would be the parts that you would not want to modify unless you know exactly what will happen.

public class Example_Landing_Controller {
 
    /**@description Message to display when the fields have not all been completed */
    @TestVisible private static FINAL String INCOMPLETEFIELDS = 'You must complete all fields to continue';
    /**@description The visitor details property used by the VF page */
    public visitorDetails visitorDetails { get; set; }
 
    private System.savePoint sp;  //Savepoint when creating records
 
    /***************************************************
    * @description Constructor - Instantiates a new instance
    * of the visitorDetails
    ****************************************************/
    public Example_Landing_Controller() {
        visitorDetails = new visitorDetails();
    }
 
    /***************************************************
    * @description Action on page load, checks to see if
    * we should direct to the payment request page to
    * collect payment
    ****************************************************/
    public pagereference checkForReq() {
 
        //This parameter indicates we should got the Payment Request Page
        String req = apexPages.currentPage().getParameters().get('req');
        //Added when we directed to the payment page so the succcess page can know what the Id of the Payment Request is
        String prid = apexPages.currentPage().getParameters().get('prid');
 
        if (req != null) { //If we have the req parameter we need to goto the Make Payment Page
            pageReference pr = new PageReference('/Example_Payment?req=' + req);
            pr.getParameters().put('prid',prid);
            pr.setRedirect(true);
            return pr;
        } else if (prid != null) { //If we have this then a payment was attempted
            closeOpportunity(prid);
        }
        return null; //Neither parameter exists so this is the initial landing
    }
 
    /***************************************************
    * @description If the prid is included in the URL
    * parameters then we need to close the opportunity
    * This is passed by the success page
    ****************************************************/
 
    public void closeOpportunity(String prid) {
        System.debug(prid);
        ChargentSFA__Payment_Request__c[] pr = [
                Select ChargentSFA__Opportunity__c,
                        ChargentSFA__Status__c
                From ChargentSFA__Payment_Request__c
                Where ID = :prid
        ];
        System.debug(pr);
        try {
            if (pr.size() == 1 && pr[0].ChargentSFA__Status__c == 'Paid') {
                Opportunity o = New Opportunity(
                        id = pr[0].ChargentSFA__Opportunity__c,
                        StageName = 'Closed Won'
                );
                System.debug(o);
                update o;
            }
        } catch (Exception e) {
            //Simply debug the error as if we are here the customer was charged so we do not want to prevent anything
            system.debug(logginglevel.error, e.getMessage());
        }
    }
 
    /***************************************************
    * @description Uses the email address to see if a contact
    * already exists. If not we create the Account and
    * Contact. Once we have the contact we execute the methodd
    * to create the Opportunity and payment request
    ****************************************************/
    public PageReference checkForExistingContact() {
        //Validate that the entries are correct
        if (!visitorDetails.validate()) { //If the validation returns false
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL, INCOMPLETEFIELDS));
            return null; //return to page
        }
 
        sp = database.setsavePoint(); //Set the savepoint to rollback to in case there are errors
 
        //We will be using the email as the unique value here
        Contact[] con = [
                Select Id,
                        AccountId,
                        FirstName,
                        LastName,
                        Phone,
                        Email
                From Contact
                Where Email = :visitorDetails.visitorEmail
        ];
 
        if (con.isEmpty()) { //Does not exist
            Account a = New Account(
                    Name = visitorDetails.firstName + ' ' + visitorDetails.lastName
            );
            insert a;
            Contact c = New Contact(
                    FirstName = visitorDetails.firstName,
                    LastName = visitorDetails.lastName,
                    Email = visitorDetails.visitorEmail,
                    Phone = visitorDetails.visitorPhone,
                    AccountId = a.Id
            );
            insert c;
            con.add(c);
        }
 
        return createOpportunityAndPaymentRequest(con[0]);
 
    }
 
 
    /**@description The product to assign to the opportunity */
    public PriceBookEntry productToUse { //You can do whatever you need to including having another for for the visitor to pick the product
 
        get {
            if (productToUse == null)
                productToUse = [
                        Select ID,
                                Pricebook2Id,
                                Product2.Name,
                                UnitPrice
                        From Pricebookentry
                        Where Pricebook2.IsStandard = true
                        AND Product2.ProductCode = 'RRT-001' //Use whatever product code works for you
                ];
 
            return productToUse;
        }
        set;
 
    }
 
    /***************************************************
    * @description Create the Opportunity and
    * Payment Request record the redirect to the payment
    * page
    ****************************************************/
    public pageReference createOpportunityAndPaymentRequest(Contact visitorContact) {
        ChargentSFA__Payment_Request__c pr;
 
        try {
            Opportunity opp = New Opportunity(
                    StageName = 'Proposal/Price Quote', //Stage to set the new opportunity to
                    CloseDate = date.today(), //Close Date to set
                    ChargentSFA__Payment_Method__c = 'Credit Card', //Default payment method to Credit Card
                    AccountID = visitorContact.AccountId,
                    Name = 'Example Opportunity - ' + visitorContact.FirstName + ' ' + visitorContact.LastName,
                    ChargentSFA__Billing_First__c = visitorContact.FirstName,
                    ChargentSFA__Billing_Last__c = visitorContact.LastName
            );
 
            //Get a gateway if you have more than one - Uncomment to use
            //ID gwID = [Select ID From ChargentBase__Gateway__c Where Name = {GATEWAY NAME} ].id; //dereferencing so make sure it exists
            //opp.ChargentSFA__Gateway__c = gwID; //Set the Id of the gateway if more that one exists
            insert opp;
 
            //Create the payment request. The link will automatically be created
            // and set to the value specified in the chargent settings
            pr = New ChargentSFA__Payment_Request__c(
                    ChargentSFA__Include_Parent_ID__c = true,
                    ChargentSFA__Opportunity__c = opp.id
            );
            insert pr;
 
            //If you are not using Contact Roles you can comment this out
            OpportunityContactRole cr = New OpportunityContactRole(
                    OpportunityID = opp.id,
                    ContactID = visitorContact.id,
                    isPrimary = true,
                    Role = 'Other'
            );
            insert cr;
 
            //Create hte line item with the PriceBookEntry specified in the predetermined product
            OpportunityLineItem oli = New OpportunityLineItem(
                    PricebookentryID = productToUse.id,
                    quantity = 1,
                    OpportunityID = opp.id,
                    UnitPrice = productToUse.UnitPrice
            );
            insert oli;
 
        } catch (DMLException e) {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL, e.getDMLMessage(0)));
            database.rollback(sp); //Rollback the records
            return null;
        }
 
        return gotoPayment(pr.Id);
    }
 
    /***************************************************
    * @description Takes to the Payment Request Payment page
    * adding the prid parameter so it gets passed back on success
    ****************************************************/
    private PageReference gotoPayment(Id prId) {
 
        //Get the payment request we created eairlier, it should contain the link
        //as Chargent creates it when the record is inserted
        ChargentSFA__Payment_Request__c[] pr = [
                Select ChargentSFA__Pay_Link__c
                From ChargentSFA__Payment_Request__c
                Where Id = :prId
        ];
 
        if (pr.isEmpty()) {
            system.debug(logginglevel.error, [Select ID From ChargentSFA__Payment_Request__c]);
            throw new Example_Landing_Exception('Unable to find the Payment Request Link, please contact us.');
        }
 
        PageReference pg = New Pagereference(pr[0].ChargentSFA__Pay_Link__c);//+ '&pid=' + pr[0].id);
        pg.getParameters().put('prid', pr[0].id);
        pg.setRedirect(true);
        return pg;
    }
 
    /**
     * @group Apex Object
     * @description Visitor Details Object
     */
    public class visitorDetails {
        public string firstName { get; set; }
        public string lastName { get; set; }
        public string visitorEmail { get; set; }
        public string visitorPhone { get; set; }
 
        /***************************************************
        * @description Validates the required values have
        * been entered
        ****************************************************/
        public boolean validate() {
            if (String.isBlank(firstName) ||
                    String.isBlank(lastName) ||
                    String.isBlank(visitorEmail) ||
                    String.isBlank(visitorPhone)
                    ) {
                return false;
            } else {
                return true;
            }
        }
 
    }
 
    public class Example_Landing_Exception extends exception {
    }
 
}


Creating Force.com Site

  1. Setup -> Develop -> Sites

    Custom Payment Force.com


  2. Click New (You may need to setup your domain name first)

    Custom Payment Force.com New


  3. Complete the information according to your requirements
    1. Default Web Address - If this is the only purpose for the site no need to add a suffix
    2. Active Site Home Page - Put the page you created here
  4. Click “Save”
  5. Under “Site Visualforce Pages” click edit and add the pages you created earlier
    1. Payment
    2. Success Page
    3. Fail Page
    4. Cancel Page
  6. Assign the Permission sets and create the user croup and sharing settings according to the payment request documentation
  7. Click on the “Activate” link next to the site you just created
  8. In the sites listing, copy the Site URL
  9. Click on the Chargent Settings Tab
    1. Click on the advanced settings tab
    2. Paste the copied URL from step 5 in the Salesforce Sites Page (URL)
    3. Click Save

Note: This document and site setup assumes that you are not using payment request to send links via email under the typical configuration. If you need to have this unsolicited page setup alongside the default payment request configuration there are additional steps that need to be take


Flow Diagram



Custom Payment Flow Diagram