Skip to content

orbit-apps/elixir-shopifyapi

Repository files navigation

ShopifyAPI and Plug.ShopifyAPI

Installation

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

def deps do
  [
    {:shopify_api, github: "pixelunion/elixir-shopifyapi", tag: "v0.15.6"}
  ]
end

Add it to your phoenix routes.

scope "/shop" do
  forward("/", ShopifyAPI.Router)
end

To handle incoming webhooks from Shopify, add the ShopifyAPI.Plugs.Webhook to your Endpoint module (before the JSON parser configuration):

plug ShopifyAPI.Plugs.Webhook,
  app_name: "my-app-name",
  prefix: "/shopify/webhook",
  callback: {WebhookHandler, :handle_webhook, []}

If you want persisted Apps, Shops, and Tokens add configuration to your functions.

config :shopify_api, ShopifyAPI.AuthTokenServer,
  initializer: {MyApp.AuthToken, :init, []},
  persistence: {MyApp.AuthToken, :save, []}
config :shopify_api, ShopifyAPI.AppServer,
  initializer: {MyApp.ShopifyApp, :init, []},
  persistence: {MyApp.ShopifyApp, :save, []}
config :shopify_api, ShopifyAPI.ShopServer,
  initializer: {MyApp.Shop, :init, []},
  persistence: {MyApp.Shop, :save, []}

Installing this app in a Shop

There is a boilerplate repo for quickly getting up and running at ShopifyApp

  1. Start something like ngrok
  2. Configure your app to allow your ngrok url as one of the redirect_urls
  3. Point your browser to http://localhost:4000/shop/install?shop=your-shop.myshopify.com&app=yourapp and it should prompt you to login and authorize it.

Configuration

API Version

Shopify introduced API versioning here: https://help.shopify.com/en/api/versioning

Configure the version to use in your config.exs, it will default to a stable version as ref'd in the request module.

config :shopify_api, ShopifyAPI.REST, api_version: "2019-04"

Supervisor

The ShopifyAPI has three servers for caching commonly-used structs - AppServer, ShopServer, and AuthTokenServer. These act as a write-through caching layer for their corresponding data structure.

A supervisor ShopifyAPI.Supervisor is provided to start up and supervise all three servers. Add it to your application's supervision tree, and define hooks for preloading data.

NOTE: Make sure you place the supervisor after any dependencies used in preloading the data. (ie Ecto)

Add the following to your application:

def start(_type, _args) do
  children = [
    MyApp.Repo,
    ShopifyAPI.Supervisor
  ]

  Supervisor.start_link(children, strategy: :one_for_one)
end

Webhooks

To set up your app to receive webhooks, first you'll need to add ShopifyAPI.Plugs.Webhook to your Endpoint module:

plug ShopifyAPI.Plugs.Webhook,
  app_name: "my-app-name",
  prefix: "/shopify/webhook",
  callback: {WebhookHandler, :handle_webhook, []}

You'll also need to define a corresponding WebhookHandler module in your app:

defmodule WebhookHandler do
  def handle_webhook(app, shop, domain, payload) do
    # TODO implement me!
  end
end

And there you go!

Now webhooks sent to YOUR_URL/shopify/webhook will be interpreted as webhooks for the my-app-name app. If you append an app name to the URL in the Shopify configuration, that app will be used instead (e.g. /shopify/webhook/private-app-name).

If you'd like to install webhooks using ShopifyAPI, we need to do a small bit more work:

# Add this to your configuration so that ShopifyAPI knows the webhook prefix.
config :shopify_api, ShopifyAPI.Webhook, uri: "https://your-app-url/shop/webhook"

Now once a shop is installed, you can create webhook subscriptions. This will automatically append your app's name to the generated webhook URL:

token = ShopifyAPI.AuthTokenServer.get("shop domain", "app name")

topic = "orders/create"
server_address = ShopifyAPI.REST.Webhook.webhook_uri(token)
webhook = %{topic: topic, address: server_address}

ShopifyAPI.REST.Webhook.create(token, %{webhook: webhook})

Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/shopify_api.

GraphQL

GraphQL implementation handles GraphQL Queries against Shopify API using HTTPoison library as client, this initial implementation consists of hitting Shopify GraphQL and returning the response in a tuple {:ok, %Response{}} | {:error, %Response{}} containing the response and metadata(actualQueryCost, throttleStatus).

Configure the version to use in your config.exs, it will default to a stable version as ref'd in the graphql module.

config :shopify_api, ShopifyAPI.GraphQL, graphql_version: "2019-07"

GraphQL Response

Because GraphQL responses can be a little complex we are parsing/wraping responses %HTTPoison.response to %GraphQL.Response.

Successful response:

{:ok, %ShopifyAPI.GraphQL.Response{response: %{}, metadata: %{}, status_code: code}}

Failed response:

{:error, %HTTPoison.Response{}}

Telemetry

The shopify_api library will emit events using the :telemetry library. Consumers of shopify_api can then use these events for customized metrics aggregation and more. The following telemetry events are generated:

  • [:shopify_api, :rest_request, :success]
  • [:shopify_api, :rest_request, :failure]
  • [:shopify_api, :throttling, :over_limit]
  • [:shopify_api, :throttling, :within_limit]
  • [:shopify_api, :graphql_request, :success]
  • [:shopify_api, :graphql_request, :failure]
  • [:shopify_api, :bulk_operation, :success]
  • [:shopify_api, :bulk_operation, :failure]

As an example, you could use an external module to instrument API requests made by shopify_api:

defmodule Instrumenter do
  def setup do
    events = [
      [:shopify_api, :rest_request, :success],
      [:shopify_api, :rest_request, :failure]
    ]

    :telemetry.attach_many("my-instrumenter", events, &handle_event/4, nil)
  end

  def handle_event([:shopify_api, :rest_request, :success], measurements, metadata, _config) do
    # Ship success events
  end
end```