How-To: Fulfil Your Sales Using Shopfront
We presume you've already authenticated your application before reading this document.
The term order and sale are used interchangeably in this document as it mainly depends on the perspective of the caller as to which is correct.
Getting Started
This guide will go over embedding an application into Shopfront's UI through the use of Shopfront's Embedded API, sending sales to Shopfront for users within Shopfront to fulfil and handling the events that occur in relation to the fulfilment process.
It's designed for a number of use cases, including integrated eCommerce stores and integrated with delivery services.
The guide doesn't go over obtaining or sending data to and from your server as each integration will handle this in a different way, instead we've just substituted in generate functions that you can use to assist with guiding you with where to perform network requests to your API.
Because the Embedded API is executed on the client-side of Shopfront, all code needs to execute as JavaScript, whether you write the code in a language that compiles to JavaScript (such as TypeScript) or directly in JavaScript is up to you.
Setting up the Embedded API
Shopfront loads your application by embedding an HTML iFrame onto the page, to do this, you'll need to specify a location to serve it from.
You can inform Shopfront of the location of your application by going to your applications, logging in (if you haven't already), pressing Edit next to your application and specifying the location of your HTML file which will load your JavaScript application in the Embedded API URI field, finally press the Confirm button to save your changes.
Ensure your URL uses HTTPS otherwise your page won't load.
Shopfront will load this URL directly in the web browser, so it doesn't have to be accessible from outside where your browser can currently access (e.g. using localhost with HTTPS will work fine).
You'll then need to setup an application to be served, this is currently outside the scope of this documentation, but you will need to install the Shopfront Embedded Bridge. Information about basic usage and installation can be found here.
After your application is setup, you'll need to edit your main file to import the bridge and connect your application to Shopfront (example on the right).
Setting up the Embedded Bridge
import { Bridge, Fulfilment, Sales } from "@shopfront/bridge";
// You'll need the Vendor's subdomain to initialize the bridge.
// Shopfront passes this in as a search parameter, but if you only want it to work
// with a specific Vendor we would suggest ignoring the parameter and specifying
// manually
const url = new URL(window.location.href);
const application = Bridge.createApplication({
id : process.env.CLIENT_ID, // Your application's CLIENT_ID
vendor: url.searchParams.get("vendor"),
});
After your application has been setup and is communicating to Shopfront, you'll need to opt-in to the Fulfilment API. Whilst this step is optional, it provides a better user experience as it enables the Fulfilment tab on the sell screen instantly.
You can also specify whether your application requires an approval / accepting step (typically for delivery service integrations). In our examples, we'll presume your application requires the approval step as the related code can be easily ignored if it does not.
Opting-in to the Fulfilment API
application.addEventListener("READY", async () => {
await application.send(new Fulfilment.RegisterIntent({
requireApproval: false,
}));
});
Sending Shopfront Updates
In order for Shopfront to display your sales, you'll need to send them to Shopfront somehow, that's where the actions
OrdersSync, OrderCreate, OrderUpdate and OrderCancel come in.
Data you provide is determined as the source of truth, for example, if Shopfront changes the status and your application returns a previous status, Shopfront will go back to using your status.
On initial load, you'll need to get the currently outstanding orders from your server (typically using a GET request)
and send them in bulk to Shopfront using the OrdersSync action.
Performing Initial Synchronisations of Sales
application.addEventListener("READY", async () => {
// ... Opt-in ...
// Fetch the outstanding sales from your server
const sales = await fetchOutstandingSales();
// Transform the sales into the format Shopfront wants
// and send them to Shopfront using OrdersSync
await application.send(new Fulfilment.OrdersSync({
merge : false,
orders: sales.map(transformSaleToShopfront),
}));
});
To keep orders up-to-date, you'll typically have some method which obtains sale updates either on schedule or
through the use of something like websockets. Once you receive the orders you'll want to update Shopfront by sending
either OrderCreate (for new orders), OrderUpdate (for updates to existing orders) or OrderCancel (to remove
orders from Shopfront).
Optionally you can also use OrdersSync with the merge parameter set to false to remove all the existing sales and
update them with the currently available sales.
Updating Existing Sales in Shopfront
// onSaleUpdate is a method you would define which accepts a callback
// which is called whenever you receive a sale update, we've presumed
// it returns an object with two fields, `action` and `sale`.
// `action` is the action to be performed to the sale
// `sale` is your raw sale object
onSaleUpdate(async ({ sale, action }) => {
if(action === "CANCELLED") {
await application.send(new Fulfilment.OrderCancel(sale.id));
} else if(action === "CREATED") {
await application.send(new Fulfilment.OrderCreate(transformSaleToShopfront(sale)));
} else if(action === "UPDATED") {
await application.send(new Fulfilment.OrderUpdate(transformSaleToShopfront(sale)));
}
});
Handling Events from Shopfront
In addition to your application updating Shopfront, Shopfront is able to send your application back updates on how the sale has progressed after the user interacts with the sale.
Shopfront has the following statuses available for orders and typically moves through them from top to bottom:
PENDING_APPROVAL: The order is pending approval, this status is typically only used when you've setrequiresApprovaltotrueon opt-in.WAITING_FOR_PACKING: The order has been approved and is waiting for the user to pack and match the order.PACKED: This occurs after the order is packed and is waiting to be shipped / picked upCOLLECTED: The order has been collected / shippedCOMPLETED: The order is completed and will receive no further events through the Fulfilment API
Your application is able to move backwards or skip statuses if required, just send an
OrderUpdatewith the desired status.
For each step along the way there is a specific event, each of which sends your application a different set of data and expects a different response (if any).
When you send updates to Shopfront, you only send a summary instead of the entire order, when Shopfront requires
the latest and entire order, it will send a FULFILMENT_GET_ORDER event which it expects the entire order to be
returned.
It also includes information on how products should be matched in Shopfront, for details on how this algorithm works, check out the details on the Fulfilment API page.
Ideally you should attempt to respond to this method within one second for a good user experience. If your application takes longer than 30 seconds to respond, Shopfront will stop waiting and display an error to the user. In the future the wait time may be reduced.
Sending Full Sale Details to Shopfront
application.addEventListener("FULFILMENT_GET_ORDER", async id => {
// NOTE: id will be the ID that you provide originally
const sale = await fetchFullSaleDetails(id);
return transformFullSaleToShopfront(sale);
});
If you've enabled requiring order approval during the opt-in process, you'll want to listen to the
FULFILMENT_ORDER_APPROVAL event which will contain the ID of your order and whether it was approved or not.
Receive Order Approval Status
application.addEventListener("FULFILMENT_ORDER_APPROVAL", async ({ id, approved }) => {
if(approved) {
// Make sure as part of this process you update the order's status as WAITING_FOR_PACKING when you next send
// the order to Shopfront
await markOrderAsApproved(id);
} else {
// Make sure this order isn't sent back to Shopfront in the future
await cancelOrder(id);
}
});
Once the order has been matched and packed by a user through Shopfront, your application will receive the
FULFILMENT_PROCESS_ORDER event which contains the ID of your order and a
Sale object which you can use to either process the sale in Shopfront straight
away or hold for later processing.
Sales through the fulfilment process need to be manually processed, this can either be done through the use of GraphQL, or can be done directly through the Embedded API.
Sales from the fulfilment API do not have a payment method attached, you'll need to manually mark how they're being paid for.
If you elect to process the sale later, make sure you can still access the data required after the user potentially performs a page refresh.
Process Orders After Packing
const transformFromShopfrontSale = (id, shopfront) => {
return {
// ... perform the general transformation from the provided Shopfront object
items: shopfront.getProducts().map(product => ({
shopfrontId: product.getId(),
internalId : product.getMapped(), // Your original ID provided for the product
quantity : product.getQuantity(),
})),
};
};
application.addEventListener("FULFILMENT_PROCESS_ORDER", async ({ id, sale }) => {
await processOrder(transformFromShopfrontSale(id, sale));
// In this example we're going to process the order in Shopfront instantly, but you might want to wait
// until the status changes to something like `COLLECTED` or `COMPLETED`
sale.addPayment(
new Sales.SalePayment("<< ID of payment method >>", sale.getSaleTotal())
);
const response = await sale.create(application);
if(!response.success) {
// Handle the error, probably best to inform your server so it can be manually processed
console.error("Received an error while processing the sale", response.message);
}
});
After the order has been either picked up by the customer (if it was a click & collect order), courier (if it's a
delivery platform) or shipped, Shopfront will trigger the FULFILMENT_ORDER_COLLECTED event with the ID of your order.
Receive Order Collection Status
application.addEventListener("FULFILMENT_ORDER_COLLECTED", async (id) => {
await markOrderAsCollected(id);
});
Finally, after the sale has been finished and is marked as completed by the user, Shopfront will trigger the
FULFILMENT_ORDER_COMPLETED event with the ID of your order.
Receive Order Completion Status
application.addEventListener("FULFILMENT_ORDER_COMPLETED", async (id) => {
await markOrderAsCompleted(id);
});
If for some reason the store needs to cancel the order and the user hasn't been marked as COMPLETED, they can press
a cancel button in Shopfront's UI which will trigger the FULFILMENT_VOID_ORDER event which includes the ID of the
order they are voiding.
It's up to you how you want to handle this, if it's not possible to void the order at that point you could display a dialog box or a toast informing the user it isn't possible to redeem and then send the order back to Shopfront to add to the Fulfilment list again.
Receive Order Void Events
application.addEventListener("FULFILMENT_VOID_ORDER", async (id) => {
if(determineIfOrderCanBeVoid(id)) {
await voidOrder(id);
} else {
// Show a toast or dialog and send the order back to Shopfront
}
});