A Day in the Life of a Shopify Order

15 min read
created Jan 22 2016
A Day in the Life of a Shopify Order

This week's post is all about what goes on behind-the-scenes when changes are made over the lifetime of a Shopify Order.

The Shopify Order docs give a great overview of the types of operations that can be performed on a Shopify Order. However, for my upcoming Best Sellers Shopify app which focuses on best sellers on Shopify, I also need to understand how each of these changes impact the order data. This will allow me to accurately track best-sellers while excluding things like refunded or canceled items.

In this post, I'll cover:

  • A process for testing how Shopify handles different changes to an order.
  • Creating a test order within Shopify.
  • Detailed examples of how Shopify handles some of the most common types of order changes.

The Process

The process for testing how Shopify handles different changes to an order is fairly straight-forward:

  1. Use the Shopify Draft Order interface to create a test order.
  2. Make changes to the Order using Shopify's Admin interface.
  3. Get the current Shopify Order JSON from the Shopify Order API after each change to see what changed.

In an effort to cover the most common set of changes to an order, I moved a test order through the following set of changes:

Shopify Order Timeline

The Test Order

Here is the test order I will reference throughout the rest of this post:

Shopify Create Draft Order

The test order contains three line items to give a good sampling of the different types of line items we may find in a Shopify Order:

  • The Ectoplasm is a Custom Item, which means it is not tied to an actual Shopify product.

    Custom Items can be created manually and applied to an order within the Shopify Admin:

    Shopify Create Custom Item

    shopify-add-custom-item

  • The Ghostbusters Movie is a Product that does not have any options.

  • The Stay Puft Costume is an item that has an option for Gender. In this case, the Gender selected is Mens.

Order Created

See also our guide on How to import orders into Shopify.

After I created the test order, I called the Shopify Order API to fetch the JSON data for the initial order. Here is initial order JSON data:

{
"order": {
"id": 2209194309,
"email": "[email protected]",
"closed_at": null,
"created_at": "2016-01-18T21:44:25-05:00",
"updated_at": "2016-01-18T21:44:25-05:00",
"number": 5,
"note": "",
"token": "1234567890",
"gateway": "manual",
"test": false,
"total_price": "71.98",
"subtotal_price": "71.98",
"total_weight": 0,
"total_tax": "0.00",
"taxes_included": false,
"currency": "USD",
"financial_status": "pending",
"confirmed": true,
"total_discounts": "0.00",
"total_line_items_price": "71.98",
"cart_token": null,
"buyer_accepts_marketing": false,
"name": "#1005",
"referring_site": null,
"landing_site": null,
"cancelled_at": null,
"cancel_reason": null,
"total_price_usd": "71.98",
"checkout_token": null,
"reference": null,
"user_id": 45662789,
"location_id": null,
"source_identifier": null,
"source_url": null,
"processed_at": "2016-01-18T21:44:25-05:00",
"device_id": null,
"browser_ip": null,
"landing_site_ref": null,
"order_number": 1005,
"discount_codes": [

],
"note_attributes": [

],
"payment_gateway_names": [
"manual"
],
"processing_method": "manual",
"checkout_id": null,
"source_name": "shopify_draft_order",
"fulfillment_status": null,
"tax_lines": [

],
"tags": "",
"contact_email": "[email protected]",
"line_items": [
{
"id": 4027831301,
"variant_id": null,
"title": "Ectoplasm",
"quantity": 1,
"price": "9.99",
"grams": 0,
"sku": null,
"variant_title": "Custom",
"vendor": null,
"fulfillment_service": "manual",
"product_id": null,
"requires_shipping": false,
"taxable": true,
"gift_card": false,
"name": "Ectoplasm - Custom",
"variant_inventory_management": null,
"properties": [

],
"product_exists": false,
"fulfillable_quantity": 1,
"total_discount": "0.00",
"fulfillment_status": null,
"tax_lines": [

]
},
{
"id": 4027831365,
"variant_id": 10291617221,
"title": "Ghostbusters Movie",
"quantity": 1,
"price": "19.99",
"grams": 0,
"sku": "",
"variant_title": null,
"vendor": "Burst Dev",
"fulfillment_service": "manual",
"product_id": 3461132549,
"requires_shipping": true,
"taxable": true,
"gift_card": false,
"name": "Ghostbusters Movie",
"variant_inventory_management": null,
"properties": [

],
"product_exists": true,
"fulfillable_quantity": 1,
"total_discount": "0.00",
"fulfillment_status": null,
"tax_lines": [

]
},
{
"id": 4027831429,
"variant_id": 8852564421,
"title": "Ghostbusters Inflatable Stay Puft Marshmallow Man Costume",
"quantity": 1,
"price": "42.00",
"grams": 0,
"sku": "GHOSTBUSTM",
"variant_title": "Mens",
"vendor": "Burst Dev",
"fulfillment_service": "manual",
"product_id": 2938468037,
"requires_shipping": true,
"taxable": true,
"gift_card": false,
"name": "Ghostbusters Inflatable Stay Puft Marshmallow Man Costume - Mens",
"variant_inventory_management": "shopify",
"properties": [

],
"product_exists": true,
"fulfillable_quantity": 1,
"total_discount": "0.00",
"fulfillment_status": null,
"tax_lines": [

]
}
],
"shipping_lines": [

],
"fulfillments": [

],
"refunds": [

],
"customer": {
"id": 1839335429,
"email": "[email protected]",
"accepts_marketing": false,
"created_at": "2015-11-16T22:44:39-05:00",
"updated_at": "2016-01-18T21:44:25-05:00",
"first_name": "Peter",
"last_name": "Venkman",
"orders_count": 1,
"state": "disabled",
"total_spent": "0.00",
"last_order_id": 2209194309,
"note": null,
"verified_email": true,
"multipass_identifier": null,
"tax_exempt": false,
"tags": "",
"last_order_name": "#1005"
}
}
}

Here are some highlights of the newly created order:

  • The financial_status is set to pending.

  • The fulfillment_status is not set.

  • The Ghostbuster Movie line_item has a variant_id even though there are no options. This is because Shopify actually creates a single variant behind the scenes for products without options.

  • The Ectoplasm line_item:

    • has no value set for product_id, variant_id, SKU or vendor. This means any tracking of these types of line items must use another mechanism.
    • has product_exists set to false. This may prove useful in helping support Custom Item handling.

Payment Received

To see what happens to an order when payment is received, I marked the order as paid in the Shopify Admin. Here is a condensed version of the order JSON after marking the order as paid:

{
"order": {
"financial_status": "paid",
"customer": {
"total_spent": "71.98",
}
}
}

This resulted in the following changes to the order:

  • The fulfillment_status was set to fulfilled.
  • The customer["total_spent"] was set to the full transaction amount of 71.98.

For the remainder of this post, the JSON samples will all be condensed to only highlight the changes that occurred from the most recent changes.

Partial Fulfillment

Now let's pretend we are able to ship Ectoplasm and Ghostbusters Movie items, but the Stay Puft Costume is not ready yet. To do this, I created a fulfillment in the Shopify Admin. Here are the changes after the fulfillment was created:

{
"order": {
"updated_at": "2016-01-18T21:47:13-05:00",
"fulfillment_status": "partial",
"line_items": [
{
"title": "Ectoplasm",
"fulfillable_quantity": 0,
"fulfillment_status": "fulfilled",
},
{
"title": "Ghostbusters Movie",
"fulfillable_quantity": 0,
"fulfillment_status": "fulfilled",
},
...
],
"fulfillments": [
{
"id": 1963812037,
"order_id": 2209194309,
"status": "success",
"created_at": "2016-01-18T21:47:13-05:00",
"service": "manual",
"updated_at": "2016-01-18T21:47:13-05:00",
"tracking_company": null,
"tracking_number": null,
"tracking_numbers": [

],
"tracking_url": null,
"tracking_urls": [

],
"receipt": {
},
"line_items": [
{
"id": 4027831301,
"variant_id": null,
"title": "Ectoplasm",
"quantity": 1,
"price": "9.99",
"grams": 0,
"sku": null,
"variant_title": "Custom",
"vendor": null,
"fulfillment_service": "manual",
"product_id": null,
"requires_shipping": false,
"taxable": true,
"gift_card": false,
"name": "Ectoplasm - Custom",
"variant_inventory_management": null,
"properties": [

],
"product_exists": false,
"fulfillable_quantity": 0,
"total_discount": "0.00",
"fulfillment_status": "fulfilled",
"tax_lines": [

]
},
{
"id": 4027831365,
"variant_id": 10291617221,
"title": "Ghostbusters Movie",
"quantity": 1,
"price": "19.99",
"grams": 0,
"sku": "",
"variant_title": null,
"vendor": "Burst Dev",
"fulfillment_service": "manual",
"product_id": 3461132549,
"requires_shipping": true,
"taxable": true,
"gift_card": false,
"name": "Ghostbusters Movie",
"variant_inventory_management": null,
"properties": [

],
"product_exists": true,
"fulfillable_quantity": 0,
"total_discount": "0.00",
"fulfillment_status": "fulfilled",
"tax_lines": [

]
}
]
}
],
...
}
}

Here's a summary of the changes after a partial fulfillment:

  • The fulfillment_status for the order changed to partial.
  • For each of the fulfilled line items:
    • The fulfillable_quantity changed from 1 to 0.
    • The fulfillment_status changed from not set to fulfilled.
  • There is a new entry in the fulfillments array to represent this change. This includes the line_items that were fulfilled, as well as shipping information such as tracking numbers.

Fulfillment Complete

Next up I wanted to see what happens when all items are fulfilled in an order. Using the same technique as the last step, I marked the Stay Puft Costume as fulfilled. This resulted in the following order changes:

{
"order": {
"updated_at": "2016-01-18T21:48:09-05:00",
"fulfillment_status": "fulfilled",
"line_items": [
...
{
"title": "Ghostbusters Inflatable Stay Puft Marshmallow Man Costume",
"fulfillable_quantity": 0,
"fulfillment_status": "fulfilled",
}
],
"fulfillments": [
{
"id": 1963813701,
"order_id": 2209194309,
"status": "success",
"created_at": "2016-01-18T21:48:09-05:00",
"service": "manual",
"updated_at": "2016-01-18T21:48:09-05:00",
"tracking_company": null,
"tracking_number": null,
"tracking_numbers": [

],
"tracking_url": null,
"tracking_urls": [

],
"receipt": {
},
"line_items": [
{
"id": 4027831429,
"variant_id": 8852564421,
"title": "Ghostbusters Inflatable Stay Puft Marshmallow Man Costume",
"quantity": 1,
"price": "42.00",
"grams": 0,
"sku": "GHOSTBUSTM",
"variant_title": "Mens",
"vendor": "Burst Dev",
"fulfillment_service": "manual",
"product_id": 2938468037,
"requires_shipping": true,
"taxable": true,
"gift_card": false,
"name": "Ghostbusters Inflatable Stay Puft Marshmallow Man Costume - Mens",
"variant_inventory_management": "shopify",
"properties": [

],
"product_exists": true,
"fulfillable_quantity": 0,
"total_discount": "0.00",
"fulfillment_status": "fulfilled",
"tax_lines": [

]
}
]
},
...
],
...
}
}

Here are some of the highlights after all items in an order are fulfilled:

  • The order fulfillment_status changed from partial to fulfilled.
  • The fulfillable_quantity changed from 1 to 0 for the Stay Puft Costume.
  • The fulfillment_status changed from not set to fulfilled for the Stay Puft Costume.
  • A new entry was add to the fulfillments section.

Partial Refund

The next scenario I wanted to test is how Shopify handles refunds for items in an order. I decided to test this first as a partial refund, and later we'll perform a full refund. I created a partial refund for the Stay Puft Costume line item.

Here are the changes to the order after a partial refund:

{
"order": {
"financial_status": "partially_refunded",
"refunds": [
{
"id": 66161221,
"order_id": 2209194309,
"created_at": "2016-01-18T21:49:40-05:00",
"note": "Doesn't fit",
"restock": true,
"user_id": 45662789,
"refund_line_items": [
{
"id": 54581701,
"quantity": 1,
"line_item_id": 4027831429,
"line_item": {
"id": 4027831429,
"variant_id": 8852564421,
"title": "Ghostbusters Inflatable Stay Puft Marshmallow Man Costume",
"quantity": 1,
"price": "42.00",
"grams": 0,
"sku": "GHOSTBUSTM",
"variant_title": "Mens",
"vendor": "Burst Dev",
"fulfillment_service": "manual",
"product_id": 2938468037,
"requires_shipping": true,
"taxable": true,
"gift_card": false,
"name": "Ghostbusters Inflatable Stay Puft Marshmallow Man Costume - Mens",
"variant_inventory_management": "shopify",
"properties": [

],
"product_exists": true,
"fulfillable_quantity": 0,
"total_discount": "0.00",
"fulfillment_status": "fulfilled",
"tax_lines": [

]
}
}
],
"transactions": [
{
"id": 2580433093,
"order_id": 2209194309,
"amount": "42.00",
"kind": "refund",
"gateway": "manual",
"status": "success",
"message": "Refunded 42.00 from manual gateway",
"created_at": "2016-01-18T21:49:40-05:00",
"test": false,
"authorization": null,
"currency": "USD",
"location_id": null,
"user_id": null,
"parent_id": 2580410501,
"device_id": null,
"receipt": {
},
"error_code": null,
"source_name": "web"
}
],
"order_adjustments": [

]
}
],
"customer": {
"total_spent": "29.98",
}
}
}

Here are the highlights of the partial refund of an order:

  • The financial_status of the order changed from paid to partially_refunded.
  • An entry was added to the refunds section with full details of the line_item that was refunded.
  • The original line_item for refunded items remains unchanged. To get an accurate view of the state of each line_item, both the line_items section and refunds sections must be used.
  • The customer["total_spent"] was reduced by the amount of the refund to 29.98.

Order Archived

Next let's try archiving the order and see what happens. Here are the changes to the order JSON after archiving the order:

{
"order": {
"closed_at": "2016-01-18T21:50:43-05:00",
}
}

The only thing that changed is the closed_at property was set to the date and time of the change. As you can see from the naming of the field, archive is the same as close in Shopify. This difference in terminology was initially confusing to me. The Order API docs use the term close instead of archive.

Order Unarchived

Now let's see what happens when we unarchive an order. Here are the changes to the order JSON after unarchiving the order:

{
"order": {
"closed_at": null,
}
}

Nothing surprising here - the closed_at is no longer set.

Full Refund

Now let's pretend the customer changed their mind and wants a full refund. Here are the changes to the order JSON after refunding the remaining items on this order:

{
"order": {
"financial_status": "refunded",
"refunds": [
{
"id": 66161605,
"order_id": 2209194309,
"created_at": "2016-01-18T21:51:37-05:00",
"note": "",
"restock": true,
"user_id": 45662789,
"refund_line_items": [
{
"id": 54582341,
"quantity": 1,
"line_item_id": 4027831301,
"line_item": {
"id": 4027831301,
"variant_id": null,
"title": "Ectoplasm",
"quantity": 1,
"price": "9.99",
"grams": 0,
"sku": null,
"variant_title": "Custom",
"vendor": null,
"fulfillment_service": "manual",
"product_id": null,
"requires_shipping": false,
"taxable": true,
"gift_card": false,
"name": "Ectoplasm - Custom",
"variant_inventory_management": null,
"properties": [

],
"product_exists": false,
"fulfillable_quantity": 0,
"total_discount": "0.00",
"fulfillment_status": "fulfilled",
"tax_lines": [

]
}
},
{
"id": 54582405,
"quantity": 1,
"line_item_id": 4027831365,
"line_item": {
"id": 4027831365,
"variant_id": 10291617221,
"title": "Ghostbusters Movie",
"quantity": 1,
"price": "19.99",
"grams": 0,
"sku": "",
"variant_title": null,
"vendor": "Burst Dev",
"fulfillment_service": "manual",
"product_id": 3461132549,
"requires_shipping": true,
"taxable": true,
"gift_card": false,
"name": "Ghostbusters Movie",
"variant_inventory_management": null,
"properties": [

],
"product_exists": true,
"fulfillable_quantity": 0,
"total_discount": "0.00",
"fulfillment_status": "fulfilled",
"tax_lines": [

]
}
}
],
"transactions": [
{
"id": 2580444037,
"order_id": 2209194309,
"amount": "29.98",
"kind": "refund",
"gateway": "manual",
"status": "success",
"message": "Refunded 29.98 from manual gateway",
"created_at": "2016-01-18T21:51:37-05:00",
"test": false,
"authorization": null,
"currency": "USD",
"location_id": null,
"user_id": null,
"parent_id": 2580410501,
"device_id": null,
"receipt": {
},
"error_code": null,
"source_name": "web"
}
],
"order_adjustments": [

]
},
...
],
"customer": {
"total_spent": "0.00",
}
}
}

Here are the highlights after a the full refund of an order:

  • The financial_status of the order changed from partially_refunded to refunded.aaa
  • An entry was added to the refunds section with details of the additional line_items that were refunded.
  • The customer["total_spent"] was reduced by the amount of the refund to 0.00.

I am only showing the new refund entry in the above JSON for the sake of brevity.

Order Canceled

Since all of the items have been refunded, I went ahead and canceled the order as a final step. Here are the changes to the order JSON after we cancel the order:

{
"order": {
"cancelled_at": "2016-01-18T21:52:27-05:00",
"cancel_reason": "customer",
}
}

Canceled orders have only two properties that change:

  • The cancelled_at is set to the date and time of the cancellation.
  • The cancel_reason is set to the reason that is selected during the cancellation.

Increase sales by up to 175% with product badges

  • Use product labels to help products sell faster.
  • Highlight best sellers, new arrivals, almost gone, and more.

Milton loves staplers See the guide

Summary

This concludes our tour through the different stages of the life of a Shopify Order.

Sometimes to get a thorough understanding of a complex platform like Shopify, the best way to do so is through a series of controlled changes and then observing the result.

By using this same technique, it should be possible to get a deep understanding of what goes on behind-the-scenes of Shopify for any other process not covered in this post.