CC2018 Technical Exercises
CC2018 Technical Exercises
1.1. Audience . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2.1.1. Exercise: Using the CC Admin Data Loader to add storefront info. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3. User Interface. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.2. UI Recommendations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.5.1.2. Removing the Product Search widget from the Home Page (HP) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.5.1.3. Repositioning the Cart widget and moving it to the top of the left side navigation via a 19
Subscriber Template
3.6. UI Custom Pages (Subscriber Pages) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4. Backend. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.2.1. Sizing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.2.2. Version . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.10.1. Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
4.13. Payment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
4.13.1. Development . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
5. Appendix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
CloudCraze 4.7 Training January 2018
1
Chapter 1. About The Technical Exercises
• The purpose of this documentation is to provide an overview of the extension capabilities and the
technical details needed to implement them.
• CloudCraze was designed in a way that allows easy extensibility of its default features.
• The exercises presented are a reflection of the case study: DefaultStore, a hypothetical store.
• Throughout this training, the participants will experience working on both the Front-End and Back-End
code components.
The code artifacts used in the exercises are also included in the Training Orgs.
1.1. Audience
• The recommended audience for the training is:
Namespace
A naming convention used to help uniquely identify something within an application (e.g. "ccrz" is the
CloudCraze namespace used in the package)
Subscriber
Custom code that is not part of any managed package (e.g. your own Apex classes, VisualForce pages etc.)
Service Override
Subscriber code that is used to modify (or replace) the SOQL and DML statements run within the
CloudCraze Managed Package.
Extension
Subscriber code that is used to extend the functionality of the CloudCraze Managed Package. E.g. Global
class MyCustom_hk_Invoice extends ccrz.cc_hk_Invoice ...
Configuration Settings
These are used within CloudCraze to control the behavior of CloudCraze on a per-StoreFront/per-Page
2
basis.
Page Key
Group of settings that are mapped to a specific page or pages (e.g. "HP" for "HomePage", "PLP" for
"Product List Page", "PDP" for "Product Detail Page", etc.)
• Managed Package assets can not be changed on the org on which they are installed.
F.A.Q.
What if I need an update or a fix for the CloudCraze Managed Package ?
CloudCraze announces patches and updates to its platform periodically (we have major releases
almost every quarter!). New versions of the managed package replace and add to the assets already
installed. In addition, we also provide Concierge Services and Service Desk as part of our offering, that
can be used for support.
Where can I get the list of Page Configuration Settings and Page Keys?
Our wiki here contains the latest version of our page keys.
In the sample diagram below, DefaultStore storefront extended CloudCraze and its Market Templates'
functionality, as well as direct SalesForce customizations (e.g. additional SF objects and Scheduled Jobs) and
other managed packages.
3
A Typical CloudCraze E-Commerce implementation will be built on top of one or more SalesForce
communities, and may include additional functionality via Market Templates, custom code, and 3rd party
integrations. Other than Salesforce itself, CloudCraze does not depend on external databases or servers.
4
Back End: changes to the logic for products, orders, payments, carts, categories, etc.
• SalesForce: SalesForce is the Web Server, Database, Identity Manager, Integration Server, etc.
F.A.Q.
Why Backbone?
We wanted to have a good and mature js framework that did not tie the front-end to any particular
design paradigm (e.g. SPAs, build servers) so it will be less difficult to extend or replace if needed.
Backbone provided the flexibility to do so.
5
Chapter 2. Setting up a new Cloudcraze Org
• Besides the configuration settings, any storefront will also require information about their products, price
lists, promotions, etc. etc.
• There are multiple ways of uploading this data to a CloudCraze Org. One of them is to use is the CC
Admin’s Data Loader.
Real-world data uploads should be done with traditional tools like the SalesForce’s
DataLoader, SFDX or Third Party services.
2.1.1. Exercise: Using the CC Admin Data Loader to add storefront info
Use Case
1. Reset the Cloudcraze Org (remove the configuration settings and any existing data)
2. Load sample CloudCraze configuration settings
3. Load sample CloudCraze storefront data
Why is it important?
The default sample data provided by the OOTB CloudCraze is very useful for quick testing and
troubleshooting, and it helps understand the different data objects used in the StoreFronts.
Pre-requisites
none
Cloudcraze Objects
none
6
2.1.1.1. Working with the CC Admin’s Data Loader
7
c. Update the account (e.g. PortalAccount) with the PortalAccount Account Group.
d. Associate a contact (e.g Jon Amos) to the account above (e.g. PortalAccount).
e. Enable the customer user for the contact and set its Profile to CloudCraze Customer Community
User.
f. Change the contact’s email address to yours.
g. Verify your changes by logging into the Community as a user.
F.A.Q.
What are the "CC_CustomSettings" and "CC_Data2" files?
They are special static resource files that contains a collection of XML and JSON files with StoreFront
settings and data.
In addition to the XML files, CC_CustomSettings also include some json files (e.g. configuration-
latest.json), that sets all the Configuration Settings (Global and per Store).
Remember to create and activate a new Configuration Cache and refresh the Category
Tree Cache, Menu Cache, Product Specs and Page Label Cache indexes after uploading
data to your storefronts.
8
Chapter 3. User Interface
• The first part of this course will cover the User Interface.
• Requisites are understanding HTML, CSS, JavaScript as well as basic UI/UX concepts such as themes and
responsive design.
1. cc_tmpl_OneColRD
2. cc_tmpl_TwoColRD
3. cc_tmpl_ThreeColRD
Page Include
An "add-on" that can be placed in your storefront page or pages to extend or replace existing functionality.
Subscriber Page
A fully customizable VisualForce page, called from the "CCPage" page.
Subscriber Template
Custom layouts for the pages that are built by extending the default cc_tmpl_SubscriberTemplate
template.
Even though two slots exist (Header Include Begin and Header Include End), Page Includes
should not be used to modify or inject additional content into the HTML <head>.
Page Include slots leverage the <apex:include> mechanism in VisualForce which wraps
the Subscriber VF Page in <span> tags. Most modern browsers will not support this and will
instead terminate the </head> in the DOM at the first sight of a <span>, effectively
pushing any content from the Page Include into the <body> of the DOM.
9
• The list of CloudCraze ready to use Subscriber Templates can be found here
• When using custom Subscriber Templates, remember to setup two configuration
settings: the first one to activate cc_tmpl_SubscriberTemplate as your page layout and
another one for your custom template itself.
• For Subscriber Pages, a page key needs to be registered in advance in order for it to be
called from within CloudCraze. Check out UI Subscriber Pages for more info.
• There is also a base template called Storefront Template (cc_tmpl_storefront). This one
is read-only and contains common elements for all pages (header, footer, page include
place holders, etc.). It can’t be modified directly.
3.2. UI Recommendations
In order to provide maximum upgrade coverage and support, CloudCraze recommends trying to make your
customizations via Configuration first before considering extensions via subscriber code.
Configuration
This includes branding or skinning made using configuration settings and changes to the Theme files (CSS,
js, images, etc.)
UI Overview:
• Every CloudCraze Page (e.g. Home Page) is linked to a CC Template (e.g. Two-column template) which
10
defines the layout for its contents.
• CloudCraze pages also includes one or more VisualForce Components (e.g. Mini-Cart) which are modular
and can be shared across multiple pages and storefronts as needed.
• In addition, CloudCraze provides Page Includes (e.g. a Component Extension) which can be used to extend
or replace existing functionality within a page or pages in a StoreFront.
• They share a common Theme (e.g. DefaultStore Theme) that defines the overall look and feel of the
Storefront pages.
• CC_MyCompany_Theme
• Cloudcraze Themes are enabled via the CC Admin link in the Salesforce org.
• These themes are SalesForce static resources, and they need to have "Theme" as part
of its name to be picked up by CCAdmin (e.g. CC_MyCompany_Theme or
MyCompany_Theme_ResponsiveDesign)
• Static resources allow you to upload content that you can reference in your pages,
including archives (such as .zip and .jar files), images, style sheets, JavaScript, and other
files.
11
• When creating your own theme, sometimes it is easier to use an existing theme as a
starting point (e.g. CC_Theme_CloudburstRD) and then customize the theme based
on your business needs.
F.A.Q.
What if I want to include libraries from a CDN or additional files?
CloudCraze provides several mechanisms to do so, a few of which are:
Use Case
1. Upload a custom theme for the DefaultStore storefront
2. Make the theme compatible with Bootstrap 3
3. Modify the navbar background color across all your pages
You can skip ahead to the next use case if you’ve already enabled and tested your theme in
a previous chapter. Also, while you don’t need deep SalesForce or CSS knowledge, ideally
you should be familiar with the concept of uploading/downloading Static Resource files.
Name
ccrz__CC_Theme_CloudBurstRD
12
Description
DefaultStore Theme
File
click on the "Browse" button and locate and select the ccrz__CC_Theme_CloudBurstRD.zip file
downloaded in step 1.
Cache Control
Public
F.A.Q.
What is the configuration cache?
The configuration cache is a collection of indexed configuration settings (plus the CC theme settings)
that is used by CloudCraze to keep the thousands of storefront settings rapidly accessible across the
sites.
• When creating a new configuration cache, it is good practice to deactivate all the older
ones.
• Also, it is recommended to only keep a limited number of configuration caches and
remove the rest to prevent hitting any SalesForce limits.
13
Keep in mind that while most configuration settings can be set at the storefront and page
level, the configuration cache is global.
Module
User Interface
Configuration
Template Version
Page
All
Attribute Value
boot3
f. Click on Create
3. Create and Activate a new Configuration Cache:
a. On the right side dropdown, click on the Global Settings link.
b. On the left side bar, select Configuration Cache Management.
c. Click on Build New. After a few seconds, click on Refresh List. A new configuration cache should have
been built. Click on "Activate" to enable it.
4. Test your theme:
a. In your SalesForce Org, click on Contacts from the main tab bar.
b. Search for Jon Amos. Click on the name to open the contact info.
14
c. On the Contact Detail Section, click on Manage External User, and from the dropdown, select "Log in
to Community as User".
5. Your new theme should take effect. Notice the new look and feel.
CloudCraze UI v003 is compatible with Bootstrap 3 themes. This makes it a trivial exercise
to apply a theme downloaded from popular theme repositories like Boostrap Official
Themes or Bootswatch.
.navbar-inverse {
background-color: #004488;
border-color: #004488;
}
Ensure to create your new file by zipping the contents in the parent folder. E.g
Select css, css3, en_US et.al and then compress these folders into a zip file.
Name
CC_Theme_DefaultStore_New
Description
DefaultStore Theme
File
click on the "Browse" button, then locate and select the CC_Theme_DefaultStore_New.zip file you
just created.
15
Cache Control
Public
• Remember that there are two CSS folders in a CloudCraze Theme: css and css3. "css" is
for Bootstrap 2 overrides, while "css3" is for Bootstrap 3 overrides. Keep this in mind
when writing your themes.
• Since CC Themes are dependent on a certain file and folder structure, make sure that
when zipping the Theme resource file you are not creating an extra parent folder within
it.
16
• Layouts also include placeholders for CloudCraze widgets. The widgets can be enabled or disabled via
configuration settings on their respective pages.
• It is also possible to create custom layouts (subscriber templates)
F.A.Q.
What are common scenarios for creating subscriber templates?
Subscriber templates are useful when you want to modify the overall layout of your storefront across
multiple pages. E.g. when you want to add a custom widget (or move an existing one) on numerous
pages
Objectives
1. Configure the Home Page (HP) of DefaultStore to use 3 columns instead of 2.
2. Remove the Product Search widget from the Home Page (HP).
3. Move the My Cart widget and put it on top of the left side Navigation via a custom Subscriber
Template
1. Modify the Configuration Setting for the Home Page (HP) Template:
a. In your Salesforce Org, click CC Admin from the main tab.
b. On the right side dropdown select the DefaultStore Storefront.
c. On the left side bar, near the top, under "Settings" section, click on Configuration Settings.
d. To locate what needs to be changed faster, use the filters at the top:
Module
Template
Page
Home
17
b. On the left side bar, select Configuration Cache Management.
c. Click on Build New. After a few seconds, click on Refresh List. A new configuration cache should have
been built. Click on "Activate" to enable it.
3. Test your changes:
a. In your SalesForce Org, click on Contacts from the main tab bar.
b. Search for Jon Amos. Click on the name to open the contact info.
c. On the Contact Detail Section, click on Manage External User, and from the dropdown, select "Log in
to Community as User".
d. Verify that the home page now uses a 3-column layout as opposed to 2.
3.5.1.2. Removing the Product Search widget from the Home Page (HP)
1. Modify the Configuration Setting for the Home Page (HP) Template:
Module
Search Box
Configuration
Enabled
Page
Home (HP)
Value
FALSE
When filling out the value for the Page input field, you should keep the following in
mind. Start out by typing the name of the page of interest. E.g Type Home and then
select the appropriate value from the autocompleted suggestions in the input field.
18
b. Search for Jon Amos. Click on the name to open the contact info.
c. In the Contact Detail Section, select Manage External User, and from the dropdown, select "Log in to
Community as User".
d. Verify that the Product Search widget is no longer visible on the home page.
3.5.1.3. Repositioning the Cart widget and moving it to the top of the left side navigation via a Subscriber
Template
19
<apex:page id="cc_tmpl_Training" docType="html-5.0"
sidebar="false" showHeader="false"
standardStylesheets="false" applyHtmlTag="false"> ①
<apex:define name="htmlbody"> ②
<div class="container cc_main_container cc_tmpl_Training"> ③
<div class="row cc_main_row">
<div class="col-md-3 cc_left_col">
<apex:insert name="WidgetBoxL" />
<apex:insert name="MiniCartBox" /> ④
<div id="categories-left-nav"></div>
<div class="filterContainer"></div>
<div class="promotion-box-LeftNav"></div>
<apex:insert name="LeftNavX" />
</div>
<div class="col-md-6 cc_main_content_col">
<div class="row cc_main_content_row">
<apex:insert name="WidgetBoxC" />
<apex:insert name="Banner" />
<div class="promotion-box-Banner"></div>
<div class="effwig"></div>
<apex:insert name="ProductSpotlight" />
<apex:insert name="body" />
<apex:insert name="PromosCenter" />
<apex:insert name="FeaturedProducts" />
<apex:insert name="GuideProducts" />
<apex:insert name="UpsellProducts" />
<apex:insert name="ProductReviews" />
<apex:insert name="CenterX" />
</div>
</div>
<div class="col-md-3 cc_right_col right_column">
<div class="effright"></div>
<div class="search-box-RightNav" />
<apex:insert name="WidgetBoxR" />
<div class="widgetSection"></div>
<apex:insert name="MiniQuickOrderBox" />
<div class="promotion-box-RightNav"></div>
<apex:insert name="RightNavX" />
</div>
</div>
</div>
</apex:define>
</apex:page>
① These apex:page attributes are required to prevent SalesForce from applying its default standard
HTML styling.
c. Save the page (On the Developer Console menu, click: File → Save )
5. Modify the Configuration Setting for the Home Page (HP) Template to use the custom Subscriber
Template:
20
a. In your Salesforce Org, click CC Admin from the main tab bar.
b. On the right side dropdown select the DefaultStore Storefront.
c. Select Configuration Settings from the left side bar
d. To locate what needs to be changed faster, use the filters at the top:
Module
Template
Page
Home
e. Click on the Value link to change it to use the Subscriber Template container. Change it from:
cc_tmpl_ThreeColRD to: cc_tmpl_SubscriberTemplate.
f. Next, click on New to assign the newly created template to the Home page. Enter the following
information:
Module
Template
Configuration
page
Page
Home
Attribute Value
c__cc_tmpl_Training
If the page option isn’t available, create a metadata for it as described in the
steps below, but ensure you revisit this step afterwards.
Name
Page
API Name
page
Decription
21
Subscriber template Visualforce page.
Revisit the previous step if you created the metadata as described above.
To expedite the creation of a subscriber template, you could use the Developer Console to
view the contents of any of the default Cloudcraze templates. You can then make your edits
by adding or removing sections from it as needed.
When adding subscriber pages, remember that two configuration settings are needed (it is
quite easy to forget one of them):
22
Figure 1. Default CloudCraze Storefront Site Map (excluding administration screens, e.g. Login and Registration pages)
Most of the functionality on these pages can be extended through configuration settings and page includes.
In cases where the default page offering doesn’t meet the business needs, Cloudcraze provides a mechanism
to create a fully customizable page called Subscriber Page.
1. CC Subscriber Page Name: This is used to identify the subscriber page in the SalesForce Org.
2. Page Include: This refers to the custom VisualForce page that contains the markup for the page. It is
always prefixed with c__ to indicate it is custom.
3. Page Key: This is used in conjunction with ccrz__CCPage to render the contents of the Subscriber page in
the browser.
4. Storefront: This is the name of the storefront where the subscriber page will be active.
https://<STOREFRONT_URL>/ccrz__CCPage?pagekey=<Page_Key>
F.A.Q.
What are some common scenarios for creating subscriber pages?
Subscriber pages are used for larger bodies of content or functionalities that are not provided by
default in CloudCraze: CMS pages (e.g. About Us, Contact Us, etc.). Another possible candidate for
this would be E-commerce pages (e.g. Promotions and Deals) and perhaps a UI overhaul (e.g. Single
Page apps).
For more info on the subscriber page object, including additional properties that can be
added, see Subscriber Pages Wiki
23
3.6.1. Exercise: Creating a Custom Page
Use Case
1. DefaultStore would like to have an additional page to provide customers with contact information.
2. The page should share the same look and feel as the rest of the storefront site.
① These apex:page attributes are required to prevent SalesForce from applying its default standard
HTML styling.
c. Save the page (On the Developer Console menu, click: File → Save )
24
4. Assigning a Subscriber Page to the Visual Force page:
a. In your Salesforce Org, click on CC Subscriber Page from the main tab bar
b. Click the New button.
c. In the New CC Subscriber Page screen, enter the following:
Page Include
c__ccTraining_SP_ContactUs
Page Key
ContactUs
Storefront
DefaultStore
ccrz.cc_util_Reflection.upsertPageUIKey('cc_ContactUs','ContactUs','Contac
t Us');
DisplayName
Contact Us
Store ID
DefaultStore
Link Type
URL
URL
25
/DefaultStore/ccrz__CCPage?pagekey=ContactUs
Bonus Points:
1. Improve the look and feel by manipulating the HTML and the Theme’s CSS.
2. Make the content configurable, by using either CC Content or CC Page Labels.
3. Add a controller for custom logic.
Page Includes leverages the <apex:include> mechanism in VisualForce which wraps the
Subscriber VF Page in <span> tags.
26
Figure 2. Page Include Slots
• The most commonly used Page Include slots are the Body Include Begin (BIB)
and Body Include End (BIE)
27
F.A.Q.
What are common scenarios for using page includes?
Page includes are used to extend or override the look and feel of an existing CloudCraze page or to
add new functionality.
Use Case
1. DefaultStore would like to have an additional widget on the home page that displays the top 5 news
from reddit.
2. The info should be retrieved by invoking their open and free web service here:
https://www.reddit.com/r/news/top/.json?limit=5
28
<apex:page id="ccTraining_PI_TopNews" docType="html-5.0"
sidebar="false" showHeader="false"
standardStylesheets="false" applyHtmlTag="false">
</apex:page>
29
① anchorClass is the parent element where we will like our rendered handlebar template to
appear.
② #each is a Handlebars helper function which iterates across the list of items named children
③ Register a custom backbone view (TopNews__BBView)
④ Associate the backbone view with the Handlebars template
⑤ Variable for the selector where the rendered Handlebars template will be mounted.
⑥ renderDesktop and renderPhone are Cloudcraze helper functions that are executed when the
Backbone view is rendered. depending on the type of design (responsive/boot3 uses
renderDesktop, while adaptive/classic uses both depending on the screen width).
⑦ Sets the selector where the rendered Handlebars template will be mounted.
⑧ REST web service endpoint to call (GET)
⑨ Sets the Handlebars contents to use the data retrieved from the web service call
⑩ Initializes and render the TopNews_BBView Backbone view.
c. Save your changes (On the Developer Console menu, click: File → Save )
4. Configure the Page Include to be used on the Home Page:
When filling out the value for the Page input field referenced below, you should keep
the following in mind. Start out by typing the name of the page of interest. E.g Type
Home and then select the appropriate value from the autocompleted suggestions in the
input field.
Module
Body Includes End
Configuration
Enabled
Page
Home (HP)
Value
TRUE
a. Next, click on New button to create a new Configuration Setting and enter the following to set the
page include (don’t forget the c__ prefix before the name of your VF page):
30
Module
Body Includes End
Configuration
Page Include Name
Page
Home (HP)
Value
c__ccTraining_PI_TopNews
Bonus Points:
1. Improve the look and feel by manipulating the HTML and the Theme’s CSS.
2. Make the content configurable, by using either CC Content or CC Page Labels.
3. Move the web service call logic into a custom Backbone Model.
4. Call other webservices and webservices verbs (e.g. POST, PUT, etc.).
5. Add a controller for custom logic.
6. Use remote actions.
31
Chapter 4. Backend
4.1. BACK-END OVERVIEW
There are primarily two ways of extending the back-end functionality in CloudCraze.
• ccApi
• ccApiAccount
32
• ccApiAddress
• ccApiAddressBook
• ccApiCart
• ccApiCategory
• ccApiConfig
• ccApiContact
• ccApiCoupon
• ccApil18N
• ccApiInvoice
• ccApiOrder
• ccApiPrivateCache
• ccApiProduct
• ccApiPromotion
• ccApiPublicCache
• ccApiRelatedProduct
• ccApiSeller
• ccApiSpec
• ccApiStorefront
• ccApiSubscPage
• ccApiSubscription
• ccApiTerms
• ccApiPriceList
• ccApiStoredPayment
• ccApiSubProdTerm
• ccApiTransactionPayment
An up-to-date list of the all the Global APIs can be found here on the wiki.
4.2.1. Sizing
Field Sizing
• Small – ccAPI.SZ_S
• Medium – ccAPI.SZ_M
• Large – ccAPI.SZ_L (Default)
• X-Large – ccAPI.SZ_XL
33
Related Query Sizing
ccAPI.SZ_REL
Return Format
API calls, by default, return maps of string objects (Map<String, Object>) where the object data is hashed
under a return key. The object data, by default, is returned as a list of Map<String, Object> where the fields
names form the key. Internally, sometimes this behavior is undesirable and the caller would like to work
directly with sObject types. To that end, the APIs can return a list of sObjects. Since it is not always
possible to stitch the data back together to create a consolidated sObject, the data is returned in separate
strings, defined by each API. ccAPI.SZ_SKIPTRZ is the Boolean flag that allow data to come back as an
sObject.
Refetch Data
Some API’s support an implicit refetch of the data to save the caller to do a fetch directly after, say, a
create. APIs will mark which services and calls support the refetch mechanism. ccAPI.SZ_REFETCH is
the Boolean flag that signifies the caller wants to use the implicit refetch mechanism.
4.2.2. Version
The Global APIs have various versions. CC 4.7 is currently on version 6 of the APIs.
Invoking an API with its current version or perhaps a specific version can be done as shown below:
ccrz.ccApi.API_VERSION ⇒ ccAPI.CURRENTVERSION
ccrz.ccApi.API_VERSION ⇒ 4
The recommended approach is to use a Global API instead of a SOQL query when retrieving records for
custom objects that are part of the managed package.
34
public with sharing class ccTrainingAPIExample
{
public ccTrainingAPIExample()
{
}
try
{
Map<String, Object> outputData = ccrz.ccAPIProduct.fetch(inputData);
if (outputData.get(ccrz.ccAPIProduct.PRODUCTLIST) != null)
{
// The cast to List<Map<String, Object>> is necessary...
List<Map<String, Object>> outputProductList = (List<Map<String, Object>>)
outputData.get(ccrz.ccAPIProduct.PRODUCTLIST);
getFieldsMap
The select clause in the query
getSubQueryMap
Configuring related queries
getDirectQueryMap
Configuring direct queries
getFilterMap
Configuring the where clause in the query
35
getOrderByMap
Set order for the query
buildQuery
pulls all methods together
Use Case
1. Extend the Data Service Provider class for the CC Product Custom object
2. Override the getFieldsMap method and fetch an additional attribute for the product object
a. The attribute will be added to the to the OBJECTFIELDS constant in CloudCraze
3. Configure CloudCraze to use the new data service provider
You must create the Custom Field of type Text on Product with the API name Brand__c.
See below for further instructions.
36
2. Override the getFieldsMap method.
3. Inside the getFieldsMap override, retreive the base fields this class returns by default
4. While still inside this method, concatenate Brand__c to the String that contains the default base
fields
5. Return the newly created String
objectFields += ',Brand__c' ; ④
37
<apex:page docType="html-5.0" sidebar="false" showHeader="false"
standardStylesheets="false" applyHtmlTag="false">
<script>
CCRZ.uiProperties.productDetailView.desktop.tmpl = 'CCTrainingProductDetail-
Desktop'; ①
</script>
38
<img class="alternate cc_alternate img-responsive
thumbnail" src="{{productImage this}}" data-id="{{this.uri}}"/>
</div>
{{/each}}
{{#each this.mediaWrappers.[Alternate Images]}}
<div class="col-xs-3">
<img class="alternate cc_alternate img-responsive
thumbnail" src="{{productImage this}}" data-id="{{this.uri}}"/>
</div>
{{/each}}
</div>
{{/if}}
</div>
{{else}}
{{#if this.mediaWrappers.[SVG Interactive Diagram]}}
{{else}}
<div class="cc_product_detail_photo_container" id="photoContainer">
<div class="row">
<div class="col-md-12">
<div class="cc_main_prod_image img-
responsive">{{displayImage this.product.mediaWrapper 'mainProdImage prodDetail'}}</div>
</div>
</div>
</div>
{{/if}}
{{/if}}
</div>
</div>
<div class="col-md-7">
<div class="product_detail_item wishFinder cc_wish_finder" data-
sku="{{this.product.prodBean.sku}}">
<h4 class="product_title
cc_product_title">{{this.product.prodBean.name}}</h4>
{{#ifDisplay 'PD.DsplSku'}}
<div class="sku cc_sku">
<span class="cc_label">{{pageLabelMap
'ProductDetailsInc_SKU'}}</span>
<span class="value cc_value">{{this.product.prodBean.sku}}</span>
</div>
{{/ifDisplay}}
{{#ifDisplay 'PD.DsplUOM'}}
<div class="uom cc_uom">
<span class="cc_label">{{pageLabelMap
'ProductDetails_UnitOfMeasure'}}</span>
<span class="value cc_value">{{pageLabelPrefixMap 'UOM_'
this.product.prodBean.UnitOfMeasure}}</span>
</div>
{{/ifDisplay}}
{{#ifDisplay 'PD.DsplAvlb'}}
<div class="inventory cc_inventory">
<span class="cc_label">{{pageLabelMap
'ProductDetails_Availability'}}</span>
{{#ifStoreSetting 'InventoryCheckFlag__c'}}
<span class="value cc_value">{{{this.product.inventory}}}</span>
{{else}}
<span class="value cc_value">{{{pageLabelMap
this.product.availMsg}}}</span>
{{/ifStoreSetting}}
39
</div>
{{/ifDisplay}}
<!-- TODO Exercise-->
<div class="brand">
<span class="cc_label">{{pageLabelMap
'CCTrainingProductDetails_Brand'}}</span>
③
</div>
{{#ifDisplay 'PR.Enabled'}}
<div id="avgRating" class="rateit cc_rateit" data-rateit-
value="{{this.product.avgRating}}" data-rateit-ispreset="true" data-rateit-readonly="true">
<span class="cc_label">{{pageLabelMap 'NumberofReviews'
this.numberOfReviews }}</span>
</div>
{{/ifDisplay}}
{{#ifDisplay 'PD.DsplSDesc'}}
<div class="shortDesc cc_short_desc">
<p class="pblock
cc_pblock">{{{this.product.prodBean.shortDesc}}}</p>
</div>
{{/ifDisplay}}
<hr>
<div class="row">
<div class="col-md-12">
{{#if this.product.showPricing}}
{{#ifDisplay 'PD.DsplPrc'}}
{{#if this.product.price}}
<div class="price_block cc_price_block">
{{#ifDisplay 'PD.DsplListPrc'}}
{{#if this.product.basePrice}}
<p class="baseprice cc_baseprice">
<span class="cc_label">{{pageLabelMap
'ProductDetails_ListPrice'}}</span>
<span class="value cc_value">{{price
this.product.basePrice}}</span>
</p>
{{/if}}
{{/ifDisplay}}
<p class="price cc_price">
<span class="cc_label">{{pageLabelMap
'Price'}}</span>
{{#if this.highAttrPrice}}
{{price
this.lowAttrPrice}} - {{price this.highAttrPrice}}
{{else}}
{{price
this.product.price}}
{{/if}}
</span>
</p>
<p class="cc_sold_by">
{{#if this.product.sellerID}}
<span class="soldbylabel
cc_sold_by_label">{{pageLabelMap 'Prod_SoldBy'}}</span>
<span class="soldbyname cc_sold_by_name">{{pdp-
seller-field 'sfdcName' this.product.sellerID this.sellers}}</span>
{{/if}}
</p>
{{#ifDisplay 'PD.DsplSvPrc'}}
40
{{#if this.product.savings}}
<p class="savings">
<span class="cc_label">{{pageLabelMap
'YouSave'}}</span>
<span class="value cc_value">{{price
this.product.savings}}</span>
</p>
{{/if}}
{{/ifDisplay}}
</div>
<hr>
{{/if}}
{{/ifDisplay}}
{{/if}}
</div>
</div>
{{#if this.product.canAddtoCart}}
{{#unless this.primaryAttr}}
<div class="quantity_block gp_quantity_block cc_quantity_block">
{{#if this.product.qtySkipIncrement}}
<div class="row cc_qty_control_row">
<div class="col-md-10 col-md-offset-2">
<div class="form-group">
<div class="input-group cc_input_group">
<span class="input-group-btn
cc_input_group_btn">
<input type="button" value="{{pageLabelMap
'Prod_QtyDecrFF'}}" class="btn btn-default btn-sm minusFF cc_minusff">
<input type="button" value="{{pageLabelMap
'Prod_QtyDecrSingle'}}" class="btn btn-default btn-sm minus cc_minus">
</span>
<input type="text" readonly="true" name="qty"
value="0" class="entry form-control input-sm cc_entry" maxlength="7" />
<span class="input-group-btn cc_input_group_btn">
<input type="button" value="{{pageLabelMap
'Prod_QtyIncrSingle'}}" class="btn btn-default btn-sm plus cc_plus">
<input type="button" value="{{pageLabelMap
'Prod_QtyIncrFF'}}" class="btn btn-default btn-sm plusFF cc_plusff">
</span>
</div>
</div>
</div>
</div>
{{else}}
{{#ifEquals this.product.qtyIncrement 1}}
<div class="row cc_qty_control_row">
<div class="col-md-6 col-md-offset-6">
<div class="form-group">
<div class="input-group cc_input_group">
<span class="input-group-btn
cc_input_group_btn">
<input type="button"
value="{{pageLabelMap 'Prod_QtyDecrSingle'}}" class="btn btn-default btn-sm minus
cc_minus">
</span>
<input id="qty{{index}}"
name="quickadd[{{index}}].qty" value="0" class="qty entry form-control input-sm cc_entry"
maxlength="7" />
<span class="input-group-btn cc_input_group_btn">
41
<input type="button"
value="{{pageLabelMap 'Prod_QtyIncrSingle'}}" class="btn btn-default btn-sm plus cc_plus">
</span>
</div>
</div>
</div>
</div>
{{else}}
<div class="row cc_qty_control_row">
<div class="col-md-12">
<div class="form-horizontal">
<div class="form-group">
<label for="qty" class="col-sm-7 control-label
cc_qty">{{pageLabelMap 'Qty'}}</label>
<div class="col-sm-5">
<input type="text" id="qty" name="qty"
value="1" class="input-text entry plus_minus cc_entry form-control" maxlength="7" />
</div>
</div>
</div>
</div>
</div>
{{/ifEquals}}
{{/if}}
<input type="hidden" name="qtyIncrement"
value="{{this.product.qtySingleIncrement}}" class="item_qtyIncrement cc_item_qty_increment"
/>
<input type="hidden" name="qtySkipIncrement"
value="{{this.product.qtySkipIncrement}}" class="item_qtySkipIncrement
cc_item_qty_skip_increment" />
</div>
{{#unless this.showNewSubscriptionSelection}}
{{#if this.product.prodBean.showSubscriptionSelection}}
<div class="row">
<div class="col-md-12">
<div class="cc_subscription_selection_div">
<p class="subscription_selection
cc_subscription_selection">
<span class="subscriptionLabel
cc_subscription_label">{{pageLabelMap 'Subscribe_And_Save_Label'}}</span>
<select class="subscriptionFrequencySelection
cc_subscription_frequency_selection" data-subscription="{{this.product.prodBean.sku}}">
{{#each
this.product.prodBean.subscriptionFrequencies}}
<option value="{{safeQuote
this}}">{{pageLabelMapMultiString 'Subscribe_And_Save_' this}}</option>
{{/each}}
</select>
</p>
</div>
</div>
</div>
{{/if}}
<div class="row">
<div class="col-md-10 col-md-offset-2">
<div class="wishButtons plus_minus cc_plus_minus pull-
right"></div>
</div>
</div>
42
<div class="row">
<div class="col-md-8 col-md-offset-4">
<div class="action cc_action">
<button type="button" class="btn btn-primary btn-sm
addItem cc_add_item pull-right" data-sku="{{this.product.prodBean.sku}}" data-
seller="{{this.product.sellerID}}">{{pageLabelMap
'Component_MiniwishList_AddToCart'}}</button>
</div>
</div>
</div>
{{/unless}}
{{/unless}}
{{/if}}
</div>
</div>
</div>
{{#if this.primaryAttr}}
<div class="row">
<div class="col-md-12">
<div class="cc_product_attributes"></div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="cc_product_attributes_batch_header">
</div>
</div>
</div>
{{/if}}
<div class="row">
<div class="col-md-12">
<div class="cc_product_attributes"></div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="cc_product_attributes_batch_header">
</div>
</div>
</div>
<div class="row">
<div class="col-md-7 col-md-offset-5">
{{#if this.showNewSubscriptionSelection}}
{{#if this.product.canAddtoCart}}
<div class="cc_subscription_selection_div">
<h4>{{pageLabelMap 'Subscribe_And_Save_Label'}}</h4>
{{#each this.subProdTerms}}
{{#if this.CC_NO_SUBSCRIPTION}}
<div class="cc_item_container">
<div class="radio cc_radio">
<label class="cc_radio_name">
<input value="nosuboption" data-nme="nosuboption"
type="radio" name="subOptionGuider{{this.productSKU}}" {{this.checked}}/>
{{pageLabelMap 'Prod_NoSubscriptionOption' (price
this.productPrice)}}</label>
</div>
</div>
{{else}}
<div class="cc_item_container">
43
<div class="radio cc_radio">
<label class="cc_radio_name">
<input value="{{this.sfid}}" data-
nme="{{this.name}}" type="radio" name="subOptionGuider{{this.productSKU}}"
{{this.checked}}/>
{{#if this.modifierSubscriptions}}
{{insertTokens this.pdpDisplayName
this.displayName (price this.productPrice) (price this.subscriptionPrice)
(pageLabelPrefixMap 'Subscriptions_Frequency_' this.orderFrequencyUOM) (pageLabelPrefixMap
'Subscriptions_Frequency_' this.installmentFrequencyUOM) this.orderFrequency
this.installmentFrequency this.installmentCount this.orderCount (price
this.modifierSubscriptions.[0].productPrice) (price
this.modifierSubscriptions.[0].subscriptionPrice) this.modifierSubscriptions.[0].orderCount
this.modifierSubscriptions.[0].installmentCount}}
{{else}}
{{insertTokens this.pdpDisplayName
this.displayName (price this.productPrice) (price this.subscriptionPrice)
(pageLabelPrefixMap 'Subscriptions_Frequency_' this.orderFrequencyUOM) (pageLabelPrefixMap
'Subscriptions_Frequency_' this.installmentFrequencyUOM) this.orderFrequency
this.installmentFrequency this.installmentCount this.orderCount}}
{{/if}}
</label>
</div>
</div>
{{/if}}
{{/each}}
<div class="action pull-right cc_action" >
<button type="button" class="btn btn-primary addItem
cc_add_item" data-sku="{{this.product.prodBean.sku}}">{{pageLabelMap
'Component_MiniwishList_AddToCart'}}</button>
</div>
</div>
{{/if}}
{{/if}}
</div>
</div>
</div>
</div>
<div class="tabSection"></div>
<div class="widgetSection"></div>
</script>
</apex:page>
① Handlebars template override, this template override can also be done in the uiproperties.js
file.
② Handlebars template ID defined here should always match the value in the override from the
previous step.
③ Placeholder for the brand attribute needs to be displayed. Copy and paste <span
class="value cc_value">{{{pageLabelMap
this.product.prodBean.brand}}}</span> into the annotated section in the reference
code.
44
A. In your Salesforce Org, click CC Admin from the main tab.
B. On the right side dropdown select the DefaultStore Storefront.
C. On the left side bar, near the top, under "Settings" section, click on Configuration Settings.
When filling out the value for the Page input field referenced below, you should
keep the following in mind. Start out by typing the name of the page of interest.
E.g Type Product Details and then select the appropriate value from the
autocompleted suggestions in the input field.
D. Select New to create a Configuration Setting and enter the following to enable the page include
Module
Body Includes End
Configuration
Enabled
Page
Product Details (PDP)
Value
TRUE
E. Select New to create a Configuration Setting and enter the following to set the page include (don’t
forget the c__ prefix before the name of your VF page):
Module
Body Includes End
Configuration
Page Include Name
Page
Product Details (PDP)
Value
c__ccTrainingProductDetailBIE
F. Create a page label for the field that exposes the value of the brand attribute
Page Name
ProductDetails
45
Value
Brand
Storefront
DefaultStore
Version specific logic classes will reference the version that they are linked to. e.g.
ccLogic<ENTITY><FUNCTION><VERSION> → ccLogicProductPricing6
46
• ccLogicAccountValidateNew
• ccLogicAddressBookCreateAddressBook
• ccLogicAddressBookRemoveAddressBook
• ccLogicAddressCreateAddress
• ccLogicCartAddTo
• ccLogicCartPrice
• ccLogicCartAdjustment
• ccLogicCartClone
• ccLogicCartCreate
A complete list of the all the logic providers can be found in the service management section in CC Admin.
Use Case
1. Create a new attribute with an API name of RestrictedMaterial__c on the account object.
This will reference the material an account is restricted from purchasing.
2. Update the data service providers for Account objects to include this new field. You should only
create this class if you haven’t done so in a previous exercise.
3. Configure CloudCraze to use your data service provider as needed.
4. Create a new attribute with an API name of Material__c on the product object. This will
reference the material used for the product.
5. Update the data service providers for the Product object to include this new field. You should create
this class data if you haven’t done so in a previous exercise.
6. Configure CloudCraze to use your data service provider as needed.
7. Override the Logic Provider class for the add-to-cart functionality.
8. Inside the overriden method, determine if a product can be added to the cart based on its material.
9. Configure CloudCraze to use the new logic provider and data service providers as needed.
Prior to this exercise, you must create an extension for the data service providers for CC
Product and Accounts. See below for further instructions.
47
2. While in the setup menu, Type Account in the quick find box.
3. Select Fields under the Build → Customize → Account → Fields menu.
4. Scroll down to the Custom Fields and Relationships section and then select New
5. Add a picklist of restricted materials with the following details
i. Field Label: Restricted Material
ii. Values: Metal, Aluminum and Titanium
iii. Ensure you enter each of these values on a separate line.
6. Ensure that the auto-generated field name is RestrictedMaterial and not
Restricted_Material
7. Make the field visible for all the profiles.
8. Add the Restricted Material attribute to the Account layout and save your changes.
9. Keep advancing through the setup screens and then save your changes.
b. Implement the data service provider for Accounts
objectFields += ',RestrictedMaterial__c' ; ④
48
6. Save your changes
d. Add a custom field to the CC Product object
1. Create a new attribute with an API Name of Material__c on the CC Product object. This will be
used to reference the product’s material.
2. While in the setup menu, Type Object in the quick find box.
3. Select Objects under the Build → Create → Objects menu.
4. Select CC Product from the list of the available custom objects. Do not select the Edit button.
5. Scroll down to the Custom Fields and Relationships section and then select New
6. Select Picklist as the field type on the next screen
7. Add a picklist of materials with the following details
i. Field Label: Material
ii. Values: Metal, Aluminum and Titanium
iii. Ensure you enter each of these values on a separate line.
8. Verify that the auto-generated field name is Material
9. Make the field visible for all the profiles.
10. Add the Material attribute to the layout for CC Product and save your changes.
11. Keep advancing through the setup screens and then save your changes.
e. Implement the data service provider for CC Product
49
global with sharing class ccTrainingProductService extends ccrz.ccServiceProduct ①
{
global virtual override Map<String, Object> getFieldsMap(Map<String, Object> inputData) ②
{
inputData = super.getFieldsMap(inputData); ③
objectFields += ',Brand__c,Material__c' ; ④
f. Configure CloudCraze to use your new data service provider if it’s not already in place
1. In your Salesforce Org, click CC Admin from the main tab.
2. On the right side dropdown select the DefaultStore Storefront.
3. On the left side bar, select Service Management
4. Scroll down to the Logic provider section and search for ccServiceProduct
5. Set the class name to c.ccTrainingProductService
g. Update the restricted material on the account record associated with your user
1. Go to the Account tab in your Salesforce org and edit the account record linked to your user. Add a
new Restricted Material (e.g. Metal), and save your changes.
2. Update the material on the product record we’ll be using for the exercise - You can pick any product of
your choosing.
3. Go to the CC Product tab in Salesforce, search for the product you are interested in and set its
material to the same value as that referenced in your user’s account (e.g. Metal).
4. Ensure you are using a product the user is entitled to in the storefront.
5. Save your changes.
h. Implement your logic provider
50
global with sharing class ccTrainingLogicCartAddTo extends ccrz.ccLogicCartAddTo ①
{
global virtual override Map<String,Object> processInputData(Map<String,Object> inputData) ②
{
Boolean restrictedProduct = false;
if(!ccrz.ccUtil.isKeyValued(inputData, ccrz.ccApiCart.COUPON_CODE)) ③
{
List<Object> incomingLineData = (List<Object>) inputData.get(ccrz.ccApiCart.LINE_DATA);
if(!String.isEmpty(prodId))
{
productIdSet.add(prodId);
}
else if(!String.isEmpty(prodSku))
{
productSkuSet.add(prodSku);
}
}
if(productIdSet.size() > 0)
{
inputMap.put(ccrz.ccApiProduct.PRODUCTIDLIST, productIdSet);
}
else if(productSkuSet.size() > 0)
{
inputMap.put(ccrz.ccApiProduct.PRODUCTSKULIST, productSkuSet);
}
if(outputProductList[0].get('material') != null) ⑤
{
String restrictedProdMaterial = (String) outputProductList[0].get('material');
if(!String.isEmpty(restrictedProdMaterial))
51
{
⑤
Map<String,Object> accountData = ccrz.ccApiAccount.fetch(new
Map<String,Object>{
ccrz.ccApi.API_VERSION => inputData.get(ccrz.ccApi.API_VERSION),
ccrz.ccApiAccount.ID => ccrz.cc_CallContext.effAccountId,
ccrz.ccAPI.SIZING => new Map<String, Object>{
ccrz.ccAPIProduct.ENTITYNAME => new Map<String, Object>{
ccrz.ccAPI.SZ_DATA => ccrz.ccAPI.SZ_XL
}
}
});
if(outputAccountList[0].get('restrictedMaterial') != null) ⑥
{
String restrictedMaterialAccount = (String)
outputAccountList[0].get('restrictedMaterial'); ⑥
if(!String.isEmpty(restrictedMaterialAccount))
{
if(restrictedMaterialAccount.equals(restrictedProdMaterial)) ⑦
{
ccrz.cclog.log('restricted product for account'); ⑦
restrictedProduct = true;
}
}
}
}
}
}
return super.processInputData(inputData);
}
52
j. Verify your work in the storefront
1. Search for the product you edited in a prior step in your storefront - this would be the product the user
is restricted from adding to the cart.
2. Try to add this product to the cart on either the Product Detail Page or the Product Listing Page.
3. You should observe the product was not added to the cart.
4. Try to add another product with no restricted material referenced on it and it should be successfully
added to the cart.
53
4.5. REST APIs
CloudCraze Global APIs are wrapped and surfaced for REST by the CloudCraze REST API Wrappers. These
classes build on the Force.com APEX Rest class approach in the following manner.
Note: CloudCraze recommends only using the REST APIs via an integration user. Because of the need to
enable API access on the user profile/permission set CloudCraze does not recommend allowing access to the
REST APIs for external users.
Versioning
Versioning is handled through the requestURI routing system mentioned above. The version will be
prefixed with a v and the route/integer will follow.
Minimum Version
The minimum version of the API that can be invoked is currently version 1. In the future, the minimum
version may be increased. Invoking a REST API with a version lower than that (e.g. 0) will return a HTTP
400 response with messaging indicating that the version below the minimum version was requested.
Maximum Version
The maximum version of the API that can be invoked is currently version 4. In the future, the maximum
version may be increased. Invoking a REST API with a version higher than the maximum (e.g. 9001) will
return a HTTP 400 response with messaging indicating that the version above the maximum was
requested.
Unavailable Version
Other than the general restriction imposed on API’s via the minimum and maximum avaialable versions.
There are some API’s that begin at a particular version E.g. Foo API only honors requests starting at
version 3.In cases where the API is invoked with a lower version number, a HTTP 500 response will be
returned and it will contain a message noting that the requsted version is unavailable.
• ccrz.cc_api_CartExtension
• ccrz.cc_api_DeliveryDate
• ccrz.cc_api_InventoryExtension
• ccrz.cc_api_OutboundOrderCancel
54
• ccrz.cc_api_PriceAdjustment
• ccrz.cc_api_ProductQuantityRule
• ccrz.cc_api_ShippingAndHandling
• ccrz.cc_hk_Catalog
• ccrz.cc_hk_Category
• ccrz.cc_hk_DynamicTheme
• ccrz.cc_hk_EffectiveAccount
• ccrz.cc_hk_Invoice
• ccrz.cc_hk_Menu
• ccrz.cc_hk_Order
• ccrz.cc_hk_Payment
• ccrz.cc_hk_Pricing
• ccrz.cc_hk_Promotion
• ccrz.cc_hk_SSO
• ccrz.cc_hk_Subscriptions
• ccrz.cc_hk_TaxCalculation
• ccrz.cc_hk_UserInterface
• ccrz.cc_if_OutboundOrder
• ccrz.cc_hk_DynamicTheme – setting a theme dynamically
• ccrz.cc_hk_UserInterface
• ccrz.cc_hk_Menu – fetches all the menu items from the public cache
• ccrz.cc_hk_Catalog – filters out specific categories
• ccrz.cc_hk_Category – returns the category tree or just sub categories
• ccrz.cc_hk_Promotion – filter out promotions
• ccrz.cc_api_ProductQuantityRule – retrieves rules that can be applied to the cart
• ccrz.cc_api_InventoryExtension – returns the inventory
• ccrz.cc_hk_Pricing – returns pricing
55
package.
56
4.7. Cart Extension Point
The ccrz.cc_api_CartExtension class allows for specific business validation, messaging, and
processing on the cart page. Note that allowCheckout and getCartMessages are called as part of the same
remote action call within CloudCraze. So, it is often useful to use a single validateCart method that is
called by these two methods that then shares the results.
57
4.7.1. Exercise: Create a new Cart Extension
In this exercise, we’ll show you how to inject custom logic into the life cycle of the Cart details page.
Use Case
1. Create a new cart extension point
2. Add a validation method to check the credit limit
3. Configure CloudCraze to use your new cart extension
4. Verify your changes
Prior to this exercise, you must create a Custom Field of type Currency with an API name of
CreditLimit__c on the Account object. You can ignore this step if this field has been
created in a prior exercise.
58
6. If the total amount is not greater than the credit limit, set the allowCheckout variable to true.
validate(cartBeanSummary);
return isAllowCheckout;
}
validate(cartBeanSummary);
return retMessages;
}
cartidlist.add(cartBean.sfid);
if (outputDataCart.get(ccrz.ccAPICart.CART_OBJLIST) != null)
{
List<Map<String, Object>> outputCartList = (List<Map<String, Object>>)
outputDataCart.get(ccrz.ccAPICart.CART_OBJLIST);
if(outputCartList[0].get('totalAmount') != null)
{
totalAmount = (Double) outputCartList[0].get('totalAmount');
}
59
}
if(outputDataAccount.get(ccrz.ccAPIAccount.ACCOUNTS) != null)
{
List<Map<String,Object>> accounts = (List<Map<String,Object>>)
outputDataAccount.get(ccrz.ccAPIAccount.ACCOUNTS);
if(account.get('creditLimit') != null)
{
creditLimit = (Double)account.get('creditLimit');
}
}
if(creditLimit != 0.0)
{
if(totalAmount > creditLimit) ④
{
if(!isAllowCheckout) ⑤
{
⑤
retMessages.add(msg);
}
}
else
{
isAllowCheckout = true; ⑥
}
}
}
isValidated = true;
}
}
60
amount on your order
iv. Storefront : DefaultStore
e. Rebuild your page label cache
f. Verify your changes
i. Add a product to the cart that exceeds your credit limit
ii. Observe that the Checkout button will no longer be visible
iii. You’ll also notice that Checkout button will be visible if the cart total doesn’t exceed the credit limit.
61
4.8. Extending My Account Page
Use Case
1. Create an Apex controller that will be referenced in your page include
2. Create a new Page Include for the My Account Page and reference the controller above on it.
3. Update the getFieldsMap method for the data service provider for Accounts
4. Configure CloudCraze to use your new Page Include
5. Verify your changes
Prior to this exercise, ensure you have a custom field of type Currency on the account
object with an API name of CreditLimit__c
62
global with sharing class ccTrainingMyAccountCtrl ①
{
global Decimal remainingCredit{get;private set;} ②
global ccTrainingMyAccountCtrl() ③
{
④
List<Object> acctRes = (List<Object>)ccrz.ccApiAccount.fetch(new Map<String,Object>{
ccrz.ccApi.API_VERSION => ccrz.ccApi.CURRENT_VERSION,
ccrz.ccApiAccount.ID => ccrz.cc_CallContext.effAccountId,
ccrz.ccApi.SIZING=>new Map<String, Object>{
ccrz.ccApiAccount.ENTITYNAME => new Map<String, Object>{
ccrz.ccAPI.SZ_SKIPTRZ=>true
}
}
}).get(ccrz.ccApiAccount.ACCOUNTS);
if(acctRes.get(0) != null)
{
account = (Account)acctRes.get(0);
remainingCredit = account.CreditLimit__c; ⑤
}
userCurrency = ccrz.cc_CallContext.userCurrency; ⑥
}
}
i. In your SalesForce Org, click the Developer Console link under the dropdown menu under your
name - this should be on the top right hand corner on the page.
ii. A new window should pop-up.
iii. In the Developer Console menu, select: File → New → VisualForce Page
iv. In the popup window (New Apex Page), enter "ccTrainingMyAccountBIE" as your page name and
click ok.
v. Remove the boilerplate code (delete the contents of the page).
vi. Enter the following: please note that the code below was added for your convenience. It is also
available on the wiki here
<script>
CCRZ.uiProperties.contactInfoView.desktop.tmpl = 'CCTrainingMyAccount-
ContactInformation-Desktop'; ①
</script>
63
②
<script id="CCTrainingMyAccount-ContactInformation-Desktop" type="text/template">
<div class="panel panel-default cc_panel cc_myaccount_profile">
<div class="panel-body cc_body cc_myaccount_content">
<h3 class="cc_title">{{pageLabelMap 'MyAccount_Profile'}}</h3>
{{#ifEquals this.commerceType "B2B"}}
<div class="panel panel-default cc_panel cc_myaccount_information">
<div class="panel-heading cc_heading">
<h3 class="panel-title cc_title">{{pageLabelMap
'MyAccount_Profile_Account_Information'}}</h3>
</div>
<div class="panel-body cc_body cc_myaccount_general">
<p class="myAccProfileNote cc_profile_note">
{{pageLabelMap 'MyAccount_Profile_Note'}}
</p>
{{#ifDisplay 'reg.addlInf'}}
<p class="myAccProfileCompany cc_profile_company">
<span class="cc_profile_company_label">{{pageLabelMap
'MyAccount_Profile_Company'}}:</span>
<span class="cc_profile_company_valuel">{{accountBean.name}}</span>
</p>
{{/ifDisplay}}
<!-- Exercise TODO - Add your code to this section -->
<p class="myAccProfileCompany cc_profile_company">
<span class="cc_profile_company_label">{{pageLabelMap
'CCTrainingMyAccount_Remaining_Credit'}}:</span>
<span class="cc_profile_company_valuel"></span> ③
</p>
<p class="myAccAccountGroup cc_profile_account_group">
<span class="cc_profile_account_group_label">{{pageLabelMap
'MyAccount_Profile_Account_Group'}}:</span>
<span
class="cc_profile_account_group_value">{{accountBean.accountGroupName}}</span>
</p>
<p class="myAccProfilePhone cc_profile_phone">
<span class="cc_profile_phone_label">{{pageLabelMap
'MyAccount_Profile_Phone'}}:</span>
<span class="cc_profile_phone_value">{{accountBean.phone}}</span>
</p>
<div class="row">
<div class="col-md-6 myAccBillingAddr cc_billing_address">
<span class="cc_profile_billing_label">{{pageLabelMap
'MyAccount_Profile_Account_Billing_Address'}}</span>
<span class="cc_profile_billing_value">{{> addressDisplay
this.accountBean.billingAddress}}</span>
</div>
<div class="col-md-6 myAccShippingAddr cc_shipping_address">
<span class="cc_profile_shipping_label">{{pageLabelMap
'MyAccount_Profile_Account_Shipping_Address'}}</span>
<span class="cc_profile_shipping_value">{{> addressDisplay
this.accountBean.shippingAddress}}</span>
</div>
</div>
</div>
</div>
<div class="panel panel-default cc_panel cc_myaccount_contact_information">
<div class="panel-heading cc_heading">
<h3 class="panel-title cc_title">{{pageLabelMap
'MyAccount_Profile_Contact_Information'}}</h3>
64
</div>
<div class="panel-body cc_body cc_myaccount_contact">
<p class="myAccProfileName cc_profile_name">
<span class="cc_profile_name_label">{{pageLabelMap
'MyAccount_Profile_Name'}}:</span>
{{#if contactBean.firstName}}
<span class="cc_profile_name_value">{{contactBean.firstName}}
{{contactBean.lastName}}</span>
{{else}}
<span class="cc_profile_name_label">No name stored.</span>
{{/if}}
</p>
<p class="myAccProfilePhone cc_profile_phone">
<span class="cc_profile_phone_label">{{pageLabelMap
'MyAccount_Profile_Phone'}}:</span>
{{#if contactBean.phone}}
<span class="cc_profile_phone_value">{{contactBean.phone}}</span>
{{else}}
<span class="cc_profile_phone_value">No phone number stored.</span>
{{/if}}
</p>
<div class="row">
<div class="col-md-6 myAccMailingAddr cc_mailing_address">
<span class="cc_profile_mailing_label">{{pageLabelMap
'MyAccount_Profile_Contact_Mailing_Address'}}</span>
<span class="cc_profile_mailing_value">{{> addressDisplay
this.contactBean.mailingAddress}}</span>
</div>
<div class="col-md-6 myAccOtherAddr cc_other_address">
<span class="cc_profile_other_label">{{pageLabelMap
'MyAccount_Profile_Contact_Other_Address'}}</span>
<span class="cc_profile_other_valuel">{{> addressDisplay
this.contactBean.otherAddress}}</span>
</div>
</div>
</div>
</div>
{{/ifEquals}}
<div class="panel panel-default cc_panel cc_myaccount_user_information">
<div class="panel-heading cc_heading">
<h3 class="panel-title cc_title">{{pageLabelMap
'MyAccount_Profile_User_Information'}}</h3>
</div>
<div class="panel-body cc_body cc_myaccount_user">
<p class="myAccName cc_name">
<span class="cc_profile_name_label">{{pageLabelMap
'MyAccount_Profile_Name'}}:</span>
<span class="cc_profile_name_value">{{userFirstName}} {{userLastName}}</span>
</p>
<p class="myAccPhone cc_acct_phone">
<span class="cc_acct_phone_label">{{pageLabelMap
'MyAccount_Profile_Phone'}}:</span>
<span class="cc_acct_phone_value">{{userPhone}}</span>
</p>
<p class="myAccUserName cc_acct_username">
<span class="cc_acct_username_label">{{pageLabelMap
'MyAccount_Profile_Username'}}:</span>
<span class="cc_acct_username_value">{{username}}</span>
</p>
65
<p class="myAccEmailAddr cc_acct_email">
<span class="cc_acct_email_label">{{pageLabelMap
'MyAccount_Profile_Email'}}:</span>
<span class="cc_acct_email_value">{{emailAddress}}</span>
</p>
<p class="myAccLanguage cc_acct_language">
<span class="cc_acct_language_label">{{pageLabelMap
'MyAccount_Profile_Language'}}:</span>
<span class="cc_acct_language_value">{{language}}</span>
</p>
<p class="myAccCurrency cc_acct_currency">
<span class="cc_acct_currency_label">{{pageLabelMap
'MyAccount_Profile_Currency'}}:</span>
<span class="cc_acct_currency_value">{{currencyName}}</span>
</p>
{{#ifDisplay 'reg.tmZn'}}
<p class="myAccTimeZone cc_acct_timezone">
<span class="cc_acct_timezone_label">{{pageLabelMap
'MyAccount_Profile_TimeZone'}}:</span>
<span class="cc_acct_timezone_value">{{timeZone}}</span>
</p>
{{/ifDisplay}}
</div>
</div>
{{#if hideEditProfile}}
{{else}}
<input type="button" class="btn btn-default btn-sm gotoSectionContactInfoEdit
cc_edit_profile" value="{{pageLabelMap 'MyAccount_EditProfile'}}" />
{{/if}}
</div>
</div>
</script>
</apex:page>
① Handlebars template override, this template override can also be done in the uiproperties.js file
② Handlebars template ID defined here should always match the value in the override from the previous
step.
③ Placeholder for the code that should be added as part of this exercise. See below for more details.
Page Name
All
66
Value
Remaining Credit
Storefront
DefaultStore
objectFields += ',CreditLimit__c,RestrictedMaterial__c' ; ④
When filling out the value for the Page input field referenced below, you should
keep the following in mind. Start out by typing the name of the page of interest. E.g
Type My Account and then select the appropriate value from the autocompleted
suggestions in the input field.
67
Module
Body Includes End
Configuration
Enabled
Page
My Account (MA)
Value
TRUE
5. Select New to create a new Configuration Setting and enter the following to set the page include:
Module
Body Includes End
Configuration
Page Include Name
Page
My Account (MA)
Value
c__ccTrainingMyAccountBIE
68
4.9. Extending the Cart Page
Use Case
1. Create new attributes on the Case and CC Cart to track the requested Credit Override.
2. Create an Apex controller that will be referenced on your page include.
3. Create a Page Include for the Cart Page.
4. Configure CloudCraze to use your new Page Include.
5. Verify your changes
69
iv. Leave other fields as is in their default state
8. Make the field visible for all the profiles.
9. Keep advancing through the setup screens and then save your changes.
c. Implement your new Apex Controller
1. Create a new Apex class called ccTrainingCartCtrl
2. Create a dummy merge variable named blank with a getter and a setter
3. Create a remote action method named createCreditCase that accepts one argument of type
ccrz.cc_RemoteActionContext
4. Inside the createCreditCase method, do the following.
i. Initialize the remote action context
ii. Create an instance variable of type cc_RemoteActionResult
iii. Set the default values for the variable referenced in the above step.
5. Create a List of type String and add the encrypted ID of the cart to this list
6. Invoke ccrz.ccAPICart.fetch by passing in the appropriate input data of type
Map<String,Object>
7. Extract the list of cart objects from the response of the fetch call.
8. Verify that the API returned at least one record
9. Set the value on the CreditOverrideRequested__c field to TRUE
10. Create a case and set its values to the following. They are currently set to their default values in the
provided snippet. You’ll want them to replace the default values with the below.
Origin
'Web';
Reason
'Credit Override'
Type
'Cart'
Subject
'Credit override request for '+ ccrz.cc_CallContext.effAccount.Name
SourceCart__c
This value was left out intentionally,the reference to the cart needs to be set here
ContactId
ccrz.cc_CallContext.currUser.ContactId
AccountId
ccrz.cc_CallContext.effAccountId
70
11. Insert the case record in Salesforce
12. Update the cart record in Salesforce
13. Set the value of the success variable of type ccrz.cc_RemoteActionResult to true.
@RemoteAction
global static ccrz.cc_RemoteActionResult createCreditCase(final ccrz.cc_RemoteActionContext
ctx) ③
{
ccrz.cc_CallContext.initRemoteContext(ctx); ④
try
{
List<String> cartidlist = new List<String>{};
cartidlist.add(ctx.currentCartId); ⑤
⑥
Map<String, Object> cartApiFetchResponse = ccrz.ccApiCart.fetch(new
Map<String,Object>{
ccrz.ccApi.API_VERSION => ccrz.ccApi.CURRENT_VERSION,
ccrz.ccApiCart.CART_ENCIDLIST => cartidlist,
ccrz.ccApi.SIZING=>new Map<String, Object>{
ccrz.ccApiCart.ENTITYNAME => new Map<String, Object>{
ccrz.ccAPI.SZ_SKIPTRZ=>true
} }
});
⑦
List<ccrz__E_Cart__c> cartRes =
(List<ccrz__E_Cart__c>)cartApiFetchResponse.get(ccrz.ccApiCart.CART_OBJLIST);
if(cartRes.get(0) != null) ⑧
{
theCart = (ccrz__E_Cart__c)cartRes.get(0);
theCart.CreditOverrideRequested__c = TRUE; ⑨
insert caseRequest; ⑪
71
update theCart; ⑫
res.success = true; ⑬
}
}
catch(Exception e)
{
res.data = e;
res.success = false;
}
return res;
}
}
72
type="button" class="btn btn-default btn-sm cc_clear_coupon">{{pageLabelMap
'CartInc_ClearCoupon'}}</button></p>
</div>
</form>
{{else}}
<form id="couponAddForm" class="cc_coupon_add_form">
<div class="discount_code cc_discount_code">
<p id="couponAddError" class="cc_coupon_add_error"></p>
<p class="cc_enter_coupon_code">{{pageLabelMap
'CartInc_EntercouponCode'}}</p>
<div class="form-group">
<label for="addCouponId" class="sr-only">Add Coupon</label>
<input type="text" id="addCouponId" name="couponId"
class="form-control cc_add_coupon_id" />
<div class="couponMessagingSection-Error"></div>
</div>
<button id="addCouponBtn" type="button" class="btn btn-default
btn-sm cc_add_coupon">{{pageLabelMap 'CartInc_ApplyCoupon'}}</button>
</div>
</form>
{{/if}}
</div>
</div>
{{/ifStoreSetting}}
</div>
<div class="col-md-7">
<div class="row">
<div class="col-md-12">
<div class=" cc_action_totals pull-right">
<button class="btn btn-default btn-sm continueShoppingButton
cc_continue_shopping_button" name="" type="button" >{{pageLabelMap
'CartInc_ContinueShopping'}}</button>
{{#if {!ISPICKVAL($User.UserType, 'Guest')} }}
{{ else }}
{{#ifStoreSetting 'AutoCalcPriceAdjust__c'}}
{{else}}
<button class="btn btn-default btn-sm getExtPricingButton
cc_get_ext_pricing_button" type="button" >{{pageLabelMap
'CartInc_ApplyDiscounts'}}</button>
{{/ifStoreSetting}}
{{#ifDisplay 'WL.PkrOn'}}
<button class="btn btn-default btn-sm cc_create_cart_wishlist" data-
toggle="modal" data-target="#wishMod" name="" type="button" >{{pageLabelMap
'Create_Cart_Wishlist'}}</button>
{{/ifDisplay}}
{{/if}}
<button class="btn btn-default btn-sm updateCartButton
cc_update_cart_button" name="" type="button" >{{pageLabelMap 'CartInc_Update'}}</button>
{{#if {!ISPICKVAL($User.UserType, 'Guest')} }}
{{#ifDisplay 'C.EmailAnon'}}
<a href="#emailModal" id="emailCartLink" class="cc_email_cart_link"
data-toggle="modal"><button class="btn btn-default btn-sm remove cc_email_cart_link_button"
type="button" >{{pageLabelMap 'CartOrderEmailer_Header'}}</button></a>
{{/ifDisplay}}
{{else}}
{{#ifDisplay 'C.EnableEmailCart'}}
<a href="#emailModal" id="emailCartLink" class="cc_email_cart_link"
data-toggle="modal"><button class="btn btn-default btn-sm remove cc_email_cart_link_button"
type="button" >{{pageLabelMap 'CartOrderEmailer_Header'}}</button></a>
73
{{/ifDisplay}}
{{/if}}
<!-- rfq button -->
{{#if {!ISPICKVAL($User.UserType, 'Guest')} }}
{{#ifDisplay 'rfq.GuestUser'}}
<a href="#rfqModal" id="rfqLink" class="cc_rfq_link" data-
toggle="modal"><button class="btn btn-default btn-sm remove cc_rfq_link_button"
type="button" >{{pageLabelMap 'CartRFQForm_Header'}}</button></a>
{{/ifDisplay}}
{{else}}
{{#ifDisplay 'rfq.LoggedIn'}}
<a href="#rfqModal" id="rfqLink" class="cc_rfq_link" data-
toggle="modal"><button class="btn btn-default btn-sm remove cc_rfq_link_button"
type="button" >{{pageLabelMap 'CartRFQForm_Header'}}</button></a>
{{/ifDisplay}}
{{/if}}
<!-- rfq button -->
{{#ifNotEquals this.allowCheckout true}} // ④
<!-- Exercise TODO - Add the HTML code for the credit override
button here -->
{{/ifNotEquals}}
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<ul class="checkout list-unstyled cc_checkout pull-right">
{{#if subTotal}}
<li class="grand_total cc_grand_total">
<p class="price cc_price">
<span class="cc_label">{{pageLabelMap
'CartInc_Subtotal'}}:</span>
<span class="cc_value">{{{price subTotal}}}</span>
</p>
</li>
{{/if}}
{{#if this.totalInfo}}
<li class="cart_total_amount
cc_cart_total_amount">{{this.totalInfo}}</li>
{{/if}}
<li class="totalsmessagingSection cc_total_messaging_section"></li>
{{#if this.cartItems}}
{{#ifNotEquals this.cartItems.length 0}}
{{#if this.allowCheckout}}
{{#ifEquals this.preventCheckout false}}
<li>
<button type="button" class="btn btn-default btn-sm
checkOutBtn cc_checkout_btn pull-right">{{pageLabelMap 'CartInc_Checkout'}}</button>
</li>
{{/ifEquals}}
{{/if}}
{{/ifNotEquals}}
{{/if}}
</ul>
</div>
</div>
</div>
</div>
</script>
74
<script type="text/javascript">
jQuery(function($)
{
CCRZ.subsc = _.extend(CCRZ.subsc||{}); ⑤
⑥
CCRZ.subsc.cartRemoteActions = _.extend({
className : 'ccTrainingCartCtrl',
createCreditCaseAction : function(callback)
{
this.invokeCtx('createCreditCase',
function(resp)
{
callback(resp);
},
{
buffer : false, // this call will be executed by itself
nmsp : false // defines that this is a call to a subscriber
class
}); // end invokeCtx call
}
},CCRZ.RemoteInvocation);
⑥
CCRZ.pubSub.on('view:CartDetailView:refresh', function(cartDetailView) ⑦
{
cartDetailView.createCreditCaseAction = function(event) ⑧
{
CCRZ.subsc.cartRemoteActions.createCreditCaseAction(function(resp)
{
if(resp && resp.success)
{
⑨
CCRZ.pubSub.trigger('pageMessage',{
messages:[
{
type : 'CUSTOM',
severity : 'INFO',
classToAppend : 'messagingSection-Info',
labelId : 'CCTraining_CreditOverrideRequested'
}
]
});
}
});
};
75
③ Handlebars template ID defined here should always match the value in the override from the
previous step. The next step is to Add the new credit override button to this page.
④ Placeholder for where the credit override button needs to be added. Copy and paste <button
class="btn btn-default btn-sm remove cc_rfq_link_button
createCreditCase" type="button">{{pageLabelMap
'CCTrainingCart_CreditOverrideBtn'}}</button> here. Afterwards, we’ll add the
Javascript logic for invoking your remote action when a user requests a credit limit override.
When filling out the value for the Page input field referenced below, you should
keep the following in mind. Start out by typing the name of the page of interest. E.g
Type Cart and then select the appropriate value from the autocompleted
suggestions in the input field.
76
i. Module: Body Includes End
ii. Configuration: Enabled
iii. Page: Cart (CA)
iv. Value: TRUE
5. Select New to create a new Configuration Setting and enter the following:
i. Module: Body Include End
ii. Configuration: Page Include Name
iii. Page: Cart (CA)
iv. Value: c__ccTrainingCartBIE
i. Create and Activate a new Configuration Cache:
1. On the right side dropdown, click on the Global Settings link.
2. On the left side bar, select Configuration Cache Management.
3. Click on Build New. After a few seconds, click on Refresh List. A new configuration cache should have
been built. Click on "Activate" to enable it.
j. Verify your changes on the My Account Page
1. Add any value to the credit limit field on your account record
2. Verify that the amount is displayed correctly on My Account → Contact Information
A defect was intentionally introduced in the reference code provided for the Cart page.
You need to resolve this issue in order to verify your changes as described below.
77
3. Create a JavaScript function that extends the CCRZ.RemoteInvocation function.
4. Link the HTML element of interest (e.g button) to the function created in the previous step.
78
4.10. Order Extension Hook
4.10.1. Overview
The ccrz.cc_hk_Order extension is used to extend all CloudCraze order functionality.
Placing Orders
• place
• createTransaction
• placeTarget
Viewing Orders
• fetchOrders
• reorder
Use Case
1. Create a new Order Extension
2. Decrement the Credit Limit for the Account during the place method
3. Configure CloudCraze to use your new Order extension
4. Verify your changes
Prior to this exercise, you must create a Custom Field of type Currency on Account with the
API name CreditLimit__c
79
4. Next, fetch the total amount of the current order. This is needed as the argument of type
ccrz__E_Order__c passed into this method doesn’t reference this value by default.
5. While still inside the private method, decrement the Account’s Credit Limit by the current order total.
6. Update the account record
7. Override the place method.
8. Inside the place override, check if the current step is equal to ccrz.cc_hk_Order.STEP_END
9. If the current step is STEP_END, invoke the private method created in the previous step and pass in
the ccrz__E_Order__c instance from PARAM_ORDER to this private method.
10. Inside the place method, return the same Map<String,Object> instance passed in as the method
argument.
update currentAccount; ⑥
}
if (ccrz.cc_hk_Order.STEP_END.equals(currentStep)) ⑦
{
ccrz__E_Order__c currentOrder =
(ccrz__E_Order__c)inputData.get(ccrz.cc_hk_Order.PARAM_ORDER);
⑧
updateAccountCreditLimitForOrder(currentOrder);
}
return inputData; ⑨
}
80
4. Scroll down to the Order Hook section and set the "Order Hook API Class" to
c.ccTraining_hk_Order
c. Verify your changes
1. Observe the current value of your credit limit by navigating to the My Account → Profile page.
2. Navigate to the Homepage and select or perhaps seach for any product that is within your credit limit.
3. Add the product to the cart
4. Complete the checkout process
5. Navigate to the My Account → Profile page to verify that the credit limit has since decreased.
Make sure you have cc_hlpr_GenericHKFactory set under the Order Hook
Factory Class. This is required for all legacy hook extension classes to be properly
insantiated inside the CloudCraze Managed Package.
81
4.11. Case Handling via Process Builder
Lightning Process Builder is a workflow tool that automate business processes within Salesforce.
Salesforce Help
Use Case
1. Create new attributes on the Case and CC Cart to track the requested Credit Override.
2. Create a Process Workflow to transfer the requested credit limit override amount from the Case to
the Cart
3. Create a data service provider for Carts
4. Configure CloudCraze to use the new data service provider
5. Update the cart extension point to check for the credit limit override amount
6. Verify your changes
82
6. Select Currency as the Data type on the next screen
7. Set the following on the subsequent screen.
i. Field Label: Credit Limit Override Amount
ii. Field Name: CreditOverride
iii. Length: 16
iv. Decimal Places: 2
v. Leave other fields as is
8. Make the field visible for all the profiles.
9. Keep advancing through the setup screens and then save your changes.
c. Create the Process Builder
1. While in the setup menu, Type Process builder in the quick find box.
2. Select Process Builder under the Build → Create → Workflow & Approvals → Process Builder menu.
3. Select New in the Process Builder User Interface and enter the following:
i. Process Name: Credit Case Approval
ii. API Name: Credit_Case_Approval
iii. Description: Process for handling cases
iv. The process starts when: A record changes
4. Save your changes
5. Select the + Add Object button and a separate window should open.
6. Enter the following on this screen.
i. Object: Select Case from the dropdown options list
ii. Start the process: When a record is created or edited
iii. Save your changes
7. Select the + Add Criteria button and a separate window should open.
8. Enter the following on this screen.
i. Criteria Name: Override Amount Set
ii. Criteria for Executing Actions: Conditions are met
iii. Set Conditions (choose from drop down menus) :
A. Field – Credit Limit Override Amount
B. Operator – Does not equal
C. Type – Global Constant
D. Value - $GlobalConstant.Null
E. Conditions – All of the conditions are met (AND)
F. Save your changes
83
9. Select the + Add Action button and a separate window should open.
10. Enter the following on this screen.
i. Action Type: Update Records
ii. Action Name: Set Cart Override
iii. Record Type : Select the magnifying glass icon and enter the following:
A. Choose the Select a record related to the Case option.
B. Select Reference Cart from the dropdown options list
C. Select the Choose button
iv. Criteria for Updating Records: No criteria – just update records!
v. Set new field values for the records you update:
A. Field: Credit Limit Override Amount
B. Type: Reference
C. Value: Case.CreditLimitOverrideAmount
11. Save your changes
12. Click Activate to enable the process.
d. Create a data service provider for Carts
objectFields += ',CreditOverride__c' ; ④
84
3. On the left side bar, select "Service Management"
4. Scroll down to the data service provider section and search for ccServiceCart
5. Set the class name to c.ccTrainingServiceCart
f. Update the Cart extension point to check for the Credit Limit Override Amount
validate(cartBeanSummary);
return isAllowCheckout;
}
validate(cartBeanSummary);
return retMessages;
}
85
if(!isValidated)
{
List<String> cartidlist = new List<String>{};
cartidlist.add(cartBean.sfid);
if (outputDataCart.get(ccrz.ccAPICart.CART_OBJLIST) != null)
{
List<Map<String, Object>> outputCartList = (List<Map<String, Object>>)
outputDataCart.get(ccrz.ccAPICart.CART_OBJLIST);
if(outputCartList[0].get('totalAmount') != null)
{
totalAmount = (Double) outputCartList[0].get('totalAmount');
}
if(outputCartList[0].get('creditOverride') != null)
{
creditOverride = (Double) outputCartList[0].get('creditOverride'); ③
}
}
if(outputDataAccount.get(ccrz.ccAPIAccount.ACCOUNTS) != null)
{
List<Map<String,Object>> accounts = (List<Map<String,Object>>)
outputDataAccount.get(ccrz.ccAPIAccount.ACCOUNTS);
if(account.get('creditLimit') != null)
{
creditLimit = (Double)account.get('creditLimit');
}
}
if(creditLimit != 0.0)
{
if(totalAmount > creditLimit)
{
if(creditOverride == 0.0)
{
isAllowCheckout = false; ④
}
86
else if(totalAmount > creditOverride) ⑤
{
isAllowCheckout = false; ⑤
}
else
{
isAllowCheckout = true;
retMessages.add(msg);
}
if(!isAllowCheckout)
{
ccrz.cc_bean_Message msg = new ccrz.cc_bean_Message();
msg.labelId = 'CCTraining_CreditExceeded';
msg.type = ccrz.cc_bean_Message.MessageType.CUSTOM;
msg.severity = ccrz.cc_bean_Message.MessageSeverity.ERROR;
msg.classToAppend = 'messagingSection-Info';//add to message
section specific to the item
retMessages.add(msg);
}
}
else
{
isAllowCheckout = true;
}
}
}
isValidated = true;
}
}
87
4.12. Invoice Extension Hook
• CloudCraze provides invoice capabilities which is essentially just billing. i.e. collecting payment outside
the checkout process.
• Invoices can be created during the order creation process for orders and they can also be created
independent of an order.
• The extension point we will edit for this business case is ccrz.cc_hk_Invoice.
◦ This method will only work for signed in users and it creates the Invoice object.
Pay an Invoice
• validatePayment – Called when the user enters a payment against one or more. invoices on the invoice
payment screen. Callouts should be made from this method.
• parseFetchPaymentData - Parses passed payment data from the user interface.
Use Case
1. Create a new Invoice Extension
2. Update the Credit Limit amount based on Invoice payments
3. Configure CloudCraze to use your new Invoice extension
Prior to this exercise, you must create a custom field of type Currency on Account with the
API name CreditLimit__c. Ignore this step if you’ve created this field in a prior exercise.
88
1. Create a new Apex class called ccTraining_hk_Invoice that extends ccrz.cc_hk_Invoice
2. Override the applyPayment method.
3. Invoke the base implementation (super) of the applyPayment method first. This is needed because
we would like to add to the credit after the payment has been processed.
4. After invoking the base implemementation of applyPayment, access the deserialized invoice
payment data as a Map<String,Object>. It is worth noting that the keys are the invoice Salesforce
id’s while the values are payment amounts formatted as Strings.
5. Create a local variable to store the total dollar amount paid.
6. Iterate through the list of payments and add all the decimal values of the payments from
ccrz.cc_hk_Invoice.PARAM_INVOICE_PAYMENTS.
i. These should be rounded to two decimal points using the HALF_EVEN rounding method at each
step.
7. Leverage ccrz.cc_CallContext.effAccountId to query for the Credit Limit associated with
the current user’s account.
8. Increase the Account’s Credit Limit by the amount paid for the current invoice.
9. Return the Map<String,Object> data received from the call to super.applyPayment
currentAccount.CreditLimit__c += totalAmountPaid; ⑧
update currentAccount;
return retData; ⑨
}
}
89
3. On the left side bar, near the middle, under "Integrations" section, click on Orders.
4. Scroll down to the Invoice Hook section and set the "Invoice Hook API Class" to
c.ccTraining_hk_Invoice
Make sure you have cc_hlpr_GenericHKFactory set under the "Invoice Hook Factory
Class." This is required for all legacy hook extension classes to be properly insantiated
inside the CloudCraze Managed Package.
90
4.13. Payment
Adding a new payment type is a combination of configuration and development that involves the following:
1. Configure the new payment type in the Configuration Module and Settings menu
2. Create VisualForce Pages and Apex Classes for the new payment type functionality.
CloudCraze, by default has an existing set of configurations for purchase orders. It’s encouraged for
suscribers to use this as a reference when implementing the functionality for a new payment type.
You can access the above by navigating to CC Admin → Configuration Modules and then selecting Payment
PO.
Note that the configuration modules options is not storefront specific and hence only accessible at the
Global Settings level.
Configuration Setup
The corresponding Configuration Settings for the module described above is as follows.
Pay (pmt_po.pay)
Configuration setting that indicates the VisualForce Page leveraged for processing Purcahse Order
payment type in the Checkout flow.
Edit (pmt_po.edit)
Configuration setting that indicates the VisualForce Page leveraged for editing existing Purcahse Order
payment information via the My Account page.
91
New (pmt_po.new)
Configuration setting that indicates the VisualForce Page leveraged for saving new Purchase Order
payment information via the My Account page
Since these settings are provided by default in CloudCraze, they are global and can hence be accessed as
follows
Navigate to CC Admin → Configuration Setting and then select Payment PO from the dropdown options
for Module.
Conversely, this can also be accessed at the storefront level if the Display Global field on the
configuration settings page is left checked.
4.13.1. Development
The payment integration varies for most clients. However, the expectation in CloudCraze is to trigger a
payment event upon successful completion of the callout. This payment event accepts a payload which
typically contains the information you’ll be needing to persist e.g (card token). This event is shown in the
image below.
Use Case
1. Create a configuration module for the new payment type
2. Create a VisualForce page for the new payment type
3. Create a configuration setting for the VisualForce page
92
3. Select Configuration Modules secfrom the menu on the left hand side.
i. As previously mentioned, we’ll want to use th existing setup as a reference for our new type.
ii. You can open a separate browser window with the existing settings for purchase orders to verify
your work.
4. Select the New button in the Configuration Module section and fill out the following and save the
changes:
Name
Payment CC
API Name
pmt_cc
5. Select the New button in the Configuration Metadata section and fill out the following:
i. Metadata for Pay page
Name
Pay page
API Name
pay
Description
VisualForce Page leveraged for processing Credit Card payments in the Checkout flow
Externally Safe
FALSE (Leave it unchecked)
Name
New page
API Name
new
Description
VisualForce Page leveraged for saving new Credit Card payment information via the My
Account page
Externally Safe
FALSE (Leave it unchecked)
Name
93
Edit page
API Name
edit
Description
VisualForce Page leveraged for editing existing credit card payment information via the My
Account page
Externally Safe
FALSE (Leave it unchecked)
b. Create a VisualForce page for processing credit card payments for the Checkout flow
i. In your SalesForce Org, click the Developer Console link under the dropdown menu under your
name - this should be on the top right hand corner on the page.
ii. A new window should pop-up.
iii. In the Developer Console menu, select: File → New → VisualForce Page
iv. In the popup window (New Apex Page), enter ccTrainingPaymentCC as your page name and click
ok.
v. Remove the boilerplate code (delete the contents of the page).
vi. Enter the following: note that the code below was added for your convenience. As previously
mentioned you can use the existing pages for purchase orders as a starting point. The VisualForce
pages for the purchase order payment type are available in your Salesforce org by default.
94
<option value="03">03</option>
<option value="04">04</option>
<option value="05">05</option>
<option value="06">06</option>
<option value="07">07</option>
<option value="08">08</option>
<option value="09">09</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
</select>
Y:<select name="expirationYear"> ⑤
<option value="2015">2015</option>
<option value="2016">2016</option>
<option value="2017">2017</option>
<option value="2018">2018</option>
<option value="2019">2019</option>
<option value="2020">2020</option>
</select></p>
</fieldset>
<p class="two_buttons">
<div class="right">
<input type="button" class="button makeCCPayment" id="btnMakeCCPayment"
data-id="newCCForm" value="{{pageLabelMap 'CCTrainingPayment_MakePayment'}}" /> ⑥
<input type="hidden" name="accountType" value="cc"/> ⑦
</div>
</p>
</form>
</div>
</div>
</script>
<script>
⑧
jQuery(function($)
{
CCRZ.models.PaymentsCCModel = CCRZ.CloudCrazeModel.extend(); ⑨
CCRZ.views.PaymentsCCView = CCRZ.CloudCrazeView.extend({ ⑩
viewName : "PaymentsCCView",
managedSubView : true,
templateDesktop : CCRZ.util.template("PaymentCC-Both"), ⑪
templatePhone : CCRZ.util.template("PaymentCC-Both"), ⑪
init : function(options)
{
this.selector = options.selector;
this.render();
CCRZ.pubSub.trigger('action:paymentViewInit',this);
},
events:
{
"click .makeCCPayment" : "makeCCPayment" ⑫
},
validateInfo: function(formName, fieldData)
{
$("#"+formName).validate({
invalidHandler: function(event, validator)
{
CCRZ.handleValidationErrors(event, validator, 'ccPayment-
95
messagingSection-Error', false);
},
rules:
{
accountNumber : { required: true, minlength: 4 }
},
messages:
{
accountNumber : { required : 'Card number is required', minlength:
'Card number is not valid' }
},
errorPlacement: function(error, element) {
}
});
return $("#"+formName).valid();
},
parentPreRender : function()
{
//Look for the instantiated iframe
//Detach it from the DOM
//Re-render the view but don't create the iframe
//Re-attach the iframe as appropriate in the rendered view
},
renderDesktop : function()
{
this.setElement(this.selector);
this.data={};
this.$el.html(this.templateDesktop(this.data));
},
renderPhone : function()
{
this.setElement(this.selector);
this.data={};
this.$el.html(this.templatePhone(this.data));
},
makeCCPayment : function(event) ⑬
{
var formName = $(event.target).data("id");
var formData = form2js(formName, '.', false, function(node) {}, false);
if (this.validateInfo(formName, formData))
{
var paymentData = formData;
paymentData.token = 'ABCD123456789'; ⑭
paymentData.accountNumber = ''; ⑮
CCRZ.pubSub.trigger('action:processPayment', paymentData); ⑯
}
}
});
CCRZ.pubSub.trigger('action:paymentViewReady','cc',function(options)
{
CCRZ.payment = CCRZ.payment||{views:{}};
96
});
</script>
</apex:page>
① Handlebars template ID defined here should match the value referenced in the templateDesktop and
templatePhone properties in the Javascript section on this page.
② List of payment options. This is hard-coded here for convenience. In practice, you’ll ideally want to
save these values in a custom metadata type and subsequently fecth them via the logic in your Apex
controller.
⑤ List of expiration years. This is hard-coded here for convenience. In practice, you’ll ideally want to save
these values in a custom metadata type and subsequently fecth them via the logic in your Apex
controller.
⑧ Javascript section
⑨ Backbone model leveraged on this page.
⑩ Backbone view leveraged on this page.
⑪ Template ID described in step 1 is referenced here
⑫ Logic that binds the click event of the button to the Javascript function that initiates the payment
process.
97
make our settings storefront specific, let’s do the following.
1. In your Salesforce Org, select CC Admin from the main tab.
2. Change the selection from Global Settings to DefaultStore.
i. This is accessible in the dropdown options list on the right hand side.
3. Select the Configuration Settings menu from the menu on the left side.
4. Click on New button to create a new Configuration Setting and enter the following:
Module
Payment CC
Configuration
Pay Page
Page
all
Attribute Value
c__ccTrainingPaymentCC
e. Add the new VisualForce page to the list of acceptable pages for payment processing (White Listing)
While still in the storefront specific configuration settings in CC Admin, do the following:
1. Select Payment from the the dropdown list for Module
i. This should filter the list of configuration settings.
2. Search for the configuration setting named White List from this list.
3. Select the corresponding Override link for this setting - this should be on the right hand side.
4. Enter the following in the popup window
Attribute Value
Add ,c__ccTrainingPaymentCC at the end of existing value.
Attribute Value
Add ,cc at the end of the existing value. The new value should be po,cc
98
5. Leave other fields as is and save your changes.
g. Create and Activate a new Configuration Cache
i. Select Global Settings as described in the previous steps above.
ii. Select Configuration Cache Management from the menu on the left hand side.
iii. Select Build New, wait for a few seconds and then select Refresh List.
h. A new configuration cache should have been built. Click on Activate to enable it.
i. Verify your Changes
i. Go through the checkout flow and place an order with the newly configured payment page
99
Chapter 5. Appendix
100
Figure 6. How Backbone Works in Cloudcraze
101
Figure 7. Extending the Cloudcraze View
102
103
5.4. Order Hook Override
Order extension to demonstrate the many steps involved in the order placement and how ccLog works. To
test, place an order with the ccLog enabled in the URL
ccrz.ccLog.log(System.LoggingLevel.Info,
'M:E:ccTraining_hk_Order:place:currentStep',
currentStep); ③
if (ccrz.cc_hk_Order.STEP_END.equals(currentStep)) { ④
ccrz__E_Order__c currentOrder =
(ccrz__E_Order__c)inputData.get(ccrz.cc_hk_Order.PARAM_ORDER);
ccrz.ccLog.log(System.LoggingLevel.DEBUG,
'M:X:ccTraining_hk_Order:place:currentOrder',
currentOrder);
}
return inputData; ⑤
}
}
To do: Better description Updates needed in CCAdmin Better Code commenting Improve the case for
autocomplete (e.g. extend what is searched or returned, etc.)
104
global with sharing class ccTraining_hk_Catalog extends ccrz.cc_hk_Catalog {
global override Map<String, Object> filterCatalogData(Map<String, Object> inputData) {
ccrz.ccLog.log(System.LoggingLevel.Info,
'M:E:ccTraining_hk_Catalog:filterCatalogData'); ①
ccrz.ccLog.log(System.LoggingLevel.Info,
'M:X:ccTraining_hk_Catalog:filterCatalogData:filteredSkus',
filteredSkus); ③
ccrz.ccLog.log(System.LoggingLevel.Info,
'M:E:ccTraining_hk_Catalog:autoComplete:searchString',
searchString); ④
return super.autoComplete(inputData);
}
}
Handlebars Overrides:
105
• DataLoader CLI
• Jenkins and Dataloader.jar
• SFDX
• Heroku Connect
• DBAmp
• How to upgrade and patch
• Deploying code, custom themes
• SalesForce limits
Article sections start at level 1 and can be nested up to four levels deep. [1: An example footnote.]
And now for something completely different: monkeys, lions and tigers (Bengal and Siberian) using the
alternative syntax index entries. Note that multi-entry terms generate separate index entries.
106
Index
B
Backbone, 100
Big cats
Lions, 106
Tigers
Bengal Tiger, 106
Siberian Tiger, 106
C
CloudCraze Architecture, 100
E
Example index entry, 106
H
Hook
order, 104
M
monkeys, 100, 106
O
Order
hook, 104
107