@PorchlightZach , I think that field only works for BigCommerce, not Shopify.
@PorchlightZach Please make sure your URL is ending with .png or .gif or .jpg. Then it would work. This is a requirement from Shopify.
@PorchlightZach , I think that field only works for BigCommerce, not Shopify.
Thank you for the answer, do you know why this field isn’t supported with Shopify? The “URL Handle” field is available via the Shopify API.
After some more testing I kind of suspected this wasn’t supported so I implemented the desired functionality with a customization using the ShopifySharp library. I had to use version 6.4.0 as that was the latest release that only had .NET Standard 2.0 has a dependency. Newer releases require later versions of System.Text.Json that seem to conflict with Acumatica (23R1, anyway).
I figure I’ll share some of my code in case anyone else may have cause to programmatically control their product URLs.
Here is the view I use to get the ExternID that holds the Shopify Product ID for a particular inventory item. I don’t know if this is the “right” way to find this information, I just kind of reverse engineered this using the DAC schema browser and the database. It seems to work with the inventory items I’ve tested, so I’m going with this for now.
public SelectFrom<BCSyncStatus>
.Where<BCSyncStatus.localID.IsEqual<InventoryItem.noteID.FromCurrent>
.And<BCSyncStatus.entityType.IsEqual<BCEntitiesAttribute.stockItem>>>
.View SyncStatus;
Here is a slightly modified function from my customizationthat updates the Shopify Product’s URL Handle. This method gets called from inside a PXAction on my customizations InventoryItemMaint graph extension. private void SetShopifyProductHandle(InventoryItem item,
ProductService productService)
{
var status = SyncStatus.Select().RowCast<BCSyncStatus>().First();
if (status == null || string.IsNullOrEmpty(status.ExternID)) return;
var bcItem = item.GetExtension<BCInventoryItem>();
if (string.IsNullOrEmpty(bcItem.CustomURL)) return;
try
{
var productId = (long)Convert.ToDouble(status.ExternID);
var product = Task.Run(() => productService.GetAsync(productId)).Result;
if (product != null && product.Handle != bcItem.CustomURL)
{
var handleProduct = new Product() { Handle = bcItem.CustomURL };
var productResult =
Task.Run(() => productService.UpdateAsync(productId, handleProduct)).Result;
}
}
catch (Exception ex)
{
PXTrace.WriteWarning(
$"Cannot set product handle {bcItem.CustomURL} for {item.InventoryCD} - {ex.Message}");
}
return;
}
I’m not including the PXAction code as it is pretty standard for the most part. The two important things it does, other than start a PXLongOperation, is create a ProductService from the ShopifySharp library (argument 2 in the function above) before calling the function and then updating the inventory item with a boolean value (custom field) that I use to track whether the product URL handle got updated.
As an aside, I was getting deadlocks when trying to do a typical await for an async function call with a .Result at the end to get the value. Here is an example of what I’m referring to:
var productResult = (await productService.UpdateAsync(productId, handleProduct)).Result;
That code would deadlock every time. I haven’t run into this before, and I suspect it has something to do with the ShopifySharp library. Thankfully a google search result recommended using the Task.Run(() => asyncMethodCall()).Result; approach for preventing deadlocks and it worked.
As a follow up to my example, I should point out that the SelectFrom I included to get the ExternID from BCSyncStatus is only appropriate to use if you have a single Shopify store. If you have more than one store or multiple commerce integration types, you’d want to modify the query so that you’re selecting records based on the BindingID of the store you want to modify the URL on and the ConnectorType (i.e. “SPC” for Shopify).
If your configuration is for more than one store, the simple SelectFrom may work sometimes but not others, depending on whether an inventory item is in multiple stores or not.
@PorchlightZach , I don’t know why it was not implemented in the integration, but I suppose it’s because it’s not straightforward. product resource in Shopify Rest API doesn’t have handle as an input field. If it works, it’s undocumented. Even the latest API version doesn’t have a handle field: https://shopify.dev/docs/api/admin-rest/2024-07/resources/product#post-products
If you’ve got to work with the customization, it’s great!
@KarthikGajendran , this is something you and your team might want to look at. @PorchlightZach , the best approach (if you want to see this in the core product), would be to create an idea on this forum, and let folks vote on it.
Thanks for your detailed writeup! It may be useful for other merchants.
@PorchlightZach , I don’t know why it was not implemented in the integration, but I suppose it’s because it’s not straightforward. product resource in Shopify Rest API doesn’t have handle as an input field. If it works, it’s undocumented. Even the latest API version doesn’t have a handle field: https://shopify.dev/docs/api/admin-rest/2024-07/resources/product#post-products
While I do not see handle listed in the examples for the Create/Update endpoints, the JSON Product Resource documentation linked just above that does show the field. This is what I’m referring to:
https://shopify.dev/docs/api/admin-rest/2024-07/resources/product#resource-object
Back when our company was on Acumatica 2019R1, I had to create a .NET Core console application to bring orders into the ERP from Shopify and send updates back when the order shipped. This was like 4 years ago and our project started before the Shopify connector was available. Anyway, I used ShopifySharp for that project and my experience was that any fields listed in the JSON resource documentation could be updated unless otherwise noted.
That old application didn’t have to work with the products area of the API, so maybe there is an exception going on that I’m not aware of. I haven’t had to maintain it in many years, so maybe something changed since then. I just sort of figured if the product property documentation lists a field, it would be fair game to be changed via the API even if the examples didn’t show it explicitly.
I do know that Shopify requires the product handle have a limited set of characters that can be in the field, which makes sense seeing as it is a URL. Perhaps the combination of restrictive text requirements happening before the API POST/PUT requests happen is part of the issue. It could also be that supporting multiple store connectors with a single Custom URL field is part of the issue. If you have more than one store, I could see some customers having an issue with only being able to have a single URL for a product on all stores. The user interface for a per-store custom URL could get pretty gnarly. I assume BigCommerce implements this in such a way where it isn’t a problem.
For what it is worth, the official Shopify Ruby API library includes “handle” in the list of properties the product resource supports. Here is a link to the line showing handle as an available property: https://github.com/Shopify/shopify-api-ruby/blob/bd811ca0257b4174004d8537d3db4a52e53d84a0/lib/shopify_api/rest/resources/2024_07/product.rb#L24. I’m sharing this because it seems like we should be able to modify handle if Shopify’s official libraries has support for it.
For the time being, my customization is working fine for updates using ShopifySharp, hopefully it will continue to function until the REST APIs are no longer supported. My implementation gets to make assumptions about which store I’m updating. Even though we have more than one Shopify store, only one of them is going to get a programmatically created URL. That simplifies things for my code (i.e. no need to specify custom URLs on a per store basis).
@PorchlightZach , based on the Shopify’s documentation, the description of handle field is
“A unique human-friendly string for the product. Automatically generated from the product's title
. Used by the Liquid templating language to refer to objects.”. So we think this is a read-only field and exclude it from the create/update scope. But the field is in the product object.
Now I checked the handle field in the GraphQL API, its description has changed to “A unique human-friendly string of the product's title.” and it’s included in the Product mutation API, so we can support this field in the following changes.
@KarthikGajendran