The Function Execution Logic in Shoplazza provides developers with the ability to customize and extend key platform functionalities by executing custom logic during specific workflows.
Below is a step-by-step explanation of how a function modifies product prices during the cart and checkout process.
Best Practice: Implementing a Cart Transform Function
To illustrate how Function Execution Logic works in practice, let’s walk through a Cart Transform Function implementation. This will serve as a step-by-step tutorial on how to develop, deploy, and test a pricing adjustment function.
1. Input Protocol
When a customer triggers an operation, Shoplazza will pass the shopping cart data (including product details, original prices, metafields, etc.) to the Function.
Example Input Data
{
"cart": {
"line_items": [
{
"product": {
"product_id": "1231",
"variant_id": "1231",
"price": "10.00",
"title": "test product",
"metafields": [
{
"namespace": "custom-option",
"key": "adjust-10-price",
"value": "true"//Set the value to verify if need to adjust the price
}
]
},
"id": "1",
"quantity": 1,
"properties": "{\"Color\":\"Red\"}"
}
]
}
}
2. Output Protocol
The function must return an operations object which adhere Output protocol.
Example Output Data
{
"operations": {
"update": [
{
"id": "1",
"price": {
"adjustment_fixed_price": "20.00" // The adjusted new price (overwrites the original price)
}
}
]
}
}
Implement the Function
Once the function receives the input data, it analyzes the product metadata (metafields) and determines whether the price needs to be modified.
The following functions adjust cart item prices by adding 10 to the original price when a specific metafield condition is met.
- Checks if
metafield.value
is"true"
before triggering price adjustment.- Calculates the new price based on the original price (
original price + 10
).
JavaScript Function Example
// 上传到function时,需要运行; 本地测试时,需要注释掉
// Read input from stdin
const input = readInput();
// Call the function with the input
const result = run(input);
// Write the result to stdout
writeOutput(result);
// Read input from stdin
function readInput() {
const chunkSize = 1024;
const inputChunks = [];
let totalBytes = 0;
// Read all the available bytes
while (1) {
const buffer = new Uint8Array(chunkSize);
// Stdin file descriptor
const fd = 0;
const bytesRead = Javy.IO.readSync(fd, buffer);
totalBytes += bytesRead;
if (bytesRead === 0) {
break;
}
inputChunks.push(buffer.subarray(0, bytesRead));
}
// Assemble input into a single Uint8Array
const { finalBuffer } = inputChunks.reduce((context, chunk) => {
context.finalBuffer.set(chunk, context.bufferOffset);
context.bufferOffset += chunk.length;
return context;
}, { bufferOffset: 0, finalBuffer: new Uint8Array(totalBytes) });
return JSON.parse(new TextDecoder().decode(finalBuffer));
}
// Write output to stdout
function writeOutput(output) {
const encodedOutput = new TextEncoder().encode(JSON.stringify(output));
const buffer = new Uint8Array(encodedOutput);
// Stdout file descriptor
// var ret = JSON.parse(encodedOutput);
const fd = 1;
Javy.IO.writeSync(fd, buffer);
}
// function执行业务逻辑
function run(input) {
const runResult = {
operation: {
update: []
}
};
input.cart.line_items.forEach((lineItem) => {
lineItem.product.metafields.forEach((metafield) => {
// key是自己定义的metafield名。如果有同名的key,可用namespace用于区分
if (metafield.namespace === 'ymq-option' && metafield.key === 'add-price-100') {
runResult.operation.update.push({
id: lineItem.id,
price: {
adjustment_fixed_price: '100' // 设置定制的价格100元
}
});
}
});
});
return runResult;
}
Rust Function Example
use serde::{Deserialize, Serialize};
use std::io::{self, Read, Write};
#[derive(Debug, Deserialize)]
struct Input {
cart: Cart,
}
#[derive(Debug, Deserialize)]
struct Cart {
line_items: Vec<LineItem>,
}
#[derive(Debug, Deserialize)]
struct LineItem {
id: String,
price: String,
quantity: u32,
product: Product,
}
#[derive(Debug, Deserialize)]
struct Product {
product_id: String,
variant_id: Option<String>,
product_title: Option<String>,
metafields: Vec<Metafield>,
}
#[derive(Debug, Deserialize)]
struct Metafield {
namespace: String,
key: String,
}
#[derive(Debug, Serialize)]
struct Output {
operation: Operation,
}
#[derive(Debug, Serialize)]
struct Operation {
update: Vec<UpdateItem>,
}
#[derive(Debug, Serialize)]
struct UpdateItem {
id: String,
price: PriceUpdate,
}
#[derive(Debug, Serialize)]
struct PriceUpdate {
adjustment_fixed_price: String,
}
fn main() {
let input = read_input();
let result = run(input);
write_output(&result);
}
fn read_input() -> Input {
let mut buffer = String::new();
io::stdin().read_to_string(&mut buffer).expect("Failed to read input");
serde_json::from_str(&buffer).expect("Failed to parse JSON")
}
fn write_output(output: &Output) {
let json = serde_json::to_string(output).expect("Failed to serialize JSON");
io::stdout().write_all(json.as_bytes()).expect("Failed to write output");
}
fn run(input: Input) -> Output {
let mut updates = Vec::new();
for line_item in input.cart.line_items {
let mut adjusted_price = None;
for metafield in &line_item.product.metafields {
if metafield.namespace == "ymq-option" && metafield.key == "add-price-100" {
adjusted_price = Some("100".to_string());
}
}
if let Some(price) = adjusted_price {
updates.push(UpdateItem {
id: line_item.id,
price: PriceUpdate {
adjustment_fixed_price: price,
},
});
}
}
Output {
operation: Operation { update: updates },
}
}
Returning the Updated Cart Data
The function must return an operations object that contains the necessary modifications. In this case, it specifies that the product price should be increased by 10.00.
Example Output
{
"operations": {
"update": [
{
"id": "1",
"price": {
"adjustment_fixed_price": "10.00"
}
}
]
}
}
Expected Behavior: Applying the Changes to the Cart
Shoplazza processes the function’s response and applies the price adjustments to the cart. The final cart now reflects the modified pricing:
Updated Cart Data
{
"line_items": [
{
"product_id": "1231",
"variant_id": "1231",
"price": "20.00", // Original price 10.00 + Adjusted price 10.00
"title": "test product",
"quantity": 1,
"trunk_price": "10.00" // Original price before adjustment
}
]
}
Upload Function Code to Shoplazza via Create Function API
Create Function API allows developers to upload a compiled .wasm file along with the associated source code to the Shoplazza platform, registering it as an executable Function.
POST https://partners.shoplazza.com/openapi/2024-07/functions
curl --location --request POST 'https://{storename}.shoplazza.com/openapi/2024-07/functions' \
--header 'Access-Token: Access-Token' \
--header 'App-Client-Id: App-Client-Id' \
--form 'namespace="cart_transform"' \
--form 'name="cart transform template"' \
--form 'file=@"/Users/Projects/public/function-js/js_function.wasm"' \
--form 'source_code="function main() {
try {
writeOutput(run(readInput()))
} catch (e) {
console.error(e.name + \":\" + e.message + \";error stack:\" + e.stack)
}
}
main();
// Read input from stdin
function readInput() {
const chunkSize = 1024;
const inputChunks = [];
let totalBytes = 0;
// Read all the available bytes
while (1) {
const buffer = new Uint8Array(chunkSize);
// Stdin file descriptor
const fd = 0;
const bytesRead = Javy.IO.readSync(fd, buffer);
totalBytes += bytesRead;
if (bytesRead === 0) {
break;
}
inputChunks.push(buffer.subarray(0, bytesRead));
}
// Assemble input into a single Uint8Array
const { finalBuffer } = inputChunks.reduce((context, chunk) => {
context.finalBuffer.set(chunk, context.bufferOffset);
context.bufferOffset += chunk.length;
return context;
}, { bufferOffset: 0, finalBuffer: new Uint8Array(totalBytes) });
return JSON.parse(new TextDecoder().decode(finalBuffer));
}
// Write output to stdout
function writeOutput(output) {
const encodedOutput = new TextEncoder().encode(JSON.stringify(output));
const buffer = new Uint8Array(encodedOutput);
// Stdout file descriptor
// var ret = JSON.parse(encodedOutput);
const fd = 1;
Javy.IO.writeSync(fd, buffer);
}
// function执行业务逻辑
function run(input) {
const runResult = {
operation: {
update: []
}
};
input.cart.line_items.forEach((lineItem) => {
lineItem.product.metafields.forEach((metafield) => {
// key是自己定义的metafield名。如果有同名的key,可用namespace用于区分
if (metafield.namespace === '\''custom-option'\'' && metafield.key === '\''adjust-10-price'\'') {
runResult.operation.update.push({
id: lineItem.id,
price: {
adjustment_fixed_price: '\''10'\'' // 设置定制的价格10元
}
});
}
});
// product_id add price $1
if (lineItem.product.product_id === '\''d69a73db-b456-4c10-a9ea-21408410bb19'\'') {
runResult.operation.update.push({
id: lineItem.id,
price: {
adjustment_fixed_price: '\''10'\''
}
});
}
// add price $10 when product_id = product_id
if (lineItem.product.variant_id === '\''bb49fa7d-4b7e-4240-8d2b-1390d0f84b8d'\'') {
runResult.operation.update.push({
id: lineItem.id,
price: {
adjustment_fixed_price: '\''20'\''
}
});
}
// add $price $12 when product_title = '\''i am test product title'\''
if (lineItem.product.product_title === '\''复制复制function-商品3'\'') {
runResult.operation.update.push({
id: lineItem.id,
price: {
adjustment_fixed_price: '\''30'\''
}
});
}
// add $price $13 when price = '\''1024'\''
if (lineItem.price === '\''1024'\'') {
runResult.operation.update.push({
id: lineItem.id,
price: {
adjustment_fixed_price: '\''40'\''
}
});
}
// add price $15 when quantity >= 100
if (lineItem.quantity >= 100) {
runResult.operation.update.push({
id: lineItem.id,
price: {
adjustment_fixed_price: '\''15'\''
}
});
}
});
return runResult;
}
module.exports = { run };"'
This request uploads the compiled .wasm file and the corresponding source code to Shoplazza. Upon successful upload, a function_id will be returned, which is required for further operations such as binding the function to a specific business scenario.
Register Function to Cart Price Adjustment
The Function Registration API allows developers to bind an uploaded function to the cart price adjustment process in Shoplazza. Once registered, Shoplazza will automatically execute the function when a buyer adds items to the cart or proceeds to checkout.
POST https://test-store.shoplazza.com/openapi/2024-07/function/cart-transform
curl --location --request POST 'https://{storename}.myshoplaza.com/openapi/2024-07/function/cart-transform' \
--header 'Access-Token: Access-Token' \
--header 'App-Client-Id: App-Client-Id' \
--header 'Content-Type: application/json' \
--data-raw '{
"function_id": "485316634840455963",
"block_on_failure": false,
"input_query": "{\"product_metafields_query\":[{\"namespace\":\"you_metafield_namespace\",\"key\":\"you_metafield_key\"}]}"
}'
Execute Function Logic
- When a customer adds items to the cart or proceeds to checkout, Shoplazza automatically executes the bound function.
- The function recalculates the price and updates the order accordingly, allowing users to view the adjusted prices in their shopping cart.