Unleashing Your E-Commerce Potential with CommerceJS and Headless Integration

Loading...

When approached with creating an e-commerce app, it's pretty easy to pop up the same old Shopify or Squarespace page. But what happens if you want a more tailored customer experience that the larger companies don't offer with the convenience of all the tools they provide? With the power of headless e-commerce, you can have the best of both worlds.

Many options are out there, and many more are popping up by the day, but for this article, I'll talk specifically about how great it was using CommerceJS. They made it a super smooth experience to get up and running. Once you create your account, you are ready to start integrating their SDK into your page. I first noticed how seamless it was to create a product and add all of the descriptors/attributes in a Shopify-like manner. Then, it was as easy as running:

export async function getStaticProps() {
  const { data: products } = await commerce.products.list()

  return {
    props: {
      products,
    },
  }
}

And that's it! You have a list of products that you can use to populate your page. You can also create a cart, checkout, and even integrate with Stripe to accept payments. One useful feature I found was their ability to create a custom checkout flow. I think many people struggle with developing a checkout flow, and CommerceJS makes it super easy to do so. They make shipping, taxes, and discounts a breeze to implement. In this article they show how to integrate Shippo to get real-time shipping rates. One of the more difficult tasks to implement without a big company like Shopify or Squarespace. Below is a code snippet of a quick serverless NodeJS function I scaffolded out using CommerceJS and Shippo SDKs to get real-time shipping rates:

import Commerce from "@chec/commerce.js";
const commerce = new Commerce(process.env.CHEC_P_KEY);
const shippo = require("shippo")(process.env.SHIPPO_KEY);

export default async function handler(req, res) {
  const { token, address } = req.query;

  if (!token || !address) {
    return res.status(422).json({
      code: "MISSING_ATTRIBUTES",
      message: "A token or address must be provided",
    });
  }

  const checkAddress = JSON.parse(address);

  const validation = await shippo.address.create(
    {
      name: checkAddress.name,
      street1: checkAddress.street1,
      street2: checkAddress.street2,
      city: checkAddress.city,
      state: checkAddress.state,
      zip: checkAddress.postCode,
      country: checkAddress.country,
      validate: true,
    },
    function (err, address) {
      console.info(err);
      console.info(address);
    }
  );

  const promises = [];
  promises.push(commerce.checkout.getLive(token));

  const merchantAddress = {
    name: "example",
    street1: "example",
    street2: "example",
    city: "example",
    zip: "example",
    state: "example",
    country: "example",
    phone: "5127399361",
  };

  // add customs declaration if necessary
  let shipment = {};
  if (address.country !== "US") {
    var customsItem = {
      description: "T-Shirt Purchase",
      quantity: 1,
      net_weight: "2",
      mass_unit: "lb",
      value_amount: "200",
      value_currency: "USD",
      origin_country: "US",
    };

    const customsDeclaration = await shippo.customsdeclaration.create(
      {
        contents_type: "MERCHANDISE",
        contents_explanation: "T-Shirt purchase",
        non_delivery_option: "RETURN",
        certify: true,
        certify_signer: "Brett Schneider",
        items: [customsItem],
      },
      function (err, customsDeclaration) {
        // asynchronously called
        console.info(err);
        console.info(customsDeclaration);
      }
    );

    shipment = await shippo.shipment.create(
      {
        address_from: merchantAddress,
        address_to: JSON.parse(address),
        customs_declaration: customsDeclaration,
        parcels: {
          length: "10",
          width: "10",
          height: "5",
          distance_unit: "in",
          weight: "2",
          mass_unit: "lb",
        },
      },
      function (err, shipment) {
        console.info(err);
        console.info(shipment);
      }
    );
  } else {
    shipment = await shippo.shipment.create(
      {
        address_from: merchantAddress,
        address_to: JSON.parse(address),
        parcels: {
          length: "10",
          width: "10",
          height: "5",
          distance_unit: "in",
          weight: "2",
          mass_unit: "lb",
        },
      },
      function (err, shipment) {
        console.info(err);
        console.info(shipment);
      }
    );
  }

  console.info(validation.validation_results);

  if (validation.validation_results.is_valid === false) {
    return res.status(422).json({
      code: validation.validation_results.messages[0].code,
      message: validation.validation_results.messages[0].text,
    });
  }

  // Shippo address check comes back invalid, run error message
  if (!shipment.address_to.is_complete || !shipment.rates) {
    return res.status(422).json({
      code: "INVALID_ADDRESS",
      message:
        "The provided address does not appear to be valid or can not be shipped to",
    });
  }

  // inject shippo shipping method to commercejs api portal
  const response = await fetch(`https://api.chec.io/v1/checkouts/${token}`, {
    method: "PUT",
    headers: {
      "Content-Type": "application/json",
      "X-Authorization": process.env.CHEC_S_KEY,
    },
    body: JSON.stringify({
      shipping_methods: shipment.rates.map(
        ({ object_id: id, provider, amount, servicelevel: { name } }) => ({
          id,
          description: `${provider}: ${name}`,
          price: amount,
        })
      ),
    }),
  }).then((response) => response.json());

  return res.status(200).json(response.shipping_methods);
}

Though the code I included is a little long, it underscores the seamless capability to execute complex tasks, including international shipping considerations, within a concise framework. This functionality, along with other payment processing features, enabled the swift creation of a fully functional e-commerce site. All operational aspects—products, orders, customers—are managed through the CommerceJS portal, simplifying backend complexities.

CommerceJS order example

While this overview touches on key features, CommerceJS offers a wealth of possibilities for e-commerce development. Its potential excites me, and I encourage you to explore further if you're venturing into online retail. For more information, visit CommerceJS