Skip to content

Instantly share code, notes, and snippets.

@choonkeat
Created May 10, 2014 14:38
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save choonkeat/09a56da222f506e627c5 to your computer and use it in GitHub Desktop.
Save choonkeat/09a56da222f506e627c5 to your computer and use it in GitHub Desktop.
ActiveResource access to Shopify Discount API using email+password login

Usage

Provide SHOPIFY_SUBDOMAIN, SHOPIFY_EMAIL and SHOPIFY_PASSWORD environment variables, and discount.rb will configure itself.

Supported APIs are

  • Discount.find(:all)
  • Discount.find(:first)
  • Discount.create(...)
  • Discount#destroy

NOTE: Discount.find(:id) is not supported

Sample rails console session:

$ SHOPIFY_SUBDOMAIN=demo SHOPIFY_EMAIL=demo@example.com SHOPIFY_PASSWORD=topsecret rails console
Loading development environment (Rails 4.1.0)

2.1.1 :001 > Discount.find(:all)
 => #<ActiveResource::Collection:0x007f9f150da938 @elements=[], @resource_class=Discount, @original_params={}>

2.1.1 :002 > d = Discount.create(code: "ONETWOTHREE", discount_type: "percentage", value: 1)
 => #<Discount:0x007f9f150bbbc8 @attributes={"code"=>"ONETWOTHREE", "discount_type"=>"percentage", "value"=>"1.0", "id"=>9999999, "applies_once"=>false, "applies_to_id"=>nil, "ends_at"=>nil, "minimum_order_amount"=>"0.00", "starts_at"=>"2014-05-10T22:22:40+08:00", "status"=>"enabled", "usage_limit"=>nil, "applies_to_resource"=>nil, "times_used"=>0}, @prefix_options={}, @persisted=true, @remote_errors=nil, @validation_context=nil, @errors=#<ActiveResource::Errors:0x007f9f150bb268 @base=#<Discount:0x007f9f150bbbc8 ...>, @messages={}>> 

2.1.1 :003 > Discount.find(:first) == d
 => true 

2.1.1 :004 > d.destroy
 => #<Mechanize::File:0x007f9f11fe2b50 @uri=#<URI::HTTPS:0x007f9f15012f78 URL:https://demo.myshopify.com/admin/discounts/9999999.json>, @body="{}", @code="200", @full_path=false, @response={"server"=>"nginx", "date"=>"Sat, 10 May 2014 14:23:00 GMT", "content-type"=>"application/json; charset=utf-8", "transfer-encoding"=>"chunked", "vary"=>"Accept-Encoding", "status"=>"200 OK", "x-xss-protection"=>"1; mode=block", "x-content-type-options"=>"nosniff", "x-ua-compatible"=>"chrome=1", "x-shopid"=>"1234567", "x-shardid"=>"0", "x-shopify-asset-version"=>"ffff", "x-stats-userid"=>"7654321", "x-frame-options"=>"SAMEORIGIN", "cache-control"=>"no-cache, no-store", "set-cookie"=>"request_method=DELETE; path=/", "x-request-id"=>"ffff-ffff", "p3p"=>"CP=\"NOI DSP COR NID ADMa OPTa OUR NOR\"", "content-encoding"=>"gzip"}, @filename="9999999.json"> 

2.1.1 :005 > Discount.find(:all)
 => #<ActiveResource::Collection:0x007f9f11f50ae8 @elements=[], @resource_class=Discount, @original_params={}> 
class Discount < ActiveResource::Base
class MechanizedConnection < ::ActiveResource::Connection
def initialize(mechanize, csrf_token, site)
@mechanize = mechanize
@csrf_token = csrf_token
self.site = site
end
def http
@mechanize
end
def get(path, headers = {})
with_auth { request(:get, path, params = {}, referer = nil, build_request_headers(headers, :get, self.site.merge(path))) }
end
def delete(path, headers = {})
with_auth { request(:delete, path, params = {}, build_request_headers(headers, :delete, self.site.merge(path))) }
end
# the only headers we need for shopify
def build_request_headers(*args)
headers = {
'X-CSRF-Token' => @csrf_token,
'X-Requested-With' => 'XMLHttpRequest',
'Content-Type' => 'application/json',
}
end
end
self.include_root_in_json = true
self.site = "https://#{ENV['SHOPIFY_SUBDOMAIN']}.myshopify.com/admin"
def self.connection(refresh = false)
@connection = nil if refresh
@connection ||= begin
agent = Mechanize.new { |agent| agent.user_agent_alias = 'Mac Safari' }
page = agent.get("https://#{ENV['SHOPIFY_SUBDOMAIN']}.myshopify.com/admin/auth/login")
form = page.form_with(action: /login/)
logged_in = page.form_with(action: /login/) {|form| form.login = ENV['SHOPIFY_EMAIL']; form.password = ENV['SHOPIFY_PASSWORD'] }.submit
raise Exception.new("Login failed #{logged_in.inspect}") unless logged_in.link_with(href: /logout/)
csrf_token = Nokogiri::HTML(logged_in.body).css("meta[name='csrf-token']").first['content']
MechanizedConnection.new(agent, csrf_token, self.site)
end
end
end
@SebastianSzturo
Copy link

It seams like Shopify changed something. Any idea how to get the authentication working again?

@SebastianSzturo
Copy link

Fixed the login issue! Thats the working code as of today.

class Discount < ActiveResource::Base
    class MechanizedConnection < ::ActiveResource::Connection
        def initialize(mechanize, csrf_token, site)
            @mechanize = mechanize
            @csrf_token = csrf_token
            self.site = site
        end
        def http
            @mechanize
        end
        def get(path, headers = {})
            with_auth { request(:get, path, params = {}, referer = nil, build_request_headers(headers, :get, self.site.merge(path))) }
        end
        def delete(path, headers = {})
            with_auth { request(:delete, path, params = {}, build_request_headers(headers, :delete, self.site.merge(path))) }
        end
        # the only headers we need for shopify
        def build_request_headers(*args)
            headers = {
                'X-CSRF-Token' => @csrf_token,
                'X-Requested-With' => 'XMLHttpRequest',
                'Content-Type' => 'application/json',
            }
        end
    end

    self.include_root_in_json = true
    self.site = "https://#{ENV['SHOPIFY_SUBDOMAIN']}.myshopify.com/admin"
    def self.connection(refresh = false)
        @connection = nil if refresh
        @connection ||= begin
            agent = Mechanize.new 
            agent.user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/600.1.25 (KHTML, like Gecko) Version/8.0 Safari/600.1.25"
            agent.request_headers = { "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" } # Shopify Server checks that
            agent.redirect_ok = true

            page = agent.get("https://#{ENV['SHOPIFY_SUBDOMAIN']}.myshopify.com/admin/auth/login")
            form = page.form_with(action: /login/)

            begin
                login_page = page.form_with(action: /login/) {|form| form.login = ENV['SHOPIFY_EMAIL']; form.password = ENV['SHOPIFY_PASSWORD'] }.submit
            rescue Mechanize::ResponseCodeError => exception
                if exception.response_code == '403'
                    login_page = exception.page
                else
                    raise # Some other error, re-raise
                end
            end

            csrf_token = Nokogiri::HTML(login_page.body).css("meta[name='csrf-token']").first['content']
            MechanizedConnection.new(agent, csrf_token, self.site)
        end
    end

end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment