paypal-sdk-core 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. data/Gemfile +11 -0
  2. data/README.md +16 -0
  3. data/Rakefile +5 -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 +32 -0
  8. data/lib/paypal-sdk-core.rb +40 -0
  9. data/lib/paypal-sdk/core/api/base.rb +166 -0
  10. data/lib/paypal-sdk/core/api/data_types/base.rb +236 -0
  11. data/lib/paypal-sdk/core/api/data_types/enum.rb +26 -0
  12. data/lib/paypal-sdk/core/api/data_types/simple_types.rb +47 -0
  13. data/lib/paypal-sdk/core/api/merchant.rb +122 -0
  14. data/lib/paypal-sdk/core/api/platform.rb +75 -0
  15. data/lib/paypal-sdk/core/authentication.rb +79 -0
  16. data/lib/paypal-sdk/core/config.rb +138 -0
  17. data/lib/paypal-sdk/core/credential/base.rb +27 -0
  18. data/lib/paypal-sdk/core/credential/certificate.rb +32 -0
  19. data/lib/paypal-sdk/core/credential/signature.rb +22 -0
  20. data/lib/paypal-sdk/core/credential/third_party/subject.rb +25 -0
  21. data/lib/paypal-sdk/core/credential/third_party/token.rb +39 -0
  22. data/lib/paypal-sdk/core/logging.rb +43 -0
  23. data/lib/paypal-sdk/core/util/oauth_signature.rb +64 -0
  24. data/lib/paypal-sdk/core/version.rb +7 -0
  25. data/spec/config/cert_key.pem +33 -0
  26. data/spec/config/paypal.yml +29 -0
  27. data/spec/core/api/data_type_spec.rb +164 -0
  28. data/spec/core/api/merchant_spec.rb +114 -0
  29. data/spec/core/api/platform_spec.rb +107 -0
  30. data/spec/core/config_spec.rb +48 -0
  31. data/spec/core/logging_spec.rb +28 -0
  32. data/spec/log/test.log +84 -0
  33. data/spec/spec_helper.rb +11 -0
  34. metadata +109 -0
@@ -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,47 @@
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
+ end
13
+
14
+ class Integer < ::Integer
15
+ def self.new(number)
16
+ number.to_i
17
+ end
18
+ end
19
+
20
+ class Float < ::Float
21
+ def self.new(float)
22
+ float.to_f
23
+ end
24
+ end
25
+
26
+ class Boolean
27
+ def self.new(boolean)
28
+ ( boolean == 0 || boolean == "" || boolean =~ /^(false|f|no|n|0)$/i ) ? false : !!boolean
29
+ end
30
+ end
31
+
32
+ class Date < ::Date
33
+ def self.new(date)
34
+ date.is_a?(::Date) ? date : Date.parse(date.to_s)
35
+ end
36
+ end
37
+
38
+ class DateTime < ::DateTime
39
+ def self.new(date_time)
40
+ date_time.is_a?(::DateTime) ? date_time : parse(date_time.to_s)
41
+ end
42
+ end
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,122 @@
1
+ require 'xmlsimple'
2
+
3
+ module PayPal::SDK::Core
4
+
5
+ module API
6
+
7
+ # Use SOAP protocol to communicate with the Merchant Web services
8
+ # == Example
9
+ # api = API::Merchant.new
10
+ # response = api.request("TransactionSearch", { "StartDate" => "2012-09-30T00:00:00+0530",
11
+ # "EndDate" => "2012-10-01T00:00:00+0530" })
12
+ class Merchant < Base
13
+
14
+
15
+ Namespaces = {
16
+ "@xmlns:soapenv" => "http://schemas.xmlsoap.org/soap/envelope/",
17
+ "@xmlns:ns" => "urn:ebay:api:PayPalAPI",
18
+ "@xmlns:ebl" => "urn:ebay:apis:eBLBaseComponents",
19
+ "@xmlns:cc" => "urn:ebay:apis:CoreComponentTypes",
20
+ "@xmlns:ed" => "urn:ebay:apis:EnhancedDataTypes"
21
+ }
22
+ ContentKey = API::DataTypes::Base::ContentKey.to_s
23
+ DEFAULT_API_VERSION = "94.0"
24
+ XML_OUT_OPTIONS = { 'RootName' => nil, 'AttrPrefix' => true, 'ContentKey' => ContentKey,
25
+ 'noindent' => true, 'SuppressEmpty' => true }
26
+ XML_IN_OPTIONS = { 'AttrPrefix' => true, 'ForceArray' => false, 'ContentKey' => ContentKey }
27
+ DEFAULT_PARAMS = { :"ebl:Version" => DEFAULT_API_VERSION }
28
+ SKIP_ATTRIBUTES = [ "@xmlns", "@xsi:type" ]
29
+ SOAP_HTTP_AUTH_HEADER = {
30
+ :authorization => "X-PP-AUTHORIZATION"
31
+ }
32
+ SOAP_AUTH_HEADER = {
33
+ :username => "ebl:Username",
34
+ :password => "ebl:Password",
35
+ :signature => "ebl:Signature",
36
+ :subject => "ebl:Subject"
37
+ }
38
+
39
+ # Get SOAP or default end point
40
+ def service_endpoint
41
+ config.merchant_end_point || super || default_end_point(:merchant)
42
+ end
43
+
44
+ # Format the HTTP request content
45
+ # === Arguments
46
+ # * <tt>action</tt> -- Request action
47
+ # * <tt>params</tt> -- Parameters for Action in Hash format
48
+ # === Return
49
+ # * <tt>request_path</tt> -- Soap request path. DEFAULT("/")
50
+ # * <tt>request_content</tt> -- Request content in SOAP format.
51
+ def format_request(action, params)
52
+ credential_properties = credential(uri.to_s).properties
53
+ user_auth_header = map_header_value(SOAP_AUTH_HEADER, credential_properties)
54
+ content_key = params.keys.first.is_a?(Symbol) ? ContentKey.to_sym : ContentKey.to_s
55
+ request_content = XmlSimple.xml_out({
56
+ "soapenv:Envelope" => {
57
+ "soapenv:Header" => { "ns:RequesterCredentials" => {
58
+ "ebl:Credentials" => user_auth_header
59
+ } },
60
+ "soapenv:Body" => body(action, params)
61
+ }.merge(Namespaces)
62
+ }, XML_OUT_OPTIONS.merge( 'ContentKey' => content_key ))
63
+ header = map_header_value(SOAP_HTTP_AUTH_HEADER, credential_properties)
64
+ [ @uri, request_content, header ]
65
+ end
66
+
67
+ # Format Response object
68
+ # === Arguments
69
+ # * <tt>action</tt> -- Request action
70
+ # * <tt>response</tt> -- Response object
71
+ # === Return
72
+ # Parse the SOAP response content and return Hash object
73
+ def format_response(action, response)
74
+ if response.code == "200"
75
+ hash = XmlSimple.xml_in(response.body, XML_IN_OPTIONS)
76
+ hash = skip_attributes(hash)
77
+ hash["Body"].find{|key_val| key_val[0] =~ /^[^@]/ }[1] || {}
78
+ else
79
+ format_error(response, response.message)
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ # Generate soap body
86
+ # == Arguments
87
+ # * <tt>action</tt> -- Request Action name
88
+ # * <tt>params</tt> -- Parameters for the action.
89
+ def body(action, params = {})
90
+ { "ns:#{action}Req" => { "ns:#{action}Request" => DEFAULT_PARAMS.merge(params) } }
91
+ end
92
+
93
+ # Remove specified attributes from the given Hash
94
+ # === Arguments
95
+ # * <tt>hash</tt> -- Hash object
96
+ # * <tt>attrs</tt> -- (Optional) Attribute list
97
+ # * <tt>content_key</tt> -- (Optional) content key
98
+ def skip_attributes(hash, attrs = SKIP_ATTRIBUTES, content_key = ContentKey)
99
+ hash.each do |key, value|
100
+ if attrs.include? key
101
+ hash.delete(key)
102
+ elsif value.is_a? Hash
103
+ hash[key] = skip_attributes(value, attrs, content_key)
104
+ elsif value.is_a? Array and value[0].is_a? Hash
105
+ value.each_with_index do |array_value, index|
106
+ value[index] = skip_attributes(array_value, attrs, content_key)
107
+ end
108
+ end
109
+ end
110
+ ( hash.one? and hash[content_key] ) ? hash[content_key] : ( hash.empty? ? nil : hash )
111
+ end
112
+
113
+ # Format Error object.
114
+ # == Arguments
115
+ # * <tt>exception</tt> -- Exception object or HTTP response object.
116
+ # * <tt>message</tt> -- Readable error message.
117
+ def format_error(exception, message)
118
+ { "Ack" => "Failure", "Errors" => { "ShortMessage" => message, "LongMessage" => exception.to_s } }
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,75 @@
1
+ require 'json'
2
+
3
+ module PayPal::SDK::Core
4
+ module API
5
+
6
+ # Use NVP protocol to communicate with Platform Web services
7
+ # == Example
8
+ # api = API::Platform.new("AdaptivePayments")
9
+ # response = client.request("ConvertCurrency", {
10
+ # "baseAmountList" => { "currency" => [ { "code" => "USD", "amount" => "2.0"} ]},
11
+ # "convertToCurrencyList" => { "currencyCode" => ["GBP"] } })
12
+ class Platform < Base
13
+
14
+ NVP_AUTH_HEADER = {
15
+ :username => "X-PAYPAL-SECURITY-USERID",
16
+ :password => "X-PAYPAL-SECURITY-PASSWORD",
17
+ :signature => "X-PAYPAL-SECURITY-SIGNATURE",
18
+ :app_id => "X-PAYPAL-APPLICATION-ID",
19
+ :authorization => "X-PAYPAL-AUTHORIZATION",
20
+ :sandbox_email_address => "X-PAYPAL-SANDBOX-EMAIL-ADDRESS",
21
+ :device_ipaddress => "X-PAYPAL-DEVICE-IPADDRESS"
22
+ }
23
+ DEFAULT_NVP_HTTP_HEADER = {
24
+ "X-PAYPAL-REQUEST-DATA-FORMAT" => "JSON",
25
+ "X-PAYPAL-RESPONSE-DATA-FORMAT" => "JSON"
26
+ }
27
+ DEFAULT_PARAMS = {
28
+ "requestEnvelope" => { "errorLanguage" => "en_US" }
29
+ }
30
+
31
+ # Get NVP service end point
32
+ def service_endpoint
33
+ config.platform_end_point || super || default_end_point(:platform)
34
+ end
35
+
36
+ # Format the Request.
37
+ # === Arguments
38
+ # * <tt>action</tt> -- Action to perform
39
+ # * <tt>params</tt> -- Action parameters will be in Hash
40
+ # === Return
41
+ # * <tt>request_path</tt> -- Generated URL for requested action
42
+ # * <tt>request_content</tt> -- Format parameters in JSON with default values.
43
+ def format_request(action, params)
44
+ uri = @uri.dup
45
+ uri.path = @uri.path.sub(/\/?$/, "/#{action}")
46
+ credential_properties = credential(uri.to_s).properties
47
+ header = map_header_value(NVP_AUTH_HEADER, credential_properties).
48
+ merge(DEFAULT_NVP_HTTP_HEADER)
49
+ [ uri, DEFAULT_PARAMS.merge(params).to_json, header ]
50
+ end
51
+
52
+ # Format the Response object
53
+ # === Arguments
54
+ # * <tt>action</tt> -- Requested action name
55
+ # * <tt>response</tt> -- HTTP response object
56
+ # === Return
57
+ # Parse response content using JSON and return the Hash object
58
+ def format_response(action, response)
59
+ if response.code == "200"
60
+ JSON.parse(response.body)
61
+ else
62
+ format_error(response, response.message)
63
+ end
64
+ end
65
+
66
+ # Format Error object.
67
+ # == Arguments
68
+ # * <tt>exception</tt> -- Exception object or HTTP response object.
69
+ # * <tt>message</tt> -- Readable error message.
70
+ def format_error(exception, message)
71
+ {"responseEnvelope" => {"ack" => "Failure"}, "error" => [{"message" => message}]}
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,79 @@
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 third party credential
34
+ def third_party_credential(url)
35
+ if config.token and config.token_secret
36
+ Credential::ThirdParty::Token.new(base_credential, config, url)
37
+ elsif config.subject
38
+ Credential::ThirdParty::Subject.new(base_credential, config)
39
+ end
40
+ end
41
+
42
+ # Clear cached variables on changing the configuration.
43
+ def set_config(*args)
44
+ super
45
+ @base_credential = nil
46
+ end
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
64
+ end
65
+
66
+ # Configure ssl certificate to HTTP object
67
+ # === Argument
68
+ # * <tt>http</tt> -- Net::HTTP object
69
+ def add_certificate(http)
70
+ if base_credential.is_a? Credential::Certificate
71
+ http.cert = base_credential.cert
72
+ http.key = base_credential.key
73
+ else
74
+ http.cert = nil
75
+ http.key = nil
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,138 @@
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
+ def set_config(env, override_configurations = {})
29
+ @config = env.is_a?(Config) ? env : Config.config(env, override_configurations)
30
+ end
31
+
32
+ alias_method :config=, :set_config
33
+ end
34
+
35
+ # Config class is used to hold the configurations.
36
+ # == Examples
37
+ # # To load configurations from file
38
+ # Config.load('config/paypal.yml', 'development')
39
+ #
40
+ # # Get configuration
41
+ # Config.config # load default configuration
42
+ # Config.config(:development) # load development configuration
43
+ # Config.config(:development, :app_id => "XYZ") # Override configuration
44
+ #
45
+ # # Read configuration attributes
46
+ # config = Config.config
47
+ # config.username
48
+ # config.end_point
49
+ class Config
50
+ attr_accessor :username, :password, :signature, :app_id, :cert_path,
51
+ :token, :token_secret, :subject,
52
+ :http_timeout, :http_retry, :http_proxy, :ca_file,
53
+ :device_ipaddress, :sandbox_email_address,
54
+ :mode, :end_point, :merchant_end_point, :platform_end_point, :redirect_url, :dev_central_url,
55
+ :logfile
56
+
57
+ # Create Config object
58
+ # === Options(Hash)
59
+ # * <tt>username</tt> -- Username
60
+ # * <tt>password</tt> -- Password
61
+ # * <tt>signature</tt> (Optional if certificate present) -- Signature
62
+ # * <tt>app_id</tt> -- Application ID
63
+ # * <tt>cert_path</tt> (Optional if signature present) -- Certificate file path
64
+ def initialize(options)
65
+ options.each do |key, value|
66
+ send("#{key}=", value)
67
+ end
68
+ end
69
+
70
+ class << self
71
+
72
+ @@config_cache = {}
73
+
74
+ # Load configurations from file
75
+ # === Arguments
76
+ # * <tt>file_name</tt> -- Configuration file path
77
+ # * <tt>default_environment</tt> (Optional) -- default environment configuration to load
78
+ # === Example
79
+ # Config.load('config/paypal.yml', 'development')
80
+ def load(file_name, default_env = default_environment)
81
+ @@configurations = read_configurations(file_name)
82
+ @@default_environment = default_env
83
+ config
84
+ end
85
+
86
+ # Get default environment name
87
+ def default_environment
88
+ @@default_environment ||= ENV['PAYPAL_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'] || ENV['ENV'] || "development"
89
+ end
90
+
91
+ # Create or Load Config object based on given environment and configurations.
92
+ # === Attributes
93
+ # * <tt>env</tt> (Optional) -- Environment name
94
+ # * <tt>override_configuration</tt> (Optional) -- Override the configuration given in file.
95
+ # === Example
96
+ # Config.config
97
+ # Config.config(:development)
98
+ # Config.config(:development, { :app_id => "XYZ" })
99
+ def config(env = default_environment, override_configuration = {})
100
+ if env.is_a? Hash
101
+ override_configuration = env
102
+ env = default_environment
103
+ end
104
+ env = (env || default_environment).to_s
105
+ raise "Configuration[#{env}] NotFound" unless configurations[env]
106
+ if override_configuration.nil? or override_configuration.empty?
107
+ @@config_cache[env] ||= new configurations[env]
108
+ else
109
+ new configurations[env].merge(override_configuration)
110
+ end
111
+ end
112
+
113
+ def logger=(logger)
114
+ Logging.logger = logger
115
+ end
116
+
117
+ def logger
118
+ Logging.logger
119
+ end
120
+
121
+ private
122
+ # Read configurations from the given file name
123
+ # === Arguments
124
+ # * <tt>file_name</tt> (Optional) -- Configuration file path
125
+ def read_configurations(file_name = "config/paypal.yml")
126
+ erb = ERB.new(File.read(file_name))
127
+ erb.filename = file_name
128
+ YAML.load(erb.result)
129
+ end
130
+
131
+ # Get raw configurations in Hash format.
132
+ def configurations
133
+ @@configurations ||= read_configurations
134
+ end
135
+
136
+ end
137
+ end
138
+ end