Product Implementation
The APEX Platform provides a standardized product flow that helps you surface your products to users. This guide explains how to implement the necessary components for successful integration.
Product Flow
When a tenant initiates a new product flow, the system goes through several steps to display content to the user:
-
Initiation - The tenant initiates the start request to the platform, which is used by all tenants to surface products.
-
Legal - The platform retrieves data from the product catalog for the specified product and checks for any legal requirements before proceeding.
-
Start - The platform communicates with your product by calling the Start endpoint based on your product's configured base URL.
-
Verify Inputs - If your Start endpoint indicates that user inputs are required, the platform displays your web component to collect data from the user, then sends this data to your Verify Inputs endpoint.
-
Quoting - The platform generates a quote for the product. If the user has the appropriate contract permissions, the flow continues; otherwise, the quote displays for user input.
-
Payment - After quote acceptance, the platform collects funds from the user through the specified payment method (debit order or credit card).
-
Process - Once the previous steps complete successfully, the platform calls your Process endpoint to surface the final purchased product as a web component.
Product API Implementation
Your product needs to implement specific endpoints to be compatible with the platform. The APEX Platform library provides predefined response types to help you get your product running quickly.
Start Endpoint
The Start endpoint initializes your product. The platform expects one of two responses:
ShowUIStartResponse
- When user input is requiredContinueStartResponse
- When no user input is needed
Examples
When no user input is required:
- C#
- Java
- Python
public async override Task<ActionResult<StartResponse>> Start([FromBody] ProductFlowInstanceStartModel input)
{
return new ContinueStartResponse
{
ProductDescription = "PRODUCT NAME"
};
}
@Override
public CompletableFuture<ActionResult<StartResponse>> start(ProductFlowInstanceStartModel input) {
ContinueStartResponse response = new ContinueStartResponse();
response.setProductDescription("PRODUCT NAME");
return CompletableFuture.completedFuture(new ActionResult<>(response));
}
async def start(self, input: ProductFlowInstanceStartModel) -> ActionResult[StartResponse]:
return ActionResult(ContinueStartResponse(
product_description="PRODUCT NAME"
))
When user input is required:
- C#
- Java
- Python
public async override Task<ActionResult<StartResponse>> Start([FromBody] ProductFlowInstanceStartModel input)
{
return new ShowUIStartResponse
{
ElementName = INPUT_ELEMENT_NAME,
ElementUrl = INPUT_ELEMENT_URL
};
}
@Override
public CompletableFuture<ActionResult<StartResponse>> start(ProductFlowInstanceStartModel input) {
ShowUIStartResponse response = new ShowUIStartResponse();
response.setElementName(INPUT_ELEMENT_NAME);
response.setElementUrl(INPUT_ELEMENT_URL);
return CompletableFuture.completedFuture(new ActionResult<>(response));
}
async def start(self, input: ProductFlowInstanceStartModel) -> ActionResult[StartResponse]:
return ActionResult(ShowUIStartResponse(
element_name=INPUT_ELEMENT_NAME,
element_url=INPUT_ELEMENT_URL
))
Validate Inputs Endpoint
When a user completes inputs from a ShowUIStartResponse
, the inputs are posted to the Validate Inputs endpoint. You should validate the inputs and return an appropriate response. If validation fails, the platform will redisplay the inputs screen.
Example
- C#
- Java
- Python
public async override Task<ActionResult<ValidateInputResponse>> ValidateInputs([FromBody] ProductFlowInstanceInput<HelloWorldInput> input)
{
return new ValidateInputResponse()
{
Succesful = true
};
}
@Override
public CompletableFuture<ActionResult<ValidateInputResponse>> validateInputs(ProductFlowInstanceInput<HelloWorldInput> input) {
ValidateInputResponse response = new ValidateInputResponse();
response.setSuccessful(true);
return CompletableFuture.completedFuture(new ActionResult<>(response));
}
async def validate_inputs(self, input: ProductFlowInstanceInput[HelloWorldInput]) -> ActionResult[ValidateInputResponse]:
return ActionResult(ValidateInputResponse(
successful=True
))
Process Endpoint
After successfully completing all previous steps, the platform calls the Process endpoint on your product API. This endpoint must return either a ContinueProcessResponse
or a ShowUIProcessResponse
based on the data received.
Examples
Simple continuation response:
- C#
- Java
- Python
public async override Task<ActionResult<ProcessResponse>> Process([FromBody] ProductFlowInstanceProcessModel<HelloWorldInput> input)
{
return new ContinueProcessResponse();
}
@Override
public CompletableFuture<ActionResult<ProcessResponse>> process(ProductFlowInstanceProcessModel<HelloWorldInput> input) {
return CompletableFuture.completedFuture(new ActionResult<>(new ContinueProcessResponse()));
}
async def process(self, input: ProductFlowInstanceProcessModel[HelloWorldInput]) -> ActionResult[ProcessResponse]:
return ActionResult(ContinueProcessResponse())
Response with UI component:
- C#
- Java
- Python
public async override Task<ActionResult<ProcessResponse>> Process([FromBody] ProductFlowInstanceProcessModel<HelloWorldInput> input)
{
return new ShowUIProcessResponse
{
Attributes = { { "name", input.Input.Data.Name } },
ElementName = FINAL_ELEMENT_NAME,
ElementUrl = FINAL_ELEMENT_URL,
Body = ""
};
}
@Override
public CompletableFuture<ActionResult<ProcessResponse>> process(ProductFlowInstanceProcessModel<HelloWorldInput> input) {
ShowUIProcessResponse response = new ShowUIProcessResponse();
Map<String, String> attributes = new HashMap<>();
attributes.put("name", input.getInput().getData().getName());
response.setAttributes(attributes);
response.setElementName(FINAL_ELEMENT_NAME);
response.setElementUrl(FINAL_ELEMENT_URL);
response.setBody("");
return CompletableFuture.completedFuture(new ActionResult<>(response));
}
async def process(self, input: ProductFlowInstanceProcessModel[HelloWorldInput]) -> ActionResult[ProcessResponse]:
return ActionResult(ShowUIProcessResponse(
attributes={"name": input.input.data.name},
element_name=FINAL_ELEMENT_NAME,
element_url=FINAL_ELEMENT_URL,
body=""
))
Handle Error Endpoint
When an error occurs during the product flow process, the platform calls your product's HandleError endpoint. You can choose to display a custom error UI or use the platform's default error message.
Examples
Using platform's default error message:
- C#
- Java
- Python
public async override Task<ActionResult<HandleErrorResponse>> HandleError([FromBody] ProductFlowInstanceHandleErrorModel errorInput)
{
return new ContinueHandleErrorResponse();
}
@Override
public CompletableFuture<ActionResult<HandleErrorResponse>> handleError(ProductFlowInstanceHandleErrorModel errorInput) {
return CompletableFuture.completedFuture(new ActionResult<>(new ContinueHandleErrorResponse()));
}
async def handle_error(self, error_input: ProductFlowInstanceHandleErrorModel) -> ActionResult[HandleErrorResponse]:
return ActionResult(ContinueHandleErrorResponse())
Displaying custom error UI:
- C#
- Java
- Python
public async override Task<ActionResult<HandleErrorResponse>> HandleError([FromBody] ProductFlowInstanceHandleErrorModel errorInput)
{
return new ShowUIHandleErrorResponse
{
ElementName = ERROR_ELEMENT_NAME,
ElementUrl = ERROR_ELEMENT_URL,
Attributes = { { "error-status", errorInput.ProductFlowInstanceStatus }},
Body = ""
};
}
@Override
public CompletableFuture<ActionResult<HandleErrorResponse>> handleError(ProductFlowInstanceHandleErrorModel errorInput) {
ShowUIHandleErrorResponse response = new ShowUIHandleErrorResponse();
Map<String, String> attributes = new HashMap<>();
attributes.put("error-status", errorInput.getProductFlowInstanceStatus());
response.setAttributes(attributes);
response.setElementName(ERROR_ELEMENT_NAME);
response.setElementUrl(ERROR_ELEMENT_URL);
response.setBody("");
return CompletableFuture.completedFuture(new ActionResult<>(response));
}
async def handle_error(self, error_input: ProductFlowInstanceHandleErrorModel) -> ActionResult[HandleErrorResponse]:
return ActionResult(ShowUIHandleErrorResponse(
element_name=ERROR_ELEMENT_NAME,
element_url=ERROR_ELEMENT_URL,
attributes={"error-status": error_input.product_flow_instance_status},
body=""
))
Attributes and Body Properties
When using ShowUI responses, you can customize the component rendering with:
- Body - A JSON string containing data to pass to your custom web component
- Attributes - Key-value pairs set as attributes for your custom web component
Example
- C#
- Java
- Python
return new ShowUIProcessResponse
{
Attributes = { { "custom-attr", "value" } },
ElementName = "custom-component"
};
ShowUIProcessResponse response = new ShowUIProcessResponse();
Map<String, String> attributes = new HashMap<>();
attributes.put("custom-attr", "value");
response.setAttributes(attributes);
response.setElementName("custom-component");
return response;
return ShowUIProcessResponse(
attributes={"custom-attr": "value"},
element_name="custom-component"
)
This generates HTML like:
<custom-component custom-attr="value"></custom-component>
Micro Front Ends
Your product also needs to include Micro Front Ends as custom web components. The platform renders these components on the tenant side based on responses from your product API.
Inputs Micro Front End
The inputs micro front end collects additional information from users. It can consist of multiple pages and have its own web services to improve user experience.
The micro front end properties are populated by the API using the attributes property. The body of the custom web component can also pass complex data objects as a JSON string.
When input collection is complete, the component must raise a custom event to notify the product flow:
let inputComplete = new CustomEvent('capability-input-complete', {
detail: {
data: {}
},
bubbles: true,
composed: true
});
this.dispatchEvent(inputComplete);
Product Micro Front End
The final step in the product flow calls the Process endpoint of your API and passes any collected input data.
Your API can then set various attributes and complex data as the body of your custom web component, allowing you to render the final product to the client.
For a sample implementation using LitElement, refer to the samples project.