payoneer_api 0.1.0 → 1.0.0

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.
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