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,26 @@
1
+ module PayPal::SDK::Core
2
+ module API
3
+ module DataTypes
4
+
5
+ class Enum < SimpleTypes::String
6
+ class << self
7
+ def options
8
+ @options ||= []
9
+ end
10
+
11
+ def options=(options)
12
+ if options.is_a? Hash
13
+ options.each do |const_name, value|
14
+ const_set(const_name, value)
15
+ end
16
+ @options = options.values
17
+ else
18
+ @options = options
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,52 @@
1
+ require 'date'
2
+
3
+ module PayPal::SDK::Core
4
+ module API
5
+ module DataTypes
6
+
7
+ module SimpleTypes
8
+ class String < ::String
9
+ def self.new(string = "")
10
+ string.is_a?(::String) ? super : super(string.to_s)
11
+ end
12
+
13
+ def to_yaml_type
14
+ "!tag:yaml.org,2002:str"
15
+ end
16
+ end
17
+
18
+ class Integer < ::Integer
19
+ def self.new(number)
20
+ number.to_i
21
+ end
22
+ end
23
+
24
+ class Float < ::Float
25
+ def self.new(float)
26
+ float.to_f
27
+ end
28
+ end
29
+
30
+ class Boolean
31
+ def self.new(boolean)
32
+ ( boolean == 0 || boolean == "" || boolean =~ /^(false|f|no|n|0)$/i ) ? false : !!boolean
33
+ end
34
+ end
35
+
36
+ class Date < ::Date
37
+ def self.new(date)
38
+ date.is_a?(::Date) ? date : Date.parse(date.to_s)
39
+ end
40
+ end
41
+
42
+ class DateTime < ::DateTime
43
+ def self.new(date_time)
44
+ date_time = "0001-01-01T00:00:00" if date_time.to_s == "0"
45
+ date_time.is_a?(::DateTime) ? date_time : parse(date_time.to_s)
46
+ end
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,66 @@
1
+ module PayPal
2
+ module SDK
3
+ module Core
4
+ module API
5
+ module IPN
6
+
7
+ END_POINTS = {
8
+ :sandbox => "https://www.sandbox.paypal.com/cgi-bin/webscr",
9
+ :live => "https://ipnpb.paypal.com/cgi-bin/webscr"
10
+ }
11
+ VERIFIED = "VERIFIED"
12
+ INVALID = "INVALID"
13
+
14
+ class Message
15
+ include Util::HTTPHelper
16
+
17
+ attr_accessor :message
18
+
19
+ def initialize(message, env = nil, options = {})
20
+ @message = message
21
+ set_config(env, options)
22
+ end
23
+
24
+ # Fetch end point
25
+ def ipn_endpoint
26
+ config.ipn_endpoint || default_ipn_endpoint
27
+ end
28
+
29
+ # Default IPN end point
30
+ def default_ipn_endpoint
31
+ endpoint = END_POINTS[(config.mode || :sandbox).to_sym] rescue nil
32
+ endpoint || END_POINTS[:sandbox]
33
+ end
34
+
35
+ # Request IPN service for validating the content
36
+ # === Return
37
+ # return http response object
38
+ def request
39
+ uri = URI(ipn_endpoint)
40
+ query_string = "cmd=_notify-validate&#{message}"
41
+ http_call(:method => :post, :uri => uri, :body => query_string)
42
+ end
43
+
44
+ # Validate the given content
45
+ # === Return
46
+ # return true or false
47
+ def valid?
48
+ request.body == VERIFIED
49
+ end
50
+ end
51
+
52
+ class << self
53
+ def valid?(*args)
54
+ Message.new(*args).valid?
55
+ end
56
+
57
+ def request(*args)
58
+ Message.new(*args).request
59
+ end
60
+ end
61
+
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,163 @@
1
+ require 'multi_json'
2
+
3
+ module PayPal::SDK::Core
4
+ module API
5
+ class REST < Base
6
+ NVP_AUTH_HEADER = {
7
+ :sandbox_email_address => "X-PAYPAL-SANDBOX-EMAIL-ADDRESS",
8
+ :device_ipaddress => "X-PAYPAL-DEVICE-IPADDRESS"
9
+ }
10
+ DEFAULT_HTTP_HEADER = {
11
+ "Content-Type" => "application/json"
12
+ }
13
+
14
+ DEFAULT_REST_END_POINTS = {
15
+ :sandbox => "https://api.sandbox.paypal.com",
16
+ :live => "https://api.paypal.com"
17
+ }
18
+ TOKEN_REQUEST_PARAMS = "grant_type=client_credentials"
19
+
20
+ # Get REST service end point
21
+ def service_endpoint
22
+ config.rest_endpoint || super || DEFAULT_REST_END_POINTS[api_mode]
23
+ end
24
+
25
+ # Token endpoint
26
+ def token_endpoint
27
+ config.rest_token_endpoint || service_endpoint
28
+ end
29
+
30
+ # Clear cached values.
31
+ def set_config(*args)
32
+ @token_uri = nil
33
+ @token_hash = nil
34
+ super
35
+ end
36
+
37
+ # URI object token endpoint
38
+ def token_uri
39
+ @token_uri ||=
40
+ begin
41
+ new_uri = URI.parse(token_endpoint)
42
+ new_uri.path = "/v1/oauth2/token" if new_uri.path =~ /^\/?$/
43
+ new_uri
44
+ end
45
+ end
46
+
47
+ # Generate Oauth token or Get cached
48
+ def token_hash(auth_code=nil)
49
+ validate_token_hash
50
+ @token_hash ||=
51
+ begin
52
+ @token_request_at = Time.now
53
+ basic_auth = ["#{config.client_id}:#{config.client_secret}"].pack('m').delete("\r\n")
54
+ token_headers = default_http_header.merge({
55
+ "Content-Type" => "application/x-www-form-urlencoded",
56
+ "Authorization" => "Basic #{basic_auth}" })
57
+ if auth_code != nil
58
+ TOKEN_REQUEST_PARAMS.replace "grant_type=authorization_code&response_type=token&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&code="
59
+ TOKEN_REQUEST_PARAMS << auth_code
60
+ end
61
+ response = http_call( :method => :post, :uri => token_uri, :body => TOKEN_REQUEST_PARAMS, :header => token_headers )
62
+ MultiJson.load(response.body, :symbolize_keys => true)
63
+ end
64
+ end
65
+ attr_writer :token_hash
66
+
67
+ # Get access token
68
+ def token(auth_code=nil)
69
+ token_hash(auth_code)[:access_token]
70
+ end
71
+
72
+ # Get access token type
73
+ def token_type
74
+ token_hash[:token_type] || "Bearer"
75
+ end
76
+
77
+ # token setter
78
+ def token=(new_token)
79
+ @token_hash = { :access_token => new_token, :token_type => "Bearer" }
80
+ end
81
+
82
+ # Check token expired or not
83
+ def validate_token_hash
84
+ if @token_request_at and
85
+ @token_hash and @token_hash[:expires_in] and
86
+ (Time.now - @token_request_at) > @token_hash[:expires_in].to_i
87
+ @token_hash = nil
88
+ end
89
+ end
90
+
91
+ # Override the API call to handle Token Expire
92
+ def api_call(payload)
93
+ backup_payload = payload.dup
94
+ begin
95
+ response = super(payload)
96
+ rescue UnauthorizedAccess => error
97
+ if @token_hash and config.client_id
98
+ # Reset cached token and Retry api request
99
+ @token_hash = nil
100
+ response = super(backup_payload)
101
+ else
102
+ raise error
103
+ end
104
+ end
105
+ response
106
+ end
107
+
108
+ # Validate HTTP response
109
+ def handle_response(response)
110
+ super
111
+ rescue BadRequest => error
112
+ # Catch BadRequest to get validation error message from the response.
113
+ error.response
114
+ end
115
+
116
+ # Format request payload
117
+ # === Argument
118
+ # * payload( uri, action, params, header)
119
+ # === Generate
120
+ # * payload( uri, body, header )
121
+ def format_request(payload)
122
+ # Request URI
123
+ payload[:uri].path = url_join(payload[:uri].path, payload[:action])
124
+ # HTTP Header
125
+ credential_properties = credential(payload[:uri].to_s).properties
126
+ header = map_header_value(NVP_AUTH_HEADER, credential_properties)
127
+ payload[:header] = header.merge("Authorization" => "#{token_type} #{token}").
128
+ merge(DEFAULT_HTTP_HEADER).merge(payload[:header])
129
+ # Post Data
130
+ payload[:body] = MultiJson.dump(payload[:params])
131
+ payload
132
+ end
133
+
134
+ # Format response payload
135
+ # === Argument
136
+ # * payload( response )
137
+ # === Generate
138
+ # * payload( data )
139
+ def format_response(payload)
140
+ response = payload[:response]
141
+ payload[:data] =
142
+ if response.code >= "200" and response.code <= "299"
143
+ response.body && response.content_type == "application/json" ? MultiJson.load(response.body) : {}
144
+ elsif response.content_type == "application/json"
145
+ { "error" => MultiJson.load(response.body) }
146
+ else
147
+ { "error" => { "name" => response.code, "message" => response.message,
148
+ "developer_msg" => response } }
149
+ end
150
+ payload
151
+ end
152
+
153
+ # Log PayPal-Request-Id header
154
+ def log_http_call(payload)
155
+ if payload[:header] and payload[:header]["PayPal-Request-Id"]
156
+ logger.info "PayPal-Request-Id: #{payload[:header]["PayPal-Request-Id"]}"
157
+ end
158
+ super
159
+ end
160
+
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,66 @@
1
+
2
+ module PayPal::SDK::Core
3
+
4
+ # Contains methods to format credentials for HTTP protocol.
5
+ # == Example
6
+ # include Authentication
7
+ # credential(url)
8
+ # base_credential
9
+ # third_party_credential(url)
10
+ #
11
+ # add_certificate(http)
12
+ module Authentication
13
+
14
+ include Configuration
15
+
16
+ # Get credential object
17
+ # === Argument
18
+ # * <tt>url</tt> -- API request url
19
+ def credential(url)
20
+ third_party_credential(url) || base_credential
21
+ end
22
+
23
+ # Get base credential
24
+ def base_credential
25
+ @base_credential ||=
26
+ if config.cert_path
27
+ Credential::Certificate.new(config)
28
+ else
29
+ Credential::Signature.new(config)
30
+ end
31
+ end
32
+
33
+ # Get base credential type
34
+ def base_credential_type
35
+ config.cert_path ? :certificate : :three_token
36
+ end
37
+
38
+ # Get third party credential
39
+ def third_party_credential(url)
40
+ if config.token and config.token_secret
41
+ Credential::ThirdParty::Token.new(base_credential, config, url)
42
+ elsif config.subject
43
+ Credential::ThirdParty::Subject.new(base_credential, config)
44
+ end
45
+ end
46
+
47
+ # Clear cached variables on changing the configuration.
48
+ def set_config(*args)
49
+ @base_credential = nil
50
+ super
51
+ end
52
+
53
+ # Configure ssl certificate to HTTP object
54
+ # === Argument
55
+ # * <tt>http</tt> -- Net::HTTP object
56
+ def add_certificate(http)
57
+ if base_credential.is_a? Credential::Certificate
58
+ http.cert = base_credential.cert
59
+ http.key = base_credential.key
60
+ else
61
+ http.cert = nil
62
+ http.key = nil
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,249 @@
1
+ require 'erb'
2
+ require 'yaml'
3
+
4
+ module PayPal::SDK::Core
5
+
6
+ # Include Configuration module to access configuration from any object
7
+ # == Examples
8
+ # # Include in any class
9
+ # include Configuration
10
+ #
11
+ # # Access config object and attributes
12
+ # config
13
+ # config.username
14
+ #
15
+ # # Change configuration
16
+ # set_config(:development)
17
+ module Configuration
18
+
19
+ # To get default Config object.
20
+ def config
21
+ @config ||= Config.config
22
+ end
23
+
24
+ # To change the configuration to given environment or configuration
25
+ # === Arguments
26
+ # * <tt>env</tt> -- Environment
27
+ # * <tt>override_configurations</tt> (Optional) -- To override the default configuration.
28
+ # === Examples
29
+ # obj.set_config(api.config)
30
+ # obj.set_config(:http_timeout => 30)
31
+ # obj.set_config(:development)
32
+ # obj.set_config(:development, :http_timeout => 30)
33
+ def set_config(env, override_configurations = {})
34
+ @config =
35
+ case env
36
+ when Config
37
+ env
38
+ when Hash
39
+ begin
40
+ config.dup.merge!(env)
41
+ rescue Errno::ENOENT => error
42
+ Config.new(env)
43
+ end
44
+ else
45
+ Config.config(env, override_configurations)
46
+ end
47
+ end
48
+
49
+ alias_method :config=, :set_config
50
+ end
51
+
52
+ # Config class is used to hold the configurations.
53
+ # == Examples
54
+ # # To load configurations from file
55
+ # Config.load('config/paypal.yml', 'development')
56
+ #
57
+ # # Get configuration
58
+ # Config.config # load default configuration
59
+ # Config.config(:development) # load development configuration
60
+ # Config.config(:development, :app_id => "XYZ") # Override configuration
61
+ #
62
+ # # Read configuration attributes
63
+ # config = Config.config
64
+ # config.username
65
+ # config.endpoint
66
+ class Config
67
+
68
+ include Logging
69
+ include Exceptions
70
+
71
+ attr_accessor :username, :password, :signature, :app_id, :cert_path,
72
+ :token, :token_secret, :subject,
73
+ :http_timeout, :http_proxy,
74
+ :device_ipaddress, :sandbox_email_address,
75
+ :mode, :endpoint, :merchant_endpoint, :platform_endpoint, :ipn_endpoint,
76
+ :rest_endpoint, :rest_token_endpoint, :client_id, :client_secret,
77
+ :openid_endpoint, :openid_redirect_uri, :openid_client_id, :openid_client_secret,
78
+ :verbose_logging
79
+
80
+ alias_method :end_point=, :endpoint=
81
+ alias_method :end_point, :endpoint
82
+ alias_method :platform_end_point=, :platform_endpoint=
83
+ alias_method :platform_end_point, :platform_endpoint
84
+ alias_method :merchant_end_point=, :merchant_endpoint=
85
+ alias_method :merchant_end_point, :merchant_endpoint
86
+ alias_method :ipn_end_point=, :ipn_endpoint=
87
+ alias_method :ipn_end_point, :ipn_endpoint
88
+ alias_method :rest_end_point, :rest_endpoint
89
+ alias_method :rest_end_point=, :rest_endpoint=
90
+ alias_method :rest_token_end_point, :rest_token_endpoint
91
+ alias_method :rest_token_end_point=, :rest_token_endpoint=
92
+
93
+ # Create Config object
94
+ # === Options(Hash)
95
+ # * <tt>username</tt> -- Username
96
+ # * <tt>password</tt> -- Password
97
+ # * <tt>signature</tt> (Optional if certificate present) -- Signature
98
+ # * <tt>app_id</tt> -- Application ID
99
+ # * <tt>cert_path</tt> (Optional if signature present) -- Certificate file path
100
+ def initialize(options)
101
+ merge!(options)
102
+ end
103
+
104
+ def logfile=(filename)
105
+ logger.warn '`logfile=` is deprecated, Please use `PayPal::SDK::Core::Config.logger = Logger.new(STDERR)`'
106
+ end
107
+
108
+ def redirect_url=(redirect_url)
109
+ logger.warn '`redirect_url=` is deprecated.'
110
+ end
111
+
112
+ def dev_central_url=(dev_central_url)
113
+ logger.warn '`dev_central_url=` is deprecated.'
114
+ end
115
+
116
+ def ssl_options
117
+ @ssl_options ||= {}.freeze
118
+ end
119
+
120
+ def ssl_options=(options)
121
+ options = Hash[options.map{|key, value| [key.to_sym, value] }]
122
+ @ssl_options = ssl_options.merge(options).freeze
123
+ end
124
+
125
+ def ca_file=(ca_file)
126
+ logger.warn '`ca_file=` is deprecated, Please configure `ca_file=` under `ssl_options`'
127
+ self.ssl_options = { :ca_file => ca_file }
128
+ end
129
+
130
+ def http_verify_mode=(verify_mode)
131
+ logger.warn '`http_verify_mode=` is deprecated, Please configure `verify_mode=` under `ssl_options`'
132
+ self.ssl_options = { :verify_mode => verify_mode }
133
+ end
134
+
135
+ # Override configurations
136
+ def merge!(options)
137
+ options.each do |key, value|
138
+ send("#{key}=", value)
139
+ end
140
+ self
141
+ end
142
+
143
+ # Validate required configuration
144
+ def required!(*names)
145
+ names = names.select{|name| send(name).nil? }
146
+ raise MissingConfig.new("Required configuration(#{names.join(", ")})") if names.any?
147
+ end
148
+
149
+ class << self
150
+
151
+ @@config_cache = {}
152
+
153
+ # Load configurations from file
154
+ # === Arguments
155
+ # * <tt>file_name</tt> -- Configuration file path
156
+ # * <tt>default_environment</tt> (Optional) -- default environment configuration to load
157
+ # === Example
158
+ # Config.load('config/paypal.yml', 'development')
159
+ def load(file_name, default_env = default_environment)
160
+ @@config_cache = {}
161
+ @@configurations = read_configurations(file_name)
162
+ @@default_environment = default_env
163
+ config
164
+ end
165
+
166
+ # Get default environment name
167
+ def default_environment
168
+ @@default_environment ||= ENV['PAYPAL_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'] || "development"
169
+ end
170
+
171
+ # Set default environment
172
+ def default_environment=(env)
173
+ @@default_environment = env.to_s
174
+ end
175
+
176
+ def configure(options = {}, &block)
177
+ begin
178
+ self.config.merge!(options)
179
+ rescue Errno::ENOENT
180
+ self.configurations = { default_environment => options }
181
+ end
182
+ block.call(self.config) if block
183
+ self.config
184
+ end
185
+ alias_method :set_config, :configure
186
+
187
+ # Create or Load Config object based on given environment and configurations.
188
+ # === Attributes
189
+ # * <tt>env</tt> (Optional) -- Environment name
190
+ # * <tt>override_configuration</tt> (Optional) -- Override the configuration given in file.
191
+ # === Example
192
+ # Config.config
193
+ # Config.config(:development)
194
+ # Config.config(:development, { :app_id => "XYZ" })
195
+ def config(env = default_environment, override_configuration = {})
196
+ if env.is_a? Hash
197
+ override_configuration = env
198
+ env = default_environment
199
+ end
200
+ if override_configuration.nil? or override_configuration.empty?
201
+ default_config(env)
202
+ else
203
+ default_config(env).dup.merge!(override_configuration)
204
+ end
205
+ end
206
+
207
+ def default_config(env = nil)
208
+ env = (env || default_environment).to_s
209
+ if configurations[env]
210
+ @@config_cache[env] ||= new(configurations[env])
211
+ else
212
+ raise Exceptions::MissingConfig.new("Configuration[#{env}] NotFound")
213
+ end
214
+ end
215
+
216
+ # Set logger
217
+ def logger=(logger)
218
+ Logging.logger = logger
219
+ end
220
+
221
+ # Get logger
222
+ def logger
223
+ Logging.logger
224
+ end
225
+
226
+ # Get raw configurations in Hash format.
227
+ def configurations
228
+ @@configurations ||= read_configurations
229
+ end
230
+
231
+ # Set configuration
232
+ def configurations=(configs)
233
+ @@config_cache = {}
234
+ @@configurations = configs && Hash[configs.map{|k,v| [k.to_s, v] }]
235
+ end
236
+
237
+ private
238
+ # Read configurations from the given file name
239
+ # === Arguments
240
+ # * <tt>file_name</tt> (Optional) -- Configuration file path
241
+ def read_configurations(file_name = "config/paypal.yml")
242
+ erb = ERB.new(File.read(file_name))
243
+ erb.filename = file_name
244
+ YAML.load(erb.result)
245
+ end
246
+
247
+ end
248
+ end
249
+ end