paypal-sdk-rest 0.10.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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