Latest commit

History

216 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Shopify API

Build StatusHex.pm

This package allows Elixir developers to easily access the admin Shopify API.

Installation

The package can be installed by adding shopify to your list of dependencies in mix.exs:

defdepsdo[{:shopify,"~> 0.4"}]end

Getting Started

The Shopify API can be accessed in two ways - either with private apps via basic auth, or with oauth.

Private Apps

Once you have a valid API key and password, setup your config/config.exs.

config:shopify,[shop_name: "my-shop",api_key: System.get_env("SHOPIFY_API_KEY"),password: System.get_env("SHOPIFY_API_PASSWORD")]

We can now easily create a new API session.

Shopify.session

Alternatively, we can create a one-off session.

Shopify.session("my-shop-name","my-api-key","my-password")

OAuth Apps

Once you have a shopify app client ID and secret, setup your config/config.exs.

config:shopify,[client_id: System.get_env("SHOPIFY_CLIENT_ID"),client_secret: System.get_env("SHOPIFY_CLIENT_SECRET")]

To gain access to a shop via OAuth, first, generate a permission url based on your requirments.

params=%{scope: "read_orders,read_products",redirect_uri: "http://my-redirect_uri.com/"}permission_url="shop-name"|>Shopify.session()|>Shopify.OAuth.permission_url(params)

After a shop has authorized access, they will be redirected to your URI above. The redirect will include a payload that contains a 'code'. We can now generate an access token.

{:ok,%Shopify.Response{data: oauth}}="shop-name"|>Shopify.session()|>Shopify.OAuth.request_token(code)

We can now easily create a new OAuth API session.

Shopify.session("shop-name",oauth.access_token)

Making Requests

All API requests require a session struct to begin.

"shop-name"|>Shopify.session("access-token")|>Shopify.Product.find(1)# ORsession=Shopify.session("shop-name","access-token")Shopify.Product.find(session,1)

Here are some examples of the various types of requests that can be made.

# Create a session structsession=Shopify.session("shop-name","access-token")# Find a resource by ID{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id)# Find a resource and select fields{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id,%{fields: "id,images,title"})# All resources{:ok,%Shopify.Response{data: products}}=session|>Shopify.Product.all# All resources with query params{:ok,%Shopify.Response{data: products}}=session|>Shopify.Product.all(%{page: 1,limit: 5})# Find a resource and update it{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id)updated_product=%{product|title: "New Title"}{:ok,response}=session|>Shopify.Product.update(product.id,updated_product)# Update a resource without finding it{:ok,response}=session|>Shopify.Product.update(id,%{title: "New Title"})# Create a resource from the resource structnew_product=%Shopify.Product{title: "Fancy Shirt",body_html: "<strong>Good shirt!<\/strong>",vendor: "Fancy Vendor",product_type: "shirt",variants: [%{price: "10.00",sku: 123}]}{:ok,response}=session|>Shopify.Product.create(new_product)# Create a resource from a simple mapnew_product_args=%{title: "Fancy Shirt",body_html: "<strong>Good shirt!<\/strong>",vendor: "Fancy Vendor",product_type: "shirt",variants: [%{price: "10.00",sku: 123}]}{:ok,response}=session|>Shopify.Product.create(new_product_args)# Count resources{:ok,%Shopify.Response{data: count}}=session|>Shopify.Product.count# Count resources with query params{:ok,%Shopify.Response{data: count}}=session|>Shopify.Product.count(%{vendor: "Fancy Vendor"})# Search for resources{:ok,%Shopify.Response{data: customers}}=session|>Shopify.Customer.search(%{query: "country:United States"})# Delete a resource{:ok,_}=session|>Shopify.Product.delete(id)

API Versioning

Shopify supports API versioning. By default, if you dont specify an api version, your request defaults to the oldest supported stable version.

You can specify a default version through application config.

config:shopify,[api_version: "2019-04"]

You can also set a specific version per session.

Shopify.session("shop-name","access-token")|>Shopify.Session.put_api_version("2019-04")

Handling Responses

Responses are all returned in the form of a two-item tuple. Any response that has a status code below 300 returns {:ok, response}. Codes above 300 are returned as {:error, response}.

# Create a session structsession=Shopify.session("shop-name","access-token")# 'data' is returned as a %Shopify.Product struct{:ok,%Shopify.Response{code: 200,data: data}}=session|>Shopify.Product.find(id)# 'data' is returned as a list of %Shopify.Product structs{:ok,%Shopify.Response{code: 200,data: data}}=session|>Shopify.Product.all# 'message' is a text description of the error.{:error,%Shopify.Response{code: 404,data: message}}=session|>Shopify.Product.find(1)# Failed requests return %Shopify.Error struct{:error,%Shopify.Error{reason: :econnrefused,source: :httpoison}}=session|>Shopify.Product.find(1)

The %Shopify.Response{} struct contains two fields: code and data. Code is the HTTP status code that is returned from Shopify. A successful request will either set the data field with a single struct, or list of structs of the resource or resources requested.

Multipass

The Multipass is available to Shopify Plus plans. It allows your non-Shopify site to be the source of truth for authentication and login. After your site has successfully authenticated a user, redirect their browser to Shopify using the special Multipass URL: this will upsert the customer data in Shopify and log them in.

Unlike other API requests, this does not require a session: it relies on a shared secret to do decryption.

Your customer data must at a minimum provide an email address and a current datetime in 8601 format.

customer_data=%{email: "something@test.shopify.com",created_at: DateTime.to_iso8601(Timex.now())}# From your store's checkout settingsmultipass_secret=Application.get_env("MULTIPASS_SECRET")url=Shopify.Multipass.get_url("myteststore",customer_data,multipass_secret)# Redirect the browser immediately to the resulting URL:"https://myteststore.myshopify.com/account/login/multipass/moaqEVx1Yu9hsvYvVpj-LeRYDtOo6ikicfTZd8tR8-xBMRg8tFjGEfllEcjj2VdbsezmT0XuEdglyQzi_biQPkfLJnP1dkxhNtfzwtt6IMQzu3W0qCPzbrUMD_gLaytPVP-zZZuYiSBqEMNdvzFg3zf0TOQHwbizX2D7It02sFI7ZpTRhfX4m_crV0b-DmmF"

Testing

For testing a mock adapter can be configured to use fixture json files instead of doing real requests.

Lets say you have a test config file in your_project/config/test.exs and tests in your_project/test you could use this configuration:

# your_project/config/test.exsconfig:shopify,[shop_name: "test",api_key: "test-key",password: "test-paswword",client_secret: "test-secret",client_adapter: Shopify.Adapters.Mock,# Use included Mock adapterfixtures_path: Path.expand("../test/fixtures/shopify",__DIR__)# Use fixures in this directory]

When using oauth, make sure the token passed is test, otherwise authentication will fail.

Shopify.session("my-shop.myshopify.com","test")|>Product.all()

Test Adapter

This plugin provides a test adapter called Shopify.Adapters.Mock to use out of the box. It makes certain assumptions about your fixtures and is limited to the responses provided in corresponding fixture files, and for create actions it will put the resource id as 1.

If you would like to roll your own adapter, you can do so by implementing @behaviour Shopify.Adapters.Base.

defmoduleShopify.Adapters.Mockdo@moduledocfalse@behaviourShopify.Adapters.Basedefget(%Shopify.Request{}=request)dodata=%{resource: %{id: 123,attribute: "attribute"}}{:ok,%Shopify.Response{code: 200,data: data}}end# ...end

Fixtures

Fixture files must follow a certain structure, so the adapter is able to find them. If your resource is Shopify.Product.all() you need to provide a file at path_you_provided_in_config/products.json and must include a valid response json

{
"orders": [
{
"buyer_accepts_marketing": false,
"cancel_reason": null,
"cancelled_at": null,
...
}
]
}

Or for Shopify.Product.find(1)

# path_you_provided_in_config/products/1.json
{
"order": {
"id": 1,
"email": "bob.mctest@test.com",
...
}
}

Current Resources

  • Address
  • ApplicationCharge (find, all, create, activate)
  • ApplicationCredit (find, all, create)
  • Article (find, all, create, update, delete, count)
  • Article.Author (all)
  • Article.Tag (all)
  • Attribute
  • BillingAddress
  • Blog (find, all, create, update, delete, count)
  • CarrierService (find, all, create, update, delete)
  • Checkout (all, find, create, update, count, shipping_rates, complete, count)
  • ClientDetails
  • Collect (find, all, create, delete, count)
  • CollectionListing (find, all)
  • Comment (find, all, create, update, spam, not_spam, approve, remove, restore)
  • Country (find, all, create, update, delete, count)
  • Country.Province (find, all, update, count)
  • CustomCollection (find, all, create, update, delete, count)
  • Customer (find, all, create, update, delete, count, search)
  • CustomerAddress (find, all, create, delete)
  • CustomerSavedSearch (find, all, create, update, delete, count)
  • CustomerSavedSearch.Customer (all)
  • DiscountCode
  • DraftOrder (find, all, create, update, delete, count, complete, send_invoice) send_invoice is an alias of DraftOrder.DraftOrderInvoice.create/3
  • DraftOrder.DraftOrderInvoice (create)
  • MarketingEvent.Engagement (create_multiple)
  • Event (find, all, count)
  • Order.Fullfillment (find, all, count, create, update, complete, open, cancel)
  • Order.Fullfillment.Event (find, all, delete)
  • FulfillmentService (find, all, create, update, delete)
  • Image (ProductImage) (find, all, create, update, delete, count)
  • InventoryLevel (all, delete)
  • LineItem
  • Location (find, all, count)
  • MarketingEvent (find, all, count, create, update, delete, create_multiple_engagements) create_multiple_engagements is an alias of MarketingEvent.Engagement.create_multiple/3
  • Metafield
  • OAuth.AccessScope (all)
  • Option
  • Order (find, all, create, update, delete, count)
  • Order.Event (all)
  • Order.Risk (create, find, all, update, delete)
  • Page (create, find, all, update, delete, count)
  • PaymentDetails
  • Policy (all)
  • PriceRule (find, all, create, update, delete)
  • PriceRule.DiscountCode (find, all, create, update, delete)
  • Product (find, all, create, update, delete, count)
  • Product.Event (all)
  • ProductListing (find, all, create, update, delete, count, product_ids)
  • RecurringApplicationCharge (find, all, create, activate, delete)
  • Redirect (find, all, create, update, delete, count)
  • Refund (create, find, all)
  • Report (create, find, all, update, delete)
  • ScriptTag (find, all, create, count, delete)
  • ShippingAddress
  • ShippingLine
  • Shop (current)
  • SmartCollection (find, all, create, count, update, delete)
  • TaxLine
  • Theme (find, all, create, update, delete)
  • Theme.Asset (find, all, delete)
  • Transaction (find, all, create, count)
  • UsageCharge (find, all, create)
  • Variant (find, all, create, update, delete, count)
  • Webhook (find, all, create, update, delete, count)

Contributors

| Nick Sweeting
Nick Sweeting
💻 👀 📖 🚇 | Marcelo Oliveira
Marcelo Oliveira
💻 | Fabian Zitter
Fabian Zitter
💻 👀 📖 | Zach Garwood
Zach Garwood
💻 | David Becerra
David Becerra
💻 | Bryan Bryce
Bryan Bryce
📖 | humancopy
humancopy
💻 | Cmeurer10
Cmeurer10
💻 | lewisf
lewisf
💻 | vladimir-e
vladimir-e
💻 | furqanaziz
furqanaziz
💻 | balexand
balexand
💻 | :---: | :---: | :---: | :---: | :---: | :---: | :---: |

This project follows the all-contributors specification.

Documentation is generated with ExDoc. They can be found at https://hexdocs.pm/shopify.

About

Easily access the Shopify API with Elixir.

Topics

Resources

Stars

103 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

216 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Shopify API

Build StatusHex.pm

This package allows Elixir developers to easily access the admin Shopify API.

Installation

The package can be installed by adding shopify to your list of dependencies in mix.exs:

defdepsdo[{:shopify,"~> 0.4"}]end

Getting Started

The Shopify API can be accessed in two ways - either with private apps via basic auth, or with oauth.

Private Apps

Once you have a valid API key and password, setup your config/config.exs.

config:shopify,[shop_name: "my-shop",api_key: System.get_env("SHOPIFY_API_KEY"),password: System.get_env("SHOPIFY_API_PASSWORD")]

We can now easily create a new API session.

Shopify.session

Alternatively, we can create a one-off session.

Shopify.session("my-shop-name","my-api-key","my-password")

OAuth Apps

Once you have a shopify app client ID and secret, setup your config/config.exs.

config:shopify,[client_id: System.get_env("SHOPIFY_CLIENT_ID"),client_secret: System.get_env("SHOPIFY_CLIENT_SECRET")]

To gain access to a shop via OAuth, first, generate a permission url based on your requirments.

params=%{scope: "read_orders,read_products",redirect_uri: "http://my-redirect_uri.com/"}permission_url="shop-name"|>Shopify.session()|>Shopify.OAuth.permission_url(params)

After a shop has authorized access, they will be redirected to your URI above. The redirect will include a payload that contains a 'code'. We can now generate an access token.

{:ok,%Shopify.Response{data: oauth}}="shop-name"|>Shopify.session()|>Shopify.OAuth.request_token(code)

We can now easily create a new OAuth API session.

Shopify.session("shop-name",oauth.access_token)

Making Requests

All API requests require a session struct to begin.

"shop-name"|>Shopify.session("access-token")|>Shopify.Product.find(1)# ORsession=Shopify.session("shop-name","access-token")Shopify.Product.find(session,1)

Here are some examples of the various types of requests that can be made.

# Create a session structsession=Shopify.session("shop-name","access-token")# Find a resource by ID{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id)# Find a resource and select fields{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id,%{fields: "id,images,title"})# All resources{:ok,%Shopify.Response{data: products}}=session|>Shopify.Product.all# All resources with query params{:ok,%Shopify.Response{data: products}}=session|>Shopify.Product.all(%{page: 1,limit: 5})# Find a resource and update it{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id)updated_product=%{product|title: "New Title"}{:ok,response}=session|>Shopify.Product.update(product.id,updated_product)# Update a resource without finding it{:ok,response}=session|>Shopify.Product.update(id,%{title: "New Title"})# Create a resource from the resource structnew_product=%Shopify.Product{title: "Fancy Shirt",body_html: "<strong>Good shirt!<\/strong>",vendor: "Fancy Vendor",product_type: "shirt",variants: [%{price: "10.00",sku: 123}]}{:ok,response}=session|>Shopify.Product.create(new_product)# Create a resource from a simple mapnew_product_args=%{title: "Fancy Shirt",body_html: "<strong>Good shirt!<\/strong>",vendor: "Fancy Vendor",product_type: "shirt",variants: [%{price: "10.00",sku: 123}]}{:ok,response}=session|>Shopify.Product.create(new_product_args)# Count resources{:ok,%Shopify.Response{data: count}}=session|>Shopify.Product.count# Count resources with query params{:ok,%Shopify.Response{data: count}}=session|>Shopify.Product.count(%{vendor: "Fancy Vendor"})# Search for resources{:ok,%Shopify.Response{data: customers}}=session|>Shopify.Customer.search(%{query: "country:United States"})# Delete a resource{:ok,_}=session|>Shopify.Product.delete(id)

API Versioning

Shopify supports API versioning. By default, if you dont specify an api version, your request defaults to the oldest supported stable version.

You can specify a default version through application config.

config:shopify,[api_version: "2019-04"]

You can also set a specific version per session.

Shopify.session("shop-name","access-token")|>Shopify.Session.put_api_version("2019-04")

Handling Responses

Responses are all returned in the form of a two-item tuple. Any response that has a status code below 300 returns {:ok, response}. Codes above 300 are returned as {:error, response}.

# Create a session structsession=Shopify.session("shop-name","access-token")# 'data' is returned as a %Shopify.Product struct{:ok,%Shopify.Response{code: 200,data: data}}=session|>Shopify.Product.find(id)# 'data' is returned as a list of %Shopify.Product structs{:ok,%Shopify.Response{code: 200,data: data}}=session|>Shopify.Product.all# 'message' is a text description of the error.{:error,%Shopify.Response{code: 404,data: message}}=session|>Shopify.Product.find(1)# Failed requests return %Shopify.Error struct{:error,%Shopify.Error{reason: :econnrefused,source: :httpoison}}=session|>Shopify.Product.find(1)

The %Shopify.Response{} struct contains two fields: code and data. Code is the HTTP status code that is returned from Shopify. A successful request will either set the data field with a single struct, or list of structs of the resource or resources requested.

Multipass

The Multipass is available to Shopify Plus plans. It allows your non-Shopify site to be the source of truth for authentication and login. After your site has successfully authenticated a user, redirect their browser to Shopify using the special Multipass URL: this will upsert the customer data in Shopify and log them in.

Unlike other API requests, this does not require a session: it relies on a shared secret to do decryption.

Your customer data must at a minimum provide an email address and a current datetime in 8601 format.

customer_data=%{email: "something@test.shopify.com",created_at: DateTime.to_iso8601(Timex.now())}# From your store's checkout settingsmultipass_secret=Application.get_env("MULTIPASS_SECRET")url=Shopify.Multipass.get_url("myteststore",customer_data,multipass_secret)# Redirect the browser immediately to the resulting URL:"https://myteststore.myshopify.com/account/login/multipass/moaqEVx1Yu9hsvYvVpj-LeRYDtOo6ikicfTZd8tR8-xBMRg8tFjGEfllEcjj2VdbsezmT0XuEdglyQzi_biQPkfLJnP1dkxhNtfzwtt6IMQzu3W0qCPzbrUMD_gLaytPVP-zZZuYiSBqEMNdvzFg3zf0TOQHwbizX2D7It02sFI7ZpTRhfX4m_crV0b-DmmF"

Testing

For testing a mock adapter can be configured to use fixture json files instead of doing real requests.

Lets say you have a test config file in your_project/config/test.exs and tests in your_project/test you could use this configuration:

# your_project/config/test.exsconfig:shopify,[shop_name: "test",api_key: "test-key",password: "test-paswword",client_secret: "test-secret",client_adapter: Shopify.Adapters.Mock,# Use included Mock adapterfixtures_path: Path.expand("../test/fixtures/shopify",__DIR__)# Use fixures in this directory]

When using oauth, make sure the token passed is test, otherwise authentication will fail.

Shopify.session("my-shop.myshopify.com","test")|>Product.all()

Test Adapter

This plugin provides a test adapter called Shopify.Adapters.Mock to use out of the box. It makes certain assumptions about your fixtures and is limited to the responses provided in corresponding fixture files, and for create actions it will put the resource id as 1.

If you would like to roll your own adapter, you can do so by implementing @behaviour Shopify.Adapters.Base.

defmoduleShopify.Adapters.Mockdo@moduledocfalse@behaviourShopify.Adapters.Basedefget(%Shopify.Request{}=request)dodata=%{resource: %{id: 123,attribute: "attribute"}}{:ok,%Shopify.Response{code: 200,data: data}}end# ...end

Fixtures

Fixture files must follow a certain structure, so the adapter is able to find them. If your resource is Shopify.Product.all() you need to provide a file at path_you_provided_in_config/products.json and must include a valid response json

{
"orders": [
{
"buyer_accepts_marketing": false,
"cancel_reason": null,
"cancelled_at": null,
...
}
]
}

Or for Shopify.Product.find(1)

# path_you_provided_in_config/products/1.json
{
"order": {
"id": 1,
"email": "bob.mctest@test.com",
...
}
}

Current Resources

  • Address
  • ApplicationCharge (find, all, create, activate)
  • ApplicationCredit (find, all, create)
  • Article (find, all, create, update, delete, count)
  • Article.Author (all)
  • Article.Tag (all)
  • Attribute
  • BillingAddress
  • Blog (find, all, create, update, delete, count)
  • CarrierService (find, all, create, update, delete)
  • Checkout (all, find, create, update, count, shipping_rates, complete, count)
  • ClientDetails
  • Collect (find, all, create, delete, count)
  • CollectionListing (find, all)
  • Comment (find, all, create, update, spam, not_spam, approve, remove, restore)
  • Country (find, all, create, update, delete, count)
  • Country.Province (find, all, update, count)
  • CustomCollection (find, all, create, update, delete, count)
  • Customer (find, all, create, update, delete, count, search)
  • CustomerAddress (find, all, create, delete)
  • CustomerSavedSearch (find, all, create, update, delete, count)
  • CustomerSavedSearch.Customer (all)
  • DiscountCode
  • DraftOrder (find, all, create, update, delete, count, complete, send_invoice) send_invoice is an alias of DraftOrder.DraftOrderInvoice.create/3
  • DraftOrder.DraftOrderInvoice (create)
  • MarketingEvent.Engagement (create_multiple)
  • Event (find, all, count)
  • Order.Fullfillment (find, all, count, create, update, complete, open, cancel)
  • Order.Fullfillment.Event (find, all, delete)
  • FulfillmentService (find, all, create, update, delete)
  • Image (ProductImage) (find, all, create, update, delete, count)
  • InventoryLevel (all, delete)
  • LineItem
  • Location (find, all, count)
  • MarketingEvent (find, all, count, create, update, delete, create_multiple_engagements) create_multiple_engagements is an alias of MarketingEvent.Engagement.create_multiple/3
  • Metafield
  • OAuth.AccessScope (all)
  • Option
  • Order (find, all, create, update, delete, count)
  • Order.Event (all)
  • Order.Risk (create, find, all, update, delete)
  • Page (create, find, all, update, delete, count)
  • PaymentDetails
  • Policy (all)
  • PriceRule (find, all, create, update, delete)
  • PriceRule.DiscountCode (find, all, create, update, delete)
  • Product (find, all, create, update, delete, count)
  • Product.Event (all)
  • ProductListing (find, all, create, update, delete, count, product_ids)
  • RecurringApplicationCharge (find, all, create, activate, delete)
  • Redirect (find, all, create, update, delete, count)
  • Refund (create, find, all)
  • Report (create, find, all, update, delete)
  • ScriptTag (find, all, create, count, delete)
  • ShippingAddress
  • ShippingLine
  • Shop (current)
  • SmartCollection (find, all, create, count, update, delete)
  • TaxLine
  • Theme (find, all, create, update, delete)
  • Theme.Asset (find, all, delete)
  • Transaction (find, all, create, count)
  • UsageCharge (find, all, create)
  • Variant (find, all, create, update, delete, count)
  • Webhook (find, all, create, update, delete, count)

Contributors

| Nick Sweeting
Nick Sweeting
💻 👀 📖 🚇 | Marcelo Oliveira
Marcelo Oliveira
💻 | Fabian Zitter
Fabian Zitter
💻 👀 📖 | Zach Garwood
Zach Garwood
💻 | David Becerra
David Becerra
💻 | Bryan Bryce
Bryan Bryce
📖 | humancopy
humancopy
💻 | Cmeurer10
Cmeurer10
💻 | lewisf
lewisf
💻 | vladimir-e
vladimir-e
💻 | furqanaziz
furqanaziz
💻 | balexand
balexand
💻 | :---: | :---: | :---: | :---: | :---: | :---: | :---: |

This project follows the all-contributors specification.

Documentation is generated with ExDoc. They can be found at https://hexdocs.pm/shopify.

About

Easily access the Shopify API with Elixir.

Topics

Resources

Stars

103 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

216 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Shopify API

Build StatusHex.pm

This package allows Elixir developers to easily access the admin Shopify API.

Installation

The package can be installed by adding shopify to your list of dependencies in mix.exs:

defdepsdo[{:shopify,"~> 0.4"}]end

Getting Started

The Shopify API can be accessed in two ways - either with private apps via basic auth, or with oauth.

Private Apps

Once you have a valid API key and password, setup your config/config.exs.

config:shopify,[shop_name: "my-shop",api_key: System.get_env("SHOPIFY_API_KEY"),password: System.get_env("SHOPIFY_API_PASSWORD")]

We can now easily create a new API session.

Shopify.session

Alternatively, we can create a one-off session.

Shopify.session("my-shop-name","my-api-key","my-password")

OAuth Apps

Once you have a shopify app client ID and secret, setup your config/config.exs.

config:shopify,[client_id: System.get_env("SHOPIFY_CLIENT_ID"),client_secret: System.get_env("SHOPIFY_CLIENT_SECRET")]

To gain access to a shop via OAuth, first, generate a permission url based on your requirments.

params=%{scope: "read_orders,read_products",redirect_uri: "http://my-redirect_uri.com/"}permission_url="shop-name"|>Shopify.session()|>Shopify.OAuth.permission_url(params)

After a shop has authorized access, they will be redirected to your URI above. The redirect will include a payload that contains a 'code'. We can now generate an access token.

{:ok,%Shopify.Response{data: oauth}}="shop-name"|>Shopify.session()|>Shopify.OAuth.request_token(code)

We can now easily create a new OAuth API session.

Shopify.session("shop-name",oauth.access_token)

Making Requests

All API requests require a session struct to begin.

"shop-name"|>Shopify.session("access-token")|>Shopify.Product.find(1)# ORsession=Shopify.session("shop-name","access-token")Shopify.Product.find(session,1)

Here are some examples of the various types of requests that can be made.

# Create a session structsession=Shopify.session("shop-name","access-token")# Find a resource by ID{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id)# Find a resource and select fields{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id,%{fields: "id,images,title"})# All resources{:ok,%Shopify.Response{data: products}}=session|>Shopify.Product.all# All resources with query params{:ok,%Shopify.Response{data: products}}=session|>Shopify.Product.all(%{page: 1,limit: 5})# Find a resource and update it{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id)updated_product=%{product|title: "New Title"}{:ok,response}=session|>Shopify.Product.update(product.id,updated_product)# Update a resource without finding it{:ok,response}=session|>Shopify.Product.update(id,%{title: "New Title"})# Create a resource from the resource structnew_product=%Shopify.Product{title: "Fancy Shirt",body_html: "<strong>Good shirt!<\/strong>",vendor: "Fancy Vendor",product_type: "shirt",variants: [%{price: "10.00",sku: 123}]}{:ok,response}=session|>Shopify.Product.create(new_product)# Create a resource from a simple mapnew_product_args=%{title: "Fancy Shirt",body_html: "<strong>Good shirt!<\/strong>",vendor: "Fancy Vendor",product_type: "shirt",variants: [%{price: "10.00",sku: 123}]}{:ok,response}=session|>Shopify.Product.create(new_product_args)# Count resources{:ok,%Shopify.Response{data: count}}=session|>Shopify.Product.count# Count resources with query params{:ok,%Shopify.Response{data: count}}=session|>Shopify.Product.count(%{vendor: "Fancy Vendor"})# Search for resources{:ok,%Shopify.Response{data: customers}}=session|>Shopify.Customer.search(%{query: "country:United States"})# Delete a resource{:ok,_}=session|>Shopify.Product.delete(id)

API Versioning

Shopify supports API versioning. By default, if you dont specify an api version, your request defaults to the oldest supported stable version.

You can specify a default version through application config.

config:shopify,[api_version: "2019-04"]

You can also set a specific version per session.

Shopify.session("shop-name","access-token")|>Shopify.Session.put_api_version("2019-04")

Handling Responses

Responses are all returned in the form of a two-item tuple. Any response that has a status code below 300 returns {:ok, response}. Codes above 300 are returned as {:error, response}.

# Create a session structsession=Shopify.session("shop-name","access-token")# 'data' is returned as a %Shopify.Product struct{:ok,%Shopify.Response{code: 200,data: data}}=session|>Shopify.Product.find(id)# 'data' is returned as a list of %Shopify.Product structs{:ok,%Shopify.Response{code: 200,data: data}}=session|>Shopify.Product.all# 'message' is a text description of the error.{:error,%Shopify.Response{code: 404,data: message}}=session|>Shopify.Product.find(1)# Failed requests return %Shopify.Error struct{:error,%Shopify.Error{reason: :econnrefused,source: :httpoison}}=session|>Shopify.Product.find(1)

The %Shopify.Response{} struct contains two fields: code and data. Code is the HTTP status code that is returned from Shopify. A successful request will either set the data field with a single struct, or list of structs of the resource or resources requested.

Multipass

The Multipass is available to Shopify Plus plans. It allows your non-Shopify site to be the source of truth for authentication and login. After your site has successfully authenticated a user, redirect their browser to Shopify using the special Multipass URL: this will upsert the customer data in Shopify and log them in.

Unlike other API requests, this does not require a session: it relies on a shared secret to do decryption.

Your customer data must at a minimum provide an email address and a current datetime in 8601 format.

customer_data=%{email: "something@test.shopify.com",created_at: DateTime.to_iso8601(Timex.now())}# From your store's checkout settingsmultipass_secret=Application.get_env("MULTIPASS_SECRET")url=Shopify.Multipass.get_url("myteststore",customer_data,multipass_secret)# Redirect the browser immediately to the resulting URL:"https://myteststore.myshopify.com/account/login/multipass/moaqEVx1Yu9hsvYvVpj-LeRYDtOo6ikicfTZd8tR8-xBMRg8tFjGEfllEcjj2VdbsezmT0XuEdglyQzi_biQPkfLJnP1dkxhNtfzwtt6IMQzu3W0qCPzbrUMD_gLaytPVP-zZZuYiSBqEMNdvzFg3zf0TOQHwbizX2D7It02sFI7ZpTRhfX4m_crV0b-DmmF"

Testing

For testing a mock adapter can be configured to use fixture json files instead of doing real requests.

Lets say you have a test config file in your_project/config/test.exs and tests in your_project/test you could use this configuration:

# your_project/config/test.exsconfig:shopify,[shop_name: "test",api_key: "test-key",password: "test-paswword",client_secret: "test-secret",client_adapter: Shopify.Adapters.Mock,# Use included Mock adapterfixtures_path: Path.expand("../test/fixtures/shopify",__DIR__)# Use fixures in this directory]

When using oauth, make sure the token passed is test, otherwise authentication will fail.

Shopify.session("my-shop.myshopify.com","test")|>Product.all()

Test Adapter

This plugin provides a test adapter called Shopify.Adapters.Mock to use out of the box. It makes certain assumptions about your fixtures and is limited to the responses provided in corresponding fixture files, and for create actions it will put the resource id as 1.

If you would like to roll your own adapter, you can do so by implementing @behaviour Shopify.Adapters.Base.

defmoduleShopify.Adapters.Mockdo@moduledocfalse@behaviourShopify.Adapters.Basedefget(%Shopify.Request{}=request)dodata=%{resource: %{id: 123,attribute: "attribute"}}{:ok,%Shopify.Response{code: 200,data: data}}end# ...end

Fixtures

Fixture files must follow a certain structure, so the adapter is able to find them. If your resource is Shopify.Product.all() you need to provide a file at path_you_provided_in_config/products.json and must include a valid response json

{
"orders": [
{
"buyer_accepts_marketing": false,
"cancel_reason": null,
"cancelled_at": null,
...
}
]
}

Or for Shopify.Product.find(1)

# path_you_provided_in_config/products/1.json
{
"order": {
"id": 1,
"email": "bob.mctest@test.com",
...
}
}

Current Resources

  • Address
  • ApplicationCharge (find, all, create, activate)
  • ApplicationCredit (find, all, create)
  • Article (find, all, create, update, delete, count)
  • Article.Author (all)
  • Article.Tag (all)
  • Attribute
  • BillingAddress
  • Blog (find, all, create, update, delete, count)
  • CarrierService (find, all, create, update, delete)
  • Checkout (all, find, create, update, count, shipping_rates, complete, count)
  • ClientDetails
  • Collect (find, all, create, delete, count)
  • CollectionListing (find, all)
  • Comment (find, all, create, update, spam, not_spam, approve, remove, restore)
  • Country (find, all, create, update, delete, count)
  • Country.Province (find, all, update, count)
  • CustomCollection (find, all, create, update, delete, count)
  • Customer (find, all, create, update, delete, count, search)
  • CustomerAddress (find, all, create, delete)
  • CustomerSavedSearch (find, all, create, update, delete, count)
  • CustomerSavedSearch.Customer (all)
  • DiscountCode
  • DraftOrder (find, all, create, update, delete, count, complete, send_invoice) send_invoice is an alias of DraftOrder.DraftOrderInvoice.create/3
  • DraftOrder.DraftOrderInvoice (create)
  • MarketingEvent.Engagement (create_multiple)
  • Event (find, all, count)
  • Order.Fullfillment (find, all, count, create, update, complete, open, cancel)
  • Order.Fullfillment.Event (find, all, delete)
  • FulfillmentService (find, all, create, update, delete)
  • Image (ProductImage) (find, all, create, update, delete, count)
  • InventoryLevel (all, delete)
  • LineItem
  • Location (find, all, count)
  • MarketingEvent (find, all, count, create, update, delete, create_multiple_engagements) create_multiple_engagements is an alias of MarketingEvent.Engagement.create_multiple/3
  • Metafield
  • OAuth.AccessScope (all)
  • Option
  • Order (find, all, create, update, delete, count)
  • Order.Event (all)
  • Order.Risk (create, find, all, update, delete)
  • Page (create, find, all, update, delete, count)
  • PaymentDetails
  • Policy (all)
  • PriceRule (find, all, create, update, delete)
  • PriceRule.DiscountCode (find, all, create, update, delete)
  • Product (find, all, create, update, delete, count)
  • Product.Event (all)
  • ProductListing (find, all, create, update, delete, count, product_ids)
  • RecurringApplicationCharge (find, all, create, activate, delete)
  • Redirect (find, all, create, update, delete, count)
  • Refund (create, find, all)
  • Report (create, find, all, update, delete)
  • ScriptTag (find, all, create, count, delete)
  • ShippingAddress
  • ShippingLine
  • Shop (current)
  • SmartCollection (find, all, create, count, update, delete)
  • TaxLine
  • Theme (find, all, create, update, delete)
  • Theme.Asset (find, all, delete)
  • Transaction (find, all, create, count)
  • UsageCharge (find, all, create)
  • Variant (find, all, create, update, delete, count)
  • Webhook (find, all, create, update, delete, count)

Contributors

| Nick Sweeting
Nick Sweeting
💻 👀 📖 🚇 | Marcelo Oliveira
Marcelo Oliveira
💻 | Fabian Zitter
Fabian Zitter
💻 👀 📖 | Zach Garwood
Zach Garwood
💻 | David Becerra
David Becerra
💻 | Bryan Bryce
Bryan Bryce
📖 | humancopy
humancopy
💻 | Cmeurer10
Cmeurer10
💻 | lewisf
lewisf
💻 | vladimir-e
vladimir-e
💻 | furqanaziz
furqanaziz
💻 | balexand
balexand
💻 | :---: | :---: | :---: | :---: | :---: | :---: | :---: |

This project follows the all-contributors specification.

Documentation is generated with ExDoc. They can be found at https://hexdocs.pm/shopify.

About

Easily access the Shopify API with Elixir.

Topics

Resources

Stars

103 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

216 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Shopify API

Build StatusHex.pm

This package allows Elixir developers to easily access the admin Shopify API.

Installation

The package can be installed by adding shopify to your list of dependencies in mix.exs:

defdepsdo[{:shopify,"~> 0.4"}]end

Getting Started

The Shopify API can be accessed in two ways - either with private apps via basic auth, or with oauth.

Private Apps

Once you have a valid API key and password, setup your config/config.exs.

config:shopify,[shop_name: "my-shop",api_key: System.get_env("SHOPIFY_API_KEY"),password: System.get_env("SHOPIFY_API_PASSWORD")]

We can now easily create a new API session.

Shopify.session

Alternatively, we can create a one-off session.

Shopify.session("my-shop-name","my-api-key","my-password")

OAuth Apps

Once you have a shopify app client ID and secret, setup your config/config.exs.

config:shopify,[client_id: System.get_env("SHOPIFY_CLIENT_ID"),client_secret: System.get_env("SHOPIFY_CLIENT_SECRET")]

To gain access to a shop via OAuth, first, generate a permission url based on your requirments.

params=%{scope: "read_orders,read_products",redirect_uri: "http://my-redirect_uri.com/"}permission_url="shop-name"|>Shopify.session()|>Shopify.OAuth.permission_url(params)

After a shop has authorized access, they will be redirected to your URI above. The redirect will include a payload that contains a 'code'. We can now generate an access token.

{:ok,%Shopify.Response{data: oauth}}="shop-name"|>Shopify.session()|>Shopify.OAuth.request_token(code)

We can now easily create a new OAuth API session.

Shopify.session("shop-name",oauth.access_token)

Making Requests

All API requests require a session struct to begin.

"shop-name"|>Shopify.session("access-token")|>Shopify.Product.find(1)# ORsession=Shopify.session("shop-name","access-token")Shopify.Product.find(session,1)

Here are some examples of the various types of requests that can be made.

# Create a session structsession=Shopify.session("shop-name","access-token")# Find a resource by ID{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id)# Find a resource and select fields{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id,%{fields: "id,images,title"})# All resources{:ok,%Shopify.Response{data: products}}=session|>Shopify.Product.all# All resources with query params{:ok,%Shopify.Response{data: products}}=session|>Shopify.Product.all(%{page: 1,limit: 5})# Find a resource and update it{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id)updated_product=%{product|title: "New Title"}{:ok,response}=session|>Shopify.Product.update(product.id,updated_product)# Update a resource without finding it{:ok,response}=session|>Shopify.Product.update(id,%{title: "New Title"})# Create a resource from the resource structnew_product=%Shopify.Product{title: "Fancy Shirt",body_html: "<strong>Good shirt!<\/strong>",vendor: "Fancy Vendor",product_type: "shirt",variants: [%{price: "10.00",sku: 123}]}{:ok,response}=session|>Shopify.Product.create(new_product)# Create a resource from a simple mapnew_product_args=%{title: "Fancy Shirt",body_html: "<strong>Good shirt!<\/strong>",vendor: "Fancy Vendor",product_type: "shirt",variants: [%{price: "10.00",sku: 123}]}{:ok,response}=session|>Shopify.Product.create(new_product_args)# Count resources{:ok,%Shopify.Response{data: count}}=session|>Shopify.Product.count# Count resources with query params{:ok,%Shopify.Response{data: count}}=session|>Shopify.Product.count(%{vendor: "Fancy Vendor"})# Search for resources{:ok,%Shopify.Response{data: customers}}=session|>Shopify.Customer.search(%{query: "country:United States"})# Delete a resource{:ok,_}=session|>Shopify.Product.delete(id)

API Versioning

Shopify supports API versioning. By default, if you dont specify an api version, your request defaults to the oldest supported stable version.

You can specify a default version through application config.

config:shopify,[api_version: "2019-04"]

You can also set a specific version per session.

Shopify.session("shop-name","access-token")|>Shopify.Session.put_api_version("2019-04")

Handling Responses

Responses are all returned in the form of a two-item tuple. Any response that has a status code below 300 returns {:ok, response}. Codes above 300 are returned as {:error, response}.

# Create a session structsession=Shopify.session("shop-name","access-token")# 'data' is returned as a %Shopify.Product struct{:ok,%Shopify.Response{code: 200,data: data}}=session|>Shopify.Product.find(id)# 'data' is returned as a list of %Shopify.Product structs{:ok,%Shopify.Response{code: 200,data: data}}=session|>Shopify.Product.all# 'message' is a text description of the error.{:error,%Shopify.Response{code: 404,data: message}}=session|>Shopify.Product.find(1)# Failed requests return %Shopify.Error struct{:error,%Shopify.Error{reason: :econnrefused,source: :httpoison}}=session|>Shopify.Product.find(1)

The %Shopify.Response{} struct contains two fields: code and data. Code is the HTTP status code that is returned from Shopify. A successful request will either set the data field with a single struct, or list of structs of the resource or resources requested.

Multipass

The Multipass is available to Shopify Plus plans. It allows your non-Shopify site to be the source of truth for authentication and login. After your site has successfully authenticated a user, redirect their browser to Shopify using the special Multipass URL: this will upsert the customer data in Shopify and log them in.

Unlike other API requests, this does not require a session: it relies on a shared secret to do decryption.

Your customer data must at a minimum provide an email address and a current datetime in 8601 format.

customer_data=%{email: "something@test.shopify.com",created_at: DateTime.to_iso8601(Timex.now())}# From your store's checkout settingsmultipass_secret=Application.get_env("MULTIPASS_SECRET")url=Shopify.Multipass.get_url("myteststore",customer_data,multipass_secret)# Redirect the browser immediately to the resulting URL:"https://myteststore.myshopify.com/account/login/multipass/moaqEVx1Yu9hsvYvVpj-LeRYDtOo6ikicfTZd8tR8-xBMRg8tFjGEfllEcjj2VdbsezmT0XuEdglyQzi_biQPkfLJnP1dkxhNtfzwtt6IMQzu3W0qCPzbrUMD_gLaytPVP-zZZuYiSBqEMNdvzFg3zf0TOQHwbizX2D7It02sFI7ZpTRhfX4m_crV0b-DmmF"

Testing

For testing a mock adapter can be configured to use fixture json files instead of doing real requests.

Lets say you have a test config file in your_project/config/test.exs and tests in your_project/test you could use this configuration:

# your_project/config/test.exsconfig:shopify,[shop_name: "test",api_key: "test-key",password: "test-paswword",client_secret: "test-secret",client_adapter: Shopify.Adapters.Mock,# Use included Mock adapterfixtures_path: Path.expand("../test/fixtures/shopify",__DIR__)# Use fixures in this directory]

When using oauth, make sure the token passed is test, otherwise authentication will fail.

Shopify.session("my-shop.myshopify.com","test")|>Product.all()

Test Adapter

This plugin provides a test adapter called Shopify.Adapters.Mock to use out of the box. It makes certain assumptions about your fixtures and is limited to the responses provided in corresponding fixture files, and for create actions it will put the resource id as 1.

If you would like to roll your own adapter, you can do so by implementing @behaviour Shopify.Adapters.Base.

defmoduleShopify.Adapters.Mockdo@moduledocfalse@behaviourShopify.Adapters.Basedefget(%Shopify.Request{}=request)dodata=%{resource: %{id: 123,attribute: "attribute"}}{:ok,%Shopify.Response{code: 200,data: data}}end# ...end

Fixtures

Fixture files must follow a certain structure, so the adapter is able to find them. If your resource is Shopify.Product.all() you need to provide a file at path_you_provided_in_config/products.json and must include a valid response json

{
"orders": [
{
"buyer_accepts_marketing": false,
"cancel_reason": null,
"cancelled_at": null,
...
}
]
}

Or for Shopify.Product.find(1)

# path_you_provided_in_config/products/1.json
{
"order": {
"id": 1,
"email": "bob.mctest@test.com",
...
}
}

Current Resources

  • Address
  • ApplicationCharge (find, all, create, activate)
  • ApplicationCredit (find, all, create)
  • Article (find, all, create, update, delete, count)
  • Article.Author (all)
  • Article.Tag (all)
  • Attribute
  • BillingAddress
  • Blog (find, all, create, update, delete, count)
  • CarrierService (find, all, create, update, delete)
  • Checkout (all, find, create, update, count, shipping_rates, complete, count)
  • ClientDetails
  • Collect (find, all, create, delete, count)
  • CollectionListing (find, all)
  • Comment (find, all, create, update, spam, not_spam, approve, remove, restore)
  • Country (find, all, create, update, delete, count)
  • Country.Province (find, all, update, count)
  • CustomCollection (find, all, create, update, delete, count)
  • Customer (find, all, create, update, delete, count, search)
  • CustomerAddress (find, all, create, delete)
  • CustomerSavedSearch (find, all, create, update, delete, count)
  • CustomerSavedSearch.Customer (all)
  • DiscountCode
  • DraftOrder (find, all, create, update, delete, count, complete, send_invoice) send_invoice is an alias of DraftOrder.DraftOrderInvoice.create/3
  • DraftOrder.DraftOrderInvoice (create)
  • MarketingEvent.Engagement (create_multiple)
  • Event (find, all, count)
  • Order.Fullfillment (find, all, count, create, update, complete, open, cancel)
  • Order.Fullfillment.Event (find, all, delete)
  • FulfillmentService (find, all, create, update, delete)
  • Image (ProductImage) (find, all, create, update, delete, count)
  • InventoryLevel (all, delete)
  • LineItem
  • Location (find, all, count)
  • MarketingEvent (find, all, count, create, update, delete, create_multiple_engagements) create_multiple_engagements is an alias of MarketingEvent.Engagement.create_multiple/3
  • Metafield
  • OAuth.AccessScope (all)
  • Option
  • Order (find, all, create, update, delete, count)
  • Order.Event (all)
  • Order.Risk (create, find, all, update, delete)
  • Page (create, find, all, update, delete, count)
  • PaymentDetails
  • Policy (all)
  • PriceRule (find, all, create, update, delete)
  • PriceRule.DiscountCode (find, all, create, update, delete)
  • Product (find, all, create, update, delete, count)
  • Product.Event (all)
  • ProductListing (find, all, create, update, delete, count, product_ids)
  • RecurringApplicationCharge (find, all, create, activate, delete)
  • Redirect (find, all, create, update, delete, count)
  • Refund (create, find, all)
  • Report (create, find, all, update, delete)
  • ScriptTag (find, all, create, count, delete)
  • ShippingAddress
  • ShippingLine
  • Shop (current)
  • SmartCollection (find, all, create, count, update, delete)
  • TaxLine
  • Theme (find, all, create, update, delete)
  • Theme.Asset (find, all, delete)
  • Transaction (find, all, create, count)
  • UsageCharge (find, all, create)
  • Variant (find, all, create, update, delete, count)
  • Webhook (find, all, create, update, delete, count)

Contributors

| Nick Sweeting
Nick Sweeting
💻 👀 📖 🚇 | Marcelo Oliveira
Marcelo Oliveira
💻 | Fabian Zitter
Fabian Zitter
💻 👀 📖 | Zach Garwood
Zach Garwood
💻 | David Becerra
David Becerra
💻 | Bryan Bryce
Bryan Bryce
📖 | humancopy
humancopy
💻 | Cmeurer10
Cmeurer10
💻 | lewisf
lewisf
💻 | vladimir-e
vladimir-e
💻 | furqanaziz
furqanaziz
💻 | balexand
balexand
💻 | :---: | :---: | :---: | :---: | :---: | :---: | :---: |

This project follows the all-contributors specification.

Documentation is generated with ExDoc. They can be found at https://hexdocs.pm/shopify.

About

Easily access the Shopify API with Elixir.

Topics

Resources

Stars

103 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

216 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Shopify API

Build StatusHex.pm

This package allows Elixir developers to easily access the admin Shopify API.

Installation

The package can be installed by adding shopify to your list of dependencies in mix.exs:

defdepsdo[{:shopify,"~> 0.4"}]end

Getting Started

The Shopify API can be accessed in two ways - either with private apps via basic auth, or with oauth.

Private Apps

Once you have a valid API key and password, setup your config/config.exs.

config:shopify,[shop_name: "my-shop",api_key: System.get_env("SHOPIFY_API_KEY"),password: System.get_env("SHOPIFY_API_PASSWORD")]

We can now easily create a new API session.

Shopify.session

Alternatively, we can create a one-off session.

Shopify.session("my-shop-name","my-api-key","my-password")

OAuth Apps

Once you have a shopify app client ID and secret, setup your config/config.exs.

config:shopify,[client_id: System.get_env("SHOPIFY_CLIENT_ID"),client_secret: System.get_env("SHOPIFY_CLIENT_SECRET")]

To gain access to a shop via OAuth, first, generate a permission url based on your requirments.

params=%{scope: "read_orders,read_products",redirect_uri: "http://my-redirect_uri.com/"}permission_url="shop-name"|>Shopify.session()|>Shopify.OAuth.permission_url(params)

After a shop has authorized access, they will be redirected to your URI above. The redirect will include a payload that contains a 'code'. We can now generate an access token.

{:ok,%Shopify.Response{data: oauth}}="shop-name"|>Shopify.session()|>Shopify.OAuth.request_token(code)

We can now easily create a new OAuth API session.

Shopify.session("shop-name",oauth.access_token)

Making Requests

All API requests require a session struct to begin.

"shop-name"|>Shopify.session("access-token")|>Shopify.Product.find(1)# ORsession=Shopify.session("shop-name","access-token")Shopify.Product.find(session,1)

Here are some examples of the various types of requests that can be made.

# Create a session structsession=Shopify.session("shop-name","access-token")# Find a resource by ID{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id)# Find a resource and select fields{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id,%{fields: "id,images,title"})# All resources{:ok,%Shopify.Response{data: products}}=session|>Shopify.Product.all# All resources with query params{:ok,%Shopify.Response{data: products}}=session|>Shopify.Product.all(%{page: 1,limit: 5})# Find a resource and update it{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id)updated_product=%{product|title: "New Title"}{:ok,response}=session|>Shopify.Product.update(product.id,updated_product)# Update a resource without finding it{:ok,response}=session|>Shopify.Product.update(id,%{title: "New Title"})# Create a resource from the resource structnew_product=%Shopify.Product{title: "Fancy Shirt",body_html: "<strong>Good shirt!<\/strong>",vendor: "Fancy Vendor",product_type: "shirt",variants: [%{price: "10.00",sku: 123}]}{:ok,response}=session|>Shopify.Product.create(new_product)# Create a resource from a simple mapnew_product_args=%{title: "Fancy Shirt",body_html: "<strong>Good shirt!<\/strong>",vendor: "Fancy Vendor",product_type: "shirt",variants: [%{price: "10.00",sku: 123}]}{:ok,response}=session|>Shopify.Product.create(new_product_args)# Count resources{:ok,%Shopify.Response{data: count}}=session|>Shopify.Product.count# Count resources with query params{:ok,%Shopify.Response{data: count}}=session|>Shopify.Product.count(%{vendor: "Fancy Vendor"})# Search for resources{:ok,%Shopify.Response{data: customers}}=session|>Shopify.Customer.search(%{query: "country:United States"})# Delete a resource{:ok,_}=session|>Shopify.Product.delete(id)

API Versioning

Shopify supports API versioning. By default, if you dont specify an api version, your request defaults to the oldest supported stable version.

You can specify a default version through application config.

config:shopify,[api_version: "2019-04"]

You can also set a specific version per session.

Shopify.session("shop-name","access-token")|>Shopify.Session.put_api_version("2019-04")

Handling Responses

Responses are all returned in the form of a two-item tuple. Any response that has a status code below 300 returns {:ok, response}. Codes above 300 are returned as {:error, response}.

# Create a session structsession=Shopify.session("shop-name","access-token")# 'data' is returned as a %Shopify.Product struct{:ok,%Shopify.Response{code: 200,data: data}}=session|>Shopify.Product.find(id)# 'data' is returned as a list of %Shopify.Product structs{:ok,%Shopify.Response{code: 200,data: data}}=session|>Shopify.Product.all# 'message' is a text description of the error.{:error,%Shopify.Response{code: 404,data: message}}=session|>Shopify.Product.find(1)# Failed requests return %Shopify.Error struct{:error,%Shopify.Error{reason: :econnrefused,source: :httpoison}}=session|>Shopify.Product.find(1)

The %Shopify.Response{} struct contains two fields: code and data. Code is the HTTP status code that is returned from Shopify. A successful request will either set the data field with a single struct, or list of structs of the resource or resources requested.

Multipass

The Multipass is available to Shopify Plus plans. It allows your non-Shopify site to be the source of truth for authentication and login. After your site has successfully authenticated a user, redirect their browser to Shopify using the special Multipass URL: this will upsert the customer data in Shopify and log them in.

Unlike other API requests, this does not require a session: it relies on a shared secret to do decryption.

Your customer data must at a minimum provide an email address and a current datetime in 8601 format.

customer_data=%{email: "something@test.shopify.com",created_at: DateTime.to_iso8601(Timex.now())}# From your store's checkout settingsmultipass_secret=Application.get_env("MULTIPASS_SECRET")url=Shopify.Multipass.get_url("myteststore",customer_data,multipass_secret)# Redirect the browser immediately to the resulting URL:"https://myteststore.myshopify.com/account/login/multipass/moaqEVx1Yu9hsvYvVpj-LeRYDtOo6ikicfTZd8tR8-xBMRg8tFjGEfllEcjj2VdbsezmT0XuEdglyQzi_biQPkfLJnP1dkxhNtfzwtt6IMQzu3W0qCPzbrUMD_gLaytPVP-zZZuYiSBqEMNdvzFg3zf0TOQHwbizX2D7It02sFI7ZpTRhfX4m_crV0b-DmmF"

Testing

For testing a mock adapter can be configured to use fixture json files instead of doing real requests.

Lets say you have a test config file in your_project/config/test.exs and tests in your_project/test you could use this configuration:

# your_project/config/test.exsconfig:shopify,[shop_name: "test",api_key: "test-key",password: "test-paswword",client_secret: "test-secret",client_adapter: Shopify.Adapters.Mock,# Use included Mock adapterfixtures_path: Path.expand("../test/fixtures/shopify",__DIR__)# Use fixures in this directory]

When using oauth, make sure the token passed is test, otherwise authentication will fail.

Shopify.session("my-shop.myshopify.com","test")|>Product.all()

Test Adapter

This plugin provides a test adapter called Shopify.Adapters.Mock to use out of the box. It makes certain assumptions about your fixtures and is limited to the responses provided in corresponding fixture files, and for create actions it will put the resource id as 1.

If you would like to roll your own adapter, you can do so by implementing @behaviour Shopify.Adapters.Base.

defmoduleShopify.Adapters.Mockdo@moduledocfalse@behaviourShopify.Adapters.Basedefget(%Shopify.Request{}=request)dodata=%{resource: %{id: 123,attribute: "attribute"}}{:ok,%Shopify.Response{code: 200,data: data}}end# ...end

Fixtures

Fixture files must follow a certain structure, so the adapter is able to find them. If your resource is Shopify.Product.all() you need to provide a file at path_you_provided_in_config/products.json and must include a valid response json

{
"orders": [
{
"buyer_accepts_marketing": false,
"cancel_reason": null,
"cancelled_at": null,
...
}
]
}

Or for Shopify.Product.find(1)

# path_you_provided_in_config/products/1.json
{
"order": {
"id": 1,
"email": "bob.mctest@test.com",
...
}
}

Current Resources

  • Address
  • ApplicationCharge (find, all, create, activate)
  • ApplicationCredit (find, all, create)
  • Article (find, all, create, update, delete, count)
  • Article.Author (all)
  • Article.Tag (all)
  • Attribute
  • BillingAddress
  • Blog (find, all, create, update, delete, count)
  • CarrierService (find, all, create, update, delete)
  • Checkout (all, find, create, update, count, shipping_rates, complete, count)
  • ClientDetails
  • Collect (find, all, create, delete, count)
  • CollectionListing (find, all)
  • Comment (find, all, create, update, spam, not_spam, approve, remove, restore)
  • Country (find, all, create, update, delete, count)
  • Country.Province (find, all, update, count)
  • CustomCollection (find, all, create, update, delete, count)
  • Customer (find, all, create, update, delete, count, search)
  • CustomerAddress (find, all, create, delete)
  • CustomerSavedSearch (find, all, create, update, delete, count)
  • CustomerSavedSearch.Customer (all)
  • DiscountCode
  • DraftOrder (find, all, create, update, delete, count, complete, send_invoice) send_invoice is an alias of DraftOrder.DraftOrderInvoice.create/3
  • DraftOrder.DraftOrderInvoice (create)
  • MarketingEvent.Engagement (create_multiple)
  • Event (find, all, count)
  • Order.Fullfillment (find, all, count, create, update, complete, open, cancel)
  • Order.Fullfillment.Event (find, all, delete)
  • FulfillmentService (find, all, create, update, delete)
  • Image (ProductImage) (find, all, create, update, delete, count)
  • InventoryLevel (all, delete)
  • LineItem
  • Location (find, all, count)
  • MarketingEvent (find, all, count, create, update, delete, create_multiple_engagements) create_multiple_engagements is an alias of MarketingEvent.Engagement.create_multiple/3
  • Metafield
  • OAuth.AccessScope (all)
  • Option
  • Order (find, all, create, update, delete, count)
  • Order.Event (all)
  • Order.Risk (create, find, all, update, delete)
  • Page (create, find, all, update, delete, count)
  • PaymentDetails
  • Policy (all)
  • PriceRule (find, all, create, update, delete)
  • PriceRule.DiscountCode (find, all, create, update, delete)
  • Product (find, all, create, update, delete, count)
  • Product.Event (all)
  • ProductListing (find, all, create, update, delete, count, product_ids)
  • RecurringApplicationCharge (find, all, create, activate, delete)
  • Redirect (find, all, create, update, delete, count)
  • Refund (create, find, all)
  • Report (create, find, all, update, delete)
  • ScriptTag (find, all, create, count, delete)
  • ShippingAddress
  • ShippingLine
  • Shop (current)
  • SmartCollection (find, all, create, count, update, delete)
  • TaxLine
  • Theme (find, all, create, update, delete)
  • Theme.Asset (find, all, delete)
  • Transaction (find, all, create, count)
  • UsageCharge (find, all, create)
  • Variant (find, all, create, update, delete, count)
  • Webhook (find, all, create, update, delete, count)

Contributors

| Nick Sweeting
Nick Sweeting
💻 👀 📖 🚇 | Marcelo Oliveira
Marcelo Oliveira
💻 | Fabian Zitter
Fabian Zitter
💻 👀 📖 | Zach Garwood
Zach Garwood
💻 | David Becerra
David Becerra
💻 | Bryan Bryce
Bryan Bryce
📖 | humancopy
humancopy
💻 | Cmeurer10
Cmeurer10
💻 | lewisf
lewisf
💻 | vladimir-e
vladimir-e
💻 | furqanaziz
furqanaziz
💻 | balexand
balexand
💻 | :---: | :---: | :---: | :---: | :---: | :---: | :---: |

This project follows the all-contributors specification.

Documentation is generated with ExDoc. They can be found at https://hexdocs.pm/shopify.

About

Easily access the Shopify API with Elixir.

Topics

Resources

Stars

103 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

216 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Shopify API

Build StatusHex.pm

This package allows Elixir developers to easily access the admin Shopify API.

Installation

The package can be installed by adding shopify to your list of dependencies in mix.exs:

defdepsdo[{:shopify,"~> 0.4"}]end

Getting Started

The Shopify API can be accessed in two ways - either with private apps via basic auth, or with oauth.

Private Apps

Once you have a valid API key and password, setup your config/config.exs.

config:shopify,[shop_name: "my-shop",api_key: System.get_env("SHOPIFY_API_KEY"),password: System.get_env("SHOPIFY_API_PASSWORD")]

We can now easily create a new API session.

Shopify.session

Alternatively, we can create a one-off session.

Shopify.session("my-shop-name","my-api-key","my-password")

OAuth Apps

Once you have a shopify app client ID and secret, setup your config/config.exs.

config:shopify,[client_id: System.get_env("SHOPIFY_CLIENT_ID"),client_secret: System.get_env("SHOPIFY_CLIENT_SECRET")]

To gain access to a shop via OAuth, first, generate a permission url based on your requirments.

params=%{scope: "read_orders,read_products",redirect_uri: "http://my-redirect_uri.com/"}permission_url="shop-name"|>Shopify.session()|>Shopify.OAuth.permission_url(params)

After a shop has authorized access, they will be redirected to your URI above. The redirect will include a payload that contains a 'code'. We can now generate an access token.

{:ok,%Shopify.Response{data: oauth}}="shop-name"|>Shopify.session()|>Shopify.OAuth.request_token(code)

We can now easily create a new OAuth API session.

Shopify.session("shop-name",oauth.access_token)

Making Requests

All API requests require a session struct to begin.

"shop-name"|>Shopify.session("access-token")|>Shopify.Product.find(1)# ORsession=Shopify.session("shop-name","access-token")Shopify.Product.find(session,1)

Here are some examples of the various types of requests that can be made.

# Create a session structsession=Shopify.session("shop-name","access-token")# Find a resource by ID{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id)# Find a resource and select fields{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id,%{fields: "id,images,title"})# All resources{:ok,%Shopify.Response{data: products}}=session|>Shopify.Product.all# All resources with query params{:ok,%Shopify.Response{data: products}}=session|>Shopify.Product.all(%{page: 1,limit: 5})# Find a resource and update it{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id)updated_product=%{product|title: "New Title"}{:ok,response}=session|>Shopify.Product.update(product.id,updated_product)# Update a resource without finding it{:ok,response}=session|>Shopify.Product.update(id,%{title: "New Title"})# Create a resource from the resource structnew_product=%Shopify.Product{title: "Fancy Shirt",body_html: "<strong>Good shirt!<\/strong>",vendor: "Fancy Vendor",product_type: "shirt",variants: [%{price: "10.00",sku: 123}]}{:ok,response}=session|>Shopify.Product.create(new_product)# Create a resource from a simple mapnew_product_args=%{title: "Fancy Shirt",body_html: "<strong>Good shirt!<\/strong>",vendor: "Fancy Vendor",product_type: "shirt",variants: [%{price: "10.00",sku: 123}]}{:ok,response}=session|>Shopify.Product.create(new_product_args)# Count resources{:ok,%Shopify.Response{data: count}}=session|>Shopify.Product.count# Count resources with query params{:ok,%Shopify.Response{data: count}}=session|>Shopify.Product.count(%{vendor: "Fancy Vendor"})# Search for resources{:ok,%Shopify.Response{data: customers}}=session|>Shopify.Customer.search(%{query: "country:United States"})# Delete a resource{:ok,_}=session|>Shopify.Product.delete(id)

API Versioning

Shopify supports API versioning. By default, if you dont specify an api version, your request defaults to the oldest supported stable version.

You can specify a default version through application config.

config:shopify,[api_version: "2019-04"]

You can also set a specific version per session.

Shopify.session("shop-name","access-token")|>Shopify.Session.put_api_version("2019-04")

Handling Responses

Responses are all returned in the form of a two-item tuple. Any response that has a status code below 300 returns {:ok, response}. Codes above 300 are returned as {:error, response}.

# Create a session structsession=Shopify.session("shop-name","access-token")# 'data' is returned as a %Shopify.Product struct{:ok,%Shopify.Response{code: 200,data: data}}=session|>Shopify.Product.find(id)# 'data' is returned as a list of %Shopify.Product structs{:ok,%Shopify.Response{code: 200,data: data}}=session|>Shopify.Product.all# 'message' is a text description of the error.{:error,%Shopify.Response{code: 404,data: message}}=session|>Shopify.Product.find(1)# Failed requests return %Shopify.Error struct{:error,%Shopify.Error{reason: :econnrefused,source: :httpoison}}=session|>Shopify.Product.find(1)

The %Shopify.Response{} struct contains two fields: code and data. Code is the HTTP status code that is returned from Shopify. A successful request will either set the data field with a single struct, or list of structs of the resource or resources requested.

Multipass

The Multipass is available to Shopify Plus plans. It allows your non-Shopify site to be the source of truth for authentication and login. After your site has successfully authenticated a user, redirect their browser to Shopify using the special Multipass URL: this will upsert the customer data in Shopify and log them in.

Unlike other API requests, this does not require a session: it relies on a shared secret to do decryption.

Your customer data must at a minimum provide an email address and a current datetime in 8601 format.

customer_data=%{email: "something@test.shopify.com",created_at: DateTime.to_iso8601(Timex.now())}# From your store's checkout settingsmultipass_secret=Application.get_env("MULTIPASS_SECRET")url=Shopify.Multipass.get_url("myteststore",customer_data,multipass_secret)# Redirect the browser immediately to the resulting URL:"https://myteststore.myshopify.com/account/login/multipass/moaqEVx1Yu9hsvYvVpj-LeRYDtOo6ikicfTZd8tR8-xBMRg8tFjGEfllEcjj2VdbsezmT0XuEdglyQzi_biQPkfLJnP1dkxhNtfzwtt6IMQzu3W0qCPzbrUMD_gLaytPVP-zZZuYiSBqEMNdvzFg3zf0TOQHwbizX2D7It02sFI7ZpTRhfX4m_crV0b-DmmF"

Testing

For testing a mock adapter can be configured to use fixture json files instead of doing real requests.

Lets say you have a test config file in your_project/config/test.exs and tests in your_project/test you could use this configuration:

# your_project/config/test.exsconfig:shopify,[shop_name: "test",api_key: "test-key",password: "test-paswword",client_secret: "test-secret",client_adapter: Shopify.Adapters.Mock,# Use included Mock adapterfixtures_path: Path.expand("../test/fixtures/shopify",__DIR__)# Use fixures in this directory]

When using oauth, make sure the token passed is test, otherwise authentication will fail.

Shopify.session("my-shop.myshopify.com","test")|>Product.all()

Test Adapter

This plugin provides a test adapter called Shopify.Adapters.Mock to use out of the box. It makes certain assumptions about your fixtures and is limited to the responses provided in corresponding fixture files, and for create actions it will put the resource id as 1.

If you would like to roll your own adapter, you can do so by implementing @behaviour Shopify.Adapters.Base.

defmoduleShopify.Adapters.Mockdo@moduledocfalse@behaviourShopify.Adapters.Basedefget(%Shopify.Request{}=request)dodata=%{resource: %{id: 123,attribute: "attribute"}}{:ok,%Shopify.Response{code: 200,data: data}}end# ...end

Fixtures

Fixture files must follow a certain structure, so the adapter is able to find them. If your resource is Shopify.Product.all() you need to provide a file at path_you_provided_in_config/products.json and must include a valid response json

{
"orders": [
{
"buyer_accepts_marketing": false,
"cancel_reason": null,
"cancelled_at": null,
...
}
]
}

Or for Shopify.Product.find(1)

# path_you_provided_in_config/products/1.json
{
"order": {
"id": 1,
"email": "bob.mctest@test.com",
...
}
}

Current Resources

  • Address
  • ApplicationCharge (find, all, create, activate)
  • ApplicationCredit (find, all, create)
  • Article (find, all, create, update, delete, count)
  • Article.Author (all)
  • Article.Tag (all)
  • Attribute
  • BillingAddress
  • Blog (find, all, create, update, delete, count)
  • CarrierService (find, all, create, update, delete)
  • Checkout (all, find, create, update, count, shipping_rates, complete, count)
  • ClientDetails
  • Collect (find, all, create, delete, count)
  • CollectionListing (find, all)
  • Comment (find, all, create, update, spam, not_spam, approve, remove, restore)
  • Country (find, all, create, update, delete, count)
  • Country.Province (find, all, update, count)
  • CustomCollection (find, all, create, update, delete, count)
  • Customer (find, all, create, update, delete, count, search)
  • CustomerAddress (find, all, create, delete)
  • CustomerSavedSearch (find, all, create, update, delete, count)
  • CustomerSavedSearch.Customer (all)
  • DiscountCode
  • DraftOrder (find, all, create, update, delete, count, complete, send_invoice) send_invoice is an alias of DraftOrder.DraftOrderInvoice.create/3
  • DraftOrder.DraftOrderInvoice (create)
  • MarketingEvent.Engagement (create_multiple)
  • Event (find, all, count)
  • Order.Fullfillment (find, all, count, create, update, complete, open, cancel)
  • Order.Fullfillment.Event (find, all, delete)
  • FulfillmentService (find, all, create, update, delete)
  • Image (ProductImage) (find, all, create, update, delete, count)
  • InventoryLevel (all, delete)
  • LineItem
  • Location (find, all, count)
  • MarketingEvent (find, all, count, create, update, delete, create_multiple_engagements) create_multiple_engagements is an alias of MarketingEvent.Engagement.create_multiple/3
  • Metafield
  • OAuth.AccessScope (all)
  • Option
  • Order (find, all, create, update, delete, count)
  • Order.Event (all)
  • Order.Risk (create, find, all, update, delete)
  • Page (create, find, all, update, delete, count)
  • PaymentDetails
  • Policy (all)
  • PriceRule (find, all, create, update, delete)
  • PriceRule.DiscountCode (find, all, create, update, delete)
  • Product (find, all, create, update, delete, count)
  • Product.Event (all)
  • ProductListing (find, all, create, update, delete, count, product_ids)
  • RecurringApplicationCharge (find, all, create, activate, delete)
  • Redirect (find, all, create, update, delete, count)
  • Refund (create, find, all)
  • Report (create, find, all, update, delete)
  • ScriptTag (find, all, create, count, delete)
  • ShippingAddress
  • ShippingLine
  • Shop (current)
  • SmartCollection (find, all, create, count, update, delete)
  • TaxLine
  • Theme (find, all, create, update, delete)
  • Theme.Asset (find, all, delete)
  • Transaction (find, all, create, count)
  • UsageCharge (find, all, create)
  • Variant (find, all, create, update, delete, count)
  • Webhook (find, all, create, update, delete, count)

Contributors

| Nick Sweeting
Nick Sweeting
💻 👀 📖 🚇 | Marcelo Oliveira
Marcelo Oliveira
💻 | Fabian Zitter
Fabian Zitter
💻 👀 📖 | Zach Garwood
Zach Garwood
💻 | David Becerra
David Becerra
💻 | Bryan Bryce
Bryan Bryce
📖 | humancopy
humancopy
💻 | Cmeurer10
Cmeurer10
💻 | lewisf
lewisf
💻 | vladimir-e
vladimir-e
💻 | furqanaziz
furqanaziz
💻 | balexand
balexand
💻 | :---: | :---: | :---: | :---: | :---: | :---: | :---: |

This project follows the all-contributors specification.

Documentation is generated with ExDoc. They can be found at https://hexdocs.pm/shopify.

About

Easily access the Shopify API with Elixir.

Topics

Resources

Stars

103 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

216 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Shopify API

Build StatusHex.pm

This package allows Elixir developers to easily access the admin Shopify API.

Installation

The package can be installed by adding shopify to your list of dependencies in mix.exs:

defdepsdo[{:shopify,"~> 0.4"}]end

Getting Started

The Shopify API can be accessed in two ways - either with private apps via basic auth, or with oauth.

Private Apps

Once you have a valid API key and password, setup your config/config.exs.

config:shopify,[shop_name: "my-shop",api_key: System.get_env("SHOPIFY_API_KEY"),password: System.get_env("SHOPIFY_API_PASSWORD")]

We can now easily create a new API session.

Shopify.session

Alternatively, we can create a one-off session.

Shopify.session("my-shop-name","my-api-key","my-password")

OAuth Apps

Once you have a shopify app client ID and secret, setup your config/config.exs.

config:shopify,[client_id: System.get_env("SHOPIFY_CLIENT_ID"),client_secret: System.get_env("SHOPIFY_CLIENT_SECRET")]

To gain access to a shop via OAuth, first, generate a permission url based on your requirments.

params=%{scope: "read_orders,read_products",redirect_uri: "http://my-redirect_uri.com/"}permission_url="shop-name"|>Shopify.session()|>Shopify.OAuth.permission_url(params)

After a shop has authorized access, they will be redirected to your URI above. The redirect will include a payload that contains a 'code'. We can now generate an access token.

{:ok,%Shopify.Response{data: oauth}}="shop-name"|>Shopify.session()|>Shopify.OAuth.request_token(code)

We can now easily create a new OAuth API session.

Shopify.session("shop-name",oauth.access_token)

Making Requests

All API requests require a session struct to begin.

"shop-name"|>Shopify.session("access-token")|>Shopify.Product.find(1)# ORsession=Shopify.session("shop-name","access-token")Shopify.Product.find(session,1)

Here are some examples of the various types of requests that can be made.

# Create a session structsession=Shopify.session("shop-name","access-token")# Find a resource by ID{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id)# Find a resource and select fields{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id,%{fields: "id,images,title"})# All resources{:ok,%Shopify.Response{data: products}}=session|>Shopify.Product.all# All resources with query params{:ok,%Shopify.Response{data: products}}=session|>Shopify.Product.all(%{page: 1,limit: 5})# Find a resource and update it{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id)updated_product=%{product|title: "New Title"}{:ok,response}=session|>Shopify.Product.update(product.id,updated_product)# Update a resource without finding it{:ok,response}=session|>Shopify.Product.update(id,%{title: "New Title"})# Create a resource from the resource structnew_product=%Shopify.Product{title: "Fancy Shirt",body_html: "<strong>Good shirt!<\/strong>",vendor: "Fancy Vendor",product_type: "shirt",variants: [%{price: "10.00",sku: 123}]}{:ok,response}=session|>Shopify.Product.create(new_product)# Create a resource from a simple mapnew_product_args=%{title: "Fancy Shirt",body_html: "<strong>Good shirt!<\/strong>",vendor: "Fancy Vendor",product_type: "shirt",variants: [%{price: "10.00",sku: 123}]}{:ok,response}=session|>Shopify.Product.create(new_product_args)# Count resources{:ok,%Shopify.Response{data: count}}=session|>Shopify.Product.count# Count resources with query params{:ok,%Shopify.Response{data: count}}=session|>Shopify.Product.count(%{vendor: "Fancy Vendor"})# Search for resources{:ok,%Shopify.Response{data: customers}}=session|>Shopify.Customer.search(%{query: "country:United States"})# Delete a resource{:ok,_}=session|>Shopify.Product.delete(id)

API Versioning

Shopify supports API versioning. By default, if you dont specify an api version, your request defaults to the oldest supported stable version.

You can specify a default version through application config.

config:shopify,[api_version: "2019-04"]

You can also set a specific version per session.

Shopify.session("shop-name","access-token")|>Shopify.Session.put_api_version("2019-04")

Handling Responses

Responses are all returned in the form of a two-item tuple. Any response that has a status code below 300 returns {:ok, response}. Codes above 300 are returned as {:error, response}.

# Create a session structsession=Shopify.session("shop-name","access-token")# 'data' is returned as a %Shopify.Product struct{:ok,%Shopify.Response{code: 200,data: data}}=session|>Shopify.Product.find(id)# 'data' is returned as a list of %Shopify.Product structs{:ok,%Shopify.Response{code: 200,data: data}}=session|>Shopify.Product.all# 'message' is a text description of the error.{:error,%Shopify.Response{code: 404,data: message}}=session|>Shopify.Product.find(1)# Failed requests return %Shopify.Error struct{:error,%Shopify.Error{reason: :econnrefused,source: :httpoison}}=session|>Shopify.Product.find(1)

The %Shopify.Response{} struct contains two fields: code and data. Code is the HTTP status code that is returned from Shopify. A successful request will either set the data field with a single struct, or list of structs of the resource or resources requested.

Multipass

The Multipass is available to Shopify Plus plans. It allows your non-Shopify site to be the source of truth for authentication and login. After your site has successfully authenticated a user, redirect their browser to Shopify using the special Multipass URL: this will upsert the customer data in Shopify and log them in.

Unlike other API requests, this does not require a session: it relies on a shared secret to do decryption.

Your customer data must at a minimum provide an email address and a current datetime in 8601 format.

customer_data=%{email: "something@test.shopify.com",created_at: DateTime.to_iso8601(Timex.now())}# From your store's checkout settingsmultipass_secret=Application.get_env("MULTIPASS_SECRET")url=Shopify.Multipass.get_url("myteststore",customer_data,multipass_secret)# Redirect the browser immediately to the resulting URL:"https://myteststore.myshopify.com/account/login/multipass/moaqEVx1Yu9hsvYvVpj-LeRYDtOo6ikicfTZd8tR8-xBMRg8tFjGEfllEcjj2VdbsezmT0XuEdglyQzi_biQPkfLJnP1dkxhNtfzwtt6IMQzu3W0qCPzbrUMD_gLaytPVP-zZZuYiSBqEMNdvzFg3zf0TOQHwbizX2D7It02sFI7ZpTRhfX4m_crV0b-DmmF"

Testing

For testing a mock adapter can be configured to use fixture json files instead of doing real requests.

Lets say you have a test config file in your_project/config/test.exs and tests in your_project/test you could use this configuration:

# your_project/config/test.exsconfig:shopify,[shop_name: "test",api_key: "test-key",password: "test-paswword",client_secret: "test-secret",client_adapter: Shopify.Adapters.Mock,# Use included Mock adapterfixtures_path: Path.expand("../test/fixtures/shopify",__DIR__)# Use fixures in this directory]

When using oauth, make sure the token passed is test, otherwise authentication will fail.

Shopify.session("my-shop.myshopify.com","test")|>Product.all()

Test Adapter

This plugin provides a test adapter called Shopify.Adapters.Mock to use out of the box. It makes certain assumptions about your fixtures and is limited to the responses provided in corresponding fixture files, and for create actions it will put the resource id as 1.

If you would like to roll your own adapter, you can do so by implementing @behaviour Shopify.Adapters.Base.

defmoduleShopify.Adapters.Mockdo@moduledocfalse@behaviourShopify.Adapters.Basedefget(%Shopify.Request{}=request)dodata=%{resource: %{id: 123,attribute: "attribute"}}{:ok,%Shopify.Response{code: 200,data: data}}end# ...end

Fixtures

Fixture files must follow a certain structure, so the adapter is able to find them. If your resource is Shopify.Product.all() you need to provide a file at path_you_provided_in_config/products.json and must include a valid response json

{
"orders": [
{
"buyer_accepts_marketing": false,
"cancel_reason": null,
"cancelled_at": null,
...
}
]
}

Or for Shopify.Product.find(1)

# path_you_provided_in_config/products/1.json
{
"order": {
"id": 1,
"email": "bob.mctest@test.com",
...
}
}

Current Resources

  • Address
  • ApplicationCharge (find, all, create, activate)
  • ApplicationCredit (find, all, create)
  • Article (find, all, create, update, delete, count)
  • Article.Author (all)
  • Article.Tag (all)
  • Attribute
  • BillingAddress
  • Blog (find, all, create, update, delete, count)
  • CarrierService (find, all, create, update, delete)
  • Checkout (all, find, create, update, count, shipping_rates, complete, count)
  • ClientDetails
  • Collect (find, all, create, delete, count)
  • CollectionListing (find, all)
  • Comment (find, all, create, update, spam, not_spam, approve, remove, restore)
  • Country (find, all, create, update, delete, count)
  • Country.Province (find, all, update, count)
  • CustomCollection (find, all, create, update, delete, count)
  • Customer (find, all, create, update, delete, count, search)
  • CustomerAddress (find, all, create, delete)
  • CustomerSavedSearch (find, all, create, update, delete, count)
  • CustomerSavedSearch.Customer (all)
  • DiscountCode
  • DraftOrder (find, all, create, update, delete, count, complete, send_invoice) send_invoice is an alias of DraftOrder.DraftOrderInvoice.create/3
  • DraftOrder.DraftOrderInvoice (create)
  • MarketingEvent.Engagement (create_multiple)
  • Event (find, all, count)
  • Order.Fullfillment (find, all, count, create, update, complete, open, cancel)
  • Order.Fullfillment.Event (find, all, delete)
  • FulfillmentService (find, all, create, update, delete)
  • Image (ProductImage) (find, all, create, update, delete, count)
  • InventoryLevel (all, delete)
  • LineItem
  • Location (find, all, count)
  • MarketingEvent (find, all, count, create, update, delete, create_multiple_engagements) create_multiple_engagements is an alias of MarketingEvent.Engagement.create_multiple/3
  • Metafield
  • OAuth.AccessScope (all)
  • Option
  • Order (find, all, create, update, delete, count)
  • Order.Event (all)
  • Order.Risk (create, find, all, update, delete)
  • Page (create, find, all, update, delete, count)
  • PaymentDetails
  • Policy (all)
  • PriceRule (find, all, create, update, delete)
  • PriceRule.DiscountCode (find, all, create, update, delete)
  • Product (find, all, create, update, delete, count)
  • Product.Event (all)
  • ProductListing (find, all, create, update, delete, count, product_ids)
  • RecurringApplicationCharge (find, all, create, activate, delete)
  • Redirect (find, all, create, update, delete, count)
  • Refund (create, find, all)
  • Report (create, find, all, update, delete)
  • ScriptTag (find, all, create, count, delete)
  • ShippingAddress
  • ShippingLine
  • Shop (current)
  • SmartCollection (find, all, create, count, update, delete)
  • TaxLine
  • Theme (find, all, create, update, delete)
  • Theme.Asset (find, all, delete)
  • Transaction (find, all, create, count)
  • UsageCharge (find, all, create)
  • Variant (find, all, create, update, delete, count)
  • Webhook (find, all, create, update, delete, count)

Contributors

| Nick Sweeting
Nick Sweeting
💻 👀 📖 🚇 | Marcelo Oliveira
Marcelo Oliveira
💻 | Fabian Zitter
Fabian Zitter
💻 👀 📖 | Zach Garwood
Zach Garwood
💻 | David Becerra
David Becerra
💻 | Bryan Bryce
Bryan Bryce
📖 | humancopy
humancopy
💻 | Cmeurer10
Cmeurer10
💻 | lewisf
lewisf
💻 | vladimir-e
vladimir-e
💻 | furqanaziz
furqanaziz
💻 | balexand
balexand
💻 | :---: | :---: | :---: | :---: | :---: | :---: | :---: |

This project follows the all-contributors specification.

Documentation is generated with ExDoc. They can be found at https://hexdocs.pm/shopify.

About

Easily access the Shopify API with Elixir.

Topics

Resources

Stars

103 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

216 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Shopify API

Build StatusHex.pm

This package allows Elixir developers to easily access the admin Shopify API.

Installation

The package can be installed by adding shopify to your list of dependencies in mix.exs:

defdepsdo[{:shopify,"~> 0.4"}]end

Getting Started

The Shopify API can be accessed in two ways - either with private apps via basic auth, or with oauth.

Private Apps

Once you have a valid API key and password, setup your config/config.exs.

config:shopify,[shop_name: "my-shop",api_key: System.get_env("SHOPIFY_API_KEY"),password: System.get_env("SHOPIFY_API_PASSWORD")]

We can now easily create a new API session.

Shopify.session

Alternatively, we can create a one-off session.

Shopify.session("my-shop-name","my-api-key","my-password")

OAuth Apps

Once you have a shopify app client ID and secret, setup your config/config.exs.

config:shopify,[client_id: System.get_env("SHOPIFY_CLIENT_ID"),client_secret: System.get_env("SHOPIFY_CLIENT_SECRET")]

To gain access to a shop via OAuth, first, generate a permission url based on your requirments.

params=%{scope: "read_orders,read_products",redirect_uri: "http://my-redirect_uri.com/"}permission_url="shop-name"|>Shopify.session()|>Shopify.OAuth.permission_url(params)

After a shop has authorized access, they will be redirected to your URI above. The redirect will include a payload that contains a 'code'. We can now generate an access token.

{:ok,%Shopify.Response{data: oauth}}="shop-name"|>Shopify.session()|>Shopify.OAuth.request_token(code)

We can now easily create a new OAuth API session.

Shopify.session("shop-name",oauth.access_token)

Making Requests

All API requests require a session struct to begin.

"shop-name"|>Shopify.session("access-token")|>Shopify.Product.find(1)# ORsession=Shopify.session("shop-name","access-token")Shopify.Product.find(session,1)

Here are some examples of the various types of requests that can be made.

# Create a session structsession=Shopify.session("shop-name","access-token")# Find a resource by ID{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id)# Find a resource and select fields{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id,%{fields: "id,images,title"})# All resources{:ok,%Shopify.Response{data: products}}=session|>Shopify.Product.all# All resources with query params{:ok,%Shopify.Response{data: products}}=session|>Shopify.Product.all(%{page: 1,limit: 5})# Find a resource and update it{:ok,%Shopify.Response{data: product}}=session|>Shopify.Product.find(id)updated_product=%{product|title: "New Title"}{:ok,response}=session|>Shopify.Product.update(product.id,updated_product)# Update a resource without finding it{:ok,response}=session|>Shopify.Product.update(id,%{title: "New Title"})# Create a resource from the resource structnew_product=%Shopify.Product{title: "Fancy Shirt",body_html: "<strong>Good shirt!<\/strong>",vendor: "Fancy Vendor",product_type: "shirt",variants: [%{price: "10.00",sku: 123}]}{:ok,response}=session|>Shopify.Product.create(new_product)# Create a resource from a simple mapnew_product_args=%{title: "Fancy Shirt",body_html: "<strong>Good shirt!<\/strong>",vendor: "Fancy Vendor",product_type: "shirt",variants: [%{price: "10.00",sku: 123}]}{:ok,response}=session|>Shopify.Product.create(new_product_args)# Count resources{:ok,%Shopify.Response{data: count}}=session|>Shopify.Product.count# Count resources with query params{:ok,%Shopify.Response{data: count}}=session|>Shopify.Product.count(%{vendor: "Fancy Vendor"})# Search for resources{:ok,%Shopify.Response{data: customers}}=session|>Shopify.Customer.search(%{query: "country:United States"})# Delete a resource{:ok,_}=session|>Shopify.Product.delete(id)

API Versioning

Shopify supports API versioning. By default, if you dont specify an api version, your request defaults to the oldest supported stable version.

You can specify a default version through application config.

config:shopify,[api_version: "2019-04"]

You can also set a specific version per session.

Shopify.session("shop-name","access-token")|>Shopify.Session.put_api_version("2019-04")

Handling Responses

Responses are all returned in the form of a two-item tuple. Any response that has a status code below 300 returns {:ok, response}. Codes above 300 are returned as {:error, response}.

# Create a session structsession=Shopify.session("shop-name","access-token")# 'data' is returned as a %Shopify.Product struct{:ok,%Shopify.Response{code: 200,data: data}}=session|>Shopify.Product.find(id)# 'data' is returned as a list of %Shopify.Product structs{:ok,%Shopify.Response{code: 200,data: data}}=session|>Shopify.Product.all# 'message' is a text description of the error.{:error,%Shopify.Response{code: 404,data: message}}=session|>Shopify.Product.find(1)# Failed requests return %Shopify.Error struct{:error,%Shopify.Error{reason: :econnrefused,source: :httpoison}}=session|>Shopify.Product.find(1)

The %Shopify.Response{} struct contains two fields: code and data. Code is the HTTP status code that is returned from Shopify. A successful request will either set the data field with a single struct, or list of structs of the resource or resources requested.

Multipass

The Multipass is available to Shopify Plus plans. It allows your non-Shopify site to be the source of truth for authentication and login. After your site has successfully authenticated a user, redirect their browser to Shopify using the special Multipass URL: this will upsert the customer data in Shopify and log them in.

Unlike other API requests, this does not require a session: it relies on a shared secret to do decryption.

Your customer data must at a minimum provide an email address and a current datetime in 8601 format.

customer_data=%{email: "something@test.shopify.com",created_at: DateTime.to_iso8601(Timex.now())}# From your store's checkout settingsmultipass_secret=Application.get_env("MULTIPASS_SECRET")url=Shopify.Multipass.get_url("myteststore",customer_data,multipass_secret)# Redirect the browser immediately to the resulting URL:"https://myteststore.myshopify.com/account/login/multipass/moaqEVx1Yu9hsvYvVpj-LeRYDtOo6ikicfTZd8tR8-xBMRg8tFjGEfllEcjj2VdbsezmT0XuEdglyQzi_biQPkfLJnP1dkxhNtfzwtt6IMQzu3W0qCPzbrUMD_gLaytPVP-zZZuYiSBqEMNdvzFg3zf0TOQHwbizX2D7It02sFI7ZpTRhfX4m_crV0b-DmmF"

Testing

For testing a mock adapter can be configured to use fixture json files instead of doing real requests.

Lets say you have a test config file in your_project/config/test.exs and tests in your_project/test you could use this configuration:

# your_project/config/test.exsconfig:shopify,[shop_name: "test",api_key: "test-key",password: "test-paswword",client_secret: "test-secret",client_adapter: Shopify.Adapters.Mock,# Use included Mock adapterfixtures_path: Path.expand("../test/fixtures/shopify",__DIR__)# Use fixures in this directory]

When using oauth, make sure the token passed is test, otherwise authentication will fail.

Shopify.session("my-shop.myshopify.com","test")|>Product.all()

Test Adapter

This plugin provides a test adapter called Shopify.Adapters.Mock to use out of the box. It makes certain assumptions about your fixtures and is limited to the responses provided in corresponding fixture files, and for create actions it will put the resource id as 1.

If you would like to roll your own adapter, you can do so by implementing @behaviour Shopify.Adapters.Base.

defmoduleShopify.Adapters.Mockdo@moduledocfalse@behaviourShopify.Adapters.Basedefget(%Shopify.Request{}=request)dodata=%{resource: %{id: 123,attribute: "attribute"}}{:ok,%Shopify.Response{code: 200,data: data}}end# ...end

Fixtures

Fixture files must follow a certain structure, so the adapter is able to find them. If your resource is Shopify.Product.all() you need to provide a file at path_you_provided_in_config/products.json and must include a valid response json

{
"orders": [
{
"buyer_accepts_marketing": false,
"cancel_reason": null,
"cancelled_at": null,
...
}
]
}

Or for Shopify.Product.find(1)

# path_you_provided_in_config/products/1.json
{
"order": {
"id": 1,
"email": "bob.mctest@test.com",
...
}
}

Current Resources

  • Address
  • ApplicationCharge (find, all, create, activate)
  • ApplicationCredit (find, all, create)
  • Article (find, all, create, update, delete, count)
  • Article.Author (all)
  • Article.Tag (all)
  • Attribute
  • BillingAddress
  • Blog (find, all, create, update, delete, count)
  • CarrierService (find, all, create, update, delete)
  • Checkout (all, find, create, update, count, shipping_rates, complete, count)
  • ClientDetails
  • Collect (find, all, create, delete, count)
  • CollectionListing (find, all)
  • Comment (find, all, create, update, spam, not_spam, approve, remove, restore)
  • Country (find, all, create, update, delete, count)
  • Country.Province (find, all, update, count)
  • CustomCollection (find, all, create, update, delete, count)
  • Customer (find, all, create, update, delete, count, search)
  • CustomerAddress (find, all, create, delete)
  • CustomerSavedSearch (find, all, create, update, delete, count)
  • CustomerSavedSearch.Customer (all)
  • DiscountCode
  • DraftOrder (find, all, create, update, delete, count, complete, send_invoice) send_invoice is an alias of DraftOrder.DraftOrderInvoice.create/3
  • DraftOrder.DraftOrderInvoice (create)
  • MarketingEvent.Engagement (create_multiple)
  • Event (find, all, count)
  • Order.Fullfillment (find, all, count, create, update, complete, open, cancel)
  • Order.Fullfillment.Event (find, all, delete)
  • FulfillmentService (find, all, create, update, delete)
  • Image (ProductImage) (find, all, create, update, delete, count)
  • InventoryLevel (all, delete)
  • LineItem
  • Location (find, all, count)
  • MarketingEvent (find, all, count, create, update, delete, create_multiple_engagements) create_multiple_engagements is an alias of MarketingEvent.Engagement.create_multiple/3
  • Metafield
  • OAuth.AccessScope (all)
  • Option
  • Order (find, all, create, update, delete, count)
  • Order.Event (all)
  • Order.Risk (create, find, all, update, delete)
  • Page (create, find, all, update, delete, count)
  • PaymentDetails
  • Policy (all)
  • PriceRule (find, all, create, update, delete)
  • PriceRule.DiscountCode (find, all, create, update, delete)
  • Product (find, all, create, update, delete, count)
  • Product.Event (all)
  • ProductListing (find, all, create, update, delete, count, product_ids)
  • RecurringApplicationCharge (find, all, create, activate, delete)
  • Redirect (find, all, create, update, delete, count)
  • Refund (create, find, all)
  • Report (create, find, all, update, delete)
  • ScriptTag (find, all, create, count, delete)
  • ShippingAddress
  • ShippingLine
  • Shop (current)
  • SmartCollection (find, all, create, count, update, delete)
  • TaxLine
  • Theme (find, all, create, update, delete)
  • Theme.Asset (find, all, delete)
  • Transaction (find, all, create, count)
  • UsageCharge (find, all, create)
  • Variant (find, all, create, update, delete, count)
  • Webhook (find, all, create, update, delete, count)

Contributors

| Nick Sweeting
Nick Sweeting
💻 👀 📖 🚇 | Marcelo Oliveira
Marcelo Oliveira
💻 | Fabian Zitter
Fabian Zitter
💻 👀 📖 | Zach Garwood
Zach Garwood
💻 | David Becerra
David Becerra
💻 | Bryan Bryce
Bryan Bryce
📖 | humancopy
humancopy
💻 | Cmeurer10
Cmeurer10
💻 | lewisf
lewisf
💻 | vladimir-e
vladimir-e
💻 | furqanaziz
furqanaziz
💻 | balexand
balexand
💻 | :---: | :---: | :---: | :---: | :---: | :---: | :---: |

This project follows the all-contributors specification.

Documentation is generated with ExDoc. They can be found at https://hexdocs.pm/shopify.

About

Easily access the Shopify API with Elixir.

Topics

Resources

Stars

103 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages