paypal-sdk-core 0.1.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/{spec/config/cacert.pem → data/paypal.crt} +0 -0
- data/lib/generators/paypal/sdk/templates/paypal.yml +16 -19
- data/lib/paypal-sdk-core.rb +7 -4
- data/lib/paypal-sdk/core/api/base.rb +71 -87
- data/lib/paypal-sdk/core/api/data_types/base.rb +18 -6
- data/lib/paypal-sdk/core/api/ipn.rb +8 -18
- data/lib/paypal-sdk/core/api/merchant.rb +36 -16
- data/lib/paypal-sdk/core/api/platform.rb +21 -14
- data/lib/paypal-sdk/core/api/rest.rb +139 -0
- data/lib/paypal-sdk/core/authentication.rb +6 -19
- data/lib/paypal-sdk/core/config.rb +77 -7
- data/lib/paypal-sdk/core/exceptions.rb +87 -0
- data/lib/paypal-sdk/core/logging.rb +2 -2
- data/lib/paypal-sdk/core/util/http_helper.rb +146 -0
- data/lib/paypal-sdk/core/version.rb +1 -1
- data/spec/config/paypal.yml +5 -3
- data/spec/core/api/data_type_spec.rb +12 -0
- data/spec/core/api/ipn_spec.rb +11 -4
- data/spec/core/api/merchant_spec.rb +58 -20
- data/spec/core/api/platform_spec.rb +58 -7
- data/spec/core/api/rest_spec.rb +141 -0
- data/spec/core/config_spec.rb +76 -2
- data/spec/log/rest_http.log +104 -0
- data/spec/log/test.log +69 -1381
- data/spec/spec_helper.rb +3 -0
- metadata +13 -19
@@ -24,12 +24,16 @@ module PayPal::SDK::Core
|
|
24
24
|
"X-PAYPAL-REQUEST-DATA-FORMAT" => "JSON",
|
25
25
|
"X-PAYPAL-RESPONSE-DATA-FORMAT" => "JSON"
|
26
26
|
}
|
27
|
-
DEFAULT_PARAMS = Util::OrderedHash.new.merge!({
|
27
|
+
DEFAULT_PARAMS = Util::OrderedHash.new.merge!({
|
28
28
|
"requestEnvelope" => { "errorLanguage" => "en_US" } })
|
29
|
+
DEFAULT_END_POINTS = {
|
30
|
+
:sandbox => "https://svcs.sandbox.paypal.com/",
|
31
|
+
:live => "https://svcs.paypal.com/"
|
32
|
+
}
|
29
33
|
|
30
|
-
# Get
|
34
|
+
# Get service end point
|
31
35
|
def service_endpoint
|
32
|
-
config.
|
36
|
+
config.platform_endpoint || config.endpoint || DEFAULT_END_POINTS[api_mode]
|
33
37
|
end
|
34
38
|
|
35
39
|
# Format the Request.
|
@@ -39,13 +43,14 @@ module PayPal::SDK::Core
|
|
39
43
|
# === Return
|
40
44
|
# * <tt>request_path</tt> -- Generated URL for requested action
|
41
45
|
# * <tt>request_content</tt> -- Format parameters in JSON with default values.
|
42
|
-
def format_request(
|
43
|
-
uri =
|
44
|
-
|
45
|
-
credential_properties = credential(uri.to_s).properties
|
46
|
+
def format_request(payload)
|
47
|
+
payload[:uri].path = url_join(payload[:uri].path, payload[:action])
|
48
|
+
credential_properties = credential(payload[:uri].to_s).properties
|
46
49
|
header = map_header_value(NVP_AUTH_HEADER, credential_properties).
|
47
50
|
merge(DEFAULT_NVP_HTTP_HEADER)
|
48
|
-
[
|
51
|
+
payload[:header] = header.merge(payload[:header])
|
52
|
+
payload[:body] = MultiJson.dump(DEFAULT_PARAMS.merge(payload[:params]))
|
53
|
+
payload
|
49
54
|
end
|
50
55
|
|
51
56
|
# Format the Response object
|
@@ -54,12 +59,14 @@ module PayPal::SDK::Core
|
|
54
59
|
# * <tt>response</tt> -- HTTP response object
|
55
60
|
# === Return
|
56
61
|
# Parse response content using JSON and return the Hash object
|
57
|
-
def format_response(
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
62
|
+
def format_response(payload)
|
63
|
+
payload[:data] =
|
64
|
+
if payload[:response].code == "200"
|
65
|
+
MultiJson.load(payload[:response].body)
|
66
|
+
else
|
67
|
+
format_error(payload[:response], payload[:response].message)
|
68
|
+
end
|
69
|
+
payload
|
63
70
|
end
|
64
71
|
|
65
72
|
# Format Error object.
|
@@ -0,0 +1,139 @@
|
|
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 = 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
|
49
|
+
@token ||=
|
50
|
+
begin
|
51
|
+
basic_auth = ["#{config.client_id}:#{config.client_secret}"].pack('m').delete("\r\n")
|
52
|
+
token_headers = default_http_header.merge( "Authorization" => "Basic #{basic_auth}" )
|
53
|
+
response = http_call( :method => :post, :uri => token_uri, :body => TOKEN_REQUEST_PARAMS, :header => token_headers )
|
54
|
+
MultiJson.load(response.body)["access_token"]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# token setter
|
59
|
+
def token=(new_token)
|
60
|
+
@token = new_token
|
61
|
+
end
|
62
|
+
|
63
|
+
# Override the API call to handle Token Expire
|
64
|
+
def api_call(payload)
|
65
|
+
backup_payload = payload.dup
|
66
|
+
begin
|
67
|
+
response = super(payload)
|
68
|
+
rescue UnauthorizedAccess => error
|
69
|
+
if @token and config.client_id
|
70
|
+
# Reset cached token and Retry api request
|
71
|
+
@token = nil
|
72
|
+
response = super(backup_payload)
|
73
|
+
else
|
74
|
+
raise error
|
75
|
+
end
|
76
|
+
end
|
77
|
+
response
|
78
|
+
end
|
79
|
+
|
80
|
+
# Validate HTTP response
|
81
|
+
def handle_response(response)
|
82
|
+
super
|
83
|
+
rescue BadRequest => error
|
84
|
+
# Catch BadRequest to get validation error message from the response.
|
85
|
+
error.response
|
86
|
+
end
|
87
|
+
|
88
|
+
# Format request payload
|
89
|
+
# === Argument
|
90
|
+
# * payload( uri, action, params, header)
|
91
|
+
# === Generate
|
92
|
+
# * payload( uri, body, header )
|
93
|
+
def format_request(payload)
|
94
|
+
# Request URI
|
95
|
+
payload[:uri].path = url_join(payload[:uri].path, payload[:action])
|
96
|
+
# HTTP Header
|
97
|
+
credential_properties = credential(payload[:uri].to_s).properties
|
98
|
+
header = map_header_value(NVP_AUTH_HEADER, credential_properties)
|
99
|
+
payload[:header] = header.merge("Authorization" => "Bearer #{token}").
|
100
|
+
merge(DEFAULT_HTTP_HEADER).merge(payload[:header])
|
101
|
+
# Post Data
|
102
|
+
payload[:body] = MultiJson.dump(payload[:params])
|
103
|
+
payload
|
104
|
+
end
|
105
|
+
|
106
|
+
# Format response payload
|
107
|
+
# === Argument
|
108
|
+
# * payload( response )
|
109
|
+
# === Generate
|
110
|
+
# * payload( data )
|
111
|
+
def format_response(payload)
|
112
|
+
payload[:data] =
|
113
|
+
if payload[:response].code >= "200" and payload[:response].code <= "299"
|
114
|
+
MultiJson.load(payload[:response].body)
|
115
|
+
elsif payload[:response].content_type == "application/json"
|
116
|
+
{ "error" => MultiJson.load(payload[:response].body) }
|
117
|
+
else
|
118
|
+
{ "error" => { "name" => payload[:response].code, "message" => payload[:response].message,
|
119
|
+
"developer_msg" => payload[:response] } }
|
120
|
+
end
|
121
|
+
payload
|
122
|
+
end
|
123
|
+
|
124
|
+
# Formate Exception object
|
125
|
+
def format_error(exception, message)
|
126
|
+
{ "error" => { "name" => "ERROR", "message" => message, "developer_msg" => exception } }
|
127
|
+
end
|
128
|
+
|
129
|
+
# Log PayPal-Request-Id header
|
130
|
+
def log_http_call(payload)
|
131
|
+
if payload[:header] and payload[:header]["PayPal-Request-Id"]
|
132
|
+
logger.info "PayPal-Request-Id: #{payload[:header]["PayPal-Request-Id"]}"
|
133
|
+
end
|
134
|
+
super
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -30,6 +30,11 @@ module PayPal::SDK::Core
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
+
# Get base credential type
|
34
|
+
def base_credential_type
|
35
|
+
config.cert_path ? :certificate : :three_token
|
36
|
+
end
|
37
|
+
|
33
38
|
# Get third party credential
|
34
39
|
def third_party_credential(url)
|
35
40
|
if config.token and config.token_secret
|
@@ -41,26 +46,8 @@ module PayPal::SDK::Core
|
|
41
46
|
|
42
47
|
# Clear cached variables on changing the configuration.
|
43
48
|
def set_config(*args)
|
44
|
-
super
|
45
49
|
@base_credential = nil
|
46
|
-
|
47
|
-
|
48
|
-
# Generate header based on given header keys and properties
|
49
|
-
# === Arguments
|
50
|
-
# * <tt>header_keys</tt> -- List of Header keys for the properties
|
51
|
-
# * <tt>properties</tt> -- properties
|
52
|
-
# === Return
|
53
|
-
# Hash with header as key property as value
|
54
|
-
# === Example
|
55
|
-
# map_header_value( { :username => "X-PAYPAL-USERNAME"}, { :username => "guest" })
|
56
|
-
# # Return: { "X-PAYPAL-USERNAME" => "guest" }
|
57
|
-
def map_header_value(header_keys, properties)
|
58
|
-
header = {}
|
59
|
-
properties.each do |key, value|
|
60
|
-
key = header_keys[key]
|
61
|
-
header[key] = value if key
|
62
|
-
end
|
63
|
-
header
|
50
|
+
super
|
64
51
|
end
|
65
52
|
|
66
53
|
# Configure ssl certificate to HTTP object
|
@@ -25,8 +25,25 @@ module PayPal::SDK::Core
|
|
25
25
|
# === Arguments
|
26
26
|
# * <tt>env</tt> -- Environment
|
27
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)
|
28
33
|
def set_config(env, override_configurations = {})
|
29
|
-
@config =
|
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
|
30
47
|
end
|
31
48
|
|
32
49
|
alias_method :config=, :set_config
|
@@ -45,15 +62,31 @@ module PayPal::SDK::Core
|
|
45
62
|
# # Read configuration attributes
|
46
63
|
# config = Config.config
|
47
64
|
# config.username
|
48
|
-
# config.
|
65
|
+
# config.endpoint
|
49
66
|
class Config
|
67
|
+
|
68
|
+
include Logging
|
69
|
+
include Exceptions
|
70
|
+
|
50
71
|
attr_accessor :username, :password, :signature, :app_id, :cert_path,
|
51
72
|
:token, :token_secret, :subject,
|
52
|
-
:http_timeout, :
|
73
|
+
:http_timeout, :http_proxy,
|
53
74
|
:device_ipaddress, :sandbox_email_address,
|
54
|
-
:mode, :
|
55
|
-
:
|
56
|
-
|
75
|
+
:mode, :endpoint, :merchant_endpoint, :platform_endpoint, :ipn_endpoint,
|
76
|
+
:rest_endpoint, :rest_token_endpoint, :client_id, :client_secret
|
77
|
+
|
78
|
+
alias_method :end_point=, :endpoint=
|
79
|
+
alias_method :end_point, :endpoint
|
80
|
+
alias_method :platform_end_point=, :platform_endpoint=
|
81
|
+
alias_method :platform_end_point, :platform_endpoint
|
82
|
+
alias_method :merchant_end_point=, :merchant_endpoint=
|
83
|
+
alias_method :merchant_end_point, :merchant_endpoint
|
84
|
+
alias_method :ipn_end_point=, :ipn_endpoint=
|
85
|
+
alias_method :ipn_end_point, :ipn_endpoint
|
86
|
+
alias_method :rest_end_point, :rest_endpoint
|
87
|
+
alias_method :rest_end_point=, :rest_endpoint=
|
88
|
+
alias_method :rest_token_end_point, :rest_token_endpoint
|
89
|
+
alias_method :rest_token_end_point=, :rest_token_endpoint=
|
57
90
|
|
58
91
|
# Create Config object
|
59
92
|
# === Options(Hash)
|
@@ -66,6 +99,37 @@ module PayPal::SDK::Core
|
|
66
99
|
merge!(options)
|
67
100
|
end
|
68
101
|
|
102
|
+
def logfile=(filename)
|
103
|
+
logger.warn '`logfile=` is deprecated, Please use `PayPal::SDK::Core::Config.logger = Logger.new(STDERR)`'
|
104
|
+
end
|
105
|
+
|
106
|
+
def redirect_url=(redirect_url)
|
107
|
+
logger.warn '`redirect_url=` is deprecated.'
|
108
|
+
end
|
109
|
+
|
110
|
+
def dev_central_url=(dev_central_url)
|
111
|
+
logger.warn '`dev_central_url=` is deprecated.'
|
112
|
+
end
|
113
|
+
|
114
|
+
def ssl_options
|
115
|
+
@ssl_options ||= {}.freeze
|
116
|
+
end
|
117
|
+
|
118
|
+
def ssl_options=(options)
|
119
|
+
options = Hash[options.map{|key, value| [key.to_sym, value] }]
|
120
|
+
@ssl_options = ssl_options.merge(options).freeze
|
121
|
+
end
|
122
|
+
|
123
|
+
def ca_file=(ca_file)
|
124
|
+
logger.warn '`ca_file=` is deprecated, Please configure `ca_file=` under `ssl_options`'
|
125
|
+
self.ssl_options = { :ca_file => ca_file }
|
126
|
+
end
|
127
|
+
|
128
|
+
def http_verify_mode=(verify_mode)
|
129
|
+
logger.warn '`http_verify_mode=` is deprecated, Please configure `verify_mode=` under `ssl_options`'
|
130
|
+
self.ssl_options = { :verify_mode => verify_mode }
|
131
|
+
end
|
132
|
+
|
69
133
|
# Override configurations
|
70
134
|
def merge!(options)
|
71
135
|
options.each do |key, value|
|
@@ -74,6 +138,12 @@ module PayPal::SDK::Core
|
|
74
138
|
self
|
75
139
|
end
|
76
140
|
|
141
|
+
# Validate required configuration
|
142
|
+
def required!(*names)
|
143
|
+
names = names.select{|name| send(name).nil? }
|
144
|
+
raise MissingConfig.new("Required configuration(#{names.join(", ")})") if names.any?
|
145
|
+
end
|
146
|
+
|
77
147
|
class << self
|
78
148
|
|
79
149
|
@@config_cache = {}
|
@@ -141,7 +211,7 @@ module PayPal::SDK::Core
|
|
141
211
|
# Set configuration
|
142
212
|
def configurations=(configs)
|
143
213
|
@@config_cache = {}
|
144
|
-
@@configurations = Hash[configs.map{|k,v| [k.to_s, v] }]
|
214
|
+
@@configurations = configs && Hash[configs.map{|k,v| [k.to_s, v] }]
|
145
215
|
end
|
146
216
|
|
147
217
|
private
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module PayPal::SDK::Core
|
2
|
+
module Exceptions
|
3
|
+
class ConnectionError < StandardError # :nodoc:
|
4
|
+
attr_reader :response
|
5
|
+
|
6
|
+
def initialize(response, message = nil)
|
7
|
+
@response = response
|
8
|
+
@message = message
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
message = "Failed."
|
13
|
+
message << " Response code = #{response.code}." if response.respond_to?(:code)
|
14
|
+
message << " Response message = #{response.message}." if response.respond_to?(:message)
|
15
|
+
message
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Raised when a Timeout::Error occurs.
|
20
|
+
class TimeoutError < ConnectionError
|
21
|
+
def initialize(message)
|
22
|
+
@message = message
|
23
|
+
end
|
24
|
+
def to_s; @message ;end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Raised when a OpenSSL::SSL::SSLError occurs.
|
28
|
+
class SSLError < ConnectionError
|
29
|
+
def initialize(message)
|
30
|
+
@message = message
|
31
|
+
end
|
32
|
+
def to_s; @message ;end
|
33
|
+
end
|
34
|
+
|
35
|
+
# 3xx Redirection
|
36
|
+
class Redirection < ConnectionError # :nodoc:
|
37
|
+
def to_s
|
38
|
+
response['Location'] ? "#{super} => #{response['Location']}" : super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class MissingParam < ArgumentError # :nodoc:
|
43
|
+
end
|
44
|
+
|
45
|
+
class MissingConfig < StandardError # :nodoc:
|
46
|
+
end
|
47
|
+
|
48
|
+
# 4xx Client Error
|
49
|
+
class ClientError < ConnectionError # :nodoc:
|
50
|
+
end
|
51
|
+
|
52
|
+
# 400 Bad Request
|
53
|
+
class BadRequest < ClientError # :nodoc:
|
54
|
+
end
|
55
|
+
|
56
|
+
# 401 Unauthorized
|
57
|
+
class UnauthorizedAccess < ClientError # :nodoc:
|
58
|
+
end
|
59
|
+
|
60
|
+
# 403 Forbidden
|
61
|
+
class ForbiddenAccess < ClientError # :nodoc:
|
62
|
+
end
|
63
|
+
|
64
|
+
# 404 Not Found
|
65
|
+
class ResourceNotFound < ClientError # :nodoc:
|
66
|
+
end
|
67
|
+
|
68
|
+
# 409 Conflict
|
69
|
+
class ResourceConflict < ClientError # :nodoc:
|
70
|
+
end
|
71
|
+
|
72
|
+
# 410 Gone
|
73
|
+
class ResourceGone < ClientError # :nodoc:
|
74
|
+
end
|
75
|
+
|
76
|
+
# 5xx Server Error
|
77
|
+
class ServerError < ConnectionError # :nodoc:
|
78
|
+
end
|
79
|
+
|
80
|
+
# 405 Method Not Allowed
|
81
|
+
class MethodNotAllowed < ClientError # :nodoc:
|
82
|
+
def allowed_methods
|
83
|
+
@response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|