Fulfill Orders in Shopify

Basically, all we do is each day fulfill our orders externally. Then use the REST API to mark the order as fulfilled. This then sets the status in Shopify to fulfilled, and all is good.

However, because the REST API is being retired, we are moving to GraphQL. I read the documentation, and I am still unsure how I should be “simply marking” our orders as fulfilled in Shopify.

Could you give me some direction as to how to do this?

1 Like

Hey @Robert_Palmer :waving_hand: - in GraphQL, managing fulfillments has changed a little bit for sure. The best place to start would be this guide here in our docs, but just for a quick example, you would first need your app to have the read/write_fulfillments access scopes as well as the relevant fulfillment_orders related scopes, but here’s how you would use the fulfillment API to mark an order as fulfilled:

mutation fulfillmentCreateV2($fulfillment: FulfillmentV2Input!) {
  fulfillmentCreateV2(fulfillment: $fulfillment) {
    fulfillment {
      id
      status
    }
    userErrors {
      field
      message
    }
  }
}

variables:

{
  "fulfillment": {
    "lineItemsByFulfillmentOrder": {
      "fulfillmentOrderId": "gid://shopify/FulfillmentOrder/940656279"
    }
  }
}

Our newer Fulfillments API that GraphQL usages hinges on Fulfillment Orders as the “base” for all fulfillment related actions - the docs there have a bit more info on this, but let me know if I can clarify anything on our end. Hope this helps :slight_smile:

Here is our final solution.

  1. Got fulfillment Orders
  2. For each order move location
  3. Then for each order get line items (using a cursor)
  4. Finally fulfill order
 function shopifyUpdateFulfillment($shipment_id){
       
        $shipment = Tools::getLock($shipment_id, false, ['label_pdf']);
        if($shipment === false){
            return false;
        }

        if (!empty($shipment->warehouse_code)) {
            $scope = $shipment->warehouse_code;
        } else if (!empty($shipment->warehouse_code_desired)) {
            $scope = $shipment->warehouse_code_desired;
        } else {
            $scope = 'Brisbane';
        }

        switch ($scope) {
            case 'Melbourne':
                $location_id = 123456789;
                break;
            case 'Sydney':
                $location_id = 987654321;
                break;
            default:
                $location_id = 135792468;
        }


        $returnedOrder = Connection::ShopifyConnection('query {
        order(id: "gid://shopify/Order/' . $shipment->shopify_id . '") {
        fulfillmentOrders(first: 50) {
        nodes {
        id

      }
    }
  }
}');

        if (isset($returnedOrder['data']['order'])) {
            $o = $returnedOrder['data']['order'];
            $ids = [];
            foreach ($o['fulfillmentOrders']['nodes'] as $node) {
                $ids[$node['id']] = [];
            }


        foreach($ids as $id => $value){
            $variable = [];
            $variable["id"] =  $id;
            $variable["newLocationId"] = "gid://shopify/Location/" . $location_id;


            $moved = Connection::ShopifyConnection('mutation fulfillmentOrderMove($id: ID!, $newLocationId: ID!) {
  fulfillmentOrderMove(id: $id, newLocationId: $newLocationId) {
    movedFulfillmentOrder {
      id
      status
    }
    originalFulfillmentOrder {
      id
      status
    }
    remainingFulfillmentOrder {
      id
      status
    }
    userErrors {
      field
      message
    }
  }
}', $variable);


        }
        }



        //need to paginate orders
        $returnedOrder = Connection::ShopifyConnection('query {
        order(id: "gid://shopify/Order/' . $shipment->shopify_id . '") {
        fulfillmentOrders(first: 50) {
        nodes {
        id

      }
    }
  }
}');


        $lines = '
        pageInfo{
                       hasNextPage
                       endCursor
                    }
        edges {
            node {
              id
              lineItem {
              quantity
            name
              }
            }
          }
        ';
        $first = '
    query getfulfillmentOrders($id: ID!){
        fulfillmentOrder(id: $id) {
            lineItems(first: 2){
               '.$lines.'
            }
        }
    }';
        $middle = '
    query getfulfillmentOrders($id: ID!, $cursor: String){
        fulfillmentOrder(id: $id) {
            lineItems(first: 2, after: $cursor){
               '.$lines.'
            }
        }
    }';

        if (isset($returnedOrder['data']['order'])) {
            $o = $returnedOrder['data']['order'];
            $ids = [];
            foreach ($o['fulfillmentOrders']['nodes'] as $node) {
                $ids[$node['id']] = [];
                $data = Connection::ShopifyConnection($first, ["id"=> $node['id']]);


                if(isset($data['data']['fulfillmentOrder']['lineItems'])) {
                    $fo = $data['data']['fulfillmentOrder'];


                    foreach ($fo['lineItems']['edges'] as $anode) {
                        $lineItem = $anode['node'];
                        $ids[$node['id']][] = ["id" => $lineItem['id'], "quantity" => $lineItem['lineItem']['quantity']];
                    }
		    if(isset($fo['lineItems']['pageInfo']['hasNextPage']) && $fo['lineItems']['pageInfo']['hasNextPage'] ) {

    		    	$cursor = $fo['lineItems']['pageInfo']['endCursor'];

    			do {
        			$returnedOrderMiddle = Connection::ShopifyConnection($middle, ["id" => $node['id'], "cursor" => $cursor]);
        			if (isset($returnedOrderMiddle['data']['fulfillmentOrder'])) {
            				$fo = $returnedOrderMiddle['data']['fulfillmentOrder'];
            				foreach ($fo['lineItems']['edges'] as $anode) {
                				$lineItem = $anode['node'];
               					 $ids[$node['id']][] = ["id" => $lineItem['id'], "quantity" => $lineItem['lineItem']['quantity']];
            			}
            if ($fo['lineItems']['pageInfo']['hasNextPage']) {
                $cursor = $fo['lineItems']['pageInfo']['endCursor'];
            }
        } else {
            $fo = null;
        }
    } while (!empty($fo) && $fo['lineItems']['pageInfo']['hasNextPage']);

}

                }
            }


            $input = [];
            foreach ($ids as $id => $lineItems) {

                $input[] = [
                    "fulfillmentOrderId" => $id,
                    "fulfillmentOrderLineItems" => $lineItems
                ];

            }

                if (!empty($shipment->carrier_code)) {
                    $carrier = $shipment->carrier_code;
                } else if (!empty($shipment->carrier_code_desired)) {
                    $carrier = $shipment->carrier_code_desired;
                } else {
                    $carrier = 'manual';
                }

                if ($carrier == 'manual') {
                    if ($shipment->manual_carrier_code == 'auspost') {
                        $carrier = 'auspost';
                    } else if ($shipment->manual_carrier_code == 'aramex') {
                        $carrier = 'aramex';
                    }
                }

                //set default post
                switch ($carrier) {
                    case 'fastway':
                        $tracking_company = 'Aramex Australia';
                        break;
                    case 'aramex':
                        $tracking_company = 'Aramex Australia';
                        break;
                    case 'manual':
                        $tracking_company = 'Carrier Manually Set';
                        break;
                    case 'address_only':
                        $tracking_company = 'Carrier Manually Set';
                        break;
                    case 'sherpa':
                        $tracking_company = 'Same Day Delivery';
                        break;
                    case 'rendr':
                        $tracking_company = 'Same Day Delivery (Rendr)';
                        break;
                    default:
                        $tracking_company = 'Australia Post';
                }

                //set tracking numbers
                $tracking_number = $shipment->tracking_code;
                switch ($carrier) {
                    case 'fastway':
                        $tracking_url = "https://www.aramex.com.au/tools/track/?l=$tracking_number";
                        break;
                    case 'aramex':
                        $tracking_url = "https://www.aramex.com.au/tools/track/?l=$tracking_number";
                        break;
                    case 'manual':
                        $tracking_url = $tracking_number;
                        break;
                    case 'address_only':
                        $tracking_url = $tracking_number;
                        break;
                    case 'rendr':
                        $tracking_url = $tracking_number;
                        break;

                    default:
                        $tracking_url = "https://auspost.com.au/mypost/track/#/details/$tracking_number";
                }

               

                $variables = [
                    "fulfillment" => [
                        "lineItemsByFulfillmentOrder" => $input,
                        "trackingInfo" => [
                            "company" => $tracking_company,
                            "number" => $tracking_number,
                            "url" => $tracking_url
                        ],
                        "notifyCustomer" => true
                    ]
                ];

                $data = Connection::ShopifyConnection('mutation fulfillmentCreateV2($fulfillment: FulfillmentV2Input!) {
  		fulfillmentCreateV2(fulfillment: $fulfillment) {
    			fulfillment {
      				id
      				status
    			}
    			userErrors {
      				field
      				message
    			}
  		}
		}', $variables);

                $shipment->shopify_updated = true;
                Tools::removeLockAndSave($shipment);

                return true;

            }

        return false;
    }