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 +40 -0
- data/lib/foxy_sync/api/base.rb +34 -0
- data/lib/foxy_sync/api/customer.rb +40 -0
- data/lib/foxy_sync/api/messenger.rb +85 -0
- data/lib/foxy_sync/api/response.rb +47 -0
- data/lib/foxy_sync/cart_validation.rb +23 -0
- data/lib/foxy_sync/datafeed.rb +32 -0
- data/lib/foxy_sync/sso.rb +46 -0
- metadata +100 -0
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}×tamp=#{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: []
|