foxy_sync 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []