Shopify API
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:
def deps do
[{:shopify, "~> 0.2.1"}]
endGetting 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.sessionAlternatively, 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)
# OR
session = 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 struct
session = 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)
# Create a resource
new_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)
# 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)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 struct
session = 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.
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.exs
config :shopify, [
shop_name: "test",
api_key: "test-key",
password: "test-paswword",
client_secret: "test-secret",
client_adapter: Shopify.Adapters.Mock, # Use included Mock adapter
fixtures_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.
defmodule Shopify.Adapters.Mock do
@moduledoc false
@behaviour Shopify.Adapters.Base
def get(%Shopify.Request{} = request) do
data = %{resource: %{id: 123, attribute: "attribute"}}
{:ok, %Shopify.Response{code: 200, data: data}}
end
# ...
endFixtures
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, 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_invoiceis an alias ofDraftOrder.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_engagementsis an alias ofMarketingEvent.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
| <img src=”https://avatars.githubusercontent.com/nsweeting?s=100” width=”100” alt=”Nick Sweeting” /><br /><sub>Nick Sweeting</sub><br />💻 👀 📖 🚇 | <img src=”https://avatars.githubusercontent.com/overallduka?s=100” width=”100” alt=”Marcelo Oliveira” /><br /><sub>Marcelo Oliveira</sub><br />💻 | <img src=”https://avatars.githubusercontent.com/Ninigi?s=100” width=”100” alt=”Fabian Zitter” /><br /><sub>Fabian Zitter</sub><br />💻 👀 📖 | <img src=”https://avatars.githubusercontent.com/zachgarwood?s=100” width=”100” alt=”Zach Garwood” /><br /><sub>Zach Garwood</sub><br />💻 | <img src=”https://avatars.githubusercontent.com/DavidVII?s=100” width=”100” alt=”David Becerra” /><br /><sub>David Becerra</sub><br />💻 | <img src=”https://avatars.githubusercontent.com/BryanJBryce?s=100” width=”100” alt=”Bryan Bryce” /><br /><sub>Bryan Bryce</sub><br />📖 | <img src=”https://avatars.githubusercontent.com/humancopy?s=100” width=”100” alt=”humancopy” /><br /><sub>humancopy</sub><br />💻 |
|---|
This project follows the all-contributors specification.
Documentation is generated with ExDoc. They can be found at https://hexdocs.pm/shopify.