For Zoho Services only:


I'm actually part of a bigger team at Ascent Business Solutions where we have support technicians and project consultants. Support is for smaller technical fixes but this can include developments, reports or integrations; depending on the size of the task. Projects are for more time-consuming developments such as revamps of the Zoho Suite of apps or on-site training. The advantage of a team is that if I am out-of-office for a day or so, there is always someone at Ascent Business Solutions who can deal with any queries/issues you may have.

Our support rates can be found and purchased at http://ascentbusiness.co.uk/zoho-support-2. A support bundle doesn't have an expiry date. So whether we can do what you want within the bundle and a year later need further support, if there are minutes left on the bundle then there is no additional charge.

Our project rates for bigger developments can be found at http://ascentbusiness.co.uk/crm-solutions/zoho-crm-packages-prices and will involve a dedicated project consultant along with developers who will hold your hand through the development process.

If you want help building a solution for one of the Zoho Apps in the Zoho Suite, contact us on 0121 392 8140 (UK) or by email at info@ascentbusiness.co.uk. You can also visit our website at http://ascentbusiness.co.uk.

I regularly build and specialize in 2-way API integrations for Xero, Shopify and eBay.

Zoho Creator: Shopify API Integration Oauth 2.0 - Update 2022 (Search by SKU GraphQL)

What?
This is an article which is the updated version of my article Zoho Deluge: Push Item to Shopify API for 2022 using the new policy that Shopify have implemented when creating a custom app.

Why?
My use-case scenario here is that we have a Zoho Creator app which comprises of a custom quote builder that staff use to store details about a product, and then to push that product out to their Shopify store with a pre-built description, tags and inventory details. This means the app needs to be able to sync all of its products, customers, and orders with Shopify as a 2-way integration as the Zoho app needs information from additional apps installed on the Shopify store.

Why upgrade to the latest version for Shopify when private apps created before February 2022 won't be deprecated? Because the previous version was limited by our ability to search by product SKU. The new version, taking advantage of GraphQL, will be able to search by SKU and retrieve the correct product ID, variant ID, and inventory ID from Shopify.

How?
The below details on how we set up an access token in Shopify using the new process.

Preparation:
You don't have to do this, but I store all the keys in a Zoho Creator form (I call mine "API Integration") ready with the following fields:
  1. Connection Name (Single Line)
  2. Dev ID (Single Line)
  3. Client ID (Single Line)
  4. Shop ID (Single Line)
  5. Client Secret (Single Line)
  6. Location ID (Single Line)
  7. API Version (Single Line)
  8. Location Code (Origin) (Single Line)
  9. Authorize Endpoint (Single Line)
  10. Grant URL (Url)
  11. Token Endpoint (Single Line)
  12. Session ID (Single Line)
  13. Redirect URI (Single Line)
  14. Scope(s) (Multi Line)
  15. Access Token (Multi Line)
  16. Access Token Expiry (Date-Time)
  17. Refresh Token (Multi Line)
  18. Refresh Token Expiry (Date-Time)
  19. AuthToken (Multi Line)
  20. AuthToken Expiry (Date-Time)
You should end up with something like this screenshot:
Screenshot of Creator form storing API Credentials * Some of these fields are unnecessary for this example but are used to accommodate other API integrations in the system that are using OAuth 2.0 or authtokens. Note how this is the same form in my article: Zoho Creator: Push to eBay Listings or my Zoho Deluge: Push Item to Shopify API.

Create an entry in our new form:
Access this application so you are viewing the front-end of your Creator app and go to your recently created API Integration form (tip: keep this open while you get the information from Shopify and your scripts found further below in this article):
  1. Give it a Connection Name (eg. "Shopify API Oauth")
  2. Enter the Shop ID (eg. "example-store.myshopify.com")
  3. Give it an API version (eg. "2022-01" - or whichever is the latest stable version)
  4. Location ID if you know it, otherwise leave blank (eg. "123456789")
  5. Location Code if you know it (eg. "GB")

Get access to the Shopify Admin Interface:
  1. Request access to the Shopify store owner as a developer
  2. Login to the Client's Shopify Admin as an invited developer
  3. Click on "Apps" > Develop Apps > Create an App
  4. Give it a name (eg. mycompanyname Zoho OAuth)
  5. Set its developer (preferably as the user you are logged in as)
  6. Click on "Create"
  7. First thing I do is browse to the API Credentials tab:
    • Store the API Key in your creator form you created above in the "Client ID" field.
    • Store the API Secret (click on the copy icon or the eye icon to copy & paste) into the "Client Secret" field.
    • Click on "Configure Admin API scopes": This will be dependent on what your app needs to do. In this example, mine needs to sync orders, products, customers, and some reports; the other things like discounts, fulfillments will all be done by the sales team via Shopify. So I select the following: (see: Shopify API Access scopes for more info on each):

      read_analytics,write_customers,read_customers,write_inventory,read_inventory,write_order_edits,read_order_edits,write_orders,read_orders,write_product_listings,read_product_listings,write_products,read_products,write_reports,read_reports

    • Click on "Save"
  8. Click on "Install App" button in the top right
  9. Confirm by clicking on "Install" button in the popup
  10. You will be shown your Admin API access token > Store this in the Access Token field of your Creator form.
If your app needs the Storefront API (eg. website checkouts) then set these. Mine don't as the Creator app I'm making is for staff only.

REST Admin API: Usage:
Here's a quick test I do retrieving 5 products using the REST admin API:
copyraw
//
// app specific
r_ShopifyAPI = API_Integration[Connection_Name == "Shopify API OAuth"];
v_ShopID = r_ShopifyAPI.Shop_ID;
v_ShopifyApiVersion = r_ShopifyAPI.API_Version;
v_AccessToken = r_ShopifyAPI.Access_Token;
//
// set header parameters
m_Header = Map();
m_Header.put("Content-Type","application/json");
m_Header.put("X-Shopify-Access-Token", v_AccessToken);
//
// set endpoint to retrieve 5 products
v_Endpoint = "https://" + v_ShopID + "/admin/api/" + v_ShopifyApiVersion.toString() + "/products.json?limit=5";
//
// curl (zoho invoke) request
r_GetProduct = invokeurl
[
    url :v_Endpoint
    type :GET
    headers:m_Header
];
//
// output
info r_GetProduct;
  1.  // 
  2.  // app specific 
  3.  r_ShopifyAPI = API_Integration[Connection_Name == "Shopify API OAuth"]
  4.  v_ShopID = r_ShopifyAPI.Shop_ID; 
  5.  v_ShopifyApiVersion = r_ShopifyAPI.API_Version; 
  6.  v_AccessToken = r_ShopifyAPI.Access_Token; 
  7.  // 
  8.  // set header parameters 
  9.  m_Header = Map()
  10.  m_Header.put("Content-Type","application/json")
  11.  m_Header.put("X-Shopify-Access-Token", v_AccessToken)
  12.  // 
  13.  // set endpoint to retrieve 5 products 
  14.  v_Endpoint = "https://" + v_ShopID + "/admin/api/" + v_ShopifyApiVersion.toString() + "/products.json?limit=5"
  15.  // 
  16.  // curl (zoho invoke) request 
  17.  r_GetProduct = invokeUrl 
  18.  [ 
  19.      url :v_Endpoint 
  20.      type :GET 
  21.      headers:m_Header 
  22.  ]
  23.  // 
  24.  // output 
  25.  info r_GetProduct; 

GraphQL: Usage example:
And here's a quick test to retrieve the 5 recent products using the GraphQL API:
copyraw
//
// app specific
r_ShopifyAPI = API_Integration[Connection_Name == "Shopify API OAuth"];
v_ShopID = r_ShopifyAPI.Shop_ID;
v_ShopifyApiVersion = r_ShopifyAPI.API_Version;
v_AccessToken = r_ShopifyAPI.Access_Token;
//
// set header parameters
m_Header = Map();
m_Header.put("Content-Type","application/json");
m_Header.put("X-Shopify-Access-Token", v_AccessToken);
//
// graphql
v_GraphQl = "{ products(first: 5, reverse: true) { edges { node { id title handle } } } }";
m_GraphQl = Map();
m_GraphQl.put("query", v_GraphQl);
//
// Let's test with this GraphQL query
v_Endpoint = "https://" + v_ShopID + "/admin/api/" + v_ShopifyApiVersion.toString() + "/graphql.json";
r_GetProduct = invokeurl
[
    url :v_Endpoint
    type :POST
    parameters: m_GraphQl.toString()
    headers:m_Header
];
info r_GetProduct;
  1.  // 
  2.  // app specific 
  3.  r_ShopifyAPI = API_Integration[Connection_Name == "Shopify API OAuth"]
  4.  v_ShopID = r_ShopifyAPI.Shop_ID; 
  5.  v_ShopifyApiVersion = r_ShopifyAPI.API_Version; 
  6.  v_AccessToken = r_ShopifyAPI.Access_Token; 
  7.  // 
  8.  // set header parameters 
  9.  m_Header = Map()
  10.  m_Header.put("Content-Type","application/json")
  11.  m_Header.put("X-Shopify-Access-Token", v_AccessToken)
  12.  // 
  13.  // graphql 
  14.  v_GraphQl = "{ products(first: 5, reverse: true) { edges { node { id title handle } } } }"
  15.  m_GraphQl = Map()
  16.  m_GraphQl.put("query", v_GraphQl)
  17.  // 
  18.  // Let's test with this GraphQL query 
  19.  v_Endpoint = "https://" + v_ShopID + "/admin/api/" + v_ShopifyApiVersion.toString() + "/graphql.json"
  20.  r_GetProduct = invokeUrl 
  21.  [ 
  22.      url :v_Endpoint 
  23.      type :POST 
  24.      parameters: m_GraphQl.toString() 
  25.      headers:m_Header 
  26.  ]
  27.  info r_GetProduct; 

Awesome!
Now for the primary objective of this task, which was to recover the relevant product based on any given Product SKU. Making the above into a function as well as returning the inventory item ID and the product ID:
copyraw
map API.fn_SearchShopifyBySKU( string p_SKU )
{
	//
	// app specific
	r_ShopifyAPI = API_Integration[Connection_Name == "Shopify API OAuth"];
	v_ShopID = r_ShopifyAPI.Shop_ID;
	v_ShopifyApiVersion = r_ShopifyAPI.API_Version;
	v_AccessToken = r_ShopifyAPI.Access_Token;
	//
	// send through
	m_Header = Map();
	m_Header.put("Content-Type","application/json");
	m_Header.put("X-Shopify-Access-Token", v_AccessToken);
	//
	// graphql
	v_SearchSKU = ifnull(p_SKU,"");
	v_GraphQl = "{ productVariants(first: 1, query: \"sku:'"+v_SearchSKU+"'\") { edges { node { id price sku title barcode inventoryItem { id } product { id } } } } }";
	m_GraphQl = Map();
	m_GraphQl.put("query", v_GraphQl);
	//
	// Send this GraphQL query
	v_Endpoint = "https://" + v_ShopID + "/admin/api/" + v_ShopifyApiVersion.toString() + "/graphql.json";
	r_GetProduct = invokeurl
	[
		url :v_Endpoint
		type :POST
		parameters: m_GraphQl.toString()
		headers:m_Header
	];
	return r_GetProduct.toMap();	
}
  1.  map API.fn_SearchShopifyBySKU( string p_SKU ) 
  2.  { 
  3.      // 
  4.      // app specific 
  5.      r_ShopifyAPI = API_Integration[Connection_Name == "Shopify API OAuth"]
  6.      v_ShopID = r_ShopifyAPI.Shop_ID; 
  7.      v_ShopifyApiVersion = r_ShopifyAPI.API_Version; 
  8.      v_AccessToken = r_ShopifyAPI.Access_Token; 
  9.      // 
  10.      // send through 
  11.      m_Header = Map()
  12.      m_Header.put("Content-Type","application/json")
  13.      m_Header.put("X-Shopify-Access-Token", v_AccessToken)
  14.      // 
  15.      // graphql 
  16.      v_SearchSKU = ifnull(p_SKU,"")
  17.      v_GraphQl = "{ productVariants(first: 1, query: \"sku:'"+v_SearchSKU+"'\") { edges { node { id price sku title barcode inventoryItem { id } product { id } } } } }"
  18.      m_GraphQl = Map()
  19.      m_GraphQl.put("query", v_GraphQl)
  20.      // 
  21.      // Send this GraphQL query 
  22.      v_Endpoint = "https://" + v_ShopID + "/admin/api/" + v_ShopifyApiVersion.toString() + "/graphql.json"
  23.      r_GetProduct = invokeUrl 
  24.      [ 
  25.          url :v_Endpoint 
  26.          type :POST 
  27.          parameters: m_GraphQl.toString() 
  28.          headers:m_Header 
  29.      ]
  30.      return r_GetProduct.toMap()
  31.  } 
Job done! Yields something like:
copyraw
{
  "data": {
    "productVariants": {
      "edges": [
        {
          "node": {
            "id": "gid://shopify/ProductVariant/123456789012345",
            "price": "450.95",
            "sku": "ABC123456",
            "title": "Default Title",
            "barcode": "0000012345",
            "inventoryItem": {
              "id": "gid://shopify/InventoryItem/23456789123456"
            },
            "product": {
              "id": "gid://shopify/Product/345678901234"
            }
          }
        }
      ]
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 5,
      "actualQueryCost": 5,
      "throttleStatus": {
        "maximumAvailable": 1000,
        "currentlyAvailable": 995,
        "restoreRate": 50
      }
    }
  }
}
  1.  { 
  2.    "data": { 
  3.      "productVariants": { 
  4.        "edges": [ 
  5.          { 
  6.            "node": { 
  7.              "id": "gid://shopify/ProductVariant/123456789012345", 
  8.              "price": "450.95", 
  9.              "sku": "ABC123456", 
  10.              "title": "Default Title", 
  11.              "barcode": "0000012345", 
  12.              "inventoryItem": { 
  13.                "id": "gid://shopify/InventoryItem/23456789123456" 
  14.              }, 
  15.              "product": { 
  16.                "id": "gid://shopify/Product/345678901234" 
  17.              } 
  18.            } 
  19.          } 
  20.        ] 
  21.      } 
  22.    }, 
  23.    "extensions": { 
  24.      "cost": { 
  25.        "requestedQueryCost": 5, 
  26.        "actualQueryCost": 5, 
  27.        "throttleStatus": { 
  28.          "maximumAvailable": 1000, 
  29.          "currentlyAvailable": 995, 
  30.          "restoreRate": 50 
  31.        } 
  32.      } 
  33.    } 
  34.  } 

Additional
  • Note that if your search query string has spaces in it, the above without the small apostrophes (single-quotes) will return either a parsing error or just no data. In the line:
    copyraw
    v_GraphQl = "{ productVariants(first: 1, query: \"sku:" + v_SearchSKU + "\") { edges { node { id price sku title barcode inventoryItem { id } product { id } } } } }"
    1.  v_GraphQl = "{ productVariants(first: 1, query: \"sku:" + v_SearchSKU + "\") { edges { node { id price sku title barcode inventoryItem { id } product { id } } } } }" 
    Change from \"sku:" + v_SearchSKU + "\" to \"sku:'" + v_SearchSKU + "'\"
    So instead of: query: "sku:ABC 123" send query: "sku:'ABC 123'"

  • The final graphQL for product id, variant id, inventory id, quantity, price barcode and handle?
    copyraw
    // 
    // graphql 
    v_SearchSKU = ifnull(v_SKU,"");
    v_GraphQl = "{ productVariants(first: 1, query: \"sku:'" + v_SearchSKU + "'\") { edges { node { id price inventoryQuantity sku barcode inventoryItem { id } product { id handle } } } } }";
    m_GraphQl = Map();
    m_GraphQl.put("query",v_GraphQl);
    // 
    // Send this GraphQL query 
    v_Endpoint = "https://" + v_ShopID + "/admin/api/" + v_ShopifyApiVersion.toString() + "/graphql.json";
    r_GetProduct = invokeurl
    [
        url :v_Endpoint
        type :POST
        parameters:m_GraphQl.toString()
        headers:m_Header
    ];
    //
    // parse the response
    if(!isnull(r_GetProduct.get("data")))
    {
        if(!isnull(r_GetProduct.get("data").get("productVariants")))
        {
            if(!isnull(r_GetProduct.get("data").get("productVariants").get("edges")))
            {
                l_Edges = r_GetProduct.get("data").get("productVariants").get("edges");
                for each  r_Node in l_Edges
                {
                    m_Node = r_Node.get("node");
                    v_ProductID = m_Node.get("product").get("id").getSuffix("Product/");
                    v_VariantID = m_Node.get("id").getSuffix("ProductVariant/");
                    v_InventoryID = m_Node.get("inventoryItem").get("id").getSuffix("InventoryItem/");
                    v_Handle = m_Node.get("product").get("handle");
                    v_Price = m_Node.get("price");
                    v_Qty = m_Node.get("inventoryQuantity");
                    v_Barcode = m_Node.get("barcode");
                }
            }
        }
    }
    1.  // 
    2.  // graphql 
    3.  v_SearchSKU = ifnull(v_SKU,"")
    4.  v_GraphQl = "{ productVariants(first: 1, query: \"sku:'" + v_SearchSKU + "'\") { edges { node { id price inventoryQuantity sku barcode inventoryItem { id } product { id handle } } } } }"
    5.  m_GraphQl = Map()
    6.  m_GraphQl.put("query",v_GraphQl)
    7.  // 
    8.  // Send this GraphQL query 
    9.  v_Endpoint = "https://" + v_ShopID + "/admin/api/" + v_ShopifyApiVersion.toString() + "/graphql.json"
    10.  r_GetProduct = invokeUrl 
    11.  [ 
    12.      url :v_Endpoint 
    13.      type :POST 
    14.      parameters:m_GraphQl.toString() 
    15.      headers:m_Header 
    16.  ]
    17.  // 
    18.  // parse the response 
    19.  if(!isnull(r_GetProduct.get("data"))) 
    20.  { 
    21.      if(!isnull(r_GetProduct.get("data").get("productVariants"))) 
    22.      { 
    23.          if(!isnull(r_GetProduct.get("data").get("productVariants").get("edges"))) 
    24.          { 
    25.              l_Edges = r_GetProduct.get("data").get("productVariants").get("edges")
    26.              for each  r_Node in l_Edges 
    27.              { 
    28.                  m_Node = r_Node.get("node")
    29.                  v_ProductID = m_Node.get("product").get("id").getSuffix("Product/")
    30.                  v_VariantID = m_Node.get("id").getSuffix("ProductVariant/")
    31.                  v_InventoryID = m_Node.get("inventoryItem").get("id").getSuffix("InventoryItem/")
    32.                  v_Handle = m_Node.get("product").get("handle")
    33.                  v_Price = m_Node.get("price")
    34.                  v_Qty = m_Node.get("inventoryQuantity")
    35.                  v_Barcode = m_Node.get("barcode")
    36.              } 
    37.          } 
    38.      } 
    39.  } 


  • For anyone interested in the GraphQL to query using the inventoryLevel connection, try the following to get the Locations:
    copyraw
    v_GraphQl = "{locations(first: 5, reverse: true) { edges { node { id name } } } }";
    1.  v_GraphQl = "{locations(first: 5, reverse: true) { edges { node { id name } } } }"
    Then using the Location of your product variant (where "1234567890" is the location ID) to get the recent 10 products updated today:
    copyraw
    {
      productVariants(
        first: 10
        reverse: true
        query: "updated_at:>'2022-02-14T00:00:00+0000'"
      ) {
        edges {
          node {
            id
            price
            compareAtPrice
            sku
            barcode
            inventoryQuantity
            inventoryItem {
              id
              inventoryLevel(locationId: "gid://shopify/Location/1234567890") {
                id
                available
                updatedAt
              }
            }
            product {
              id
              title
              handle
            }
            updatedAt
          }
        }
      }
    }
    1.  { 
    2.    productVariants( 
    3.      first: 10 
    4.      reverse: true 
    5.      query: "updated_at:>'2022-02-14T00:00:00+0000'" 
    6.    ) { 
    7.      edges { 
    8.        node { 
    9.          id 
    10.          price 
    11.          compareAtPrice 
    12.          sku 
    13.          barcode 
    14.          inventoryQuantity 
    15.          inventoryItem { 
    16.            id 
    17.            inventoryLevel(locationId: "gid://shopify/Location/1234567890") { 
    18.              id 
    19.              available 
    20.              updatedAt 
    21.            } 
    22.          } 
    23.          product { 
    24.            id 
    25.            title 
    26.            handle 
    27.          } 
    28.          updatedAt 
    29.        } 
    30.      } 
    31.    } 
    32.  } 
    I found this is the same as the inventoryQuantity so might as well reduce those costs (by almost a half!).



Source(s):
Category: Zoho :: Article: 806

Credit where Credit is Due:


Feel free to copy, redistribute and share this information. All that we ask is that you attribute credit and possibly even a link back to this website as it really helps in our search engine rankings.

Disclaimer: The information on this website is provided without warranty and any content is merely the opinion of the author. Please try to test in development environments prior to adapting them to your production environments. The articles are written in good faith and, at the time of print, are working examples used in a commercial setting.

Thank you for visiting and, as always, we hope this website was of some use to you!

Kind Regards,

Joel Lipman
www.joellipman.com

Related Articles

Joes Revolver Map

Accreditation

Badge - Certified Zoho Creator Associate
Badge - Certified Zoho Creator Associate

Donate & Support

If you like my content, and would like to support this sharing site, feel free to donate using a method below:

Paypal:
Donate to Joel Lipman via PayPal

Bitcoin:
Donate to Joel Lipman with Bitcoin - Valid till 8 May 2022 bc1qjtp4l4ra452wzvuk9a45yfj82zkahsyy2z379y
© 2022 Joel Lipman .com. All Rights Reserved.