|
@@ -2,13 +2,20 @@ require 'active_resource'
|
|
|
require 'digest/md5'
|
|
|
|
|
|
module ShopifyAPI
|
|
|
+
|
|
|
+ module Countable
|
|
|
+ def count(options = {})
|
|
|
+ Integer(get(:count, options))
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
|
+
|
|
|
|
|
|
|
|
|
|
|
@@ -17,7 +24,7 @@ module ShopifyAPI
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
|
+
|
|
|
|
|
|
|
|
|
|
|
@@ -33,7 +40,7 @@ module ShopifyAPI
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
|
+
|
|
|
|
|
|
|
|
|
|
|
@@ -94,12 +101,16 @@ module ShopifyAPI
|
|
|
params.each { |k,value| send("#{k}=", value) }
|
|
|
end
|
|
|
|
|
|
- def initialize(url, token = nil)
|
|
|
- raise ArgumentError.new("You must provide at least a URL to a Shopify store!") if url.blank?
|
|
|
- url.gsub!(/https?:\/\//, '')
|
|
|
- url = "#{url}.myshopify.com" unless url.include?('.')
|
|
|
-
|
|
|
+ def initialize(url, token = nil, params = nil)
|
|
|
self.url, self.token = url, token
|
|
|
+
|
|
|
+ if params && params[:signature]
|
|
|
+ unless self.class.validate_signature(params) && params[:timestamp].to_i > 24.hours.ago.utc.to_i
|
|
|
+ raise "Invalid Signature: Possible malicious login"
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ self.class.prepare_url(self.url)
|
|
|
end
|
|
|
|
|
|
def shop
|
|
@@ -112,7 +123,7 @@ module ShopifyAPI
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
|
+
|
|
|
def site
|
|
|
"#{protocol}://#{api_key}:#{computed_password}@#{url}/admin"
|
|
|
end
|
|
@@ -129,21 +140,35 @@ module ShopifyAPI
|
|
|
def computed_password
|
|
|
Digest::MD5.hexdigest(secret + token.to_s)
|
|
|
end
|
|
|
+
|
|
|
+ def self.prepare_url(url)
|
|
|
+ url.gsub!(/https?:\/\//, '')
|
|
|
+ url.concat(".myshopify.com") unless url.include?('.')
|
|
|
+ end
|
|
|
+
|
|
|
+ def self.validate_signature(params)
|
|
|
+ return false unless signature = params[:signature]
|
|
|
+
|
|
|
+ sorted_params = params.except(:signature, :action, :controller).collect{|k,v|"#{k}=#{v}"}.sort.join
|
|
|
+ Digest::MD5.hexdigest(secret + sorted_params) == signature
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ class Base < ActiveResource::Base
|
|
|
+ extend Countable
|
|
|
end
|
|
|
|
|
|
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- class Shop
|
|
|
+
|
|
|
+ class Shop < Base
|
|
|
def self.current
|
|
|
- ActiveResource::Base.find(:one, :from => "/admin/shop.xml")
|
|
|
+ find(:one, :from => "/admin/shop.xml")
|
|
|
end
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
- class CustomCollection < ActiveResource::Base
|
|
|
+ class CustomCollection < Base
|
|
|
def products
|
|
|
Product.find(:all, :params => {:collection_id => self.id})
|
|
|
end
|
|
@@ -158,34 +183,32 @@ module ShopifyAPI
|
|
|
end
|
|
|
end
|
|
|
|
|
|
- class SmartCollection < ActiveResource::Base
|
|
|
+ class SmartCollection < Base
|
|
|
def products
|
|
|
Product.find(:all, :params => {:collection_id => self.id})
|
|
|
end
|
|
|
end
|
|
|
|
|
|
|
|
|
- class Collect < ActiveResource::Base
|
|
|
+ class Collect < Base
|
|
|
end
|
|
|
|
|
|
- class ShippingAddress < ActiveResource::Base
|
|
|
+ class ShippingAddress < Base
|
|
|
end
|
|
|
|
|
|
- class BillingAddress < ActiveResource::Base
|
|
|
+ class BillingAddress < Base
|
|
|
end
|
|
|
|
|
|
- class LineItem < ActiveResource::Base
|
|
|
+ class LineItem < Base
|
|
|
end
|
|
|
|
|
|
- class ShippingLine < ActiveResource::Base
|
|
|
+ class ShippingLine < Base
|
|
|
end
|
|
|
|
|
|
- class NoteAttribute < ActiveResource::Base
|
|
|
+ class NoteAttribute < Base
|
|
|
end
|
|
|
|
|
|
- class Order < ActiveResource::Base
|
|
|
-
|
|
|
-
|
|
|
+ class Order < Base
|
|
|
def close; load_attributes_from_response(post(:close)); end
|
|
|
|
|
|
def open; load_attributes_from_response(post(:open)); end
|
|
@@ -199,7 +222,7 @@ module ShopifyAPI
|
|
|
end
|
|
|
end
|
|
|
|
|
|
- class Product < ActiveResource::Base
|
|
|
+ class Product < Base
|
|
|
|
|
|
|
|
|
|
|
@@ -234,11 +257,11 @@ module ShopifyAPI
|
|
|
end
|
|
|
end
|
|
|
|
|
|
- class Variant < ActiveResource::Base
|
|
|
+ class Variant < Base
|
|
|
self.prefix = "/admin/products/:product_id/"
|
|
|
end
|
|
|
|
|
|
- class Image < ActiveResource::Base
|
|
|
+ class Image < Base
|
|
|
self.prefix = "/admin/products/:product_id/"
|
|
|
|
|
|
|
|
@@ -248,46 +271,145 @@ module ShopifyAPI
|
|
|
end
|
|
|
|
|
|
def attach_image(data, filename = nil)
|
|
|
- attributes[:attachment] = Base64.encode64(data)
|
|
|
- attributes[:filename] = filename unless filename.nil?
|
|
|
+ attributes['attachment'] = Base64.encode64(data)
|
|
|
+ attributes['filename'] = filename unless filename.nil?
|
|
|
end
|
|
|
end
|
|
|
|
|
|
- class Transaction < ActiveResource::Base
|
|
|
+ class Transaction < Base
|
|
|
self.prefix = "/admin/orders/:order_id/"
|
|
|
end
|
|
|
|
|
|
- class Fulfillment < ActiveResource::Base
|
|
|
+ class Fulfillment < Base
|
|
|
self.prefix = "/admin/orders/:order_id/"
|
|
|
end
|
|
|
|
|
|
- class Country < ActiveResource::Base
|
|
|
+ class Country < Base
|
|
|
end
|
|
|
|
|
|
- class Page < ActiveResource::Base
|
|
|
+ class Page < Base
|
|
|
end
|
|
|
|
|
|
- class Blog < ActiveResource::Base
|
|
|
+ class Blog < Base
|
|
|
def articles
|
|
|
Article.find(:all, :params => { :blog_id => id })
|
|
|
end
|
|
|
end
|
|
|
|
|
|
- class Article < ActiveResource::Base
|
|
|
+ class Article < Base
|
|
|
self.prefix = "/admin/blogs/:blog_id/"
|
|
|
end
|
|
|
|
|
|
- class Comment < ActiveResource::Base
|
|
|
+ class Comment < Base
|
|
|
def remove; load_attributes_from_response(post(:remove)); end
|
|
|
def ham; load_attributes_from_response(post(:ham)); end
|
|
|
def spam; load_attributes_from_response(post(:spam)); end
|
|
|
def approve; load_attributes_from_response(post(:approve)); end
|
|
|
end
|
|
|
|
|
|
- class Province < ActiveResource::Base
|
|
|
+ class Province < Base
|
|
|
self.prefix = "/admin/countries/:country_id/"
|
|
|
end
|
|
|
|
|
|
- class Redirect < ActiveResource::Base
|
|
|
+ class Redirect < Base
|
|
|
+ end
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ class Asset < Base
|
|
|
+ self.primary_key = 'key'
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def self.find(*args)
|
|
|
+ if args[0].is_a?(Symbol)
|
|
|
+ super
|
|
|
+ else
|
|
|
+ find(:one, :from => "/admin/assets.xml", :params => {:asset => {:key => args[0]}})
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def value
|
|
|
+ attributes['value'] ||
|
|
|
+ (attributes['attachment'] ? Base64.decode64(attributes['attachment']) : nil)
|
|
|
+ end
|
|
|
+
|
|
|
+ def attach(data)
|
|
|
+ self.attachment = Base64.encode64(data)
|
|
|
+ end
|
|
|
+
|
|
|
+ def destroy
|
|
|
+ connection.delete(element_path(:asset => {:key => key}), self.class.headers)
|
|
|
+ end
|
|
|
+
|
|
|
+ def new?
|
|
|
+ false
|
|
|
+ end
|
|
|
+
|
|
|
+ def self.element_path(id, prefix_options = {}, query_options = nil)
|
|
|
+ prefix_options, query_options = split_options(prefix_options) if query_options.nil?
|
|
|
+ "#{prefix(prefix_options)}#{collection_name}.#{format.extension}#{query_string(query_options)}"
|
|
|
+ end
|
|
|
+
|
|
|
+ def method_missing(method_symbol, *arguments)
|
|
|
+ if %w{value= attachment= src= source_key=}.include?(method_symbol)
|
|
|
+ wipe_value_attributes
|
|
|
+ end
|
|
|
+ super
|
|
|
+ end
|
|
|
+
|
|
|
+ private
|
|
|
+
|
|
|
+ def wipe_value_attributes
|
|
|
+ %w{value attachment src source_key}.each do |attr|
|
|
|
+ attributes.delete(attr)
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ class RecurringApplicationCharge < Base
|
|
|
+ def self.current
|
|
|
+ find(:all).find{|charge| charge.status == 'active'}
|
|
|
+ end
|
|
|
+
|
|
|
+ def cancel
|
|
|
+ load_attributes_from_response(self.destroy)
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ class ApplicationCharge < Base
|
|
|
end
|
|
|
end
|