paypal-sdk-core 0.1.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 (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