foxy_sync 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/foxy_sync.rb ADDED
@@ -0,0 +1,40 @@
1
+ module FoxySync
2
+ autoload :Sso, 'foxy_sync/sso'
3
+ autoload :Datafeed, 'foxy_sync/datafeed'
4
+ autoload :CartValidation, 'foxy_sync/cart_validation'
5
+
6
+ module Api
7
+ autoload :Messenger, 'foxy_sync/api/messenger'
8
+ autoload :Response, 'foxy_sync/api/response'
9
+ autoload :Base, 'foxy_sync/api/base'
10
+ autoload :Customer, 'foxy_sync/api/customer'
11
+ end
12
+
13
+
14
+ def self.setup
15
+ yield self
16
+ end
17
+
18
+
19
+ @@api_key = nil
20
+
21
+ def self.api_key=(key)
22
+ @@api_key = key
23
+ end
24
+
25
+ def self.api_key
26
+ @@api_key
27
+ end
28
+
29
+
30
+ @@store_url = nil
31
+
32
+ def self.store_url=(url)
33
+ @@store_url = url
34
+ end
35
+
36
+ def self.store_url
37
+ @@store_url
38
+ end
39
+
40
+ end
@@ -0,0 +1,34 @@
1
+ module FoxySync::Api
2
+ #
3
+ # Intended for subclassing, +Base+ forms the foundation
4
+ # for ::Api:: classes that wrap an ::Api::Response object
5
+ # to provide behavior around specific data in the +Response+
6
+ class Base
7
+ attr_accessor :api_response
8
+
9
+ #
10
+ # [_api_response_]
11
+ # A +FoxySync::Api::Response+ instance
12
+ def initialize(api_response)
13
+ self.api_response = api_response
14
+ end
15
+
16
+ #
17
+ # Delegates to #api_response
18
+ def respond_to?(method_name, include_private = false)
19
+ api_response.respond_to? method_name, include_private
20
+ end
21
+
22
+
23
+ private
24
+
25
+ def method_missing(method_name, *args, &block)
26
+ api_response.send method_name, *args
27
+ end
28
+
29
+
30
+ def fc_api
31
+ @_fc_api ||= FoxySync::Api::Messenger.new
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,40 @@
1
+ require 'openssl'
2
+
3
+ module FoxySync::Api
4
+ #
5
+ # Each method here corresponds to a call in the "Customer" section
6
+ # of the FoxyCart API (customer_get, customer_save, etc.).
7
+ class Customer < Base
8
+ attr_accessor :user
9
+
10
+ #
11
+ # Upon initialization requests and retains the
12
+ # response to FoxyCart's customer_get API command.
13
+ # The full response is available via #api_response.
14
+ # [_user_]
15
+ # An object that responds_to? the following:
16
+ # * #email
17
+ # * #password_salt
18
+ # * #encrypted_password
19
+ # These are commonly found on +User+ class instances
20
+ # that work with authentication systems like +Devise+
21
+ def initialize(user)
22
+ self.user = user
23
+ super fc_api.customer_get :customer_email => user.email
24
+ end
25
+
26
+
27
+ #
28
+ # Sends the customer_save command to the FoxyCart API.
29
+ # Returns a FoxySync::Api::Response
30
+ def save
31
+ salt = user.password_salt
32
+ hash = OpenSSL::HMAC.hexdigest 'sha256', salt, user.encrypted_password + salt
33
+ fc_api.customer_save(
34
+ :customer_email => user.email,
35
+ :customer_password_hash => hash,
36
+ :customer_password_salt => salt
37
+ )
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,85 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+
4
+ module FoxySync::Api
5
+ #
6
+ # Use this class to send and receive messages to/from
7
+ # the FoxyCart API (http://wiki.foxycart.com/v/1.0/api).
8
+ # To use it create a new instance and then send it a message
9
+ # that corresponds to the FoxyCart API. All messages
10
+ # return an instance of +FoxySync::Api::Response+. For Example:
11
+ #
12
+ # api = FoxySync::Api::Messenger.new
13
+ # reply = api.customer_get :customer_email => 'foo@bar.com'
14
+ #
15
+ # +Messenger+ instances respond to every API call in FoxyCart v1.0.
16
+ class Messenger
17
+ URL = URI.parse "#{FoxySync.store_url}/api"
18
+
19
+
20
+ def respond_to?(method_name, include_private = false)
21
+ %w(
22
+ store_template_cache
23
+ store_includes_get
24
+ attribute_save
25
+ attribute_list
26
+ attribute_delete
27
+ category_list
28
+ downloadable_list
29
+ customer_get
30
+ customer_save
31
+ customer_list
32
+ customer_address_get
33
+ customer_address_save
34
+ transaction_get
35
+ transaction_list
36
+ transaction_modify
37
+ transaction_datafeed
38
+ subscription_get
39
+ subscription_cancel
40
+ subscription_modify
41
+ subscription_list
42
+ subscription_datafeed
43
+ ).include? method_name.to_s
44
+ end
45
+
46
+
47
+ private
48
+
49
+ def method_missing(method_name, *args, &block)
50
+ return super unless respond_to? method_name
51
+ xml = api_request method_name, args.first || {}
52
+ FoxySync::Api::Response.new xml
53
+ end
54
+
55
+
56
+ def api_request(action, params = {})
57
+ params.merge!({
58
+ :api_action => action,
59
+ :api_token => FoxySync.api_key
60
+ })
61
+
62
+ attempts = 0
63
+
64
+ begin
65
+ http = Net::HTTP.new URL.host, URL.port
66
+ http.use_ssl = true
67
+ #http.set_debug_output $stdout
68
+
69
+ request = Net::HTTP::Post.new URL.request_uri
70
+ request.set_form_data params
71
+
72
+ response = http.request request
73
+ response.body
74
+ rescue => e
75
+ if attempts < 3
76
+ attempts += 1
77
+ sleep 1
78
+ retry
79
+ end
80
+
81
+ raise e
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,47 @@
1
+ require 'nokogiri'
2
+
3
+ module FoxySync::Api
4
+ #
5
+ # A +Response+ represents an XML reply
6
+ # sent by the FoxyCart API. To use it simply
7
+ # send an instance a message that corresponds
8
+ # to an element in the XML. For example,
9
+ #
10
+ # api = FoxySync::Api::Messenger.new
11
+ # reply = api.customer_get :customer_email => 'foo@bar.com'
12
+ # reply.customer_id # is the customer's FoxyCart id
13
+ #
14
+ # Every message will return +nil+ if no matching
15
+ # element is found in the XML reply, a +String+ if
16
+ # there is one matching element in the reply, or an
17
+ # +Array+ with all element values if there is more than one
18
+ # element in the XML reply.
19
+ class Response
20
+ #
21
+ # A +Nokogiri::XML::Document+ of the API reply
22
+ attr_reader :document
23
+
24
+
25
+ def initialize(xml)
26
+ @document = Nokogiri::XML(xml) {|config| config.strict.nonet }
27
+ end
28
+
29
+
30
+ def respond_to?(method_name, include_private = false)
31
+ true # respond to everything; we don't know what will be in the response doc
32
+ end
33
+
34
+
35
+ private
36
+
37
+ def method_missing(method_name, *args, &block)
38
+ node_set = document.xpath "//#{method_name}"
39
+ return nil if node_set.nil? || node_set.empty?
40
+
41
+ contents = node_set.to_a
42
+ contents.map!{|node| node.content }
43
+ contents.delete_if{|content| content.empty? }
44
+ contents.size == 1 ? contents.first : contents
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,23 @@
1
+ require 'openssl'
2
+
3
+ module FoxySync
4
+ #
5
+ # Holds methods to help with FoxyCart's HMAC link and form (cart)
6
+ # validation (http://wiki.foxycart.com/v/1.0/hmac_validation).
7
+ # Typically mixed into a view helper.
8
+ module CartValidation
9
+
10
+ #
11
+ # Creates the value expected on name elements of form inputs
12
+ # [_name_]
13
+ # The plain text value of the name element
14
+ # [_value_]
15
+ # The value of the input with name +name+
16
+ # [_code_]
17
+ # The code of the product that the input relates to
18
+ def cart_input_name(name, value, code)
19
+ "#{name.to_s}||#{OpenSSL::HMAC.hexdigest 'sha256', FoxySync.api_key, code.to_s + name.to_s + value.to_s}"
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,32 @@
1
+ require 'rc4'
2
+ require 'cgi'
3
+
4
+ module FoxySync
5
+ #
6
+ # Holds methods that help with FoxyCart's datafeed
7
+ # requests (http://wiki.foxycart.com/v/1.0/webhooks).
8
+ # This module is typically mixed into a controller.
9
+ module Datafeed
10
+
11
+ #
12
+ # Handles the decoding and decrypting of a datafeed request.
13
+ # Returns a +FoxySync::Api::Response+ whose document is the datafeed XML
14
+ #[_params_]
15
+ # Something that responds_to? [] and has a key 'FoxyData'. In Rails
16
+ # that would be the +params+ object
17
+ def datafeed_unwrap(params)
18
+ encrypted = params['FoxyData']
19
+ rc4 = RC4.new FoxySync.api_key
20
+ xml = rc4.decrypt CGI::unescape(encrypted)
21
+ FoxySync::Api::Response.new xml
22
+ end
23
+
24
+
25
+ #
26
+ # Wrapper for the text reply that a datafeed request expects
27
+ def datafeed_response
28
+ 'foxy'
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,46 @@
1
+ require 'digest/sha1'
2
+
3
+ module FoxySync
4
+ #
5
+ # Holds methods that help with FoxyCart's SSO service
6
+ # (http://wiki.foxycart.com/v/1.0/sso). This module is
7
+ # intended to be mixed into a controller for easy completion
8
+ # of the SSO handshake.
9
+ module Sso
10
+ #
11
+ # Returns the redirect URL that FoxyCart
12
+ # expects to be requested after it hits your
13
+ # application's SSO URL.
14
+ # [_params_]
15
+ # Something that responds to [] and holds the
16
+ # 'fcsid' and 'timestamp' parameters given by
17
+ # the FoxyCart SSO request. Usually your controller's
18
+ # +params+ object.
19
+ # [_user_]
20
+ # The currently logged in user, if any
21
+ def sso_url(params, user = nil)
22
+ fc_session_id = params['fcsid']
23
+ cid = 0
24
+
25
+ if user
26
+ customer = FoxySync::Api::Customer.new user
27
+ cid = customer.customer_id
28
+ cid = customer.save.customer_id if cid.nil?
29
+ end
30
+
31
+ timestamp = params['timestamp'].to_i
32
+ later_timestamp = timestamp + 60 * 60
33
+ token = auth_token cid, later_timestamp
34
+
35
+ "#{FoxySync.store_url}/checkout?fc_auth_token=#{token}&fcsid=#{fc_session_id}&fc_customer_id=#{cid}&timestamp=#{later_timestamp}"
36
+ end
37
+
38
+
39
+ private
40
+
41
+ def auth_token(customer_id, timestamp)
42
+ Digest::SHA1.hexdigest("#{customer_id}|#{timestamp}|#{FoxySync.api_key}")
43
+ end
44
+
45
+ end
46
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: foxy_sync
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Chris Stump
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.5.5
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.5.5
30
+ - !ruby/object:Gem::Dependency
31
+ name: ruby-rc4
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 0.1.5
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.1.5
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 2.11.0
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 2.11.0
62
+ description: Encapsulates FoxyCart SSO, datafeed, and cart validation protocols
63
+ email: chris@tablexi.com
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - ./lib/foxy_sync/api/base.rb
69
+ - ./lib/foxy_sync/api/customer.rb
70
+ - ./lib/foxy_sync/api/messenger.rb
71
+ - ./lib/foxy_sync/api/response.rb
72
+ - ./lib/foxy_sync/cart_validation.rb
73
+ - ./lib/foxy_sync/datafeed.rb
74
+ - ./lib/foxy_sync/sso.rb
75
+ - ./lib/foxy_sync.rb
76
+ homepage: https://github.com/tablexi/foxy_sync
77
+ licenses: []
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 1.8.25
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: Synchronizes your Ruby application with FoxyCart
100
+ test_files: []