paypal_api 0.3.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.
@@ -0,0 +1,125 @@
1
+ module Paypal
2
+ class Request
3
+
4
+ cattr_accessor :environment, :user, :pwd, :signature, :version
5
+
6
+ PAYPAL_VERSION = "84.0"
7
+ @@paypal_info = nil
8
+ @@paypal_endpoint = nil
9
+
10
+ # class instance variables (unique per subclass, not unique per instance)
11
+ # used for dynamically created request classes
12
+ @required = []
13
+ @sequential = []
14
+
15
+ attr_accessor :payload, :error_message
16
+
17
+ def initialize(payload = {})
18
+ config
19
+
20
+ @payload = payload
21
+ @payload.each do |k,v|
22
+ self.send("#{k}=", v)
23
+ end
24
+ end
25
+
26
+ def valid?
27
+ begin
28
+ params_fulfilled?
29
+ validate!
30
+ return true
31
+ rescue
32
+ @error_message = $!.message
33
+ return false
34
+ end
35
+ end
36
+
37
+ def self.required_keys
38
+ @required
39
+ end
40
+
41
+ def self.sequential_keys
42
+ @sequential
43
+ end
44
+
45
+ def paypal_endpoint_with_defaults
46
+ return "#{@@paypal_endpoint}?PWD=#{@@paypal_info["password"] || self.class.pwd}" +
47
+ "&USER=#{@@paypal_info["username"] || self.class.user}" +
48
+ "&SIGNATURE=#{@@paypal_info["signature"] || self.class.signature}" +
49
+ "&VERSION=#{self.class.version || PAYPAL_VERSION}"
50
+ end
51
+
52
+ def sequentials_string
53
+ self.class.sequential_keys.map{|k| self.send(k).to_query_string }.join
54
+ end
55
+
56
+ def request_string
57
+ (@payload.keys | self.class.required_keys).inject(paypal_endpoint_with_defaults + sequentials_string) do |acc, key|
58
+ # if key signature is hash or optional...
59
+ "#{acc}&#{to_key(key)}=#{escape_uri_component(self.send(key))}"
60
+ end
61
+ end
62
+
63
+ # separated out so as not to stub Kernel.open in tests
64
+ def make_request
65
+ response = open(request_string)
66
+ return Paypal::Response.new(response)
67
+ end
68
+
69
+ def make(&block)
70
+ params_fulfilled?
71
+ validate!
72
+
73
+ begin
74
+ response = make_request
75
+
76
+ if block
77
+ yield response
78
+ else
79
+ return response
80
+ end
81
+ rescue OpenURI::HTTPError => error
82
+ status_code = error.io.status[0]
83
+ # Rails.logger.info "[ERROR][Paypal] #{error.message } : #{error.backtrace} " if @@rails
84
+ raise $!
85
+ # rescue Timeout::Error => time_out_error
86
+ # Rails.logger.info "[ERROR][Timeout Error] #{time_out_error.message} : #{time_out_error.backtrace}" if @@rails
87
+ # raise $!
88
+ rescue => err
89
+ # Rails.logger.info "[ERROR][Something went wrong] #{err.message} : #{err.backtrace}" if @@rails
90
+ raise $!
91
+ end
92
+ end
93
+
94
+ protected
95
+
96
+ include Paypal::Formatters
97
+
98
+ # override for custom request validation
99
+ def validate!
100
+ return true
101
+ end
102
+
103
+ def config
104
+
105
+ @@paypal_info = {}
106
+
107
+ @@paypal_info = get_info if Module.const_defined?("Rails")
108
+
109
+ @@paypal_endpoint = (@@paypal_info["environment"] == "production" || Paypal::Request.environment == "production") ? "https://api-3t.paypal.com/nvp" : "https://api-3t.sandbox.paypal.com/nvp"
110
+
111
+ end
112
+
113
+ def get_info
114
+ YAML.load(::ERB.new(File.new(Rails.root.join("config", "paypal.yml")).read).result)[Rails.env]
115
+ end
116
+
117
+ def params_fulfilled?
118
+ self.class.required_keys.each do |method|
119
+ raise Paypal::InvalidRequest, "missing required field: #{method}" if self.send(method).nil?
120
+ end
121
+
122
+ # TODO: check if sequential has been fulfilled
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,77 @@
1
+ module Paypal
2
+ class Response
3
+
4
+ attr_accessor :raw_response, :parsed_response, :error_code
5
+
6
+ def initialize(stringio)
7
+ @raw_response = stringio.class == StringIO ? stringio.read : stringio
8
+ @parsed_response = CGI.parse(@raw_response)
9
+
10
+ @success = @parsed_response["ACK"] == ["Success"]
11
+
12
+ unless @success
13
+ @error_message = @parsed_response["L_LONGMESSAGE0"][0]
14
+ @error_code = @parsed_response["L_ERRORCODE0"][0]
15
+ end
16
+ end
17
+
18
+ def [](key)
19
+ if key.class == Symbol
20
+ @parsed_response[symbol_to_key(key)][0]
21
+ else
22
+ @parsed_response[key][0]
23
+ end
24
+ end
25
+
26
+ def success?
27
+ return @success
28
+ end
29
+
30
+ def method_missing?(key)
31
+ return @parsed_response[symbol_to_key(key)]
32
+ end
33
+
34
+ def error_input
35
+ @@error_codes[@error_code]
36
+ end
37
+
38
+ def error_field
39
+ @@error_codes[@error_code] ? @@human_readable[@@error_codes[@error_code]] : nil
40
+ end
41
+
42
+ def error_message
43
+ @error_message + "[#{@error_code}]"
44
+ end
45
+
46
+ private
47
+
48
+ def symbol_to_key(symbol)
49
+ return symbol.to_s.gsub(/[^0-9a-z]/i, "").upcase
50
+ end
51
+ end
52
+ end
53
+
54
+ class Paypal::Response
55
+ @@error_codes = {
56
+ '10527' => :acct,
57
+ '10525' => :amt,
58
+ '10508' => :exp_date,
59
+ '10504' => :cvv2,
60
+ '10502' => :acct,
61
+ '10501' => :reference_id,
62
+ '10509' => :ip_address,
63
+ '10510' => :acct,
64
+ '10519' => :acct,
65
+ '10521' => :acct,
66
+ '10526' => :currency_code
67
+ }
68
+
69
+ @@human_readable = {
70
+ :acct => "credit card number",
71
+ :amt => "charge amount",
72
+ :exp_date => "expiration date",
73
+ :cvv2 => "security code",
74
+ :reference_id => "billing agreement",
75
+ :currency_code => "currency code"
76
+ }
77
+ end
@@ -0,0 +1,3 @@
1
+ module Paypal
2
+ VERSION = "0.3.0"
3
+ end
data/lib/paypal_api.rb ADDED
@@ -0,0 +1,21 @@
1
+ require "cgi"
2
+ require "open-uri"
3
+ require 'active_support/core_ext/class/attribute_accessors'
4
+
5
+ module Paypal
6
+ class InvalidRequest < StandardError; end
7
+
8
+ class InvalidParameter < StandardError; end
9
+
10
+ class PaypalApiError < StandardError; end
11
+ end
12
+
13
+ $:.push File.expand_path("../../lib", __FILE__)
14
+
15
+ require "paypal_api/version"
16
+ require "paypal_api/apis/api"
17
+ require "paypal_api/support/parameter"
18
+ require "paypal_api/support/response"
19
+ require "paypal_api/support/request"
20
+ require "paypal_api/apis/payments_pro"
21
+ require "paypal_api/apis/mass_pay"
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "paypal_api/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "paypal_api"
7
+ s.version = Paypal::VERSION
8
+ s.authors = ["Matt Handler"]
9
+ s.email = ["matt.handler@gmail.com"]
10
+ s.homepage = "https://github.com/matthandlersux/paypal_api"
11
+ s.summary = %q{an interface to paypals api}
12
+ s.description = %q{alpha - currently covers part of payments pro and all of mass pay}
13
+
14
+ s.rubyforge_project = "paypal_api"
15
+
16
+ s.files = `git ls-files`.split("\n") + ["init.rb"]
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_dependency "activesupport", "3.2.1"
22
+
23
+ s.add_development_dependency "rspec", "~> 2.6"
24
+ s.add_development_dependency "rspec-mocks", "~> 2.6"
25
+ s.add_development_dependency "rake", "0.8.7"
26
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'paypal_api' # and any other gems you need
5
+
6
+ RSpec.configure do |config|
7
+ # some (optional) config here
8
+ # config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.filter_run_excluding :slow_paypal => true
10
+ end
@@ -0,0 +1,189 @@
1
+ require "spec_helper"
2
+
3
+ describe Paypal::Api::Parameter do
4
+ before do
5
+ @api = Paypal::Api
6
+ end
7
+
8
+ describe Paypal::Api::Enum do
9
+ it "should only allow enumerated input" do
10
+ param = @api::Enum.new("first", "second", "third")
11
+
12
+ param.parse("first").should eq("first")
13
+ param.parse("second").should eq("second")
14
+ param.parse("third").should eq("third")
15
+
16
+ expect {
17
+ param.parse("anything else")
18
+ }.to raise_exception(Paypal::InvalidParameter)
19
+ end
20
+
21
+ describe "hash enumerations" do
22
+ before(:each) do
23
+ @param = @api::Enum.new({:thing => "OK", :that => "TEO"})
24
+ end
25
+
26
+ it "should allow symbols to have formatted values as output" do
27
+ @param.parse(:thing).should eq("OK")
28
+ @param.parse(:that).should eq("TEO")
29
+ end
30
+
31
+ it "should not respond to symbols that aren't in the hash" do
32
+ expect {
33
+ @param.parse("anything_else")
34
+ }.to raise_exception(Paypal::InvalidParameter)
35
+ end
36
+
37
+ it "should know it's a hash enumeration" do
38
+ @param.instance_variable_get("@hash_enum").should be_true
39
+ end
40
+ end
41
+
42
+ it "should coerce symbols to strings" do
43
+ param = @api::Enum.new("First", "Second", "ThirdThing")
44
+
45
+ param.parse(:first).should eq("First")
46
+ param.parse(:second).should eq("Second")
47
+ param.parse(:third_thing).should eq("ThirdThing")
48
+
49
+ expect {
50
+ param.parse(:anything_else)
51
+ }.to raise_exception(Paypal::InvalidParameter)
52
+ end
53
+ end
54
+
55
+ describe Paypal::Api::Coerce do
56
+ it "should allow a well formed coercion" do
57
+ param = @api::Coerce.new( lambda { |val| return [1, "1", true].include?(val) ? 1 : 0 } )
58
+
59
+ param.parse(1).should eq(1)
60
+ param.parse("1").should eq(1)
61
+ param.parse(true).should eq(1)
62
+
63
+ param.parse(23).should eq(0)
64
+ param.parse("adfa").should eq(0)
65
+ end
66
+ end
67
+
68
+ describe Paypal::Api::Sequential do
69
+ before do
70
+ class Test < @api
71
+ set_request_signature :tester, {
72
+ :sequential => @api::Sequential.new({:l_string => String, :l_fixnum => Fixnum, :l_set_category => Optional.new(String)})
73
+ }
74
+ end
75
+
76
+ @request = Test.tester
77
+ @request.sequential.push({:l_string => "sasdf", :l_fixnum => 23, :l_set_category => "asdfasdf"})
78
+
79
+
80
+ expect {
81
+ @request.sequential.push({:l_string => "sass"})
82
+ }.to raise_exception Paypal::InvalidParameter
83
+
84
+ expect {
85
+ @request.sequential.push({:l_string => "sass", :l_fixnum => "string"})
86
+ }.to raise_exception Paypal::InvalidParameter
87
+ end
88
+
89
+ it "should allow nestedly defined params" do
90
+ @request.sequentials_string.should include("sasdf")
91
+ @request.sequentials_string.should include("23")
92
+ end
93
+
94
+ it "should allow optional params" do
95
+ @request.sequential.push({:l_string => "alf", :l_fixnum => 22})
96
+ @request.sequentials_string.should_not include("L_SETCATEGORY1")
97
+ end
98
+
99
+ it "should number the items" do
100
+ @request.sequential.push({:l_string => "alf", :l_fixnum => 22})
101
+ @request.sequentials_string.should include("L_FIXNUM1")
102
+ @request.sequentials_string.should include("L_SETCATEGORY0")
103
+ end
104
+
105
+ it "should keep the first underscore" do
106
+ param = @api::Sequential.new({:l_test => String})
107
+ param.to_key(:l_test, 0).should eq("L_TEST0")
108
+ param.to_key(:l_set_category, 1).should eq("L_SETCATEGORY1")
109
+ end
110
+
111
+ it "should allow a list length limit to be set" do
112
+ expect {
113
+ param = @api::Sequential.new({:l_test => String}, 2)
114
+ }.to_not raise_exception
115
+ end
116
+
117
+ it "should raise an InvalidParameter exception when more than the limit is pushed" do
118
+ param = @api::Sequential.new({:test => String}, 2)
119
+ param.push(:test => "ok")
120
+ param.push(:test => "asdf")
121
+
122
+ expect {
123
+ param.push(:test => "asdfasdf")
124
+ }.to raise_exception(Paypal::InvalidParameter)
125
+ end
126
+
127
+ describe "when a to_key proc is provided" do
128
+ it "should use the proc to output keys" do
129
+ @lambda = lambda {|key, i|
130
+ "someThing#{i}.#{key}"
131
+ }
132
+ param = @api::Sequential.new({:test => String}, 2, @lambda)
133
+ param.push(:test => "ok")
134
+ param.push(:test => "asdf")
135
+
136
+ param.to_query_string.should eq("&someThing0.test=ok&someThing1.test=asdf")
137
+ end
138
+ end
139
+ end
140
+
141
+ describe Paypal::Api::Hash do
142
+
143
+ end
144
+
145
+ describe Paypal::Api::Optional do
146
+
147
+ it "should allow optional enum" do
148
+ param = @api::Optional.new(@api::Enum.new("test", "tamp"))
149
+
150
+ expect {
151
+ param.parse("tisk")
152
+ }.to raise_exception(Paypal::InvalidParameter)
153
+
154
+ param.parse("test").should eq("test")
155
+ end
156
+
157
+ it "should allow optional regexp" do
158
+ param = @api::Optional.new(/test/)
159
+
160
+ expect {
161
+ param.parse("tisk")
162
+ }.to raise_exception(Paypal::InvalidParameter)
163
+
164
+ param.parse("test").should eq("test")
165
+ param.parse(" sdf a test").should eq("test")
166
+ end
167
+
168
+ it "should allow optional class specifiers" do
169
+ param = @api::Optional.new(String)
170
+
171
+ expect {
172
+ param.parse(2)
173
+ }.to raise_exception(Paypal::InvalidParameter)
174
+
175
+ param.parse("test").should eq("test")
176
+ param.parse(" sdf a test").should eq(" sdf a test")
177
+ end
178
+
179
+ it "should allow a hash specifier"
180
+
181
+ describe "when hash specifier provided" do
182
+ it "should still be allowed to have optional within"
183
+
184
+ it "should validate required fields"
185
+
186
+ it "should dot the fields together somehow?"
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,143 @@
1
+ require "spec_helper"
2
+
3
+ describe Paypal::Request do
4
+
5
+ it "should create a well formed url" do
6
+ request = Paypal::Request.new
7
+
8
+ url = request.paypal_endpoint_with_defaults
9
+ URI.parse(url)
10
+ end
11
+
12
+ it "should allow you to set the environment" do
13
+ Paypal::Request.environment = "production"
14
+
15
+ Paypal::Request.new
16
+ Paypal::Request.class_variable_get("@@paypal_endpoint").should eq("https://api-3t.paypal.com/nvp")
17
+
18
+ Paypal::Request.environment = "development"
19
+
20
+ Paypal::Request.new
21
+ Paypal::Request.class_variable_get("@@paypal_endpoint").should eq("https://api-3t.sandbox.paypal.com/nvp")
22
+ end
23
+
24
+ it "should allow you to set the user pwd and signature" do
25
+ Paypal::Request.user = "asdf"
26
+ Paypal::Request.pwd = "zxcv"
27
+ Paypal::Request.signature = ";lkj"
28
+
29
+ request = Paypal::Request.new
30
+ request.request_string.should include("USER=asdf")
31
+ request.request_string.should include("PWD=zxcv")
32
+ request.request_string.should include("SIGNATURE=;lkj")
33
+ end
34
+
35
+ it "should allow you to set the version" do
36
+ Paypal::Request.version = "76.0"
37
+
38
+ request = Paypal::Request.new
39
+ request.request_string.should include("VERSION=76.0")
40
+ end
41
+
42
+ describe "should look for a config file if we're in rails" do
43
+
44
+ describe "for production" do
45
+ before do
46
+ Module.should_receive(:const_defined?).and_return(true)
47
+
48
+ Paypal::Request.any_instance.should_receive(:get_info).and_return({"environment" => "production" })
49
+ end
50
+
51
+ it "should use the real server" do
52
+ Paypal::Request.new
53
+ Paypal::Request.class_variable_get("@@paypal_endpoint").should eq("https://api-3t.paypal.com/nvp")
54
+ end
55
+ end
56
+
57
+ describe "for anything else" do
58
+ before do
59
+ Paypal::Request.any_instance.stub(:get_info).and_return({
60
+ "production" => { "environment" => "development" }
61
+ })
62
+ end
63
+
64
+ it "should use the sandbox server" do
65
+ Paypal::Request.new
66
+ Paypal::Request.class_variable_get("@@paypal_endpoint").should eq("https://api-3t.sandbox.paypal.com/nvp")
67
+ end
68
+ end
69
+ end
70
+
71
+ context "when making requests" do
72
+ before do
73
+ @api = Paypal::Api
74
+
75
+ class Test < @api
76
+ set_request_signature :tester, {
77
+ :test_field => "something",
78
+ :optional => @api::Optional.new(String),
79
+ :string => String,
80
+ :fixnum => Fixnum,
81
+ :default => @api::Default.new("tamp", Fixnum),
82
+ :enum => @api::Enum.new("One", "Two"),
83
+ :sequential => @api::Sequential.new({:l_string => String, :l_fixnum => Fixnum, :l_set_category => Optional.new(String)})
84
+ }
85
+ end
86
+
87
+ @request = Test.tester({
88
+ :string => "adsafasdf",
89
+ :fixnum => 23,
90
+ :enum => :one
91
+ })
92
+
93
+ @request.sequential.push({:l_string => "tamp", :l_fixnum => 2323})
94
+
95
+ @string = @request.request_string
96
+ end
97
+
98
+ it "should create a request string with params" do
99
+ @string.should include("TESTFIELD")
100
+ @string.should include("something")
101
+ @string.should include("adsafasdf")
102
+ @string.should_not include("OPTIONAL")
103
+ @string.should include("ENUM=One")
104
+ end
105
+
106
+ it "should add userpass and secret fields" do
107
+ @string.should include("PWD")
108
+ @string.should include("USER")
109
+ @string.should include("SIGNATURE")
110
+ @string.should include("VERSION")
111
+ end
112
+
113
+ it "should use a paypal endpoint" do
114
+ @string.should include(".paypal.com/nvp")
115
+ end
116
+
117
+ it "should have required keys set" do
118
+ @request.class.required_keys.should eq([:test_field, :string, :fixnum, :default, :enum])
119
+ end
120
+
121
+ it "should include sequential stuff in the request string" do
122
+ @string.should include("L_STRING0")
123
+ @string.should include("L_FIXNUM0")
124
+ end
125
+
126
+ it "should return a well formed url" do
127
+ parsed = nil
128
+ expect {
129
+ parsed = URI.parse(@string)
130
+ }.to_not raise_exception
131
+
132
+ parsed.query.should include("TESTFIELD")
133
+ parsed.query.should include("L_STRING0")
134
+
135
+ hash = nil
136
+ expect {
137
+ hash = CGI.parse parsed.query
138
+ }.to_not raise_exception
139
+
140
+ hash.keys.should include("L_STRING0")
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,33 @@
1
+ describe Paypal::Response do
2
+ it "should initialize with a stringio" do
3
+
4
+ end
5
+
6
+ describe "bracket access" do
7
+ it "should allow symbol access" do
8
+
9
+ end
10
+
11
+ it "should allow string access" do
12
+
13
+ end
14
+
15
+ it "should convert symbols to the right formatting" do
16
+
17
+ end
18
+ end
19
+
20
+ describe "method access" do
21
+ it "should succeed" do
22
+
23
+ end
24
+
25
+ it "should fail when the resulting key doesn't match" do
26
+
27
+ end
28
+
29
+ it "should convert symbols to the right formatting" do
30
+
31
+ end
32
+ end
33
+ end