For Zoho Services only:


I'm actually part of something bigger at Ascent Business Solutions recognized as the top Zoho Premium Solutions Partner in the United Kingdom.

Ascent Business Solutions offer support for smaller technical fixes and projects for larger developments, such as migrating to a ZohoCRM.  A team rather than a one-man-band is always available to ensure seamless progress and address any concerns. You'll find our competitive support rates with flexible, no-expiration bundles at http://ascentbusiness.co.uk/zoho-support-2.  For larger projects, check our bespoke pricing structure and receive dedicated support from our hands-on project consultants and developers at http://ascentbusiness.co.uk/crm-solutions/zoho-crm-packages-prices.

The team I manage specializes in coding API integrations between Zoho and third-party finance/commerce suites such as Xero, Shopify, WooCommerce, and eBay; to name but a few.  Our passion lies in creating innovative solutions where others have fallen short as well as working with new businesses, new sectors, and new ideas.  Our success is measured by the growth and ROI we deliver for clients, such as transforming a garden shed hobby into a 250k monthly turnover operation or generating a +60% return in just three days after launch through online payments and a streamlined e-commerce solution, replacing a paper-based system.

If you're looking for a partner who can help you drive growth and success, we'd love to work with you.  You can reach out to us on 0121 392 8140 (UK) or info@ascentbusiness.co.uk.  You can also visit our website at http://ascentbusiness.co.uk.

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: Please note that the information provided on this website is intended for informational purposes only and does not represent a warranty. The opinions expressed are those of the author only. We recommend testing any solutions in a development environment before implementing them in production. The articles are based on our good faith efforts and were current at the time of writing, reflecting our practical experience 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 bc1qf6elrdxc968h0k673l2djc9wrpazhqtxw8qqp4

Ethereum:
Donate to Joel Lipman with Ethereum 0xb038962F3809b425D661EF5D22294Cf45E02FebF
© 2024 Joel Lipman .com. All Rights Reserved.