Embedded API Events
The Shopfront Embedded API has several events that you can subscribe to in order to add functionality to the POS. This document describes the events and what is expected to be returned from them.
Any event can return a promise.
Some object tables may not include all fields, please refer to the Typescript types for all fields
READY
This listener fires when the connection between Shopfront and the Embedded API is established and is ready to use.
Arguments
- None
Returns
- None
Ready
application.addEventListener("READY", () => ({
console.log("Integration Ready to Process");
}));FORMAT_INTEGRATED_PRODUCT
This listener fires whenever a product with its type set to integrated is added to the sale
Arguments
| Field | Type | Description |
|---|---|---|
| event | FormatIntegratedProductEvent | Contains the product that was added to the sale |
Returns
| Field | Type | Description |
|---|---|---|
| event | FormatIntegratedProductEvent | Contains the modified product |
FormatIntegratedProductEvent
| Field | Type | Description |
|---|---|---|
| product | FormattedSaleProduct | The modified product |
FormattedSaleProduct
| Field | Type | Description |
|---|---|---|
| uuid | string | The unique identifier of the product |
| type | string | The type assigned to the product |
| name | string | The product name |
| caseQuantity | number | The case quantity of the product |
| metaData | Record<string, unknown> | Extra data about the product |
Format Integrated Product
application.addEventListener("FORMAT_INTEGRATED_PRODUCT", event => {
const product = event.product;
if (
typeof product.additional === "object" &&
product.additional !== null &&
product.additional.my_integration === 1
) {
return {
product: {
...product,
metaData: {
...product.metaData,
customInfo: {
name: "myIntegration",
},
},
},
};
}
});
PAYMENT_METHODS_ENABLED
This allows you to both enable and disable payment methods on the finalise payment screen
Arguments
| Field | Type | Description |
|---|---|---|
| methods | Array<SellScreenPaymentMethod> | An array of all current payment methods |
Returns
- Array<SellScreenPaymentMethod>:
| Field | Type | Description |
|---|---|---|
| background_colour | string | The background colour of the payment method option |
| gateway_url | string | The URL that is navigated too when the method is selected |
| name | string | The name of the payment method |
| text_colour | string | The colour of the payment method's name |
| type | string | The type of payment method |
| uuid | string | The unique identifier of the payment method |
Payment Methods Enabled
//Return a custom payment method
const getPaymentMethod = async () => {
let customMethod: LocalPaymentMethod | undefined;
const methods = await application.database.all<LocalPaymentMethod>("paymentMethods");
for(let i = 0, l = methods.length; i < l; i++) {
if (methods[i].type === "custom" || methods[i].gateway_url === "https://mycustom.url") {
customMethod = methods[i];
}
}
return customMethod;
};
application.addEventListener("PAYMENT_METHODS_ENABLED", async methods => {
const method = await getPaymentMethod();
if (method) {
//Add the payment method
methods.push({
...method,
default_pay_exact: true,
type : "custom"
});
}
return methods;
});
REGISTER_CHANGED
This fires anytime the user, outlet or register changes
Arguments
| Field | Type | Description |
|---|---|---|
| event | RegisterChangedEvent | An object containing the current user, outlet and register id |
Returns
- None
RegisterChangedEvent
| Field | Type | Description |
|---|---|---|
| register | string | The current register id |
| outlet | string | The current outlet id |
| user | string | The current user id |
Register Changed
application.addEventListener("REGISTER_CHANGED", event => ({
console.log(`New register id: ${event.register}`);
}));REQUEST_BUTTONS
This allows you to add custom buttons to several pages within Shopfront.
Arguments
| Field | Type | Description |
|---|---|---|
| location | string | The location that the button will be appearing (see below for locations) |
Locations:
- ORDER_VIEW
- REGISTER_CLOSURE_VIEW
- FULFILMENT_PAGE
Returns
- Array<Button>
Request Buttons
import { Button } from "@shopfront/bridge";
const orderViewButtons = () => {
const button = new Button("Send to Integration", "https://example.com/icon.png");
button.addEventListener("click", () => {
// Perform action here
});
return [button];
};
application.addEventListener("REQUEST_BUTTONS", location => {
switch(location) {
case "ORDER_VIEW":
return orderViewButtons();
}
return [];
});REQUEST_CUSTOMER_LIST_OPTIONS
This allows you to add your own button to the Add Customer screen. Particularly useful if you wish to implement a way for Shopfront users to add custom loyalty customers to a sale
Arguments
- None
Returns
- Array<SellScreenCustomerListOption>:
| Field | Type | Description |
|---|---|---|
| contents | string | The name of the new option |
| onClick | function | The function that is executed when it is clicked |
Request Customer List Options
application.addEventListener("REQUEST_CUSTOMER_LIST_OPTIONS", () => {
return [
{
contents: "Add Loyalty Customer",
onClick : () => {
console.log("Adding loyalty customer");
}
}
];
});
REQUEST_SALE_KEYS
This allows you to create custom Sale Keys for your integration. Users can then add them to their Sale Key layouts.
Arguments
- None
Returns
- Array<SaleKey>:
| Field | Type | Description |
|---|---|---|
| supportedEvents | Array<string> | The supported events that can be assigned to the button |
| id | string | The id of the Sale Key |
| name | string | The name given to the Sale Key |
Request Sale Keys
import { SaleKey } from "@shopfront/bridge";r
application.addEventListener("REQUEST_SALE_KEYS", () => ({
const keys: SaleKey[] = [];
const saleKey = new SaleKey(
"@onshopfront/example/newKey",
"A New Sale Key"
);
saleKey.addEventListener("click", () => {
console.log("Button clicked");
};
keys.push(saleKey);
return keys;
});REQUEST_SELL_SCREEN_OPTIONS
Listening to this event will allow you to add options to the sell screen actions (next to Sales Keys and Parked Sales).
Any URL specified must be using the HTTPS protocol.
Arguments
- None
Returns
- Array<SellScreenOption>:
| Field | Type | Description |
|---|---|---|
| url | string | The URL to display in a frame when this option is selected |
| title | string | The button label of the option, this can either be plain text or any valid HTML (this will inherit style from the POS) |
Request Sell Screen Options
application.addEventListener("REQUEST_SELL_SCREEN_OPTIONS", () => ({
"url": "https://example.com",
"title": "Example Option",
});REQUEST_SETTINGS
This allows you to add a settings page for your integration. It will be added next to
the Revoke button on the Integrations Settings page
If you provide a URL to your settings page it must be using the HTTPS protocol.
Arguments
- None
Returns
- Object:
| Field | Type | Description |
|---|---|---|
| logo | string? | The URL to the logo you would like to display |
| description | string? | The description of what your integration does |
| url | string? | The URL to the settings page you wish to display |
Request Settings
application.addEventListener("REQUEST_SETTINGS", () => ({
logo: "https://example.com/logo-location.png",
description: "Sends purchases to your accounting software",
url: "https://example.com/shopfront-settings"
}));REQUEST_TABLE_COLUMNS
Listening to this event will allow you to add additional columns to several tables within Shopfront.
Arguments
| Field | Type | Description |
|---|---|---|
| location | string | The location that the table is from (see below for locations) |
| data | object | Information about the table to be added to (see below along with locations) |
Locations:
- ORDER_VIEW
| Field | Type | Description |
|---|---|---|
| id | string | The ID of the order |
| from | string | The ID of who the order is from |
| to | string | The ID of who the order is to |
| status | string | The status of the order |
| type | string | The type of order |
| sentAt | string | null | The time the order was sent |
| products | Array<object> | The products in the order |
| products.id | string | The ID of the product |
| products.caseQuantity | number | The case quantity of the product |
| products.orderedQuantity | number | The number of items ordered |
| products.receivedQuantity | number | The number of items received |
| products.baseCost | number | The base cost for the product |
| products.fees | number | The fees for the product |
| products.freight | number | The freight for the product |
| products.paymentFees | number | The payment fees for the product |
| products.rebate | number | The rebate amount for the product |
| products.supplierCode | string | The supplier code for the product |
Returns
nullor Object:
| Field | Type | Description |
|---|---|---|
| headers | Array<object> | The headers for the table |
| headers.label | string | The label for the header |
| headers.key | string | The key for the body and footer values |
| headers.weight | number | The weight of the column, the higher the value the further to the right the column is |
| body | Array<Object<key, label>> | Each element in the array is a row which contains an object map to provide the cell contents |
| footer | Object<key, label> | The value of the footer |
Request Table Columns
application.addEventListener("REQUEST_TABLE_COLUMNS", (location, data) => {
if(location === "ORDER_VIEW") {
if(data.status === "SENT" && data.type === "ORDER") {
return {
headers: [{
label : "10 Or More",
key : "more",
weight: 10, // Will be 10 from the left
}],
body: data.products.map(product => ({
more: product.orderedQuantity > 10 ? "Yes" : "No",
})),
footer: {
more: "",
},
}
}
}
return null;
});SALE_COMPLETE
This listener fires whenever a sale is finalised.
Arguments
| Field | Type | Description |
|---|---|---|
| event | SaleCompletedEvent | An object containing an object of the finalised sale |
Returns
- None
SaleCompletedEvent
| Field | Type | Description |
|---|---|---|
| sale | CompletedSale | An object containing details of the sale |
CompletedSale
| Field | Type | Description |
|---|---|---|
| products | Array<CompletedSaleProduct> | An array containing details of each product in the sale |
| customer | false OR object | If set, containers a uuid field with the customers id |
| payments | Array<CompletedSalePayment> | An array containing all the payment methods used in the sale |
| notes | object | An object containing both internal and sale notes |
| totals | object | An object containing information on the payment totals |
| registerId | string | The register that the sale was finalised on |
| userId | string | The user who finalised the sale |
| status | string | The status of the sale |
| createdAt | string | The date and time the sale was finalised |
| metaData | Record<string, unknown> | A record containing extra information about the sale |
CompletedSaleProduct
| Field | Type | Description |
|---|---|---|
| name | string | The product name |
| caseQuantity | number | The amount of products in a case |
| quantity | number | The quantity being sold in this transaction |
| prices | object | An object containing information on the different prices the product has |
| metaData | Record<string, unknown> | An object containing all custom information about Shopfront, this doesn't have any predefined format |
CompletedSalePayment
| Field | Type | Description |
|---|---|---|
| method | string | The method of the payment (e.g CASH) |
| type | string | The type of the payment method (e.g INTEGRATED) |
| status | string | The processing status of the payment method |
| amount | number | The amount that was paid for by this method |
| metadata | string | A string containing extra information about the sale |
Sale Complete
application.addEventListener("SALE_COMPLETE", event => ({
console.log("Sale was finalized");
}));AUDIO_READY
This listener fires when the Embedded API is ready to receive Audio events.
Arguments
- None
Returns
- None
Audio Ready
application.addEventListener("AUDIO_READY", () => ({
console.log("Audio Events Ready to Process");
}));UI_PIPELINE
The UI_PIPELINE allows you to modify the content which is displayed in certain parts of the POS. Each application is passed the previous application’s response allowing you to continually modify and add on to the content to be displayed in Shopfront. If you’re listening to this event and don’t need to modify the content, it is highly recommended to return the content which was passed in originally, otherwise content from other applications may be lost.
Arguments
| Field | Type | Description |
|---|---|---|
| existing | Array<UIPipelineResponse> | The current content in the UI Pipeline |
| context | UIPipelineContext | Information about the Shopfront context when this listener was fired |
Returns
- Array<UIPipelineResponse>:
| Field | Type | Description |
|---|---|---|
| name | string | The name of the content |
| content | string | The raw html that is being inserted |
UIPipelineContext
| Field | Type | Description |
|---|---|---|
| location | string | The location when the listener was fired |
| trigger | () => void | A callback that will refresh the UI Pipeline when called |
UI Pipeline
application.addEventListener("UI_PIPELINE", (existing, context) => ({
if (context.location === "shopfront:sell:transaction:products:pre") {
const content = `<p>Your new content</p>`
existing.push({
"My new content",
content
});
}
return existing;
});SALE_PRE_FINISH_PIPELINE
The SALE_PRE_FINISH_PIPELINE allows you to execute code and modify the sale state after a sale has entered the completing stage, but before it is fully finished, you can use this to do things like activate gift cards or perform modifications to the sale.
If you don't want the sale to complete, you should return false which will then exit.
Data isn't verified after the state is returned from this event, so be careful to maintain the same shape of the object that was passed in.
This can be called multiple times for a single sale, other events in the pipeline may reject the sale which can lead to the sale being attempted again.
All types of sales will trigger this, so make sure you check the status of the sale before performing actions on it.
Arguments
| Field | Type | Description |
|---|---|---|
| sale | SaleState | The current sale to be processed through the sale pipeline |
| context | PreSalePipelineContext | Information about the current state of Shopfront when this listener was fired |
Returns
SaleStateorfalse
PreSalePipelineContext
| Field | Type | Description |
|---|---|---|
| user | string | false | The user who performed the sale |
| register | string | The register the sale was performed on, this can result in an empty string if it is unknown |
Pre Sale Finish Pipeline
application.addEventListener("SALE_PRE_FINISH_PIPELINE", (sale, context) => {
if(shouldFailSale(sale, context)) {
return false;
}
return {
...sale,
notes: {
...sale.notes,
internal: `${sale.notes.internal}\nAdded an internal note`,
},
};
});
INTERNAL_PAGE_MESSAGE
This event occurs whenever one of your custom pages (such as a sell screen option, or the settings page) sends your application an internal message. You can reply to the message by using the provided reference's send method.
We expect you verify the contents of the message before processing it to ensure the message came from your application page.
Arguments
- InternalPageMessageEvent:
| Field | Type | Description |
|---|---|---|
| method | string | The method that this message is coming from, e.g. REQUEST_SELL_SCREEN_OPTIONS, REQUEST_SETTINGS |
| url | string | The URL that the message originated from, this will be the URL you originally provided |
| clientId | string | The client ID of the custom page that the message was sent from |
| message | any | The message your application has sent |
| reference | Object | A reference to the sender, you can use it to reply and send future messages to your page |
| reference.send | (message: any) => void | A function that takes a message to send to the custom page |
Returns
- None
Internal Page Message (Embedded Application)
application.addEventListener("INTERNAL_PAGE_MESSAGE", event => {
if(event.url !== process.env.CUSTOM_PAGE_URL || event.clientId !== process.env.CLIENT_ID) {
return;
}
if(typeof event.message !== "object") {
return;
}
if(event.method === "REQUEST_SELL_SCREEN") {
if(event.message.type === "PING") {
// You can keep a copy of event.reference around if you want
// to send more messages without having to send them from the
// custom page first.
event.reference.send({
type: "PONG",
});
}
}
});
INTERNAL_PAGE_MESSAGE (custom page side)
This event is separate from the embedded bridge, but it allows you to receive messages from your embedded application. Before you're able to receive a message, you must first send a message to your application (in order for the embedded bridge to obtain a reference to your page).
Communication occurs using the browser's built-in postMessage API.
To send a message you must perform a postMessage to the parent window (which is a Shopfront window for the Vendor that
has integrated the application), the message must be an object with the following structure:
| Field | Type | Description |
|---|---|---|
| type | "INTERNAL_PAGE_MESSAGE" | Must be the string "INTERNAL_PAGE_MESSAGE" |
| from | string | The URL that you are sending this from, it must match the original URL that your application was loaded on |
| clientId | string | Your application's client ID |
| message | any | Typically an object, this is the message to be sent to your application |
Once the message has been sent, your embedded application will receive it using the
INTERNAL_PAGE_MESSAGE event and can respond using the provided reference. To receive a reply
you must listen for messages on the Window. Messages will be received as a
MessageEvent with the following object as the data
property:
| Field | Type | Description |
|---|---|---|
| type | "INTERNAL_PAGE_MESSAGE" | Will be the string "INTERNAL_PAGE_MESSAGE" |
| url | string | Your settings URL that the message is for |
| clientId | string | Your application's client ID |
| message | any | Typically an object, this is the message that has been sent from your application |
Internal Page Message (Custom Page)
window.addEventListener("message", event => {
if(event.origin !== "https://<vendor>.onshopfront.com") {
return;
}
if(typeof event.data !== "object") {
return;
}
if(event.data.url !== process.env.CUSTOM_PAGE_URL || event.data.clientId !== process.env.CLIENT_ID) {
return;
}
if(event.data.type !== "INTERNAL_PAGE_MESSAGE") {
return;
}
if(typeof event.data.message !== "object") {
return;
}
if(event.data.message.type === "PONG") {
console.log("Communication Successful!");
}
}, false);
window.parent.postMessage({
type : "INTERNAL_PAGE_MESSAGE",
from : process.env.CUSTOM_PAGE_URL,
clientId: process.env.CLIENT_ID,
message : {
type: "PING",
},
}, "https://<vendor>.onshopfront.com");
GIFT_CARD_CODE_CHECK
Listening to this event (GIFT_CARD_CODE_CHECK) allows you check the gift card code being used in Shopfront against the rules of your application.
If a conflict is found, a message should be returned from your application.
For example, it is used to verify that the Shopfront's in-built gift card code, entered on the sell screen, won't conflict with codes reserved by third-party integrations.
Arguments
| Field | Type | Description |
|---|---|---|
| code | string | The gift card code to be checked. |
| message | string | null | A message containing the information/error returned by the application - null if no conflict |
Returns
| Field | Type | Description |
|---|---|---|
| code | string | The gift card code to be checked. |
| message | string | null | A message containing the information/error returned by the application - null if no conflict |
Gift Card Code Check
application.addEventListener("GIFT_CARD_CODE_CHECK", async event => {
const reservedRange = ["00001002", "00002002"]
if (event.message !== null) {
return event
}
const codeInt = parseInt(event.code);
if (codeInt >= parseInt(reservedRange[0]) && codeInt <= parseInt(reservedRange[1])) {
// Check to make sure the length of the cardNumber is within Card Ranges length
const minLength = reservedRange[0].length;
const maxLength = reservedRange[1].length;
if (event.code.length >= minLength && event.code.length <= maxLength) {
return {
code : event.code,
message: `Your Application: Gift card code refused - ${event.code} is within a reserved range of values.`
};
}
}
return event;
});Direct Events
Shopfront allows you to plug in to several internal events in order to react to changes made by the user. These events don't provide any parameters nor do they expect any return value.
The currently supported events are:
SALE_ADD_PRODUCTSALE_REMOVE_PRODUCTSALE_CHANGE_QUANTITYSALE_UPDATE_PRODUCTSSALE_ADD_CUSTOMERSALE_REMOVE_CUSTOMERSALE_RETRIEVESALE_CLEAR
If you wish to hook into any trigger not listed above, feel free to reach out to us.
Direct Events
application.addEventListener("SALE_PRODUCT_ADD", () => {
// Do something...
});