Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check here for common issues with fulfilling line items and orders #828

Open
alexhauser opened this issue Jan 11, 2023 · 21 comments
Open

Check here for common issues with fulfilling line items and orders #828

alexhauser opened this issue Jan 11, 2023 · 21 comments
Labels
documentation fulfillments Questions and issues dealing with Shopify's fulfillment and fulfillment orders systems.

Comments

@alexhauser
Copy link
Contributor

The fulfillment example code in the readme.md does not compile any more, since it does not incorporate the breaking changes introduced with the switch to the 2022-07 API.

https://github.com/nozzlegear/ShopifySharp#fulfillments

@alexhauser
Copy link
Contributor Author

In particular, this code in my app is not working anymore since switching to the latest release (I was a bit behind in releases, so this might as well have been caused by a breaking change in a previous update).

var fulfillment = new Fulfillment()
{
    TrackingCompany = "DPD",
    TrackingUrl = String.Format(Settings.Default.ShopifyDPDTrackingUrl, mTrackingNr),
    TrackingNumber = mTrackingNr,
    LocationId = mShopconfig.FulfillmentLocationId, 
    NotifyCustomer = true
};

fulfillment = await mFulfillmentService.CreateAsync(mOrder.Id.Value, fulfillment);

Can anyone please give a hint on how this code should look like nowadays?

I am aware that I can create FulfillmentShipping and pass that to FulfillmentService.CreateAsync(), however I don't seem to understand how that then relates to a particular order, since nowhere in this process I need to pass in an order id.

Thanks for the awsome work in this project btw!

@nozzlegear
Copy link
Owner

nozzlegear commented Jan 12, 2023

Thanks for the heads up! That definitely needs to be updated. Fulfillments are slightly more complicated now, you need to work with the FulfillmentOrderService and the FulfillmentService to fulfill things. To put it simply, once an order is created Shopify will create one or more fulfillment orders automatically for the order and then it's up to the fulfiller to pick out those fulfillment orders, add line items to them, and then use the FulfllmentShipping class to create a fulfillment.

You can find our tests for the FulfillmentService right here, but here's some example code for how you'd find fulfillment orders and create a fulfillment:

var fulfillments = new FulfillmentService(domain, accessToken);
var fulfillmentOrders = new FulfillmentOrderService(domain, accessToken);
var orderId = 123456;

// Find open fulfillment orders for this order
var openFulfillmentOrders = await fulfillmentOrders.ListAsync(orderId);
openFulfillmentOrders = openFulfillmentOrders.Where(f => f.Status == "open").ToList();

// Fulfill the line items
var lineItems = openFulfillmentOrders.Select(o => new LineItemsByFulfillmentOrder
{
  FulfillmentOrderId = o.Id.Value
  // Optionally specify a list of line items if you're doing a partial fulfillment
  // FulfillmentRequestOrderLineItems = ...
});
var fulfillment = await fulfillments.CreateAsync(new FulfillmentShipping
{
  Message = "items are shipping!",
  FulfillmentRequestOrderLineItems = lineItems,
  NotifyCustomer = true,
  TrackingInfo = new TrackingInfo
  {
    Company = "UPS",
    Url = "...",
    Number = "..."
  }
});

Let me know if that answers your question!

Edit

A note on partially fulfilling line items. The ShopifySharp.FulfillmentRequestOrderLineItem.Id is not the same as the fulfillmentLineItem.LineItemId value. This is not what Shopify is expecting when you're partially fulfilling a fulfillment order, and if you use it Shopify will return an error. What they actually want is the fulfillmentLineItem.Id.

Yes, it's confusing.

Here's an example that I'm using in a guide I'm writing for using ShopifySharp to do fulfillments. In the example, the user will be able to select which line items in a single fulfillment order (not an order, a fulfillment order) will be fulfilled.

Here's how we'd do that:

var fulfillmentService = new FulfillmentService(domain, accessToken);
var fulfillmentOrderService = new FulfillmentOrderService(domain, accessToken);
var fulfillmentOrderId = 123456789L;

// Get this fulfillment order
var fulfillmentOrder = await fulfillmentOrderService.GetAsync(fulfillmentOrderId);

// In this example, we let the user choose which line items in this particular fulfillment order will be fulfilled
// Where request = some kind of form post or json model
long[] itemIds = request.ItemsToFulfill;

// Wrap the desired line items together with the fulfillment order id
var fulfillmentOrderLineItems = new LineItemsByFulfillmentOrder
{
  FulfillmentOrderId = fulfillmentOrderId,
  // This is where you do a partial fulfillment. Using this property means you're telling Shopify "only fulfill these parts of the fulfillment order".
  FulfillmentRequestOrderLineItems = itemIds.Select(itemId => new FulfillmentRequestOrderLineItem
  {
    // NOTE: This Id property is not the same as a `FulfillmentOrderLineItem.LineItemId` value!
    // You want to use `FulfillmentOrderLineItem.Id` instead.
    Id = itemId,
    // You need to tell Shopify how much of this single item to fulfill. You must ensure that 
    // you are not fulfilling more than the fulfillable quantity, or else Shopify will throw an error.
    // In this example, we just want to fulfill all of the available line item, so we pass in the
    // entire fulfillable quantity.
    Quantity = fulfillmentOrder.FulfillmentOrderLineItems
      .First(li => li.Id == itemId)
      .FulfillableQuantity
  })
};

// Create a new fulfillment using that fulfillment order with partial line items
var fulfillment = await fulfillments.CreateAsync(new FulfillmentShipping
{
  Message = "items are shipping!",
  FulfillmentRequestOrderLineItems = new [] { lineItem },
  NotifyCustomer = true,
  TrackingInfo = new TrackingInfo
  {
    Company = "UPS",
    Url = "...",
    Number = "..."
  }
});

Thanks to @ajvanlaningham below for pointing out the LineItemId gotcha. That caused me trouble in production.

@alexhauser
Copy link
Contributor Author

Thank you so much, that's exactly what I was looking for!

@nozzlegear
Copy link
Owner

Great! I'll keep this open as a reminder for me to get the fulfillment example code updated.

@bQvle
Copy link

bQvle commented Jan 13, 2023

Thanks for the update, What is the practice when we have multipe TrackingNumbers/Urls? before we could send them as an array. Now the FulfillmentShipping only supports a single "TrackingInfo".

I could create multiple fulfillments for each tracking number, but this is a problem for many of my customers, because this will trigger some payment providers to create multiple invoices.

@alexhauser
Copy link
Contributor Author

For anyone who might stumble across this issue after ugrading to the 2022-07 API-version:

If fulfillmentOrders.ListAsync(orderId) comes back empty and returns no FulfillmentOrders, the problem for me was that I had not granted my app access to some of the newly added fulfillment-related API-permissions like read_merchant_managed_fulfillment_orders or write_merchant_managed_fulfillment_orders.

It's weird that the call just succeeds and returns an empty array, instead of giving a permission error, but that's exactly what happens. (I know this is the Shopify API's fault, ShopifySharp has nothing to do with this).

Hope this will save someone the hassle of figuring this out the hard way. 😉

@FlySociety
Copy link

I noticed in the fulfillment example code, there is a ToDo list which is still incomplete. For example as of writing this, 'Mark fulfillment order as open' is not checked as complete. Does that mean the code still needs working on, or just the documentation?

@alexhauser
Copy link
Contributor Author

@nozzlegear, my app uses the current 2022-07 API release of ShopifySharp and I'm following the fulfillment example code that you have provided a few posts above this one (so NOT the deprecated one in the readme).

But today, I was notified by Shopify, that...

TL;DR: Your custom app is leveraging the deprecated Fulfillment API. Please note that the Fulfillment API will stop working as intended on March 31st, 2023 in favor of our new Fulfillment Order API.

Is it possible that the FulfillmentService class or any other part of the library is still using the now deprecated Fulfillment API under the hood?

This is the code I'm using:

private async Task FulfillOrder()
{
	var openFulfillmentOrders = await mFulfillmentOrderService.ListAsync(mOrder.Id.Value);
	openFulfillmentOrders = openFulfillmentOrders.Where(f => f.Status == "open").ToList();

	var lineItems = openFulfillmentOrders.Select(o => new LineItemsByFulfillmentOrder()
	{
		FulfillmentOrderId = o.Id.Value
	});

	var fulfillmentShipping = new FulfillmentShipping()
	{
		NotifyCustomer = true,
		Message = "Ihre Bestellung wird nun versendet!",
		FulfillmentRequestOrderLineItems = lineItems,
		TrackingInfo = new TrackingInfo()
		{
			Company = "DPD",
			Url = String.Format(Settings.Default.ShopifyDPDTrackingUrl, mTrackingNr),
			Number = mTrackingNr
		}
	};

	var fulfillment = await mFulfillmentService.CreateAsync(fulfillmentShipping);
}

Thanks in advance for your input!

@nturini-cascinanet
Copy link

nturini-cascinanet commented Feb 20, 2023

Thanks for the heads up! That definitely needs to be updated. Fulfillments are slightly more complicated now, you need to work with the FulfillmentOrderService and the FulfillmentService to fulfill things. To put it simply, once an order is created Shopify will create one or more fulfillment orders automatically for the order and then it's up to the fulfiller to pick out those fulfillment orders, add line items to them, and then use the FulfllmentShipping class to create a fulfillment.

You can find our tests for the FulfillmentService right here, but here's some example code for how you'd find fulfillment orders and create a fulfillment:

var fulfillments = new FulfillmentService(domain, accessToken);
var fulfillmentOrders = new FulfillmentOrderService(domain, accessToken);
var orderId = 123456;

// Find open fulfillment orders for this order
var openFulfillmentOrders = await fulfillmentOrders.ListAsync(orderId);
openFulfillmentOrders = openFulfillmentOrders.Where(f => f.Status == "open").ToList();

// Fulfill the line items
var lineItems = openFulfillmentOrders.Select(o => new LineItemsByFulfillmentOrder
{
  FulfillmentOrderId = o.Id.Value
  // Optionally specify a list of line items if you're doing a partial fulfillment
  // FulfillmentRequestOrderLineItems = ...
});
var fulfillment = await fulfillments.CreateAsync(new FulfillmentShipping
{
  Message = "items are shipping!",
  FulfillmentRequestOrderLineItems = lineItems,
  NotifyCustomer = true,
  TrackingInfo = new TrackingInfo
  {
    Company = "UPS",
    Url = "...",
    Number = "..."
  }
});

Let me know if that answers your question!

Hi @nozzlegear , according to you "once an order is created Shopify will create one or more fulfillment orders automatically for the order" if i call "ListAsync" function from FulfillmentOrderService i must receive some response, but my response list is empty; maybe it's because i don't use 2022-07 api? where i can see the API version i'm using?

EDIT: Sorry didn't see this response "If fulfillmentOrders.ListAsync(orderId) comes back empty and returns no FulfillmentOrders, the problem for me was that I had not granted my app access to some of the newly added fulfillment-related API-permissions like read_merchant_managed_fulfillment_orders or write_merchant_managed_fulfillment_orders." after setting this permissions correctly work

@patrickebates
Copy link

Am I missing how/where to set the Fulfillment Location with the new objects?

@brujah
Copy link

brujah commented Apr 18, 2023

Am I missing how/where to set the Fulfillment Location with the new objects?

same here

@ajvanlaningham
Copy link

ajvanlaningham commented Apr 25, 2023

A NOTE ABOUT PARTIAL FULFILLMENT:
It took a day for me to figure out so I wanted to expand on this thread for anyone who might find it helpful.

var fulfillments = new FulfillmentService(domain, accessToken);
var fulfillmentOrders = new FulfillmentOrderService(domain, accessToken);
var orderId = 123456;
//*** NEW CODE ***
var lineItemId = 654321; //the long? ShopifySharp.LineItem.Id of the item that you want to partially fulfill

// Find open fulfillment orders for this order
var openFulfillmentOrders = await fulfillmentOrders.ListAsync(orderId);
openFulfillmentOrders = openFulfillmentOrders.Where(f => f.Status == "open").ToList();
//***NEW CODE ***
//grab the open fulfullment order list of line items
var fulfullmentLineItems = openFulfillments.FirstOrDefault().FulfillmentOrderLineItems.ToList();
var fulfillmentLineItem =  fulfillmentLineItems.Find(li => li.lineItemId == lineItemId); //pick the correct line item

// Fulfill the line items
var lineItems = openFulfillmentOrders.Select(o => new LineItemsByFulfillmentOrder
{
  FulfillmentOrderId = o.Id.Value,
  //***NEW CODE***
  //Specify a list of line items if you're doing a partial fulfillment for
  FulfillmentRequestOrderLineItems = new List<FulfullmentRequestOrderLineItems>
   {
         new ShopifySharp.FulfillmentRequestOrderLineItem
          {
             Id = fulfillmentLineItem.Id,  //NOTE: this is not fulfillmentLineItem.LineItemId!! 
             Quantity = ... // The quantity you want to partially fulfill.
          }
    }
});
var fulfillment = await fulfillments.CreateAsync(new FulfillmentShipping
{
  Message = "items are shipping!",
  FulfillmentRequestOrderLineItems = lineItems,
  NotifyCustomer = true,
  TrackingInfo = new TrackingInfo
  {
    Company = "UPS",
    Url = "...",
    Number = "..."
  }
});

It took me a fair amount of time to figure out why I was getting an error, and ultimately it was because I was using the "FulfillmentOrderLineItem.LineItemId" instead of the "FulfillmentOrderLineItem.Id"... both of which are different from the actual ShopifySharp.LineItem.Id.

I can't see anything wrong with using the above code always, especially if you are doing work for a shopify store front in which partial fulfillments are a regular occurance. If it's a full fulfillment, just loop through all the items in the fulfillmentLineItems list and fill the full quantity. I'd love to hear from a more experienced developer if I'm wrong though!

@nozzlegear
Copy link
Owner

nozzlegear commented Apr 25, 2023 via email

@joshbybee
Copy link

If you're like me, and you commonly had multiple tracking #'s, my version of the code ended up looking like this:

    public class ShopifyFulfillment
    {
        [JsonPropertyName("location_id")]
        public long LocationId { get; set; }

        [JsonPropertyName("tracking_numbers")]
        public string[] TrackingNumbers { get; set; }

        [JsonPropertyName("tracking_company")]
        public string TrackingCompany { get; set; }

        [JsonPropertyName("line_items")]
        public ShopifyLineItem[] LineItems { get; set; }
    }

    public class ShopifyLineItem
    {
        [JsonPropertyName("id")]
        public long Id { get; set; }

        [JsonPropertyName("fulfillable_quantity")]
        public int? FulfillableQuantity { get; set; }

        [JsonPropertyName("fulfillment_service")]
        public string FulfillmentService { get; set; }

        [JsonPropertyName("fulfillment_status")]
        public string FulfillmentStatus { get; set; }

        [JsonPropertyName("name")]
        public string Name { get; set; }

        [JsonPropertyName("product_id")]
        public long? ProductId { get; set; }

        [JsonPropertyName("quantity")]
        public int? Quantity { get; set; }

        [JsonPropertyName("requires_shipping")]
        public bool? RequiresShipping { get; set; }

        [JsonPropertyName("sku")]
        public string SKU { get; set; }

        [JsonPropertyName("title")]
        public string Title { get; set; }

        [JsonIgnore]
        public string TrackingNumber { get; set; }

    }
public async Task FulfillOrder(List<ShopifyFulfillment> orderFulfillment, long orderId)
        {
            var fulfillments = new FulfillmentService(_config["shopifyBaseAddress"], _config["shopifyApiSecret"]);
            var fulfillmentOrders = new FulfillmentOrderService(_config["shopifyBaseAddress"], _config["shopifyApiSecret"]);

            // Find open fulfillment orders for this order
            var openFulfillmentOrders = await fulfillmentOrders.ListAsync(orderId);
            openFulfillmentOrders = openFulfillmentOrders.Where(f => f.Status == "open").ToList();

            //grab the open fulfillment order list of line items
            var fulfillmentLineItems = openFulfillmentOrders.FirstOrDefault().FulfillmentOrderLineItems.ToList();


            foreach (var shipment in orderFulfillment)
            {
                var lineItems = new List<LineItemsByFulfillmentOrder>();
                foreach (var shipmentLineItem in shipment.LineItems)
                {
                    var fulfillmentLineItem =  fulfillmentLineItems.Find(li => li.LineItemId == shipmentLineItem.Id);
                    var lineItem = openFulfillmentOrders.Select(o => new LineItemsByFulfillmentOrder
                    {
                        FulfillmentOrderId = o.Id.Value,
                        FulfillmentRequestOrderLineItems = new List<FulfillmentRequestOrderLineItem>
                        {
                            new() {Id = fulfillmentLineItem.Id, Quantity = shipmentLineItem.Quantity}
                        }
                    });
                    lineItems.AddRange(lineItem);
                }

                var fulfillment = await fulfillments.CreateAsync(new FulfillmentShipping
                {
                    Message = "Your shipment is on it's way!",
                    FulfillmentRequestOrderLineItems = lineItems,
                    NotifyCustomer = true,
                    TrackingInfo = new TrackingInfo
                    {
                        Company = shipment.TrackingCompany,
                        Url = null,
                        Number = shipment.TrackingNumbers[0]
                    }
                });
            }
           
        }
        ```

@z0ha1b
Copy link

z0ha1b commented Oct 19, 2023

I am having 404 error while executing the following code.

       var lineItems = fulfillment.LineItems.Select(x => new FulfillmentRequestOrderLineItem
                  {
                      Id = x.Id,
                      Quantity = x.Quantity,
                  });

        var service = new FulfillmentService(acct.ShopifyURL, acct.ShopifyAccessToken);
        var fulfillmentShipping = new FulfillmentShipping()
        {
            Message = "Items will be shipped now.",
            NotifyCustomer = fulfillment.NotifyCustomer,
            TrackingInfo = new TrackingInfo
            {
                Company = fulfillment.TrackingCompany,
                Url = fulfillment.TrackingUrl,
                Number = fulfillment.TrackingNumber,
            },
            FulfillmentRequestOrderLineItems = new List<LineItemsByFulfillmentOrder>()
            {
                new LineItemsByFulfillmentOrder()
                {
                    FulfillmentOrderId = orderId,
                    FulfillmentRequestOrderLineItems = lineItems.ToArray()
                }
            }
        };

        fulfillment = await service.CreateAsync(fulfillmentShipping);

@nozzlegear
Copy link
Owner

@z0ha1b That looks correct I think. One thing I'd investigate is that orderId you have -- are you positive that's the fulfillment order's id, and not the full order's id? It has to be the fulfillment order.

@z0ha1b
Copy link

z0ha1b commented Oct 20, 2023

@z0ha1b That looks correct I think. One thing I'd investigate is that orderId you have -- are you positive that's the fulfillment order's id, and not the full order's id? It has to be the fulfillment order.

thanks, it worked fine :)

@nozzlegear nozzlegear changed the title Fulfillment example code is obsolete Check here for common issues with fulfilling line items and orders Oct 26, 2023
@nozzlegear nozzlegear added the fulfillments Questions and issues dealing with Shopify's fulfillment and fulfillment orders systems. label Oct 26, 2023
@nozzlegear nozzlegear pinned this issue Oct 26, 2023
@Laurabee530 Laurabee530 added the stale Issues that have become inactive and need attention/review from @nozzlegear label Jan 25, 2024
@PendelinP
Copy link
Sponsor

PendelinP commented Feb 11, 2024

Hi there :)

I'm sitting here for hours to get the initial sample from @nozzlegear to work (see here: #828 (comment)). I'm using the basic version of the code (the first one) but I'm always getting a 404 from the API without any error message. I don't have any clue what I'm doing wrong. I also tried to enable all scopes but it simply doesn't work.

I also tried the code from the tests right here: https://github.com/nozzlegear/ShopifySharp/blob/6169078e4c2452e54ee0fef08d4f727fa78fea69/ShopifySharp.Tests/Fulfillment_Tests.cs

Is there a chance to get a more detailed error message since I'm desperatly trying for hours now and all I see is this 404?

I receive the correct line items and everything works as expected until I want to create the fulfillment itself :/

Thanks so much in advance :)

This is my current version:

  var orderService = new ShopifySharp.OrderService(domain, accessToken);
  var fulfillmentService = new ShopifySharp.FulfillmentService(domain, accessToken);
  var fulfillmentOrderService = new FulfillmentOrderService(domain, accessToken);

  // find order id for order number
  var order = await orderService.ListAsync(new OrderFilterWithName { Name = orderIdString });
  var orderId = order.Items.First().Id.Value;

  var fulfillmentOrders = await fulfillmentOrderService.ListAsync(orderId);
  var openFulfillmentOrders = fulfillmentOrders.Where(f => f.Status == "open").ToList();

  var lineItems = openFulfillmentOrders.Select(o => new LineItemsByFulfillmentOrder
  {
      FulfillmentOrderId = o.Id.Value,
  });
 
  try
  {
      var fulfillment = await fulfillmentService.CreateAsync(new FulfillmentShipping
      {
          Message = "Items are shipping now!",
          FulfillmentRequestOrderLineItems = lineItems,
          NotifyCustomer = notifyCustomer
      });

  }
  catch (ShopifyException e)
  {
      telemetryData.Add("Error message", e.Message);
      telemetryClient.TrackEvent("OrderFulfillment", telemetryData);
      return req.CreateResponse(HttpStatusCode.BadRequest);
  }

@nozzlegear nozzlegear removed the stale Issues that have become inactive and need attention/review from @nozzlegear label Feb 11, 2024
@nozzlegear
Copy link
Owner

Hey @PendelinP! Sorry to hear you're having trouble. If the code from the tests didn't work, I think my first suspicion would be to check the shop domain that you're using. Shopify will redirect requests and potentially cause 404s if you're not using the right myshopify domain.

Make sure the domain you're passing in to the service constructors looks like example.myshopify.com and not example.com. On top of that, we've recently found that even the myshopify domain can get redirected if a store has more than one of them. Check issue #968 and make sure that's not the case for you!

@PendelinP
Copy link
Sponsor

PendelinP commented Feb 11, 2024

Hey @PendelinP! Sorry to hear you're having trouble. If the code from the tests didn't work, I think my first suspicion would be to check the shop domain that you're using. Shopify will redirect requests and potentially cause 404s if you're not using the right myshopify domain.

Make sure the domain you're passing in to the service constructors looks like example.myshopify.com and not example.com. On top of that, we've recently found that even the myshopify domain can get redirected if a store has more than one of them. Check issue #968 and make sure that's not the case for you!

Wow - that was super fast and this was exactly my problem (example.com instead of example.myshopify.com). I'm soooooo thankful! I would have never found this issue :D Again, thank you so, so much!

@nozzlegear
Copy link
Owner

Awesome, glad it's working now @PendelinP!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation fulfillments Questions and issues dealing with Shopify's fulfillment and fulfillment orders systems.
Projects
None yet
Development

No branches or pull requests