Skip to content

Latest commit

 

History

History
2487 lines (1800 loc) · 76.3 KB

readme.md

File metadata and controls

2487 lines (1800 loc) · 76.3 KB

ShopifySharp: A .NET library for Shopify.

Now with .NET Core support!

NuGet Build status license

ShopifySharp is a .NET library that enables you to authenticate and make API calls to Shopify. It's great for building custom Shopify Apps using C# and .NET. You can quickly and easily get up and running with Shopify using this library.

IMPORTANT: If you're using .NET Framework 4.5, calls to the Shopify API may fail with SocketException errors and "Response does not indicate success: Status: 0" after May 31st, 2018. This is because Shopify has deprecated TLS 1.0 and TLS 1.1 which are used by .NET Framework. To fix this, you can do one of the following:

  1. Update your project to target .NET Framework 4.6 or newer.
  2. Add the following to your global.asax.cs file to explicitly enable the newer protocols: System.Net.ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;

The Shopify Development Handbook

Learn how to build rock-solid Shopify apps with C# and ASP.NET

Building an app or integration for the Shopify store is hard work. There are a ton of things you need to keep in mind when stitching together all of the API calls, redirect URLs and app settings that you'll need to use.

You're going to be asking yourself all of these questions when you try to build an app for the Shopify store:

  • How can I charge my users when they use my app?
  • What in the world is an embedded app?
  • How should I be using Shopify's redirect URLs?
  • When should I be using a proxy page?
  • Am I dealing with webhooks the right way?
  • How can I let my user's actual customers interact with the app?
  • Can I add custom scripts to their website, and what can those scripts even do?
  • How the heck do I go about testing my app?

It's difficult to find blog posts or tutorials about building Shopify apps, and downright impossible if you're trying to build them with C# and ASP.NET. Shopify's own partner blog puts a huge focus on designing themes over building real, functional apps, and their API docs only go so far if you don't know what you're looking for.

The Shopify Development Handbook is a premium educational course that distills the experience of building Shopify applications and integrations into one concise and comprehensive course.

Click here to learn more about The Shopify Development Handbook, and get a FREE sample chapter on integrating a merchant's Shopify store with your app.

Installation

ShopifySharp is available on NuGet. Use the package manager console in Visual Studio to install it:

Install-Package ShopifySharp

If you're using .NET Core, you can use the dotnet command from your favorite shell:

dotnet add package shopifysharp

Version 4.0.0

Version 4.0.0 is a major update to ShopifySharp, it contains breaking changes by removing the Shopify prefix from almost every class, interface and object (the exception being ShopifyException and ShopifyRateLimitException. On top of that, every single entity property has been made nullable to both prevent deserialization errors that have plagued us humble C# developers since 1.0.0.

Version 4.0.0 contains a bunch of great enhancements, though. Chiefly, it adds support for .NET Core apps! In addition, the library now supports sending partial classes (thanks to making properties nullable) when creating or updating a Shopify object.

A work-in-progress

Currently, the only other .NET library for Shopify is Shopify.net, which hasn't been updated in over 3 years and requires that you know the exact URL paths of the Shopify API, along with creating your own entity classes for each resource. That's why I'm building ShopifySharp — .NET developers need a fully-featured library for interacting with Shopify and building Shopify apps.

With that said, Shopify is constantly adding new APIs and altering old ones. I try my best to keep up with them, but I tend to prioritize the support of new APIs by how much I need them in my own Shopify apps.

ShopifySharp currently supports the following Shopify APIs:

More functionality will be added each week until it reaches full parity with Shopify's REST API.

Unimplemented APIs

The following APIs are not yet implemented by ShopifySharp, but I'm slowly working through the list to reach 100% API parity. APIs are implemented in random order (mostly based on how much I need them in my own apps). Need one of these APIs right now? Please open an issue or make a pull request! I'm happy to offer guidance or help with writing tests.

API Notes
CarrierService
Comments
Country
FulfillmentService Not FulfillmentService.
Multipass Requires Shopify Plus.
Province
Refund

Contributors

These generous people have contributed their own hard work and time to improving ShopifySharp:

Thank you!

(If I missed you, just shoot me an email at [email protected])

Using ShopifySharp with a public Shopify app

Note: All instances of shopAccessToken in the examples below do not refer to your Shopify API key. An access token is the token returned after authenticating and authorizing a Shopify app installation with a real Shopify store.

All instances of myShopifyUrl refer to your users' *.myshopify.com URL (although their custom domain should work too).

var service = new ProductService(myShopifyUrl, shopAccessToken);

Using ShopifySharp with a private Shopify app

ShopifySharp should work out of the box with your private Shopify application, all you need to do is replace the shopAccessToken with your private app's password when initializing a ShopifyService:

var service = new ProductService(myShopifyUrl, privateAppPassword)

If you just need an access token for a private Shopify app, or for running the tests in this library, refer to the Tests section below.

Authorization and authentication

NOTICE: If you're using ASP.NET MVC5 (or any version that isn't AspNet Core) you'll have compilation errors when trying to pass Request.QueryString or Request.Headers to the authorization methods described below. See this issue for a workaround.

Ensure a given URL is a valid *myshopify.com URL

This is a convenience method that validates whether a given URL is a valid Shopify API domain (the Shopify API is hosted on each individual shop rather than at once central URL). It's great for ensuring you don't redirect a user to an incorrect URL when you need them to authorize your app installation, and is ideally used in conjunction with AuthorizationService.BuildAuthorizationUrl.

ShopifySharp will call the given URL and check for an X-ShopId header in the response. That header is present on all Shopify shops and it's existence signals that the URL is indeed a Shopify URL.

Note, however, that this feature is undocumented by Shopify and may break at any time. Use at your own discretion. In addition, it's possible for a malicious site to fake the X-ShopId header which would make this method return true.

string urlFromUser = "https://example.myshopify.com";
bool isValidDomain = await AuthorizationService.IsValidShopDomainAsync(urlFromUser).

Build an authorization URL

Redirect your users to this authorization URL, where they'll be prompted to install your app to their Shopify store.

//This is the user's store URL.
string usersMyShopifyUrl = "https://example.myshopify.com";

// A URL to redirect the user to after they've confirmed app installation.
// This URL is required, and must be listed in your app's settings in your Shopify app dashboard.
// It's case-sensitive too!
string redirectUrl = "https://example.com/my/redirect/url";

//An array of the Shopify access scopes your application needs to run.
var scopes = new List<AuthorizationScope>()
{
    AuthorizationScope.ReadCustomers,
    AuthorizationScope.WriteCustomers
};

//Or, use an array of string permissions
var scopes = new List<string>()
{
    "read_customers",
    "write_customers"
}

//All AuthorizationService methods are static.
string authUrl = AuthorizationService.BuildAuthorizationUrl(scopes, usersMyShopifyUrl, shopifyApiKey, redirectUrl);

Authorize an installation and generate an access token

Once you've sent a user to the authorization URL and they've confirmed your app installation, they'll be redirected back to your application at either the default app URL, or the redirect URL you passed in when building the authorization URL.

The access token you receive after authorizing should be stored in your database. You'll need it to access the shop's resources (e.g. orders, customers, fulfillments, etc.)

//The querystring will have several parameters you need for authorization.
string code = Request.QueryString["code"];
string myShopifyUrl = Request.QueryString["shop"];

string accessToken = await AuthorizationService.Authorize(code, myShopifyUrl, shopifyApiKey, shopifySecretKey);

Determine if a request is authentic

Any (non-webhook, non-proxy-page) request coming from Shopify will have a querystring parameter called 'hmac' that you can use to verify that the request is authentic. This signature is a hash of all querystring parameters and your app's secret key.

Pass the entire querystring to AuthorizationService to verify the request.

var qs = Request.QueryString;

if(AuthorizationService.IsAuthenticRequest(qs, shopifySecretKey))
{
    //Request is authentic.
}
else
{
    //Request is not authentic and should not be acted on.
}

Determine if a proxy page request is authentic

Nearly identical to authenticating normal requests, a proxy page request only differs in the way the HMAC is generated. All proxy page requests coming from Shopify will have a querystring parameter named hmac that you can use to verify the request. This signature is a hash of all querystring parameters and your app's secret key.

var qs = Request.QueryString;

if(AuthorizationService.IsAuthenticProxyRequest(qs, shopifySecretKey))
{
    //Request is authentic.
}
else
{
    //Request is not authentic and should not be acted on.
}

Determine if a webhook request is authentic

Any webhook request coming from Shopify will have a header called X-Shopify-Hmac-SHA256 that you can use to verify that the webhook is authentic. The header is a hash of the entire request body and your app's secret key.

Pass the entire header collection and the request's input stream to AuthorizationService to verify the request.

NameValueCollection requestHeaders = Request.Headers;
Stream inputStream = Request.InputStream;

if(AuthorizationService.IsAuthenticWebhook(requestHeaders, inputStream, shopifySecretKey))
{
    //Webhook is authentic.
}
else
{
    //Webhook is not authentic and should not be acted on.
}

You can also pass in the request body as a string, rather than using the input stream. However, the request body string needs to be identical to the way it was sent from Shopify. If it has been modified the verification will fail -- even if just one space is in the wrong place.

NameValueCollection requestHeaders = Request.Headers;
string requestBody = null;

//Reset the input stream. MVC controllers often read the stream to determine which parameters to pass to an action.
Request.InputStream.Position = 0;

//Read the stream into a string
using(StreamReader reader = new StreamReader(Request.InputStream))
{
    requestBody = await reader.ReadToEndAsync();
}

if(AuthorizationService.IsAuthenticWebhook(requestHeaders, requestBody, shopifySecretKey))
{
    //Webhook is authentic.
}
else
{
    //Webhook is not authentic and should not be acted on.
}

Recurring Application Charges (charge shop owners to use your app)

The Shopify billing API lets you create a recurring charge on a shop owner's account, letting them pay you for using your application. There are pros and cons to using the Shopify billing API versus a service like Stripe, BrainTree or PayPal.

I've put together a small guide called Shopify Billing 101: A Developer's Guide To Getting Paid For Your Apps, and you can get for free by joining the mailing list for Mastering Shopify Development (a training course for building Shopify apps with C# and ASP.NET).

Just head over here to get your free guide to the Shopify billing API.

Create a recurring charge

var service = new RecurringChargeService(myShopifyUrl, shopAccessToken);
var charge = new RecurringCharge()
{
    Name = "Lorem Ipsum Plan",
    Price = 12.34,
    Test = true, //Marks this charge as a test, meaning it won't charge the shop owner.
    TrialDays = 21
}

charge = await service.CreateAsync(charge);

Retrieve a recurring charge

var service = new RecurringChargeService(myShopifyUrl, shopAccessToken);

var charge = await service.GetAsync(chargeId);

Listing recurring charges

var service = new RecurringChargeService(myShopifyUrl, shopAccessToken);

IEnumerable<RecurringCharge> charges = await service.ListAsync();

Activating a charge

Creating a charge does not actually charge the shop owner or even start their free trial. You need to send them to the charge's ConfirmationUrl, have them accept the charge, then activate it.

var service = new RecurringChargeService(myShopifyUrl, shopAccessToken);

await service.ActivateAsync(chargeId);

Deleting a charge

Charges cannot be deleted unless they've been activated. Shopify automatically deletes pending charges after 48 hours pass without activation.

var service = new RecurringChargeService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(chargeId);

One-time application charges

Just like with the above recurring charges, the Shopify billing API lets you create a one-time application charge on the shop owner's account. One-time charges cannot be deleted.

Create a one-time charge

var service = new ChargeService(myShopifyUrl, shopAccessToken);
var charge = new Charge()
{
    Name = "Lorem Ipsum Charge",
    Price = 12.34,
    Test = true, //Marks this charge as a test, meaning it won't charge the shop owner.
}

charge = await service.CreateAsync(charge);

Retrieve a one-time charge

var service = new ChargeService(myShopifyUrl, shopAccessToken);

var charge = await service.GetAsync(chargeId);

Listing one-time charges

var service = new ChargeService(myShopifyUrl, shopAccessToken);

IEnumerable<Charge> charges = await service.ListAsync();

Activating a charge

Just like recurring charges, creating a one-time charge does not actually charge the shop owner. You need to send them to the charge's ConfirmationUrl, have them accept the charge, then activate it.

var service = new ChargeService(myShopifyUrl, shopAccessToken);

await service.ActivateAsync(chargeId);

Usage charges

Shopify's Usage Charges let you set a capped amount on a recurring application charge, and only charge for usage. For example, you can create a charge that's capped at $100.00 per month, and then charge e.g. $1.00 for every 1000 emails your user sends using your app.

To create a UsageCharge, you first need to create a RecurringCharge with a CappedAmount value and a Terms string. Your customers will see the terms when activating the recurring charge, so set it to something they can read like "$1.00 per 1000 emails".

Create a usage charge

var service = new UsageChargeService(myShopifyUrl, shopAccessToken);

string description = "Used 1000 emails";
double price = 1.00;

var usageCharge = await service.CreateAsync(recurringChargeId, description, price);

Get a usage charge

var service = new UsageChargeService(myShopifyUrl, shopAccessToken);

var usageCharge = await service.GetAsync(recurringChargeId, usageChargeId);

List usage charges

var service = new UsageChargeService(myShopifyUrl, shopAccessToken);

var usageCharges = await service.ListAsync(recurringChargeId);

Shops

Retrieving shop information

var service = new ShopService(myShopifyUrl, shopAccessToken);

var shop = await service.GetAsync();

Uninstalling your app

In cases where user intervention is not required, you can send a request to a Shopify shop to force it to uninstall your application. After sending this request, your shop access token will be immediately revoked and invalidated.

Uninstalling an application is an irreversible operation. Be entirely sure that you no longer need to make API calls for the shop in which the application has been installed.

Uninstalling an application also performs various cleanup tasks within Shopify. Registered Webhooks, ScriptTags and App Links will be destroyed as part of this operation. Also if an application is uninstalled during key rotation, both the old and new Access Tokens will be rendered useless.

var service = new ShopService(myShopifyUrl, shopAccessToken);

var shop = await service.UninstallAppAsync()

Customers

Creating a customer

var service =  new CustomerService(myShopifyUrl, shopAccessToken);
var customer = new Customer()
{
    FirstName = "John",
    LastName = "Doe",
    Email = "[email protected]",
    Addresses = new List<ShopifyAddress>()
    {
        new ShopifyAddress()
        {
            Address1 = "123 4th Street",
            City = "Minneapolis",
            Province = "Minnesota",
            ProvinceCode = "MN",
            Zip = "55401",
            Phone = "555-555-5555",
            FirstName = "John",
            LastName = "Doe",
            Company = "Tomorrow Corporation",
            Country = "United States",
            CountryCode = "US",
            Default = true,
        }
    },
    VerifiedEmail = true,
    Note = "Test note about the customer.",
    State = "enabled"
}

customer = await service.CreateAsync(customer);

Retrieving a customer

var service =  new CustomerService(myShopifyUrl, shopAccessToken);
var customer = await service.GetAsync(customerId);

Retrieving a customer with certain fields

var service =  new CustomerService(myShopifyUrl, shopAccessToken);
var customer = await service.GetAsync(customerId, "first_name,last_name,email");

//Returns a customer with only FirstName, LastName and Email fields. All other fields are null.

Updating a customer

var service =  new CustomerService(myShopifyUrl, shopAccessToken);
var customer = await service.UpdateAsync(customerId, new Customer()
{
    Email = "[email protected]"
});

Deleting a customer

var service =  new CustomerService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(customerId);

Counting customers

var service =  new CustomerService(myShopifyUrl, shopAccessToken);
int customerCount = await service.CountAsync();

Listing customers

var service =  new CustomerService(myShopifyUrl, shopAccessToken);
IEnumerable<Customer> customers = await Service.ListAsync();

Searching customers

var service =  new CustomerService(myShopifyUrl, shopAccessToken);
IEnumerable<Customer> customers = await Service.SearchAsync("Jane country:United States");

//Searches for a customer from the United States with a name like 'Jane'.
//There is a noticeable 3-30 second delay between creating a customer and Shopify
//indexing it for a search.

Orders

Creating an order

var service = new OrderService(myShopifyUrl, shopAccessToken);
var order = new Order()
{
    CreatedAt = DateTime.UtcNow,
    BillingAddress = new ShopifyAddress()
    {
        Address1 = "123 4th Street",
        City = "Minneapolis",
        Province = "Minnesota",
        ProvinceCode = "MN",
        Zip = "55401",
        Phone = "555-555-5555",
        FirstName = "John",
        LastName = "Doe",
        Company = "Tomorrow Corporation",
        Country = "United States",
        CountryCode = "US",
        Default = true,
    },
    LineItems = new List<LineItem>()
    {
        new LineItem()
        {
            Name = "Test Line Item",
            Title = "Test Line Item Title"
        }
    },
    FinancialStatus = "paid",
    TotalPrice = 5.00,
    Email = Guid.NewGuid().ToString() + "@example.com",
    Note = "Test note about the customer.",
};

order = await service.CreateAsync(order);

Retrieving an order

var service = new OrderService(myShopifyUrl, shopAccessToken);
var order = await service.GetAsync(orderId);

Updating an order

var service = new OrderService(myShopifyUrl, shopAccessToken);
var order = await service.UpdateAsync(orderId, new Order()
{
    Notes = "test notes."
});

Deleting an order

var service = new OrderService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(orderId);

Counting orders

var service = new OrderService(myShopifyUrl, shopAccessToken);
int orderCount = await service.CountAsync();

Listing orders

var service = new OrderService(myShopifyUrl, shopAccessToken);
IEnumerable<Order> orders = await service.ListAsync();

List orders for a certain customer

var service = new OrderService(myShopifyUrl, shopAccessToken);
IEnumerable<Order> orders = await service.ListForCustomerAsync(customerId);

Close an order

var service = new OrderService(myShopifyUrl, shopAccessToken);

await service.CloseAsync(orderId);

Reopen a closed order

var service = new OrderService(myShopifyUrl, shopAccessToken);

await service.OpenAsync(orderId);

Cancel an order

var service = new OrderService(myShopifyUrl, shopAccessToken);

await service.CancelAsync(orderId);

Products

Creating a product

var service = new ProductService(myShopifyUrl, shopAccessToken);
var product = new Product()
{
    Title = "Burton Custom Freestlye 151",
    Vendor = "Burton",
    BodyHtml = "<strong>Good snowboard!</strong>",
    ProductType = "Snowboard",
    Images = new List<ProductImage>
    {
        new ProductImage
        {
            Attachment = "R0lGODlhAQABAIAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
        }
    },
};

product = await service.CreateAsync(product);

//By default, creating a product will publish it. To create an unpublished product:+1:
product = await service.CreateAsync(product, new ProductCreateOptions() { Published = false });

Retrieving a product

var service = new ProductService(myShopifyUrl, shopAccessToken);
var product = await service.GetAsync(productId);

Updating a product

var service = new ProductService(myShopifyUrl, shopAccessToken);
var product = await service.UpdateAsync(productId, new Product()
{
    Title = "New product title"
});

Deleting a product

var service = new ProductService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(productId);

Counting products

var service = new ProductService(myShopifyUrl, shopAccessToken);
int productCount = await service.CountAsync();

Listing products

var service = new ProductService(myShopifyUrl, shopAccessToken);
IEnumerable<Product> products = await service.ListAsync();

//Optionally filter the results
var filter = new ProductFilterOptions()
{
    Ids = new[]
    {
        productId1,
        productId2,
        productId3
    }
};
products = await service.ListAsync(filter);

Publishing a product

var service = new ProductService(myShopifyUrl, shopAccessToken);
var product = await service.PublishAsync(productId);

Unpublishing a product

var service = new ProductService(myShopifyUrl, shopAccessToken);
var product = await service.UnpublishAsync(productId);

Webhooks

Creating a webhook

var service = new WebhookService(myShopifyUrl, shopAccessToken);
Webhook hook = new Webhook()
{
    Address = "https://my.webhook.url.com/path",
    CreatedAt = DateTime.Now,
    Fields = new List<string>() { "field1", "field2" },
    Format = "json",
    MetafieldNamespaces = new List<string>() { "metafield1", "metafield2" },
    Topic = "app/uninstalled",
};

hook = await service.CreateAsync(hook);

Retrieving a webhook

var service = new WebhookService(myShopifyUrl, shopAccessToken);
var webhook = await service.GetAsync(webhookId);

Updating a webhook

var service = new WebhookService(myShopifyUrl, shopAccessToken);
var webhook = await service.UpdateAsync(webhookId, new Webhook()
{
    Address = "https://my.webhook.url.com/new/path
});

Deleting a webhook

var service = new WebhookService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(webhookId);

Counting webhooks

var service = new WebhookService(myShopifyUrl, shopAccessToken);
int webhookCount = await service.CountAsync();

Listing webhooks

var service = new WebhookService(myShopifyUrl, shopAccessToken);
IEnumerable<Webhook> webhooks = await service.ListAsync();

Script Tags

Script tags let you add remote javascript tags that are loaded into the pages of a shop's storefront, letting you dynamically change the functionality of their shop without manually editing their store's template.

Creating a script tag

var service = new ScriptTagService(myShopifyUrl, shopAccessToken);
var tag = new ScriptTag()
{
    Event = "onload",
    Src  = "https://example.com/my-javascript-file.js",
    DisplayScope = 'all'
}

tag = await service.CreateAsync(tag);

Retrieving a script tag

var service = new ScriptTagService(myShopifyUrl, shopAccessToken);
var tag = await service.GetAsync(tagId);

Updating a script tag

var service = new ScriptTagService(myShopifyUrl, shopAccessToken);
var tag = await service.UpdateAsync(tagId, new ScriptTag()
{
    Src = "https://example.com/my-new-javascript-file.js";
});

Deleting a script tag

var service = new ScriptTagService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(tagId);

Counting script tags

var service = new ScriptTagService(myShopifyUrl, shopAccessToken);
int tagCount = await service.CountAsync();

//Optionally filter the count to only those tags with a specific Src
int filteredTagCount = await service.CountAsync("https://example.com/my-filtered-url.js");

Listing script tags

var service = new ScriptTagService(myShopifyUrl, shopAccessToken);
var tags = await service.ListAsync();

//Optionally filter the list to only those tags with a specific Src
var filteredTags = await service.ListAsync(new ScriptTagListOptions() {
    Src = FilteredSrc
});

Assets

The AssetService lets you create, update and delete a store theme's asset files. Unlike other API services in ShopifySharp, the AssetService has a single .CreateOrUpdateAsync method due to the way Shopify's API handles assets. If an asset has a unique Key value, it will be created. If not, it will be updated. You can copy an asset by setting the new asset's SourceKey to the target's Key value.

Shopify asset's do not have an id, but rather a key string; they're also organized into type 'buckets'. For a liquid template, it's full key would be templates/liquid.index; for an image, its key would be assets/my-image.png.

Finally, all assets are tied to a specific theme, and you need that theme's id to interact with assets. You can use the ThemeService to get a list of the shop's themes, or the ShopService to get the currently active theme's id.

Creating an asset

var service = new AssetService(myShopifyUrl, shopAccessToken);
var asset = new Asset()
{
    ContentType = "text/x-liquid",
    Key = "templates/test.liquid",
    Value  = "<h1>Hello, world!</h1>"
}

//Note: Creating an asset does not return it's 'Value' property.
//You must specifically refresh it with service.GetAsync
asset = await service.CreateAsync(themeId, asset);

Retrieving an asset

var service = new AssetService(myShopifyUrl, shopAccessToken);
var key = "templates/index.liquid";

var asset = await service.GetAsync(themeId, key);

Listing assets

var service = new AssetService(myShopifyUrl, shopAccessToken);

var assets = await service.ListAsync(themeId);

Updating assets

var service = new AssetService(myShopifyUrl, shopAccessToken);

//Note: Updating an asset does not return it's 'Value' property.
//You must specifically refresh it with service.GetAsync
var asset = await service.UpdateAsync(themeId, assetId, new Asset()
{
    Value = "<h1>Hello, world! I've been updated.</h1>";
});

Copying an asset

You can create a new asset by copying an already existing one. Just set the new asset's SourceKey property to match the target's Key property.

var service = new AssetService(myShopifyUrl, shopAccessToken);
var asset = new Asset()
{
    Key = "templates/test.liquid",
    SourceKey = originalAsset.Key
};

//Note: Creating an asset does not return it's 'Value' property.
//You must specifically refresh it with service.GetAsync
asset = await service.UpdateAsync(themeId, assetId, asset);

Themes

The ThemeService lets you create, update, list, get and delete a store's themes.

Creating a theme

When you create a theme, you can optionally pass in a URL that points to a .zip file containing all of the new theme's files. Shopify will then copy those files into the theme. Be aware that copying files is not instant, and the theme's Processing flag will be set to true until it's done.

You cannot update or delete a theme that is still processing.

var service = new ThemeService(myShopifyUrl, shopAccessToken);
var theme = new Theme()
{
    Name = "My new theme.",
    Role = "unpublished"
}

theme = await service.CreateAsync(theme);

//Or, create a theme and copy its files from a .zip file URL
theme = await service.CreateAsync(theme, 'https://my-domain.com/my-theme-files.zip');

Retrieving a theme

var service = new ThemeService(myShopifyUrl, shopAccessToken);
var theme = await service.GetAsync(themeId);

Updating a theme

Remember, you can't update a theme if its Processing flag is set to true. Shopify will automatically set it to false once it's done processing. Additionally, you cannot set a theme's role from "main" to "unpublished". Instead, you need to set a different theme's role to "main".

var service = new ThemeService(myShopifyUrl, shopAccessToken);
var theme = await service.UpdateAsync(themeId, new Theme()
{
    Role = ThemeRole.Main,
    Name = "My updated theme."
});

Deleting a theme

var service = new ThemeService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(themeId);

Listing themes

var service = new ThemeService(myShopifyUrl, shopAccessToken);
var themes = await service.ListAsync();

Redirects

A Redirect lets you create URL redirects on a Shopify store. When a store visitor navigates to a redirect's Path, they'll be redirected to the redirect's Target.

Creating a redirect

var service = new RedirectService(myShopifyUrl, shopAccessToken);
var redirect = new Redirect()
{
    Path = "/ipod",
    Target  = "https://apple.com/ipod"
}

redirect = await service.CreateAsync(redirect);

Retrieving a redirect

var service = new RedirectService(myShopifyUrl, shopAccessToken);
var redirect = await service.GetAsync(redirectId);

Updating a redirect

var service = new RedirectService(myShopifyUrl, shopAccessToken);
var redirect = await service.UpdateAsync(redirectId, new Redirect()
{
    Target = "https://apple.com/ipad",
    Path = "/ipad"
});

Deleting a redirect

var service = new RedirectService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(redirectId);

Counting redirects

var service = new RedirectService(myShopifyUrl, shopAccessToken);
int redirectCount = await service.CountAsync();

//Optionally filter the count to only those redirects with a specific path or target
int filteredRedirectCount = await service.CountAsync(path: "/ipod", target: "https://apple.com/ipod/");

Listing redirects

var service = new RedirectService(myShopifyUrl, shopAccessToken);
var redirects = await service.ListAsync();

//Optionally filter the list to only those redirects with a specific path or target
var filteredRedirects = await service.ListAsync(new RedirectListOptions() {
    Path = "/ipod",
    Target = "https://apple.com/ipod"
});

Collects

A Collect is an object that connects a product to a custom collection.

Creating a collect

var service = new CollectService(myShopifyUrl, shopAccessToken);
var collect = new Collect()
{
    CollectionId = collectionId,
    ProductId = productId
}

collect = await service.CreateAsync(collect);

Retrieving a collect

var service = new CollectService(myShopifyUrl, shopAccessToken);
var collect = await service.GetAsync(collectId);

Deleting a collect

var service = new CollectService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(collectId);

Counting collects

var service = new CollectService(myShopifyUrl, shopAccessToken);
int collectCount = await service.CountAsync();

//Optionally filter the count to only those collects for a certain product or collection
int filteredCollectCount = await service.CountAsync(new CollectFilterOptions()
{
    ProductId = productId,
    CollectionId = collectionId
});

Listing collects

var service = new CollectService(myShopifyUrl, shopAccessToken);
var collects = await service.ListAsync();

//Optionally filter the list to only those collects for a certain product or collection
var filteredCollects = await service.CountAsync(new CollectFilterOptions()
{
    ProductId = productId,
    CollectionId = collectionId
});

Fulfillments

A fulfillment represents a shipment of one or more items in an order. All fulfillments are tied to a single order.

Creating a fulfillment

This will completely fulfill all of the line items in the order.

var service = new FulfillmentService(myShopifyUrl, shopAccessToken);
var fulfillment = new Fulfillment()
{
    TrackingCompany = "Jack Black's Pack, Stack and Track",
    TrackingUrl = "https://example.com/123456789",
    TrackingNumber = "123456789",
}

fulfillment = await service.CreateAsync(orderId, fulfillment);

Creating a partial fulfillment

This will partially fulfill the given line items, dependent on the line item's quantity.

var service = new FulfillmentService(myShopifyUrl, shopAccessToken);
var fulfillment = new Fulfillment()
{
    TrackingCompany = "Jack Black's Pack, Stack and Track",
    TrackingUrl = "https://example.com/123456789",
    TrackingNumber = "123456789",
    LineItems = new List<ShopifyLineItem>()
    {
        new ShopifyLineItem()
        {
            Id = lineItemId,
            Quantity = 1 //Fulfills 1 qty of this line item.
        }
    }
}

fulfillment = await service.CreateAsync(orderId, fulfillment);

Creating a single fulfillment

This will completely fulfill the given line items.

var service = new FulfillmentService(myShopifyUrl, shopAccessToken);
var fulfillment = new Fulfillment()
{
    TrackingCompany = "Jack Black's Pack, Stack and Track",
    TrackingUrl = "https://example.com/123456789",
    TrackingNumber = "123456789",
    LineItems = new List<ShopifyLineItem>()
    {
        new ShopifyLineItem()
        {
            Id = lineItemId
        }
    }
}

fulfillment = await service.CreateAsync(orderId, fulfillment);

Retrieving a fulfillment

var service = new FulfillmentService(myShopifyUrl, shopAccessToken);
var fulfillment = await service.GetAsync(orderId, fulfillmentId);

Updating a fulfillment

var service = new FulfillmentService(myShopifyUrl, shopAccessToken);
var fulfillment = await service.UpdateAsync(orderId, fulfillmentId, new Fulfillment()
{
    TrackingCompany = "John Doe's Tracking Company"
});

Counting fulfillments

var service = new FulfillmentService(myShopifyUrl, shopAccessToken);
int fulfillmentCount = await service.CountAsync(orderId);

Listing fulfillments

var service = new FulfillmentService(myShopifyUrl, shopAccessToken);
var fulfillments = await service.ListAsync(orderId);

Completing a fulfillment

Fulfillments can only be completed if their Status is pending.

var service = new FulfillmentService(myShopifyUrl, shopAccessToken);
await service.CompleteAsync(orderId, fulfillmentId)

Cancelling a fulfillment

Fulfillments can only be cancelled if their Status is pending.

var service = new FulfillmentService(myShopifyUrl, shopAccessToken);
await service.CancelAsync(orderId, fulfillmentId)

Transactions

Transactions are created for every order that results in an exchange of money. All transactions are tied to a single order.

Creating a full capture transaction

By omitting an Amount value, this transaction will capture the full amount.

Note: to create a Capture transaction, the order must have an Authorization transaction on it. However, an Authorization transaction can only be created at the time the order was created.

var service = new TransactionService(myShopifyUrl, shopAccessToken);
var transaction = new Transaction()
{
    Kind = "capture"
};

await service.CreateAsync(orderId, transaction);

Creating a partial capture transaction

This method will capture a specified amount on a previously authorized order.

Note: to create a Capture transaction, the order must have an Authorization transaction on it. However, an Authorization transaction can only be created at the time the order was created.

var service = new TransactionService(myShopifyUrl, shopAccessToken);
var transaction = new Transaction()
{
    Kind = "capture",
    Amount = 5.00
};

await service.CreateAsync(orderId, transaction);

Creating a refund transaction

This method will create a refund on a previously authorized order. Like the last two examples, you can either refund a partial amount by setting the Amount value, or refund the full amount by omitting that value.

Note: to create a Refund transaction, the order must have an Authorization transaction on it. However, an Authorization transaction can only be created at the time the order was created.

Additionally, it seems you can't create a Refund transaction for any order that was created via the API. (I can't find any documentation about this behavior. Let me know if this is wrong.)

var service = new TransactionService(myShopifyUrl, shopAccessToken);
var transaction = new Transaction()
{
    Kind = "refund",
    Amount = 5.00
};

await service.CreateAsync(orderId, transaction);

Creating a cancel transaction

This method is supposed to cancel a previously authorized order's payment. However, the Shopify API will throw an error whenever you try to do this. It may be that, like the refund transaction, you can't cancel an order that was created via the API. Again, there's no documentation for this behavior, let me know if you have any information.

That in mind, I'm including this example for posterity.

var service = new TransactionService(myShopifyUrl, shopAccessToken);
var transaction = new Transaction()
{
    Kind = "void"
};

//Throws an error.
await service.CreateAsync(orderId, transaction);

Getting a transaction

var service = new TransactionService(myShopifyUrl, shopAccessToken);
var transaction = await service.GetAsync(orderId, transactionId);

Counting transactions

var service = new TransactionService(myShopifyUrl, shopAccessToken);
var count = await service.CountAsync(orderId);

Listing transactions

var service = new TransactionService(myShopifyUrl, shopAccessToken);
var transactions = await service.ListAsync(orderId);

//Optionally filter the list to those after the given id
var transactions = await service.ListAsync(orderId, sinceId);

Pages

A Page represents a web page on the merchant's Shopify storefront.

Creating a page

var service = new PageService(myShopifyUrl, shopAccessToken);
var page = new Page()
{
    CreatedAt = DateTime.UtcNow,
    Title = "Burton Custom Freestlye 151",
    BodyHtml = "<strong>Good snowboard!</strong>",
};

page = await service.CreateAsync(page);

Counting a page

var service = new PageService(myShopifyUrl, shopAccessToken);
var count = await service.CountAsync();

Listing pages

var service = new PageService(myShopifyUrl, shopAccessToken);
var pages = await service.ListAsync();

Retrieving a page

var service = new PageService(myShopifyUrl, shopAccessToken);
var page = await service.GetAsync(pageId);

Updating a page

var service = new PageService(myShopifyUrl, shopAccessToken);
var page = await service.UpdateAsync(pageId, new Page()
{
    Title = "My new page title"
});

Deleting a page

var service = new PageService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(pageId);

Metafields

Creating a metafield

var service = new MetaFieldService(myShopifyUrl, shopAccessToken);
var metafield = new MetaField()
{
    Namespace = "myNamespace",
    Key = "myKey",
    Value = "5",
    ValueType = "integer",
    Description = "This is a test meta field. It is an integer value."
};

//Create a new metafield on a product
metafield = await service.CreateAsync(metafield, productId, "products");

Counting metafields

var service = new MetaFieldService(myShopifyUrl, shopAccessToken);
var count = await service.CountAsync(productId, "products");

Listing metafields

var service = new MetaFieldService(myShopifyUrl, shopAccessToken);
var metafields = await service.ListAsync(productId, "products");

Getting a metafield

var service = new MetaFieldService(myShopifyUrl, shopAccessToken);
var metafield = await service.GetAsync(metafieldId);

Updating a metafield

var service = new MetaFieldService(myShopifyUrl, shopAccessToken);
var metafield = await service.UpdateAsync(metafieldId, new MetaField()
{
    Value = "45"
});

Deleting a metafield

var service = new MetaFieldService(myShopifyUrl, shopAccessToken);
await service.DeleteAsync(metafieldId);

Custom Collections

A custom collection is a grouping of products that a shop owner can create to make their shops easier to browse. A shop owner creates a custom collection and then selects the products that will go into it.

Creating a custom collection

var service = new CustomCollectionService(myShopifyUrl, shopAccessToken);
var collection = await service.CreateAsync(new CustomCollection()
{
    Title = "My Custom Collection",
    Published = true,
    PublishedAt = DateTime.UtcNow,
    Image = new CustomCollectionImage()
    {
        Src = "http://placehold.it/250x250"
    }
});

Getting a custom collection

var service = new CustomCollectionService(myShopifyUrl, shopAccessToken);
var collection = await service.GetAsync(collectionId);

Counting custom collections

var service = new CustomCollectionService(myShopifyUrl, shopAccessToken);
var count = await service.CountAsync();

Listing custom collections

var service = new CustomCollectionService(myShopifyUrl, shopAccessToken);
var collections = await service.ListAsync();

Updating a custom collection

var service = new CustomCollectionService(myShopifyUrl, shopAccessToken);
var collection = await service.UpdateAsync(collectionId, new Collection()
{
    Title = "My new collection title"
});

Deleting a custom collection

var service = new CustomCollectionService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(collectionId);

Product Images

Product Images represent the various different images for a product. All product images are tied to an owner product, and therefore you'll need to pass that product's id to each product image method.

Creating a product image

var service = new ProductImageService(myShopifyUrl, shopAccessToken);
var image = await service.CreateAsync(productId, new ProductImage()
{
    Metafields = new List<MetaField>()
    {
        new MetaField()
        {
            Key = "alt",
            Value = "new alt tag content",
            ValueType = "string",
            Namespace = "tags"
        }
    },
    Src = "http://placehold.it/200/300"
});

Getting a product image

var service = new ProductImageService(myShopifyUrl, shopAccessToken);
var image = await service.GetAsync(productId, imageId);

Counting product images

var service = new ProductImageService(myShopifyUrl, shopAccessToken);
var count = await service.CountAsync(productId);

Listing product images

var service = new ProductImageService(myShopifyUrl, shopAccessToken);
var images = await service.ListAsync(productId);

Updating a product image

var service = new ProductImageService(myShopifyUrl, shopAccessToken);
var image = await service.UpdateAsync(productId, imageId, new Image()
{
    Position = 2
});

Deleting a product image

var service = new ProductImageService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(productId, imageId);

Locations

A Location represents a geographical location where your stores, headquarters, and/or pop-up shops exist. These locations can be used to track sales and to help Shopify configure the tax rates to charge when selling products.

Listing locations

var service = new LocationService(myShopifyUrl, shopAccessToken);
var locations = await service.ListAsync();

Getting a location

var service = new LocationService(myShopifyUrl, shopAccessToken);
var location = await service.GetAsync(locationId);

Events

Events are generated by specific Shopify resources when specific things happen, such as the creation of an article, the placement or fulfillment of an order, the addition or deletion of a product, and so on. By requesting events, your app can get a "log" of important occurrences in the operation of a shop.

Caution: the events returned by the Events API should not be considered to be realtime. Events might not appear in the list returned by the API until a few seconds after they've occurred. In rare cases (<1% of the time) it can take up to a few minutes for some events to appear.

Counting events

var service = new EventService(myShopifyUrl, shopAccessToken);
var count = await service.CountAsync();

Getting an event

var service = new EventService(myShopifyUrl, shopAccessToken);
var event = await service.GetAsync(eventId);

Listing events

var service = new EventService(myShopifyUrl, shopAccessToken);
var events = await service.ListAsync();

Listing events for a specific subject (e.g. Order or Product)

You can filter your event list result to only the events created by a specific "subject"; i.e. you can list all events for one specific Order, Product, Article, etc. When filtering events in this way, you must supply both the "subject" type and its id.

Known subject types are 'Articles', 'Blogs', 'Custom_Collections', 'Comments', 'Orders', 'Pages', 'Products' and 'Smart_Collections'. A current list of subject types can be found at https://help.shopify.com/api/reference/event.

var service = new EventService(myShopifyUrl, shopAccessToken);
var subjectType = "Order";
var orderEvents = await service.ListAsync(orderId, subjectType);

Order Risks

The Order risk assessment is used to indicate to a merchant the fraud checks that have been done on an order.

Create an Order Risk

var service = new OrderRiskService(myShopifyUrl, shopAccessToken);
var risk = await service.CreateAsync(orderId, new OrderRisk()
{
    Message = "This looks risk!",
    Score = (decimal)0.85,
    Recommendation = "cancel",
    Source = "External",
    CauseCancel = false,
    Display = true,
});

Get an Order Risk

var service = new OrderRiskService(myShopifyUrl, shopAccessToken);
var risk = await service.GetAsync(orderId, riskId);

Update an Order Risk

var service = new OrderRiskService(myShopifyUrl, shopAccessToken);
var risk = await service.UpdateAsync(orderId, riskId, new Risk()
{
    Message = "An updated risk message"
});

List Order Risks

var service = new OrderRiskService(myShopifyUrl, shopAccessToken);
var risks = await service.ListAsync(orderId);

Delete an Order Risk

var service = new OrderRiskService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(orderId, riskId);

Smart Collections

A smart collection is a grouping of products defined by simple rules set by shop owners. A shop owner creates a smart collection and then sets the rules that determine which products go in them. Shopify automatically changes the contents of smart collections based on their rules.

Creating a Smart Collection

var service = new SmartCollectionService(myShopifyUrl, shopAccessToken);
var smartCollection = await service.CreateAsync(new SmartCollection()
{
   Title = "My Smart Collection",
   Handle = "my-url-slug",
   BodyHtml = "\<h1\>Hello world!\</h1\>",
   Image = new SmartCollectionImage()
   {
       // Base-64 image attachment
       Attachment = "R0lGODlhAQABAIAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==\n"
   }
});

Updating a Smart Collection

var service = new SmartCollectionService(myShopifyUrl, shopAccessToken);
var smartCollection = await service.UpdateAsync(smartCollectionId, new SmartCollection()
{
    Title = "My updated title"
});

Getting a Smart Collection

var service = new SmartCollectionService(myShopifyUrl, shopAccessToken);
var smartCollection = await service.GetAsync(smartCollectionId);

Counting Smart Collections

var service = new SmartCollectionService(myShopifyUrl, shopAccessToken);
var count = await service.CountAsync();

Listing Smart Collections

var service = new SmartCollectionService(myShopifyUrl, shopAccessToken);
var smartCollections = await service.ListAsync();

Deleting a Smart Collection

var service = new SmartCollectionService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(smartCollectionId);

Product Variants

A product variant is a different version of a product, such as differing sizes or differing colors. Without product variants, you would have to treat the small, medium and large versions of a t-shirt as three separate products; product variants let you treat the small, medium and large versions of a t-shirt as variations of the same product.

Creating a Product Variant

var service = new ProductVariantService(myShopifyUrl, shopAccessToken);
var variant = await service.CreateAsync(productId, new ProductVariant()
{
    Option1 = "blue",
    Price = 123.45,
});

Getting a Product Variant

var service = new ProductVariantService(myShopifyUrl, shopAccessToken);
var variant = await service.GetAsync(variantId);

Updating a Product Variant

var service = new ProductVariantService(myShopifyUrl, shopAccessToken);
var variant = await service.UpdateAsync(variantId, new Variant()
{
    Price = 543.21
});

Listing Product Variants

var service = new ProductVariantService(myShopifyUrl, shopAccessToken);
var variants = await service.ListAsync(productId);

Counting Product Variants

var service = new ProductVariantService(myShopifyUrl, shopAccessToken);
var count = await service.CountAsync(productId);

Deleting a Product Variant

var service = new ProductVariantService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(productId, variantId);

Blogs

In addition to an online storefront, Shopify shops come with a built-in blogging engine, allowing a shop to have one or more blogs. This service is for interacting with blogs themselves, not blog posts.

Creating a Blogs

var service = new BlogService(myShopifyUrl, shopAccessToken);
var blog = await service.CreateAsync(new Blog()
{
    Title = "My new blog"
});

Getting a Blog

var service = new BlogService(myShopifyUrl, shopAccessToken);
var blog = await service.GetAsync(blogId);

Updating a Blog

var service = new BlogService(myShopifyUrl, shopAccessToken);
var blog = await service.UpdateAsync(blogId, new Blog()
{
    Comments = "moderate"
});

Listing Blogs

var service = new BlogService(myShopifyUrl, shopAccessToken);
var blogs = await service.ListAsync();

Counting Blogs

var service = new BlogService(myShopifyUrl, shopAccessToken);
var count = await service.CountAsync();

Deleting a Blog

var service = new BlogService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(blogId);

Articles

Articles are objects representing a blog post. Each article belongs to a Blog.

Creating an Article

var service = new ArticleService(myShopifyUrl, shopAccessToken);
var article = await service.CreateAsync(blogId, new Article()
{
    Title = "My new Article title",
    Author = "John Smith",
    Tags = "This Post, Has Been Tagged",
    BodyHtml = "<h1>Hello world!</h1>",
    Image = new ArticleImage()
    {
        Attachment = "R0lGODlhAQABAIAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==\n"
    }
});

Getting an Article

var service = new ArticleService(myShopifyUrl, shopAccessToken);
var article = await service.GetAsync(blogId, articleId);

Updating an Article

var service = new ArticleService(myShopifyUrl, shopAccessToken);
var article = await service.UpdateAsync(blogId, articleId, new Article()
{
    Title = "My new title"
});

Listing Articles

var service = new ArticleService(myShopifyUrl, shopAccessToken);
var articles = await service.ListAsync(blogId);

Counting Articles

var service = new ArticleService(myShopifyUrl, shopAccessToken);
var count = await service.CountAsync(blogId);

Deleting an Article

var service = new ArticleService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(blogId, articleId);

Listing all Article authors

var service = new ArticleService(myShopifyUrl, shopAccessToken);
IEnumerable<string> authors = await service.ListAuthorsAsync();

Listing all Article tags

var service = new ArticleService(myShopifyUrl, shopAccessToken);
IEnumerable<string> tags = await service.ListTagsAsync();

Listing all Article tags for a single Blog

var service = new ArticleService(myShopifyUrl, shopAccessToken);
IEnumerable<string> tags = await service.ListTagsForBlogAsync(blogId);

Application Credits

Shopify's Application Credit API lets you offer credits for payments your app customers have made via the Application Charge, Recurring Application Charge, and Usage Charge APIs.

The total amount of all Application Credits created by an application must not exceed:

  1. Total amount paid to the application by the shop owner in the last 30 days.
  2. Total amount of pending receivables in the partner account associated with the application.

Additionally, Application Credits cannot be used by private applications.

Creating an Application Credit

var service = new ApplicationCreditService(myShopifyUrl, shopAccessToken);
var credit = await service.CreateAsync(new ApplicationCredit()
{
    Description = "Refund for Foo",
    Amount = 10.00m
});

Getting an Application Credit

var service = new ApplicationCreditService(myShopifyUrl, shopAccessToken);
var charge = await service.GetAsync(creditId);

Listing Application Credits

var service = new ApplicationCreditService(myShopifyUrl, shopAccessToken);
var charges = await service.ListAsync();

Discounts

Developers can create a discount code with the DiscountService. A merchant's customers can enter the discount code during the checkout process to redeem percentage-based, fixed amount, or free shipping discounts on a specific product, collection or order.

Discounts require a Shopify Plus subscription.

Creating a Discount

var service = new DiscountService(myShopifyUrl, shopAccessToken);
var discount = await service.CreateAsync(new Discount()
{
    DiscountType = "fixed_amount",
    Value = "10.00",
    DiscountCode = "AuntieDot",
    MinimumOrderAmount = "40.00",
});

Getting a Discount

var service = new DiscountService(myShopifyUrl, shopAccessToken);
var discount = await service.GetAsync(discountId):

Listing Discounts

var service = new DiscountService(myShopifyUrl, shopAccessToken);
var discounts = await service.ListAsync();

Deleting a Discount

var service = new DiscountService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(discountId);

Disabling a Discount

Discount codes can be disabled via that API, which makes them inactive and unusable until reenabled.

var service = new DiscountService(myShopifyUrl, shopAccessToken);

await service.DisableAsync(discountId);

Enabling a Discount

Once disabled, a discount cannot be used by any customer until it's enabled.

var service = new DiscountService(myShopifyUrl, shopAccessToken);

await service.EnableAsync(discountId);

Policies

Developers can get the list of policies that a merchant has configured for their store, such as their refund or privacy policies.

Listing Policies

var service = new PolicyService(myShopifyUrl, shopAccessToken);
var policies = await service.ListAsync();

Shipping Zones

Developers can get the list of shipping zones, their countries, provinces, and shipping rates.

Listing Shipping Zones

var service = new ShippingZoneService(myShopifyUrl, shopAccessToken);
var shippingZones = await service.ListAsync();

Gift Cards

Developers can create a gift card with the GiftCardService.

Gift Cards require a Shopify Plus subscription.

Listing Gift Cards

var service = new GiftCardService(myShopifyUrl, shopAccessToken);
var giftCards = await service.ListAsync();

Creating a Gift Card

var service = new GiftCardService(myShopifyUrl, shopAccessToken);
var giftCard = await service.CreateAsync(new GiftCard()
{
    InitialValue = 100,
    Code = "abc-bcd-efg"
});

Getting a Gift Card

var service = new GiftCardService(myShopifyUrl, shopAccessToken);
var giftCard = await service.GetAsync(giftCardId):

Disabling a Gift Card

Gift Cards can be disabled via that API, which makes them inactive and unusable until reenabled.

var service = new GiftCardService(myShopifyUrl, shopAccessToken);

await service.DisableAsync(discountId);

Counting a Gift Cards

var service =  new GiftCardService(myShopifyUrl, shopAccessToken);
int giftCardCount = await service.CountAsync();

Searching a Gift Cards

var service =  new GiftCardService(myShopifyUrl, shopAccessToken);
IEnumerable<GiftCard> giftCards = await Service.SearchAsync("code: abc-bcd-efg");

Price Rules

The Price Rules API allows you to dynamically create discounts with multiple conditions that can be applied at checkout to cart items or shipping lines via a discount code. Price rules can be created for a fixed value discount, a percentage discount, or a shipping line discount. You can also specify the dates for which the price rule is valid, the number of times the price rule can be applied, and to which products, collections, variants, customer groups and even shipping countries the price rule can be applied.

Creating a Price Rule

var service = new PriceRuleService(myShopifyUrl, shopAccessToken);
var priceRule = await service.CreateAsync(new PriceRule()
{
    Title = "My price rule",
    ValueType = "percentage",
    TargetType = "line_item",
    TargetSelection = "all",
    AllocationMethod = "across",
    Value = -10.0m,
    CustomerSelection = "all",
    OncePerCustomer = false,
    PrerequisiteSubtotalRange = new PrerequisiteValueRange()
    {
        GreaterThanOrEqualTo = 40m
    },
    StartsAt = new DateTimeOffset(DateTime.Now)
});

Updating a Price Rule

var service = new PriceRuleService(myShopifyUrl, shopAccessToken);
var updatedRule = await service.UpdateAsync(ruleId, new PriceRule()
{
    Value = -15.0m
});

Getting a Price Rule

var service = new PriceRuleService(myShopifyUrl, shopAccessToken);
var priceRule = await service.GetAsync(ruleId);

Listing Price Rules

var service = new PriceRuleService(myShopifyUrl, shopAccessToken);
var priceRules = await service.ListAsync();

Deleting a Price Rule

var service = new PriceRuleService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(ruleId);

Users

Developers can retrieve users with the UserService.

The Users API requires a Shopify Plus subscription.

Listing Users

var service = new UserService(myShopifyUrl, shopAccessToken);
var users = await service.ListAsync();

Getting a User

var service = new UserService(myShopifyUrl, shopAccessToken);
var user = await service.GetAsync(userId):

Abandoned Checkouts

This is used to return abandoned checkouts. A checkout is considered abandoned when a customer has entered their billing & shipping info, but has yet to complete the purchase.

Listing Abandoned Checkouts

var service = new CheckoutService(myShopifyUrl, shopAccessToken);
var checkouts = await service.ListAsync();

Count Abandoned Checkouts

var service = new CheckoutService(myShopifyUrl, shopAccessToken);
var count = await service.CountAsync();

Draft Orders

You can use the DraftOrder resource to allow merchants to create orders on behalf of customers. This is useful for Shopify merchants who receive orders through outside channels and enables a wide range of use cases including the following:

  • Create new orders for sales made by phone, in person, via chat, or by other means. Credit card payments for these orders can subsequently be entered in the Shopify admin.
  • Send invoices to customers to pay with a secure checkout link.
  • Use custom items to represent additional costs or products that aren't displayed in a shop's inventory.
  • Re-create mistaken orders.
  • Sell products at discount or wholesale rates.
  • Take pre-orders.

Listing Draft Orders

var service = new DraftOrderService(myShopifyUrl, shopAccessToken);
var draftOrders = await service.ListAsync();

Counting Draft orders

var service = new DraftOrderService(myShopifyUrl, shopAccessToken);
var count = await service.CountAsync();

Getting a Draft Order

var service = new DraftOrderService(myShopifyUrl, shopAccessToken);
var draftOrder = await service.GetAsync(draftOrderId);

Create a Draft Order

var service = new DraftOrderService(myShopifyUrl, shopAccessToken);
var draftOrder = await Service.CreateAsync(new DraftOrder()
{
    LineItems = new List<DraftLineItem>()
    {
        new DraftLineItem()
        {
            Title = "My custom line item",
            Price = 15.00m,
            Quantity = 1,
        }
    },
    Note = "Hello world!"
});

Update a Draft Order

var service = new DraftOrderService(myShopifyUrl, shopAccessToken);
var original = await Service.GetAsync(originalOrderId);
original.Note = "My new note";

var updated = await Service.UpdateAsync(originalOrderId, original);

Delete a Draft Order

var service = new DraftOrderService(myShopifyUrl, shopAccessToken);

await service.DeleteAsync(orderId);

Send a Draft Order invoice

var service = new DraftOrderService(myShopifyUrl, shopAccessToken);
var invoice = await service.SendInvoiceAsync(new DraftOrderInvoice()
{
    To = "[email protected]",
    Subject = "Your order is ready to pay",
    CustomMessage = "Please pay!"
});

Complete a Draft Order

var service = new DraftOrderService(myShopifyUrl, shopAccessToken);
bool paymentPending = false;
var draftOrder = await service.CompleteAsync(orderId, paymentPending);

Access Scopes

The Access Scope API allows you to retrieve the permissions that a merchant has granted to an app, such as read_orders and write_products. The list of access scopes is retrieved based on the access token used for the request, and only returns those scopes associated with the token.

List Access Scopes

var service = new AccessScopeService(myShopifyUrl, shopAccessToken);
var scopes = await service.ListAsync();

Handling Shopify's API rate limit

The Shopify API allows for an average of 2 API calls per second, with a burst limit of up to 40 API calls. Once you hit that 40 burst limit, Shopify will return a 429 Too Many Requests result. The limit is there to prevent you and thousands of other developers from overloading Shopify's servers by going hard in the paint with hundreds of requests every second. Unfortunately, it's pretty easy to write a for loop while trying to close a list of orders, and then start getting exceptions after closing the first 40.

By default, ShopifySharp will not retry requests that get throttled by the rate limit, and instead this package will throw a ShopifyRateLimitException that you can catch and decide to retry:

foreach (var order in listOfOrders)
{
	try
	{
		await orderService.CloseAsync(order.Id.Value);
	}
	catch (ShopifyRateLimitException e)
	{
		//Wait for 10 seconds before trying again.
		await Task.Delay(10000);

		//If this throws an exception again, loop will break and the exception will be thrown.
		await orderService.CloseAsync(order.Id.Value);
	}
}

However, ShopifySharp also has request execution policies that you can use to implement a retry strategy. Currently there are three execution policies bundled with the library:

  1. DefaultRequestExecutionPolicy: This is the default policy, which will throw a ShopifyRateLimitException when the API rate limit has been reached.
  2. RetryExecutionPolicy: If a request throws a ShopifyRateLimitException, this policy will keep retrying it until it is successful.
  3. SmartRetryExecutionPolicy: This policy attempts to use a leaky bucket strategy by proactively limiting the number of requests that will result in a ShopifyRateLimitException. For example: if 100 requests are created in parallel, only 40 should be sent immediately, and the remaining 60 requests should be throttled at 1 per 500ms.

You have two different ways to set an execution policy. You can set a policy on a per-instance basis:

var service = new ProductService(myShoipfyUrl, accessToken);

service.SetExecutionPolicy(new RetryExecutionPolicy());

Or you can set a global execution policy:

ShopifyService.SetGlobalExecutionPolicy(new RetryExecutionPolicy());

Note that instance-specific policies will always be used over global execution policies. In addition, if you clear the instance-specific policy by passing null, the instance will then switch over to the global execution policy.

Keep in mind that the RetryExecutionPolicy and the SmartRetryExecutionPolicy will keep retrying your requests – potentially until the end of time – until they are successful. It's up to you to ensure that such a strategy won't impact the performance of your applications.

If you need a custom policy to do something more complicated or to e.g. implement request logging, you can create your own request policy that extends the ShopifySharp.IRequestExecutionPolicy interface. Check here for an example.

Custom Filters

Occasionally we get requests to add certain properties to one of the List or Count filters that isn't documented anywhere by Shopify. For example, at one point it was possible to add a name prop to the OrderFilter that would make it possible to search for an Order by its name. Unfortunately this name filter was never documented and Shopify eventually removed that functionality, but it's a perfect example of wanting to use custom properties on the filters.

Officially, my stance is that I tend to favor not adding undocumented things to this package on the fear that it will someday break and I'll have a big headache fielding questions and issues here on GitHub when it does. However, in the case of Filters it's possible for you to implement your own custom filter without it being officially supported!

It's as easy as creating your own class that extends whichever filter your method accepts. For example, let's pretend that name search still works when listing Shopify orders, but this package doesn't support it. The OrderService.ListAsync method accepts an OrderFilter argument, so to get the name property sent along with the API call, all you need to do is create your own custom filter that extends OrderFilter:

public class MyCustomOrderFilter : OrderFilter
{
    [JsonProperty("name")] // This will serialize the value as `name` when sent to the API endpoint.
    public string Name { get; set; }
}

Your custom order filter still has all of the original properties of the base OrderFilter class, plus it has your new Name property. Since your custom filter extends the class that OrderService.ListAsync was looking for, you can now pass it as an argument to that method without any problems:

var list = await orderService.ListAsync(new MyCustomOrderFilter()
{
    Name = "1001"
});

If you need even more fine-grained control over what gets sent through your custom filter, you can also override the ToParameters or ToSingleParameter methods of the filter. Those methods are called by the service when it's serializing the filter to a querystring.

You can take a look at the Parameterizable class (which is used by all filters) for a look at the current implementation and what you can do in those methods.

"Why don't you use enums?"

I'm a big fan of using enums to make things easier for C# devs, because it removes a lot of the headache that comes with trying to remember all the valid string options for certain properties. With enums, we get those options hardcoded by default. We can easily scroll up and down the list of known values and select the one we need, without having to worry about typos.

Many Shopify objects have string properties that only accept a predetermined list of values, and these properties are perfect for matching to C# enums. Unfortunately, Shopify has a habit of only documenting the most used values and leaving the developer to guess the rest. On top of that, they sometimes change those enums completely, such as this case where they changed the enums used for filtering orders without announcing it.

That's a problem when it comes to strongly-typed languages like C#. If you receive an enum property that doesn't have a value matching the enum, you're going to get a big fat exception thrown in your face. This is especially problematic when these undocumented enum values are sent to you automatically in webhooks.

On top of that, if there's an enum value that you need to send but isn't in ShopifySharp, you'll need to wait until a new version of the lib is released before you can use it.

Enums would be much better suited to ShopifySharp if Shopify themselves used API versioning, but sadly that isn't the case. After struggling with undocumented values and unannounced changes that break apps through two major releases of ShopifySharp, I've made the decision to pull the plug on almost all enums in the lib.

What were previously enums in ShopifySharp 1.x and 2.x are now string properties. This change will prevent breaking your app when an enum value changes, and will allow you to quickly update your app when a new enum value is released without waiting on an update to ShopifySharp first.

Tests

The test suite relies on your own Shopify credentials, including your Shopify API key, a shop's *.myshopify.com URL, and an access token with full permissions for that shop. This blog post will show you exactly what you need to do to get a shop access token with full permissions.

Once you have those credentials you'll need to the following keys/values to your environment variables:

SHOPIFYSHARP_API_KEY = value

SHOPIFYSHARP_SECRET_KEY = value

SHOPIFYSHARP_ACCESS_TOKEN = value

SHOPIFYSHARP_MY_SHOPIFY_URL = value

New features will not be published until they have test coverage. If you'd like your pull request to be published, make sure you write tests for it!

ShopifySharp is now using xUnit for tests. New tests should all follow the format of other tests in 4.0. You can use the Article test as an example, but I would highly recommend that you use the provided ShopifySharp Test snippet in the VSCode folder instead. Create a new *_Tests.cs file and type test-shopifysharp in VSCode:

shopifysharp-test