payoneer_api 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/lib/payoneer_api.rb +12 -0
  3. data/lib/payoneer_api/api.rb +9 -0
  4. data/lib/payoneer_api/api/payees.rb +99 -0
  5. data/lib/payoneer_api/base.rb +17 -0
  6. data/lib/payoneer_api/client.rb +8 -97
  7. data/lib/payoneer_api/core_ext/hash.rb +4 -39
  8. data/lib/payoneer_api/payee.rb +34 -0
  9. data/lib/payoneer_api/payoneer_response.rb +5 -0
  10. data/lib/payoneer_api/payoneer_token.rb +18 -0
  11. data/lib/payoneer_api/request.rb +27 -0
  12. data/lib/payoneer_api/response.rb +37 -0
  13. data/lib/payoneer_api/utils.rb +33 -0
  14. data/lib/payoneer_api/version.rb +2 -2
  15. data/payoneer_api.gemspec +1 -0
  16. data/spec/fixtures/vcr_cassettes/PayoneerApi_API_Payees/_payee_details/associates_basic_contact_data_with_the_payee_object.yml +55 -0
  17. data/spec/fixtures/vcr_cassettes/PayoneerApi_API_Payees/_payee_details/returns_an_instance_of_a_Payee_object.yml +55 -0
  18. data/spec/fixtures/vcr_cassettes/{PayoneerApi_Client → PayoneerApi_API_Payees}/_payee_prefilled_signup_url/with_all_correct_params_passed_in/includes_a_token_parameter_in_this_url.yml +5 -5
  19. data/spec/fixtures/vcr_cassettes/{PayoneerApi_Client → PayoneerApi_API_Payees}/_payee_prefilled_signup_url/with_all_correct_params_passed_in/returns_a_url_to_the_prefilled_registration_page.yml +5 -5
  20. data/spec/fixtures/vcr_cassettes/{PayoneerApi_Client → PayoneerApi_API_Payees}/_payee_prefilled_signup_url/with_incorrect_login_credentials/returns_a_parsed_error_notification.yml +3 -3
  21. data/spec/fixtures/vcr_cassettes/PayoneerApi_API_Payees/_payee_signup_url/allows_you_to_us_the_to_s_method_on_that_object_to_obtain_the_registration_url.yml +49 -0
  22. data/spec/fixtures/vcr_cassettes/PayoneerApi_API_Payees/_payee_signup_url/allows_you_to_use_the_uri_method_to_get_the_URI_object_directly.yml +49 -0
  23. data/spec/fixtures/vcr_cassettes/{PayoneerApi_Client → PayoneerApi_API_Payees}/_payee_signup_url/returns_a_url_to_the_registration_page.yml +7 -7
  24. data/spec/fixtures/vcr_cassettes/PayoneerApi_API_Payees/_payee_signup_url/returns_payoneer_token.yml +49 -0
  25. data/spec/fixtures/vcr_cassettes/PayoneerApi_Client/_new_payee_prefilled_signup_url/returns_a_url_to_the_prefilled_registration_page.yml +5 -5
  26. data/spec/fixtures/vcr_cassettes/PayoneerApi_Client/_new_payee_signup_url/returns_a_url_to_the_registration_page_implicit_credentials_through_environment_variables_.yml +7 -7
  27. data/spec/payoneer_api/api/payees_spec.rb +83 -0
  28. data/spec/payoneer_api/client_spec.rb +6 -55
  29. data/spec/payoneer_api/payee_spec.rb +30 -0
  30. data/spec/spec_helper.rb +3 -0
  31. metadata +52 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 33e43fd7651b0a58cdd851610958734328104830
4
- data.tar.gz: d7ae4ae60dd310df87921b0f0c083ebdcd3443e4
3
+ metadata.gz: ef5defb10e6a13b020c469cff3a96da579859bc2
4
+ data.tar.gz: e0f6421510031fb627bb75582bf28e01115d4b0d
5
5
  SHA512:
6
- metadata.gz: 20b193951555db211b5ecd9896e060cc14c02ac175efaedb1fa4fe8e68aa250273b904a7096176de67c29c8fffdd7ab63a852006d2b2a7fedc0658300d9a9c6c
7
- data.tar.gz: 6cdef89c4d3669568b7d948a6d9a2ce7c002e407d8adc323e727bab81246cb2c33a07c445e7f90d293330bdd17eaf692b5e789b4032e77138c45c2241cb691d7
6
+ metadata.gz: 5a9f441a4b18ecba8b8470875f1b409d2d4b8bbbf3a4330fc88ebb2b10a7ce65f915dfeae9818720bc48386a53d985a8d65b7ff3891c2bff669598540e6f3d06
7
+ data.tar.gz: 79e919905afac856e721d32e23b470cae9597a5fa9a83686f022f59e3bfb9b73c034bb7a47bbbc3315e97005b76adf7248dfa40401b24ba33e451117f5674054
@@ -1,7 +1,19 @@
1
1
  require 'net/http'
2
2
  require 'net/https'
3
3
 
4
+ require 'active_support/core_ext/hash/slice'
5
+ require 'active_support/core_ext/hash/conversions'
6
+ require 'active_support/core_ext/string/inflections'
7
+ require 'active_support/concern'
4
8
  require 'payoneer_api/core_ext/hash'
5
9
 
10
+ require 'payoneer_api/base'
11
+ require 'payoneer_api/utils'
6
12
  require 'payoneer_api/payoneer_exception'
7
13
  require 'payoneer_api/client'
14
+ require 'payoneer_api/request'
15
+ require 'payoneer_api/response'
16
+ require 'payoneer_api/api'
17
+ require 'payoneer_api/payee'
18
+ require 'payoneer_api/payoneer_token'
19
+
@@ -0,0 +1,9 @@
1
+ require 'payoneer_api/api/payees'
2
+
3
+ module PayoneerApi
4
+ module API
5
+ extend ActiveSupport::Concern
6
+
7
+ include PayoneerApi::API::Payees
8
+ end
9
+ end
@@ -0,0 +1,99 @@
1
+ require 'payoneer_api/utils'
2
+ require 'payoneer_api/payee'
3
+
4
+ module PayoneerApi
5
+ module API
6
+ module Payees
7
+ extend ActiveSupport::Concern
8
+ include PayoneerApi::Utils
9
+
10
+ module ClassMethods
11
+ def payee_signup_url(member_name, options = {})
12
+ new(options).payee_signup_url(member_name)
13
+ end
14
+
15
+ def payee_prefilled_signup_url(member_name, options = {})
16
+ attributes = options.slice!(:partner_id, :username, :password)
17
+ new(options).payee_prefilled_signup_url(member_name, attributes)
18
+ end
19
+
20
+ def payee_details(payee_id, options = {})
21
+ new(options).payee_details(payee_id)
22
+ end
23
+ end
24
+
25
+ def payee_signup_url(payee_id, attributes = {})
26
+ perform_with_object :get,
27
+ payee_signup_args(attributes.merge(payee_id: payee_id)),
28
+ PayoneerApi::PayoneerToken
29
+ end
30
+
31
+ def payee_prefilled_signup_url(payee_id, attributes = {})
32
+ perform_with_object :post,
33
+ payee_prefilled_signup_args(attributes.merge(payee_id: payee_id)),
34
+ PayoneerApi::PayoneerToken
35
+ end
36
+
37
+ def payee_details(payee_id)
38
+ perform_with_object :get,
39
+ payee_request_args('GetPayeeDetails', payee_id),
40
+ PayoneerApi::Payee
41
+ end
42
+
43
+ protected
44
+
45
+ def payee_request_args(method_name, member_name)
46
+ request_args(method_name).merge(p4: member_name)
47
+ end
48
+
49
+ def payee_signup_args(args)
50
+ payee_request_args('GetToken', args[:payee_id]).merge(
51
+ p5: args[:session_id],
52
+ p6: args[:redirect_url],
53
+ p8: args[:redirect_time],
54
+ p9: bool_to_string(args[:test_card]),
55
+ p10: 'True',
56
+ p11: payout_methods_list(args[:payout_methods]),
57
+ p12: args[:registration_mode],
58
+ p13: bool_to_string(args[:hold_approval])
59
+ ).delete_blank
60
+ end
61
+
62
+ def payee_prefilled_signup_args(args)
63
+ builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
64
+ xml.PayoneerDetails do
65
+ xml.Details do
66
+ xml.userName @username
67
+ xml.password @password
68
+ xml.prid @partner_id
69
+ xml.apuid args[:payee_id]
70
+ xml.sessionid args[:session_id] if args[:session_id]
71
+ xml.redirect args[:redirect_url] if args[:redirect_url]
72
+ xml.redirectTime args[:redirect_time] if args[:redirect_time]
73
+ xml.cardType args[:card_type] if args[:card_type]
74
+ xml.BlockType args[:block_type] if args[:block_type]
75
+ xml.PayoutMethodList payout_methods_list(args[:payout_methods]) if args[:payout_methods]
76
+ xml.regMode args[:registration_mode] if args[:registration_mode]
77
+ xml.holdApproval bool_to_string(args[:hold_approval]) if args[:hold_approval]
78
+ end
79
+ xml.PersonalDetails do
80
+ xml.firstName args[:first_name] if args[:first_name]
81
+ xml.lastName args[:last_name] if args[:last_name]
82
+ xml.dateOfBirth args[:date_of_birth] if args[:date_of_birth]
83
+ xml.address1 args[:address].is_a?(Array) ? args[:address].first : args[:address]
84
+ xml.address2 args[:address].last if args[:address].is_a?(Array)
85
+ xml.city args[:city] if args[:city]
86
+ xml.country args[:country_code] if args[:country_code]
87
+ xml.state args[:state_code] if args[:state_code]
88
+ xml.zipCode args[:zip_code] if args[:zip_code]
89
+ xml.mobile args[:mobile_phone] if args[:mobile_phone]
90
+ xml.phone args[:phone] if args[:phone]
91
+ xml.email args[:email] if args[:email]
92
+ end
93
+ end
94
+ end
95
+ payee_request_args('GetTokenXML', args[:payee_id]).merge(xml: builder.to_xml)
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,17 @@
1
+ module PayoneerApi
2
+ class Base
3
+ attr_reader :attrs
4
+
5
+ # Initializes a new object
6
+ #
7
+ # @param attrs [Hash]
8
+ # @return [PayoneerApi::Base]
9
+ def initialize(attrs = {})
10
+ @attrs = attrs.deep_find(self.class.to_s.demodulize) if attrs.is_a?(Hash)
11
+ @attrs ||= attrs
12
+ @attrs.each do |k, v|
13
+ instance_variable_set("@#{k.to_s.underscore}", v) unless v.nil?
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,18 +1,15 @@
1
+ require 'payoneer_api/utils'
2
+ require 'payoneer_api/api'
3
+
1
4
  module PayoneerApi
2
5
  class Client
6
+ include PayoneerApi::Utils
7
+ include PayoneerApi::API
8
+
3
9
  SANDBOX_API_URL = 'https://api.sandbox.payoneer.com/Payouts/HttpApi/API.aspx?'
4
10
  PRODUCTION_API_URL = 'https://api.payoneer.com/payouts/HttpAPI/API.aspx?'
5
11
  API_PORT = '443'
6
12
 
7
- def self.new_payee_signup_url(member_name, options = {})
8
- new(options).payee_signup_url(member_name)
9
- end
10
-
11
- def self.new_payee_prefilled_signup_url(member_name, options = {})
12
- attributes = options.slice!(:partner_id, :username, :password)
13
- new(options).payee_prefilled_signup_url(member_name, attributes)
14
- end
15
-
16
13
  def initialize(options = {})
17
14
  @partner_id, @username, @password = options[:partner_id], options[:username], options[:password]
18
15
  @partner_id ||= ENV['PAYONEER_PARTNER_ID']
@@ -26,43 +23,8 @@ module PayoneerApi
26
23
  @environment ||= 'sandbox'
27
24
  end
28
25
 
29
- def payee_signup_url(member_name)
30
- response = get_api_call(payee_link_args(payee_id: member_name))
31
- api_result(response)
32
- end
33
-
34
- def payee_prefilled_signup_url(member_name, attributes = {})
35
- response = post_api_call(payee_prefill_args(attributes.merge(payee_id: member_name)))
36
- xml_api_result(response)
37
- end
38
-
39
26
  private
40
27
 
41
- def api_result(response)
42
- if response.code == '200'
43
- return response.body
44
- else
45
- raise PayoneerException, api_error_description(response)
46
- end
47
- end
48
-
49
- def xml_api_result(response)
50
- if response.code == '200'
51
- result = Nokogiri::XML.parse(response.body)
52
- token = result.css('PayoneerToken Token').first
53
- return token.text if token
54
- end
55
- raise PayoneerException, api_error_description(response)
56
- end
57
-
58
- def api_error_description(response)
59
- return unless response and response.body
60
- result = Nokogiri::XML.parse(response.body)
61
- error_message = result.css('PayoneerResponse Description').first
62
- return error_message if error_message
63
- result.to_s
64
- end
65
-
66
28
  def get_api_call(args_hash)
67
29
  uri = URI.parse(api_url)
68
30
  uri.query = URI.encode_www_form(args_hash)
@@ -83,66 +45,15 @@ module PayoneerApi
83
45
  http.request(request)
84
46
  end
85
47
 
86
- def payee_link_args(args)
48
+ def request_args(method_name)
87
49
  {
88
- mname: 'GetToken',
50
+ mname: method_name.to_s.camelize,
89
51
  p1: @username,
90
52
  p2: @password,
91
53
  p3: @partner_id,
92
- p4: args[:payee_id],
93
- p5: args[:session_id],
94
- }.delete_if { |_, value| value.nil? || value.empty? }
95
- end
96
-
97
- def payee_prefill_args(args)
98
- {
99
- mname: 'GetTokenXML',
100
- p1: @username,
101
- p2: @password,
102
- p3: @partner_id,
103
- p4: args[:payee_id],
104
- xml: prefill_xml_data(args)
105
54
  }
106
55
  end
107
56
 
108
- def prefill_xml_data(args)
109
- builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
110
- xml.PayoneerDetails do
111
- xml.Details do
112
- xml.userName @username
113
- xml.password @password
114
- xml.prid @partner_id
115
- xml.apuid args[:payee_id]
116
- xml.sessionid args[:session_id] if args[:session_id]
117
- xml.redirect args[:redirect_url] if args[:redirect_url]
118
- xml.redirectTime args[:redirect_time] if args[:redirect_time]
119
- xml.cardType args[:card_type] if args[:card_type]
120
- xml.BlockType args[:block_type] if args[:block_type]
121
- xml.PayoutMethodList (args[:payout_methods].is_a?(Array) ?
122
- args[:payout_methods].join(',') :
123
- args[:payout_methods]) if args[:payout_methods]
124
- xml.regMode args[:registration_mode] if args[:registration_mode]
125
- xml.holdApproval args[:hold_approval] if args[:hold_approval]
126
- end
127
- xml.PersonalDetails do
128
- xml.firstName args[:first_name] if args[:first_name]
129
- xml.lastName args[:last_name] if args[:last_name]
130
- xml.dateOfBirth args[:date_of_birth] if args[:date_of_birth]
131
- xml.address1 args[:address].is_a?(Array) ? args[:address].first : args[:address]
132
- xml.address2 args[:address].last if args[:address].is_a?(Array)
133
- xml.city args[:city] if args[:city]
134
- xml.country args[:country_code] if args[:country_code]
135
- xml.state args[:state_code] if args[:state_code]
136
- xml.zipCode args[:zip_code] if args[:zip_code]
137
- xml.mobile args[:mobile_phone] if args[:mobile_phone]
138
- xml.phone args[:phone] if args[:phone]
139
- xml.email args[:email] if args[:email]
140
- end
141
- end
142
- end
143
- builder.to_xml#.tap { |x| puts x.to_s if sandbox? }
144
- end
145
-
146
57
  def api_url
147
58
  sandbox? ? SANDBOX_API_URL : PRODUCTION_API_URL
148
59
  end
@@ -1,44 +1,9 @@
1
- # Taken from activesupport
2
-
3
1
  class Hash
4
- # Slice a hash to include only the given keys. This is useful for
5
- # limiting an options hash to valid keys before passing to a method:
6
- #
7
- # def search(criteria = {})
8
- # criteria.assert_valid_keys(:mass, :velocity, :time)
9
- # end
10
- #
11
- # search(options.slice(:mass, :velocity, :time))
12
- #
13
- # If you have an array of keys you want to limit to, you should splat them:
14
- #
15
- # valid_keys = [:mass, :velocity, :time]
16
- # search(options.slice(*valid_keys))
17
- def slice(*keys)
18
- keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
19
- keys.each_with_object(self.class.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
20
- end
21
-
22
- # Replaces the hash with only the given keys.
23
- # Returns a hash containing the removed key/value pairs.
24
- #
25
- # { a: 1, b: 2, c: 3, d: 4 }.slice!(:a, :b)
26
- # # => {:c=>3, :d=>4}
27
- def slice!(*keys)
28
- keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
29
- omit = slice(*self.keys - keys)
30
- hash = slice(*keys)
31
- hash.default = default
32
- hash.default_proc = default_proc if default_proc
33
- replace(hash)
34
- omit
2
+ def delete_blank
3
+ delete_if{|k, v| v.nil? or (v.respond_to?(:empty?) && v.empty?) or (v.instance_of?(Hash) && v.delete_blank.empty?)}
35
4
  end
36
5
 
37
- # Removes and returns the key/value pairs matching the given keys.
38
- #
39
- # { a: 1, b: 2, c: 3, d: 4 }.extract!(:a, :b) # => {:a=>1, :b=>2}
40
- # { a: 1, b: 2 }.extract!(:a, :x) # => {:a=>1}
41
- def extract!(*keys)
42
- keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) }
6
+ def deep_find(key)
7
+ key?(key) ? self[key] : self.values.inject(nil) {|memo, v| memo ||= v.deep_find(key) if v.respond_to?(:deep_find) }
43
8
  end
44
9
  end
@@ -0,0 +1,34 @@
1
+ module PayoneerApi
2
+ class Payee < PayoneerApi::Base
3
+ attr_reader :first_name, :last_name, :address1, :address2, :city, :state, :zip, :country, :email, :phone,
4
+ :mobile, :pay_out_method, :payee_status, :reg_date, :cards,
5
+ :card_id, :card_activation_status, :card_ship_date, :card_status
6
+
7
+ def initialize(attrs = {})
8
+ super
9
+ initialize_card_attrs if card?
10
+ end
11
+
12
+ def card?
13
+ pay_out_method == 'Prepaid Card'
14
+ end
15
+
16
+ def direct_deposit?
17
+ pay_out_method == 'Direct deposit'
18
+ end
19
+
20
+ def iach?
21
+ pay_out_method == 'iACH'
22
+ end
23
+
24
+ private
25
+
26
+ def initialize_card_attrs
27
+ card_data = @attrs.fetch('Cards', {}).fetch('Card', {})
28
+ @card_id = card_data['CardID']
29
+ @card_activation_status = card_data['ActivationStatus']
30
+ @card_ship_date = card_data['CardShipDate']
31
+ @card_status = card_data['CardStatus']
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ module PayoneerApi
2
+ class PayoneerResponse < PayoneerApi::Base
3
+ attr_reader :status, :description, :version, :result
4
+ end
5
+ end
@@ -0,0 +1,18 @@
1
+ module PayoneerApi
2
+ class PayoneerToken < PayoneerApi::Base
3
+ attr_reader :token
4
+
5
+ def initialize(attrs = {})
6
+ super
7
+ raise PayoneerException, attrs unless token
8
+ end
9
+
10
+ def uri
11
+ URI.parse(token)
12
+ end
13
+
14
+ def to_s
15
+ uri.to_s
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ module PayoneerApi
2
+ class Request
3
+ attr_accessor :client, :request_method, :options
4
+
5
+ # @param client [PayoneerApi::Client]
6
+ # @param request_method [String, Symbol]
7
+ # @param options [Hash]
8
+ # @return [PayoneerApi::Request]
9
+ def initialize(client, request_method, options = {})
10
+ @client = client
11
+ @request_method = request_method.to_sym
12
+ @options = options
13
+ end
14
+
15
+ # @return [Hash]
16
+ def perform
17
+ PayoneerApi::Response.new(@client.send("#{@request_method.to_s}_api_call".to_sym, @options)).body
18
+ end
19
+
20
+ # @param klass [Class]
21
+ # @param request [PayoneerApi::Request]
22
+ # @return [Object]
23
+ def perform_with_object(klass)
24
+ klass.new(perform)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ module PayoneerApi
2
+ class Response
3
+ def initialize(response)
4
+ @response = response
5
+ raise PayoneerException, api_error_description if @response.code != '200'
6
+ check_for_errors
7
+ end
8
+
9
+ def body
10
+ xml?(@response.body) ? Hash.from_xml(@response.body) : @response.body
11
+ end
12
+
13
+ def xml_body
14
+ @xml_body ||= Nokogiri::XML.parse(@response.body)
15
+ end
16
+
17
+ def xml?(text)
18
+ !Nokogiri::XML.parse(text).errors.any?
19
+ end
20
+
21
+ private
22
+
23
+ def api_error_description
24
+ return unless @response and @response.body
25
+ result = xml_body
26
+ error_message = result.css('PayoneerResponse Description').first
27
+ return error_message.text if error_message
28
+ result.to_s
29
+ end
30
+
31
+ def check_for_errors
32
+ if (result = xml_body.css('PayoneerResponse Result').first) && result.text == 'A00B556F'
33
+ raise PayoneerException, api_error_description
34
+ end
35
+ end
36
+ end
37
+ end