How To Call an AWS Lambda Function To Perform Customizations
This example demonstrates how you can hook into the SCP system and execute customizations. Here we'll be creating a Site Function and telling it to call an AWS Lambda function whenever an order is placed. You can create site functions that hook into hundreds of application events in SCP. The site function passes information about the entity being manipulated and lets you perform actions based on it. This is a "web hook" concept, but at a very fine-grained level.
For this example, our use case is, whenever a new order is placed, we wish to add a flag to prior orders that match the new order's delivery email address and products. The flag in this case simply indicates that there is a new order from the same email address, which can be useful for other processing and logic to refer to.
The details of what we are doing are not so important. This example simply serves to demonstrate how you can hook into the SCP system and add custom functionality.
We'll create a site function that invokes an AWS Lambda function whenever orders are updated. It sends the Lambda function information about the prior state of the order, and the new state of the order. The Lambda function examines the order to see if it is just now being placed by the customer. If it is, it sends API requests back to SCP to pull out any prior orders that have a matching delivery email address and matching products. If there is a prior order, it sends another API request to add an Order Setting to the prior order, serving as a flag in the system indicating there is a newer order from the same email address.
Create the AWS Lambda FunctionCreate an API User in SCP that the Lambda Function Can UseProgram the AWS Lambda FunctionCreate a Site Function in SCP To Communicate With Lambda
Create the AWS Lambda Function
First we'll create the AWS Lambda function where out custom logic will live. If you are unfamiliar with AWS Lambda, there are a numerous resources and examples out there to learn more about it. You can create the function within any AWS account - it does not need to be related to your SCP system. Within the function we'll add credentials allowing it to send API requests back to SCP. And later, within SCP, we'll create a site function that calls the Lambda function whenever an order is updated.
Within your AWS account follow these steps to set up the Lambda function and an IAM user that is allowed to invoke it. Note that since these steps are done within AWS, the process may have changed slightly from the time of this writing.
Create a Lambda Function in the US-East-1 region
On the Lambda Function's details screen, Select "Copy ARN" and save the ARN in your notes for later steps below.
Go to the IAM interface to create a new user with access to invoke the function:
Create a new User
Select no for console access
For permissions, select "Attach policies directly"
Choose "Create Policy"
For the policy, click to edit it in JSON
Paste in this JSON, replacing the resource ARN with the ARN of the new lambda function you copied above:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "lambda:InvokeFunction" ], "Resource": [ "arn:aws:lambda:us-east-1:000000000000:function:xxxxxxxxxxxxxxxxxxxx" ] } ] }
Give the new policy a name like "my-lambda-functions"
Note later you can use this same user with other lambda functions by adding them to the permissions policy list of ARNs
Back on the screen where you are creating the user, refresh the permissions policies, and select the new policy you just created
Add an access key to the new user:
Now find the user under IAM -> Users, click into it and select "Security Credentials"
Under Access Keys select Create Access Keys. Select "Other" on the next screen
Create the access key and make a note of it and the secret key for the next step
Create an API User in SCP that the Lambda Function Can Use
In the administrator in SCP we need an API User that can query orders, order items, and save order settings. The Lambda Function will use this account to make API requests back to SCP.
Under Operations -> API Users, click the plus icon to create a new user.
For status enter ACTIVE
We recommend adding a Note to explain the purpose of the user. Eg "Used by the AWS Lambda Function to add a flag on prior orders."
Click Save Changes
Now that the user is created you need to add Api Roles to allow it to do things.
Go into the new user's details by clicking "Edit New Record"
From the user's three-dots menu, click Api Roles, and then click the plus icon to add a new role for the user
Select "Order Settings Editor" for the role. This role allows the user to create and update order settings, which is how we will set flags on the prior orders.
Repeat the above process and add "Orders Viewer" and "Order Items Viewer" to the API User's roles. We'll also need these roles.
Create the user's encoded basic authentication token
SCP supports basic authentication for its API calls. To use basic authentication you simply need to Base-64 encode the user's user name and password in this form: username:password
So for example, if your user's user name is "FHOWIUHQOWIBNF" and password is "EFBNGWEBUVWEINROWEINWIENGOFNOD", you would need to Base-64 encode this string: "FHOWIUHQOWIBNF:EFBNGWEBUVWEINROWEINWIENGOFNOD"
You can use any number of tools to do the Base-64 encoding, including some available online. For this example the result is this string: RkhPV0lVSFFPV0lCTkY6RUZCTkdXRUJVVldFSU5ST1dFSU5XSUVOR09GTk9E
Save the encoded value in your notes for later steps below.
NOTE: you could also do this encoding in the Lamdba Function itself, and paste the API user's user name and password into the function. We favor encoding it first, as it is (slightly) more obscure if someone should happen to see the function's code.
Program the AWS Lambda Function
Next add the following code to the AWS Lambda function. This is the nuts and bolts of the task - as you can see it first checks to see if the order is being placed. Next it checks to see if the order contains one of the products that we are concerned about, by making a query for the order items under the order. If it does, it loads prior orders that match the new order's delivery email address, and that also contain one of the same products. Finally for each prior order it sends another API request back to SCP to add an order setting representing the flag we want.
You can paste this code directly into the AWS console under the function's Code tab, or follow their instructions on other ways to update the function's code such as uploading a ZIP file.
x
const baseUrl = "https://YOUR-SCP-DOMAIN-NAME.com/api";
const basicAuthToken = "YOUR-API-USERs-ENCODED-BASIC-AUTH-TOKEN";
export const handler = async (event) => {
let orderId = event.data.entity.orderId;
let orderNumber = event.data.entity.orderNumber;
let deliveryEmail1 = event.data.entity.deliveryEmail1;
let changingToStatus = event.data.entity.status;
let existingStatus = event.data.existingDto.status;
let intFuncResponse = {};
try {
// Run only if the order is changing to PlacedOn status
if (changingToStatus !== existingStatus && changingToStatus === 'Placed') {
// Authentication headers common to all the api calls we are going to make to SCP
const apiCallHeaders = new Headers();
apiCallHeaders.append("Content-Type", "application/json");
apiCallHeaders.append("Authorization", "Basic " + basicAuthToken);
// Check that the new order is for one of the items and not a completely different item.
let newOrderItemsUrl = baseUrl + "/orderItem/v1/list";
let newOrderItemsData = {
"filterString": "orderId=='" + orderId + "'",
"additionalFilterString": "productCode=in=('PRODUCTCODE1','PRODUCTCODE2','PRODUCTCODE3')",
"size": 1
};
let newOrderItemsString = JSON.stringify(newOrderItemsData);
const newOrderItemsCallOptions = {
method: "POST",
headers: apiCallHeaders,
body: newOrderItemsString
};
const newOrderItemsResponse = await fetch(newOrderItemsUrl, newOrderItemsCallOptions);
console.info("Status from API call", newOrderItemsResponse.status);
const newOrderItemsJsonResponse = await newOrderItemsResponse.json();
console.info("Response from API call", newOrderItemsJsonResponse);
if (!newOrderItemsJsonResponse.data || !newOrderItemsJsonResponse.data.searchList
|| newOrderItemsJsonResponse.data.searchList.length == 0) {
console.log(`New order does not have one of the items.`);
intFuncResponse.statusCode = 200;
intFuncResponse.message='New order does not have one of the items.';
return intFuncResponse;
}
// Load any prior orders with the same delivery email and for the same items
let priorOrderUrl = baseUrl + "/order/v1/list";
let priorOrderData = {
"filterString": "deliveryEmail1=='" + deliveryEmail1 + "' and placedOn!=null and orderId!='" + orderId + "'",
"additionalFilterString": "orderItems.productCode=in=('PRODUCTCODE1','PRODUCTCODE2','PRODUCTCODE3') and status!='Cancelled'",
"size": 3
};
let priorOrderString = JSON.stringify(priorOrderData);
const priorOrderCallOptions = {
method: "POST",
headers: apiCallHeaders,
body: priorOrderString
};
const priorOrderResponse = await fetch(priorOrderUrl, priorOrderCallOptions);
console.info("Status from API call", priorOrderResponse.status);
const jsonResponse = await priorOrderResponse.json();
console.info("Response from API call", jsonResponse);
if (jsonResponse.data && jsonResponse.data.searchList && jsonResponse.data.searchList.length > 0) {
intFuncResponse.statusFromAPICall = priorOrderResponse.status;
intFuncResponse.statusCode = 200;
intFuncResponse.searchListLength = jsonResponse.data.searchList.length;
intFuncResponse.message = "";
// Loop through the prior orders
let previousOrderId = 0;
for (let i = 0; i < jsonResponse.data.searchList.length; i++) {
let priorOrder = jsonResponse.data.searchList[i];
if (previousOrderId !== priorOrder.orderId) { // the query is a join on order items, so there will be duplicates
console.log(`Prior order number: ${priorOrder.orderNumber}, Prior order id: ${priorOrder.orderId}`);
// Create or update an order setting for the prior order
let orderSettingUrl = baseUrl + "/orderSetting/v1/createOrUpdate";
let orderSettingData = {
"orderId": priorOrder.orderId,
"code": "ReplacedByNewOrder",
"name": "Replaced By New Order",
"settingType": "PRIVATE",
"valueType": "BooleanValue",
"settingValue": "1",
"extra1": orderNumber
};
let orderSettingString = JSON.stringify(orderSettingData);
const orderSettingCallOptions = {
method: "POST",
headers: apiCallHeaders,
body: orderSettingString
};
const orderSettingResponse = await fetch(orderSettingUrl, orderSettingCallOptions);
console.info("Status from order setting API call", orderSettingResponse.status);
intFuncResponse.message=intFuncResponse.message + "Processed order number " + priorOrder.orderNumber + ", ";
}
previousOrderId = priorOrder.orderId;
}
} else {
console.log(`No prior orders found`);
intFuncResponse.statusFromAPICall = priorOrderResponse.status;
intFuncResponse.statusCode = 200;
intFuncResponse.message='No prior orders found';
}
return intFuncResponse;
} else {
console.log(`Order status not changing to Placed`);
intFuncResponse.statusCode = 200;
intFuncResponse.message='Order status not changing to Placed';
return intFuncResponse;
}
} catch (e) {
console.error(e);
intFuncResponse.error = e;
return intFuncResponse;
}
};
Note the following about this Lambda Function:
For this example to work for you, replace "YOUR-SCP-DOMAIN-NAME" with the domain name you use for your SCP site, and replace "YOUR-API-USERs-ENCODED-BASIC-AUTH-TOKEN" with the basic auth token you created in the previous step.
In this example we are using Node.js 18, but you can use whatever runtime environment you like that lambda supports, including Java, Python, etc. SCP is agnostic and unaware of what goes on in your function.
Note that event.data.entity and event.data.existingDto are incoming parameters for the function. They hold information about the order that was just updates, and the prior state of the order, respectively. You can examine these two object to determine what actions to take in your function.
You should be able to test the function in the AWS console by creating a Test event. At this point it's not a bad idea to test it with a variety of incoming object to verify it is working the way it should. Note, this function does add order settings back into your SCP system. So to reset your system afterward, you can find the order settings from the main order settings grid, and delete them if you need to.
Create a Site Function in SCP To Communicate With Lambda
The last step is to register a Site Function in SCP. The site function will get exectuted each time an order is updated. After the order update is saved, we'll tell it to call out to our Lambda function to perform our custom processing. The function call will happen asynchronously. In other words, it will not interrupt the main processing of SCP. Typcially there may be a delay of a few seconds, or as much as one minute, after the event in SCP, before the site function is invokes. So it does not happen in "real time" but "near real time".
To create the site function:
Click the plus icon from the main Site Functions screen, which is found under the Operations -> Site Functions and Jobs.
For Function Event Type select ENTITY_EVENT for this example.
For Function Location select AWS_LAMBDA.
For Function Endpoint, enter the ARN for the lambda function (click "Copy ARN" from the function details screen).
For Authentication Type select AWS_CREDENTIALS.
For Key Value, enter the Access Key for the IAM user from the prior step above.
For Key Secret enter the "Secret access key" for the IAM user from the prior step above.
Here is a screenshot (without credentials):