paypal-sdk-rest 0.10.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/README.md +44 -0
  4. data/lib/generators/paypal/sdk/USAGE +3 -0
  5. data/lib/generators/paypal/sdk/install_generator.rb +17 -0
  6. data/lib/generators/paypal/sdk/templates/paypal.rb +2 -0
  7. data/lib/generators/paypal/sdk/templates/paypal.yml +31 -0
  8. data/lib/paypal-sdk-core.rb +38 -0
  9. data/lib/paypal-sdk/core/api.rb +20 -0
  10. data/lib/paypal-sdk/core/api/base.rb +162 -0
  11. data/lib/paypal-sdk/core/api/data_types/array_with_block.rb +44 -0
  12. data/lib/paypal-sdk/core/api/data_types/base.rb +224 -0
  13. data/lib/paypal-sdk/core/api/data_types/enum.rb +26 -0
  14. data/lib/paypal-sdk/core/api/data_types/simple_types.rb +52 -0
  15. data/lib/paypal-sdk/core/api/ipn.rb +66 -0
  16. data/lib/paypal-sdk/core/api/rest.rb +163 -0
  17. data/lib/paypal-sdk/core/authentication.rb +66 -0
  18. data/lib/paypal-sdk/core/config.rb +249 -0
  19. data/lib/paypal-sdk/core/credential.rb +16 -0
  20. data/lib/paypal-sdk/core/credential/base.rb +27 -0
  21. data/lib/paypal-sdk/core/credential/certificate.rb +32 -0
  22. data/lib/paypal-sdk/core/credential/signature.rb +22 -0
  23. data/lib/paypal-sdk/core/credential/third_party/subject.rb +25 -0
  24. data/lib/paypal-sdk/core/credential/third_party/token.rb +39 -0
  25. data/lib/paypal-sdk/core/exceptions.rb +96 -0
  26. data/lib/paypal-sdk/core/logging.rb +45 -0
  27. data/lib/paypal-sdk/core/openid_connect.rb +122 -0
  28. data/lib/paypal-sdk/core/openid_connect/api.rb +49 -0
  29. data/lib/paypal-sdk/core/openid_connect/data_types.rb +73 -0
  30. data/lib/paypal-sdk/core/openid_connect/get_api.rb +28 -0
  31. data/lib/paypal-sdk/core/openid_connect/request_data_type.rb +52 -0
  32. data/lib/paypal-sdk/core/openid_connect/set_api.rb +36 -0
  33. data/lib/paypal-sdk/core/util.rb +11 -0
  34. data/lib/paypal-sdk/core/util/http_helper.rb +159 -0
  35. data/lib/paypal-sdk/core/util/oauth_signature.rb +64 -0
  36. data/lib/paypal-sdk/core/util/ordered_hash.rb +165 -0
  37. data/lib/paypal-sdk/rest/data_types.rb +1 -0
  38. data/lib/paypal-sdk/rest/version.rb +1 -1
  39. data/spec/config/paypal.yml +27 -0
  40. data/spec/config/sample_data.yml +3 -0
  41. data/spec/core/api/data_type_spec.rb +189 -0
  42. data/spec/core/api/rest_spec.rb +147 -0
  43. data/spec/core/config_spec.rb +192 -0
  44. data/spec/core/logging_spec.rb +28 -0
  45. data/spec/core/openid_connect_spec.rb +144 -0
  46. data/spec/log/http.log +71 -32
  47. data/spec/log/rest_http.log +133 -0
  48. data/spec/spec_helper.rb +7 -0
  49. data/spec/support/sample_data.rb +5 -0
  50. metadata +82 -5
@@ -0,0 +1,73 @@
1
+ require 'paypal-sdk-core'
2
+
3
+ module PayPal::SDK::Core
4
+ module OpenIDConnect
5
+ module DataTypes
6
+ class Base < PayPal::SDK::Core::API::DataTypes::Base
7
+ end
8
+
9
+ class Address < Base
10
+ def self.load_members
11
+ object_of :street_address, String
12
+ object_of :locality, String
13
+ object_of :region, String
14
+ object_of :postal_code, String
15
+ object_of :country, String
16
+ end
17
+ end
18
+
19
+ class Userinfo < Base
20
+ def self.load_members
21
+ object_of :user_id, String
22
+ object_of :sub, String
23
+ object_of :name, String
24
+ object_of :given_name, String
25
+ object_of :family_name, String
26
+ object_of :middle_name, String
27
+ object_of :picture, String
28
+ object_of :email, String
29
+ object_of :email_verified, Boolean
30
+ object_of :gender, String
31
+ object_of :birthday, String
32
+ object_of :zoneinfo, String
33
+ object_of :locale, String
34
+ object_of :language, String
35
+ object_of :verified, Boolean
36
+ object_of :phone_number, String
37
+ object_of :address, Address
38
+ object_of :verified_account, Boolean
39
+ object_of :account_type, String
40
+ object_of :account_creation_date, String
41
+ object_of :age_range, String
42
+ object_of :payer_id, String
43
+ end
44
+ end
45
+
46
+ class Tokeninfo < Base
47
+ def self.load_members
48
+ object_of :scope, String
49
+ object_of :access_token, String
50
+ object_of :refresh_token, String
51
+ object_of :token_type, String
52
+ object_of :id_token, String
53
+ object_of :expires_in, Integer
54
+ end
55
+ end
56
+
57
+ class Error < Base
58
+ def self.load_members
59
+ object_of :error, String
60
+ object_of :error_description, String
61
+ object_of :error_uri, String
62
+ end
63
+ end
64
+
65
+
66
+ constants.each do |data_type_klass|
67
+ data_type_klass = const_get(data_type_klass)
68
+ data_type_klass.load_members if defined? data_type_klass.load_members
69
+ end
70
+
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,28 @@
1
+ module PayPal::SDK
2
+ module Core
3
+ module OpenIDConnect
4
+ module GetAPI
5
+ # Get API object
6
+ # === Example
7
+ # Payment.api
8
+ # payment.api
9
+ def api
10
+ @api || parent_api
11
+ end
12
+
13
+ # Parent API object
14
+ def parent_api
15
+ superclass.respond_to?(:api) ? superclass.api : RequestDataType.api
16
+ end
17
+
18
+ def client_id
19
+ api.config.openid_client_id || api.config.client_id
20
+ end
21
+
22
+ def client_secret
23
+ api.config.openid_client_secret || api.config.client_secret
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,52 @@
1
+ module PayPal::SDK
2
+ module Core
3
+ module OpenIDConnect
4
+ module RequestDataType
5
+
6
+ # Get a local API object or Class level API object
7
+ def api
8
+ @api || self.class.api
9
+ end
10
+
11
+ class << self
12
+ # Global API object
13
+ # === Example
14
+ # RequestDataType.api
15
+ def api
16
+ @api ||= API.new({})
17
+ end
18
+
19
+ def client_id
20
+ api.config.openid_client_id || api.config.client_id
21
+ end
22
+
23
+ def client_secret
24
+ api.config.openid_client_secret || api.config.client_secret
25
+ end
26
+
27
+ # Setter for RequestDataType.api
28
+ # === Example
29
+ # RequestDataType.set_config(..)
30
+ include SetAPI
31
+
32
+ # Configure depended module, when RequestDataType is include.
33
+ # === Example
34
+ # class Payment < DataTypes
35
+ # include RequestDataType
36
+ # end
37
+ # Payment.set_config(..)
38
+ # payment.set_config(..)
39
+ # Payment.api
40
+ # payment.api
41
+ def included(klass)
42
+ klass.class_eval do
43
+ extend GetAPI
44
+ extend SetAPI
45
+ include SetAPI
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,36 @@
1
+ module PayPal::SDK
2
+ module Core
3
+ module OpenIDConnect
4
+ module SetAPI
5
+ # Set new api
6
+ # === Examples
7
+ # payment.set_config(:development)
8
+ # payment.set_config(:client_id => "XYZ", :client_secret => "SECRET")
9
+ # payment.set_config
10
+ # payment.api = API.new(:development)
11
+ def set_config(*args)
12
+ if args[0].is_a?(API)
13
+ @api = args[0]
14
+ else
15
+ @api ||= API.new({})
16
+ @api.set_config(*args) # Just override the configuration and Not
17
+ @api
18
+ end
19
+ end
20
+ alias_method :config=, :set_config
21
+ alias_method :set_api, :set_config
22
+ alias_method :api=, :set_config
23
+
24
+ # Override client id
25
+ def client_id=(client_id)
26
+ set_config(:client_id => client_id)
27
+ end
28
+
29
+ # Override client secret
30
+ def client_secret=(client_secret)
31
+ set_config(:client_secret => client_secret)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,11 @@
1
+ module PayPal
2
+ module SDK
3
+ module Core
4
+ module Util
5
+ autoload :OauthSignature, "paypal-sdk/core/util/oauth_signature"
6
+ autoload :OrderedHash, "paypal-sdk/core/util/ordered_hash"
7
+ autoload :HTTPHelper, "paypal-sdk/core/util/http_helper"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,159 @@
1
+ require 'net/https'
2
+ require 'uri'
3
+ require 'cgi'
4
+
5
+ module PayPal::SDK::Core
6
+ module Util
7
+ module HTTPHelper
8
+
9
+ include Configuration
10
+ include Logging
11
+ include Authentication
12
+ include Exceptions
13
+
14
+ # Create HTTP connection based on given service name or configured end point
15
+ def create_http_connection(uri)
16
+ new_http(uri).tap do |http|
17
+ if config.http_timeout
18
+ http.open_timeout = config.http_timeout
19
+ http.read_timeout = config.http_timeout
20
+ end
21
+ configure_ssl(http) if uri.scheme == "https"
22
+ end
23
+ end
24
+
25
+ # New raw HTTP object
26
+ def new_http(uri)
27
+ if config.http_proxy
28
+ proxy = URI.parse(config.http_proxy)
29
+ Net::HTTP.new(uri.host, uri.port, proxy.host, proxy.port, proxy.user, proxy.password)
30
+ else
31
+ Net::HTTP.new(uri.host, uri.port)
32
+ end
33
+ end
34
+
35
+ # Default ca file
36
+ def default_ca_file
37
+ File.expand_path("../../../../../data/paypal.crt", __FILE__)
38
+ end
39
+
40
+ # Apply ssl configuration to http object
41
+ def configure_ssl(http)
42
+ http.tap do |https|
43
+ https.use_ssl = true
44
+ https.ca_file = default_ca_file
45
+ https.verify_mode = OpenSSL::SSL::VERIFY_PEER
46
+ config.ssl_options.each do |key, value|
47
+ http.send("#{key}=", value)
48
+ end
49
+ add_certificate(https)
50
+ end
51
+ end
52
+
53
+ # Join url
54
+ def url_join(path, action)
55
+ path.sub(/\/?$/, "/#{action}")
56
+ end
57
+
58
+ # Make Http call
59
+ # * payload - Hash(:http, :method, :uri, :body, :header)
60
+ def http_call(payload)
61
+ if Config.config.verbose_logging
62
+ logger.info payload.inspect
63
+ end
64
+
65
+ response =
66
+ log_http_call(payload) do
67
+ http = payload[:http] || create_http_connection(payload[:uri])
68
+ http.start do |session|
69
+ if [ :get, :delete, :head ].include? payload[:method]
70
+ session.send(payload[:method], payload[:uri].request_uri, payload[:header])
71
+ else
72
+ session.send(payload[:method], payload[:uri].request_uri, payload[:body], payload[:header])
73
+ end
74
+ end
75
+ end
76
+
77
+ if Config.config.verbose_logging
78
+ if response.code.to_i == 200
79
+ logger.info(response.body)
80
+ else
81
+ logger.warn(response.body)
82
+ end
83
+ end
84
+
85
+ handle_response(response)
86
+ end
87
+
88
+ # Log Http call
89
+ # * payload - Hash(:http, :method, :uri, :body, :header)
90
+ def log_http_call(payload)
91
+ logger.info "Request[#{payload[:method]}]: #{payload[:uri].to_s}"
92
+ start_time = Time.now
93
+ response = yield
94
+ logger.info sprintf("Response[%s]: %s, Duration: %.3fs", response.code,
95
+ response.message, Time.now - start_time)
96
+ response
97
+ end
98
+
99
+ # Generate header based on given header keys and properties
100
+ # === Arguments
101
+ # * <tt>header_keys</tt> -- List of Header keys for the properties
102
+ # * <tt>properties</tt> -- properties
103
+ # === Return
104
+ # Hash with header as key property as value
105
+ # === Example
106
+ # map_header_value( { :username => "X-PAYPAL-USERNAME"}, { :username => "guest" })
107
+ # # Return: { "X-PAYPAL-USERNAME" => "guest" }
108
+ def map_header_value(header_keys, properties)
109
+ header = {}
110
+ properties.each do |key, value|
111
+ key = header_keys[key]
112
+ header[key] = value.to_s if key and value
113
+ end
114
+ header
115
+ end
116
+
117
+ def encode_www_form(hash)
118
+ if defined? URI.encode_www_form
119
+ URI.encode_www_form(hash)
120
+ else
121
+ hash.map{|key, value| "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}" }.join("&")
122
+ end
123
+ end
124
+
125
+ # Handles response and error codes from the remote service.
126
+ def handle_response(response)
127
+ case response.code.to_i
128
+ when 301, 302, 303, 307
129
+ raise(Redirection.new(response))
130
+ when 200...400
131
+ response
132
+ when 400
133
+ raise(BadRequest.new(response))
134
+ when 401
135
+ raise(UnauthorizedAccess.new(response))
136
+ when 403
137
+ raise(ForbiddenAccess.new(response))
138
+ when 404
139
+ raise(ResourceNotFound.new(response))
140
+ when 405
141
+ raise(MethodNotAllowed.new(response))
142
+ when 409
143
+ raise(ResourceConflict.new(response))
144
+ when 410
145
+ raise(ResourceGone.new(response))
146
+ when 422
147
+ raise(ResourceInvalid.new(response))
148
+ when 401...500
149
+ raise(ClientError.new(response))
150
+ when 500...600
151
+ raise(ServerError.new(response))
152
+ else
153
+ raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
154
+ end
155
+ end
156
+
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,64 @@
1
+ require 'uri'
2
+ require 'cgi'
3
+ require 'openssl'
4
+ require 'base64'
5
+
6
+ module PayPal::SDK::Core
7
+ module Util
8
+ class OauthSignature
9
+ attr_accessor :username, :password, :token, :token_secret, :url, :timestamp
10
+
11
+ def initialize(username, password, token, token_secret, url, timestamp = nil)
12
+ @username = username
13
+ @password = password
14
+ @token = token
15
+ @token_secret = token_secret
16
+ @url = url
17
+ @timestamp = timestamp || Time.now.to_i.to_s
18
+ end
19
+
20
+ def authorization_string
21
+ signature = oauth_signature
22
+ "token=#{token},signature=#{signature},timestamp=#{timestamp}"
23
+ end
24
+
25
+ def oauth_signature
26
+ key = [
27
+ paypal_encode(password),
28
+ paypal_encode(token_secret),
29
+ ].join("&").gsub(/%[0-9A-F][0-9A-F]/, &:downcase )
30
+
31
+ digest = OpenSSL::HMAC.digest('sha1', key, base_string)
32
+ Base64.encode64(digest).chomp
33
+ end
34
+
35
+ def base_string
36
+ params = {
37
+ "oauth_consumer_key" => username,
38
+ "oauth_version" => "1.0",
39
+ "oauth_signature_method" => "HMAC-SHA1",
40
+ "oauth_token" => token,
41
+ "oauth_timestamp" => timestamp,
42
+ }
43
+ sorted_query_string = params.sort.map{|v| v.join("=") }.join("&")
44
+
45
+ base = [
46
+ "POST",
47
+ paypal_encode(url),
48
+ paypal_encode(sorted_query_string)
49
+ ].join("&")
50
+ base = base.gsub(/%[0-9A-F][0-9A-F]/, &:downcase )
51
+ end
52
+
53
+ # The PayPalURLEncoder java class percent encodes everything other than 'a-zA-Z0-9 _'.
54
+ # Then it converts ' ' to '+'.
55
+ # Ruby's CGI.encode takes care of the ' ' and '*' to satisfy PayPal
56
+ # (but beware, URI.encode percent encodes spaces, and does nothing with '*').
57
+ # Finally, CGI.encode does not encode '.-', which we need to do here.
58
+ def paypal_encode str
59
+ CGI.escape(str.to_s).gsub('.', '%2E').gsub('-', '%2D')
60
+ end
61
+ end
62
+ end
63
+ end
64
+
@@ -0,0 +1,165 @@
1
+ module PayPal::SDK::Core
2
+ module Util
3
+ class OrderedHash < ::Hash #:nodoc:
4
+
5
+ def to_yaml_type
6
+ "!tag:yaml.org,2002:map"
7
+ end
8
+
9
+ # Hash is ordered in Ruby 1.9!
10
+ if RUBY_VERSION < '1.9'
11
+
12
+ # In MRI the Hash class is core and written in C. In particular, methods are
13
+ # programmed with explicit C function calls and polymorphism is not honored.
14
+ #
15
+ # For example, []= is crucial in this implementation to maintain the @keys
16
+ # array but hash.c invokes rb_hash_aset() originally. This prevents method
17
+ # reuse through inheritance and forces us to reimplement stuff.
18
+ #
19
+ # For instance, we cannot use the inherited #merge! because albeit the algorithm
20
+ # itself would work, our []= is not being called at all by the C code.
21
+
22
+ def initialize(*args, &block)
23
+ super
24
+ @keys = []
25
+ end
26
+
27
+ def self.[](*args)
28
+ ordered_hash = new
29
+
30
+ if (args.length == 1 && args.first.is_a?(Array))
31
+ args.first.each do |key_value_pair|
32
+ next unless (key_value_pair.is_a?(Array))
33
+ ordered_hash[key_value_pair[0]] = key_value_pair[1]
34
+ end
35
+
36
+ return ordered_hash
37
+ end
38
+
39
+ unless (args.size % 2 == 0)
40
+ raise ArgumentError.new("odd number of arguments for Hash")
41
+ end
42
+
43
+ args.each_with_index do |val, ind|
44
+ next if (ind % 2 != 0)
45
+ ordered_hash[val] = args[ind + 1]
46
+ end
47
+
48
+ ordered_hash
49
+ end
50
+
51
+ def initialize_copy(other)
52
+ super
53
+ # make a deep copy of keys
54
+ @keys = other.keys
55
+ end
56
+
57
+ def []=(key, value)
58
+ @keys << key if !has_key?(key)
59
+ super
60
+ end
61
+
62
+ def delete(key)
63
+ if has_key? key
64
+ index = @keys.index(key)
65
+ @keys.delete_at index
66
+ end
67
+ super
68
+ end
69
+
70
+ def delete_if
71
+ super
72
+ sync_keys!
73
+ self
74
+ end
75
+
76
+ def reject!
77
+ super
78
+ sync_keys!
79
+ self
80
+ end
81
+
82
+ def reject(&block)
83
+ dup.reject!(&block)
84
+ end
85
+
86
+ def keys
87
+ @keys.dup
88
+ end
89
+
90
+ def values
91
+ @keys.collect { |key| self[key] }
92
+ end
93
+
94
+ def to_hash
95
+ self
96
+ end
97
+
98
+ def to_a
99
+ @keys.map { |key| [ key, self[key] ] }
100
+ end
101
+
102
+ def each_key
103
+ @keys.each { |key| yield key }
104
+ end
105
+
106
+ def each_value
107
+ @keys.each { |key| yield self[key]}
108
+ end
109
+
110
+ def each
111
+ @keys.each {|key| yield [key, self[key]]}
112
+ end
113
+
114
+ alias_method :each_pair, :each
115
+
116
+ def clear
117
+ super
118
+ @keys.clear
119
+ self
120
+ end
121
+
122
+ def shift
123
+ k = @keys.first
124
+ v = delete(k)
125
+ [k, v]
126
+ end
127
+
128
+ def merge!(other_hash)
129
+ if block_given?
130
+ other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v }
131
+ else
132
+ other_hash.each { |k, v| self[k] = v }
133
+ end
134
+ self
135
+ end
136
+
137
+ alias_method :update, :merge!
138
+
139
+ def merge(other_hash, &block)
140
+ dup.merge!(other_hash, &block)
141
+ end
142
+
143
+ # When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.
144
+ def replace(other)
145
+ super
146
+ @keys = other.keys
147
+ self
148
+ end
149
+
150
+ def invert
151
+ OrderedHash[self.to_a.map!{|key_value_pair| key_value_pair.reverse}]
152
+ end
153
+
154
+ def inspect
155
+ "#<OrderedHash #{super}>"
156
+ end
157
+
158
+ private
159
+ def sync_keys!
160
+ @keys.delete_if {|k| !has_key?(k)}
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end