Shopfront Embedded Bridge Fulfilment API
Shopfront's Embedded Bridge allows you to easily plug into Shopfront's fulfilment system so stores don't have to leave Shopfront to process orders and prevents manual processing errors.
This page describes the Embedded Bridge's Fulfilment API, for in-depth examples on how to use the API, check out the How To Guide.
Emitable Actions
The fulfilment API is made up a number of emitable actions that can be sent to Shopfront at any time.
RegisterIntent
Whilst optional, this should be sent to Shopfront as soon as your application is ready to opt-in to the Fulfilment API. This enabled the option on the sell screen to view the fulfilment page and display your application as connected.
| Field | Type | Description |
|---|---|---|
| requireApproval | boolean | Whether sales waiting to process require approval before they're available to pack |
RegisterIntent
import { Fulfilment, Bridge } from "@shopfront/bridge";
const application = Bridge.createApplication({ /* your options */ });
application.addEventListener("READY", async () => {
await application.send(new Fulfilment.RegisterIntent({
requireApproval: true,
}));
});
Options
Update the fulfilment options previously provided on opt-in.
| Field | Type | Description |
|---|---|---|
| requireApproval | boolean | Whether sales waiting to process require approval before they're available to pack |
Options
import { Fulfilment } from "@shopfront/bridge";
application.send(new Fulfilment.Options({
requireApproval: false,
}));
OrdersSync
Synchronise a number of orders with Shopfront, this is typically called on a schedule or when your application receives an event from your server.
| Field | Type | Description |
|---|---|---|
| orders | Array<OrderCreateDetails> | The orders to send to Shopfront |
| merge | boolean | Whether the orders should be merged into the currently available orders, or if they should override the orders |
Setting
mergetotruedoesn't merge individual orders, it just prevents the removal of all previous orders for the integration. Updates to orders should use theOrderUpdateaction.
OrderCreateDetails
| Field | Type | Description |
|---|---|---|
| id | string | Your ID for the order |
| customer | { name: string, phone: string } | The customer who has performed the order |
| courier | { icon?: string, name: string, phone: string }? | (optional) The courier who will be picking up the order |
| comment | string? | (optional) Any comments for the order |
| totalPrice | number | The total price for the order |
| expiry | (string | Date)? | (optional) When the order will expire, this is purely a label and if a date is passed in it'll show the time relative to the date, otherwise it'll show the provided string |
| status | OrderStatus? | (optional) The status of the order |
| finalLabel | string? | (optional) The label to display as part of the final step in the UI |
| createdAt | string | The time the order was created |
OrderStatus
One of:
PENDING_APPROVALWAITING_FOR_PACKINGPACKEDCOLLECTEDCOMPLETED
OrdersSync
import { Fulfilment } from "@shopfront/bridge";
// onOrderRetrievalQueue is a presumed way that you would retrieve orders from your API
onOrderRetrievalQueue(async allOrders => {
await application.send(new Fulfilment.OrdersSync({
orders: allOrders,
merge : false,
}));
});
OrderCreate
Create a single order to send to Shopfront's fulfilment system. This can be used instead of the OrdersSync action.
| Field | Type | Description |
|---|---|---|
| order | OrderCreateDetails | The order to send to Shopfront |
OrderCreate
import { Fulfilment } from "@shopfront/bridge";
// onNewOrder is a presumed way that you would receive a new order notification from your API
onNewOrder(async order => {
await application.send(new Fulfilment.OrderCreate({
order,
}));
});
OrderUpdate
Update a single order that already exists within Shopfront's fulfilment system.
| Field | Type | Description |
|---|---|---|
| order | OrderSummaryDetails | The order to send to Shopfront |
OrderSummaryDetails
| Field | Type | Description |
|---|---|---|
| id | string | Your ID for the order |
| customer | { name: string, phone: string } |
The customer who has performed the order |
| courier | { icon?: string, name: string, phone: string }? |
(optional) The courier who will be picking up the order |
| comment | string? | (optional) Any comments for the order |
| totalPrice | number | The total price for the order |
| expiry | (string | Date)? | (optional) When the order will expire, this is purely a label and if a date is passed in it'll show the time relative to the date, otherwise it'll show the provided string |
| status | OrderStatus | The status of the order |
| finalLabel | string? | (optional) The label to display as part of the final step in the UI |
| createdAt | string | The time the order was created |
OrderUpdate
import { Fulfilment } from "@shopfront/bridge";
// onOrderUpdate is a presumed way that you would receive an updated order from your API
onOrderUpdate(async order => {
await application.send(new Fulfilment.OrderUpdate({
order,
}));
});
OrderCancel
This cancels / removes an order within Shopfront's fulfilment system.
| Field | Type | Description |
|---|---|---|
| id | string | The ID of the order to remove |
OrderCancel
import { Fulfilment } from "@shopfront/bridge";
// onOrderCancel is a presumed way that you would receive a new order notification from your API
onOrderCancel(async id => {
await application.send(new Fulfilment.OrderCancel({
id,
}));
});
Receivable Events
Along with registering and sending order updates, your application will also need to listen to a number of events and respond back to them with correctly formatted data.
Any event can optionally return a promise.
FULFILMENT_GET_ORDER
This event triggers when Shopfront requires the full details of a single order.
Arguments
| Field | Type | Description |
|---|---|---|
| id | string | The ID of the order |
Returns
| Field | Type | Description |
|---|---|---|
| id | string | Your ID for the order |
| customer | { name: string, phone: string } | The customer who has performed the order |
| courier | { icon?: string, name: string, phone: string }? | (optional) The courier who will be picking up the order |
| comment | string? | (optional) Any comments for the order |
| totalPrice | number | The total price for the order |
| expiry | (string | Date)? | (optional) When the order will expire, this is purely a label and if a date is passed in it'll show the time relative to the date, otherwise it'll show the provided string |
| status | OrderStatus | The status of the order |
| finalLabel | string? | (optional) The label to display as part of the final step in the UI |
| createdAt | string | The time the order was created |
| items | Array<OrderItem> | The items on the order |
OrderItem
| Field | Type | Description |
|---|---|---|
| id | string | Your ID for the product |
| image | string? | (optional) The image to use for the product |
| name | string | Your name for the product |
| quantity | number | The number of packSizes sold |
| packSize | number|PackSizeMatch | The outer size which was sold (e.g. a six-pack would return this as 6) |
| price | number | The total price for the line |
| comment | string? | (optional) Any comment on the product to display |
| match | OrderItemMatch? | (optional) The matching for the product |
PackSizeMatch
This allows you to specify a different quantity depending upon how the item was matched.
| Field | Type | Description |
|---|---|---|
| id | number? | For matching via Shopfront ID |
| barcode | ("auto"|number)? | For matching matching via barcode (if auto is specified, it will use the barcode quantity) |
| mdbId | number? | For matching via MDB ID |
| supplierCode | number? | For matching via Supplier Code |
| defaultTo | number | When a match can't be determined or a field isn't specified which was used to match |
OrderItemMatch
All fields are optional, for details on how Shopfront performs matching, see the product matching algorithm section.
| Field | Type | Description |
|---|---|---|
| id | string? | Shopfront's ID for the product |
| barcodes | Array<string>? | The barcodes of the product, these don't have to correspond to the packSize |
| mdbId | number? | The master database reference for the product |
| supplier | object? | The supplier which this product belongs to |
| supplier.id | string? | Shopfront's ID for the supplier |
| supplier.mdbId | number? | The master database reference for the supplier |
| supplierCodes | Array<string>? | The supplier codes that belong to the product |
FULFILMENT_GET_ORDER
application.addEventListener("FULFILMENT_GET_ORDER", id => {
return retrieveFullOrderFromAPI(id);
});
FULFILMENT_VOID_ORDER
Triggers when Shopfront (typically on user interaction) voids / cancels an order. It's expected that you'll send this information back to your server to handle any potential refund process and remove the order from the list of orders available to sync to Shopfront.
Arguments
| Field | Type | Description |
|---|---|---|
| id | string | The ID of the order |
Returns
- None
FULFILMENT_VOID_ORDER
application.addEventListener("FULFILMENT_VOID_ORDER", id => {
return cancelOrderViaAPI(id);
});
FULFILMENT_PROCESS_ORDER
This occurs once the order has been processed and matched on Shopfront, moving to the PACKED stage. It's expected
you'll transform and process the sale on Shopfront when ready (the sale is not automatically processed on Shopfront).
Arguments
| Field | Type | Description |
|---|---|---|
| event | FulfilmentProcessEvent | The event that occurred |
Returns
- None
FulfilmentProcessEvent
| Field | Type | Description |
|---|---|---|
| id | string | The ID of the order |
| sale | Sale | The sale from Shopfront |
FULFILMENT_PROCESS_ORDER
application.addEventListener("FULFILMENT_PROCESS_ORDER", async (event) => {
const id = event.id;
const sale = event.sale;
await markSaleAsPackedAndSaveSale(id, sale);
});
FULFILMENT_ORDER_APPROVAL
This triggers when the order is approved or declined on Shopfront, this will only be called if you have enabled
requireApproval when opting in to using the fulfilment system.
Arguments
| Field | Type | Description |
|---|---|---|
| event | FulfilmentApprovalEvent | The event that occurred |
Returns
- None
FulfilmentApprovalEvent
| Field | Type | Description |
|---|---|---|
| id | string | The ID of the order |
| approved | boolean | Whether the order was approved or not |
FULFILMENT_ORDER_APPROVAL
application.addEventListener("FULFILMENT_ORDER_APPROVAL", async (event) => {
if(!event.approved) {
await cancelOrderViaAPI(event.id);
} else {
await approveOrder(event.id);
}
});
FULFILMENT_ORDER_COLLECTED
Triggers when the order is marked as collected through Shopfront.
Arguments
| Field | Type | Description |
|---|---|---|
| id | string | The ID of the order |
Returns
- None
FULFILMENT_ORDER_COLLECTED
import { Sales } from "@shopfront/bridge";
application.addEventListener("FULFILMENT_ORDER_COLLECTED", async id => {
await markOrderAsCollected(id);
const sale = await retrieveSavedShopfrontSale(id);
sale.addPayment(
new Sales.SalePayment("<< id of payment method >> ", sale.getSaleTotal())
);
await sale.create(application);
});
FULFILMENT_ORDER_COMPLETED
Triggers when the order is marked as completed within Shopfront
Arguments
| Field | Type | Description |
|---|---|---|
| id | string | The ID of the order |
Returns
- None
FULFILMENT_ORDER_COMPLETED
application.addEventListener("FULFILMENT_ORDER_APPROVAL", async (id) => {
await markOrderAsCompleted(id);
});
Product Matching Algorithm
When the FULFILMENT_GET_ORDER event is triggered, it expects an object back that contains a list of products, each
of which can optionally contain an OrderItemMatch object.
It's suggested to provide as many details in
OrderItemMatchas you can as it'll allow for the most accurate matching and reduce the chances of algorithm changes affecting your application in the future.
Shopfront then attempts to match using the following algorithm:
- Exact
idmatch- If this matches, use the matching product
- Otherwise continue
- Match via
barcodes- If a single product matches, use the matching product
- If no products match, continue
- If multiple products match, continue using the matching products as the base result set
- Match via
mdbId- If a single products matches, use the matching product
- If no products match, continue
- If multiple products match, continue using the matching products as the base result set
- Match via
supplierCodesusing thesupplier.idas the supplier- If a single product matches, use the matching product
- If no products match, continue
- If multiple products match, continue
- Match via
supplierCodesusing thesupplier.mdbIdas the supplier- If a single product matches, use the matching product
- If no products match, continue
- If multiple products match, continue
- Stop matching and review existing products
- If multiple products exist which have moved through each of the previous steps, select the first one
- If multiple products exist from one of the previous steps (in order from 2-5), select the first one
- If no products match, an automatic match isn't possible
Even is an automatic match is possible, the user can still elect to change which product matches to your provided product.