Loyalty Customer Integration¶
Jumpmind Commerce supports extending the ICustomerService
through Microservice Endpoint implementations which allow the loyalty customers
to be maintained in a separate system from Jumpmind Commerce.
Important Notes¶
- Customers are part of customer groups. Jumpmind Commerce is built with
loyaltyandtaxExemptgroups for customers.
Add Loyalty Customer¶
The process of adding a new loyalty customer has been split into three different main steps. The amount of information
required has been reduced and is more configurable. The first screen only requires name and either email or phone
number. This is configurable in post_01_ctx-form-field.csv under loyaltySignupPhaseOne. Whichever of the two, email
or phone number, is not required in the first screen will be an optional field on the next screen.
On the second screen, various optional fields will be displayed as extra information that may be collected. This is also
configurable in post_01_ctx-form-field.csv under loyaltySignupPhaseTwo. Additionally, this screen can be skipped
entirely if the additional information is not desired. This is configurable in application-func.yml with
skipSecondSignupPhaseScreen.
Once the desired information is collected from the new loyalty customer, a prompt will appear to collect a loyalty
number. This number can either be scanned, entered manually, or automatically generated. If the number is automatically
generated, then the prompt will not appear. Whether the loyalty number is auto generated or not can be configured in
application-func.yml with generateLoyaltyNumberWhenEmpty. The method for how the loyalty number is generated can
also be configured in the generateLoyaltyNumber function in the CustomerSignupPhaseThreeState class.
After the loyalty number has been attached to the new loyalty customer, the new customer will select their communication preferences and be asked to agree with any terms and conditions provided. Once complete, the new loyalty customer information will be validated and saved.
Edit Loyalty Customer¶
When editing the information for an existing loyalty customer, a full form will be displayed with all the information.
By default, the only fields required on this form are the same fields that were required when the loyalty customer
originally signed up. The requirement of any fields in the form can be configured in post_01_ctx-form-field.csv under
loyaltyEdit.
Must Implement Endpoints¶
There are a couple required endpoints to extend for the Jumpmind Commerce to support loyalty customers from external systems. The ability to save, search, and find a customer are critical.
Save Loyalty Customer¶
To allow Jumpmind Commerce to call an external loyalty system to create customers the /rest/customer/save endpoint
must be extended.
Key Points¶
- If there is a difference between loyalty and tax-exempt customers from the systems needed or the structure of the request those mappings should be handled here.
- Separate the logic for the client that calls the third party system
- Separate the logic for the mapping to make testing easier
- It is possible to use the
ICustomerServiceto generate a customer id
Example¶
@Endpoint(path = RestApiSupport.REST_API_CONTEXT_PATH + "/customer/save", implementation = "ext-customer")
@RequiredArgsConstructor
public class ExternalSaveCustomerEndpoint {
private final ExternalCustomerClient client;
private final ExternalCustomerMapper mapper;
private final ICustomerService customerService;
public SaveCustomerResponse saveCustomer(SaveCustomerRequest request) {
CustomerModel customer = request.getCustomer();
if (StringUtils.isBlank(customer.getCustomerId())) {
// Should generate a customer id if one is not supplied by request
customer.setCustomerId(customerService
.generateCustomerId(GenerateCustomerIdRequest.builder()
.customerGroupId(request.getCustomerGroupId()).build()).getCustomerId());
}
var externalSaveCustomerRequest = mapper.toExternalSaveCustomerRequest(customer);
return mapper.fromExternalSaveCustomerRequest(client.saveCustomer(externalSaveCustomerRequest));
}
}
Search Customer¶
Jumpmind Commerce uses a standard SearchCustomerRequest object for all search lookups of a customer. These requests
will have criteria which are stored in a key value pair. The criteria key is provided from the FormFieldModel which is
typically populated via the post_01_ctx-form-field.csv base data file with a formId of loyaltyAdvancedSearch.
Key Points¶
- Search Customer is used for both the single search and advanced search
- To reduce the complexity of the endpoint break down the various search methods into difference classes
- Single Value Search is a static configuration for the initial search customer dialog before the advanced screen and must be accounted for in the search endpoint
Example¶
Search Endpoint¶
@Endpoint(path = REST_API_CONTEXT_PATH + "/customer/search", implementation = "ext-customer")
@RequiredArgsConstructor
public class ExternalSearchCustomerEndpoint {
private static final String CUSTOMER_GROUPED_ID = "customerGroupId";
private List<SearchCustomerFilter> searchCustomerFilterList;
public SearchCustomerResponse searchCustomer(SearchCustomerRequest request) {
if (!"loyalty".equals(request.getCriteria().getCriteria().get(CUSTOMER_GROUPED_ID))) {
// this will ignore every group but loyalty
return SearchCustomerResponse.builder().customers(List.of()).build();
}
try {
return searchCustomerFilterList.stream()
.map(filter -> filter.search(request))
.filter(Optional::isPresent)
.first()
.orElse(SearchCustomerResponse.builder()
.customers(List.of())
.success(true)
.summary(false)
.build());
} catch (ExternalOfflineException e) {
// SearchCustomerFilter implementations should throw an exception like this to indicate if a request could
// not be made
return SearchCustomerResponse.builder()
.customers(List.of())
.success(false)
.summary(false)
.build();
}
}
}
Single Value Search Customer¶
@Component
@RequiredArgsConstructor
public class ExternalSingleValueSearchCustomerFilter implements SearchCustomerFilter {
private final ExternalCustomerClient client;
private final ExternalCustomerMapper mapper;
public Optional<SearchCustomerResponse> search(SearchCustomerRequest request) {
if (!request.getCriteria().contains("singleSearchValue")) {
return Optional.empty();
}
String searchValue = request.getCriteria().getCriteria().get(SINGLE_SEARCH_VALUE).toString();
// run all special logic against searchValue to determine what type of search to perform
ExternalCustomerSearchRequest externalRequest; // populate with specific fields
return Optional.of(mapper.fromSearchCustomer(client.search(externalRequest)));
}
}
Advanced Search Customer¶
@Component
@RequiredArgsConstructor
public class ExternalAdvancedSearchCustomerFilter implements SearchCustomerFilter {
private final ExternalCustomerClient client;
private final ExternalCustomerMapper mapper;
public Optional<SearchCustomerResponse> search(SearchCustomerRequest request) {
ExternalCustomerSearchRequest externalRequest = mapper.toCustomerSearch(request.getCriteria());
return Optional.of(mapper.fromSearchCustomer(client.search(externalRequest)));
}
}
Find Customer By ID¶
Jumpmind Commerce will occasionally need to look up a customer by an ID. This ID is just a unique way that Jumpmind Commerce will refer to the customer. This ID does not need to be the primary key of the external system, but it must be unique in that system.
Key Points¶
- Find By ID requests will be made with the customer group id
- A not found customer should be returned as
null.
Example¶
@Endpoint(path = REST_API_CONTEXT_PATH + "/customer/findById", implementation = "ext-customer")
@RequiredArgsConstructor
public class ExternalSearchCustomerEndpoint {
private final ExternalCustomerClient client;
private final ExternalCustomerMapper mapper;
public FindCustomerByIdResponse findCustomerById(FindCustomerByIdRequest request) {
if (!"loyalty".equals(request.getCustomerGroupId())) {
// this will ignore every group but loyalty
return FindCustomerByIdResponse.builder().customerModel(null).build();
}
try {
var customer = mapper.fromExternalFindCustomerById(client.findCustomerById(request.getCustomerId()));
return FindCustomerByIdResponse.builder()
.customerModel(customer)
.build();
} catch (ExternalOfflineException e) {
// SearchCustomerFilter implementations should throw an exception like this to indicate if a request could
// not be made
return FindCustomerByIdResponse.builder()
.customerModel(null)
.build();
}
}
}