paypal_api 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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