paypal-sdk-core 0.1.5 → 0.2.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.
- 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
|