paypal_api 0.3.0 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -3,4 +3,5 @@
3
3
  .bundle
4
4
  Gemfile.lock
5
5
  pkg/*
6
- *~
6
+ *~
7
+ spec/tmp/*
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 Matt Handler
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown CHANGED
@@ -20,8 +20,8 @@ request.amt = 10.00
20
20
 
21
21
  # Add a list type field
22
22
  request.item.push {
23
- :l_email => "bro@dudeman.com",
24
- :l_amt => 23.0
23
+ :email => "bro@dudeman.com",
24
+ :amt => 23.0
25
25
  }
26
26
 
27
27
  response = request.make
@@ -56,14 +56,55 @@ test:
56
56
  username: "user_api1.something.com"
57
57
  password: "some_password_they_gave_you"
58
58
  signature: "some_signature"
59
+ application_id: "APP-80W284485P519543T" # only necessary for adaptive payments api
59
60
 
60
61
  production:
61
62
  environment: "production"
62
63
  username: <%= ENV["PAYPAL_USERNAME"] %>
63
64
  password: <%= ENV["PAYPAL_PASSWORD"] %>
64
65
  signature: <%= ENV["PAYPAL_SIGNATURE"] %>
66
+ application_id <%= ENV["PAYPAL_APP_ID"] %>
65
67
  ```
66
68
 
69
+ ## Ipn Messages
70
+
71
+ there is an ipn message model generator: `rails generate paypal:ipn_message`, it will create
72
+ a migration and the IpnMessage model.
73
+
74
+ you must edit the route and add a handler like so:
75
+
76
+ ```ruby
77
+ # config/routes
78
+ MyApp::Application.routes.draw do
79
+
80
+ match '/ipn_message', to: 'handlers#handle_ipn', as: 'ipn_message'
81
+
82
+ end
83
+
84
+ # app/controllers/handlers_controller.rb
85
+ class HandlersController < ApplicationController
86
+
87
+ def handle_ipn
88
+ @ipn_message = IpnMessage.create_from_message(params) # provided by generator
89
+
90
+ @ipn_message.success?
91
+ @ipn_message.correlation_id
92
+ @ipn_message.transaction_id # not always provided
93
+ @ipn_message.message # raw text of the message
94
+ end
95
+
96
+ end
97
+ ```
98
+
99
+ ## Testing
100
+
101
+ i'm an rspec kinda guy. note that i have test for actual requests against the paypal
102
+ sandbox server, but they are off by default. remove the line about `:slow_paypal` in
103
+ `spec/spec_helper.rb` to turn these back on.
104
+
105
+ also note that, for the Adaptive Payments api, an application id is required, for testing,
106
+ you can use this: "APP-80W284485P519543T" ([https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_APGettingStarted](https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_APGettingStarted))
107
+
67
108
  # Current Status
68
109
 
69
110
  alpha
@@ -92,6 +133,11 @@ like to contribute, i've made it pretty easy to add compatibility for a new api
92
133
 
93
134
  ## Payments Pro
94
135
 
136
+ note that paypal has a strict policy about who they approve for
137
+ payments pro. you can sign up for it, and start testing it, but as soon as your first
138
+ real charge goes through, they will vet your website and many people have gotten burned
139
+ by this (including me, sad sad me...).
140
+
95
141
  * do_direct_payment - &#10003;
96
142
 
97
143
  * do_reference_transaction - &#10003;
@@ -152,6 +198,11 @@ note that you need to request that paypal enable mass pay for your account befor
152
198
 
153
199
  ## Adaptive Payments
154
200
 
201
+ this api is very different from the others... getting it to work with the way i built things already
202
+ required some ugly stuff, but it helps keep the gem consistent from the outside.
203
+
204
+ * pay - &#10003;
205
+
155
206
  ## Adaptive Accounts
156
207
 
157
208
  ## Invoicing
@@ -0,0 +1,32 @@
1
+ require "rails/generators"
2
+ require "rails/generators/migration"
3
+
4
+ module Paypal
5
+ module Generators
6
+ class IpnMessageGenerator < Rails::Generators::Base
7
+ # include Rails::Generators::ResourceHelpers
8
+ include Rails::Generators::Migration
9
+
10
+ source_root File.expand_path("../../templates", __FILE__)
11
+
12
+ desc "creates a migration to store ipn messages, adds some model helpers, adds a route"
13
+
14
+ def create_ipn_message_migration
15
+ migration_template "migration.rb", "db/migrate/add_ipn_messages.rb"
16
+ end
17
+
18
+ def self.next_migration_number(path)
19
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
20
+ end
21
+
22
+ def add_to_ipn_message_handling_model
23
+ copy_file "ipn_message.rb", "app/models/ipn_message.rb"
24
+ end
25
+
26
+ def add_route
27
+ route %Q{match "ipn_message", :to => "callback_controller#handle_ipn", :as => :ipn_message} if FileTest.exists?("config/routes.rb")
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,28 @@
1
+ class IpnMessage < ActiveRecord::Base
2
+
3
+ alias_method :success?, :success
4
+
5
+ def self.create_from_message(hash)
6
+ ipn_message = IpnMessage.new
7
+
8
+ ipn_message.body = hash.to_yaml
9
+ ipn_message.success = hash["ACK"] == "Success"
10
+ ipn_message.correlation_id = hash["correlation_id"]
11
+ ipn_message.transaction_id = hash["transaction_id"]
12
+
13
+ ipn_message.save
14
+ return ipn_message
15
+ end
16
+
17
+ # returns hash of unique_id:string => status:bool
18
+ def unique_ids
19
+ hash = YAML.load(self.body)
20
+
21
+ return hash.inject({}){|acc, (k,v)|
22
+ k.to_s =~ /unique_id_(\d+)/
23
+ acc[v] = hash["status_#{$1}"] == "Completed" if $1
24
+ acc
25
+ }
26
+ end
27
+
28
+ end
@@ -0,0 +1,13 @@
1
+ class CreateIpnMessages < ActiveRecord::Migration
2
+ def change
3
+ create_table :ipn_messages do |t|
4
+ t.text :message
5
+ t.string :correlation_id
6
+ t.string :transaction_id
7
+ t.string :tracking_id
8
+ t.boolean :success
9
+
10
+ t.timestamps
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,4 @@
1
+ module Paypal
2
+ class AdaptiveAccounts < Paypal::Api
3
+ end
4
+ end
@@ -0,0 +1,183 @@
1
+ # note: things got a little weird here because the paypal apis
2
+ # are so different, but i tried to make it work smoothly... at least
3
+ # it is from outside interaction... wahhh
4
+
5
+ module Paypal
6
+
7
+ class AdaptivePaymentsResponse < Response
8
+ def initialize(stringio)
9
+ @raw_response = stringio.class == StringIO ? stringio.read : stringio
10
+ @parsed_response = CGI.parse(@raw_response)
11
+
12
+ @success = @parsed_response["responseEnvelope.ack"] == ["Success"]
13
+
14
+ unless @success
15
+ # "responseEnvelope.timestamp"=>["2012-02-29T13:35:28.528-08:00"],
16
+ # "responseEnvelope.ack"=>["Failure"],
17
+ # "responseEnvelope.correlationId"=>["ca0befbd1fe0b"],
18
+ # "responseEnvelope.build"=>["2486531"],
19
+ # "error(0).errorId"=>["560022"],
20
+ # "error(0).domain"=>["PLATFORM"],
21
+ # "error(0).subdomain"=>["Application"],
22
+ # "error(0).severity"=>["Error"],
23
+ # "error(0).category"=>["Application"],
24
+ # "error(0).message"=>["The X-PAYPAL-APPLICATION-ID header contains an invalid value"],
25
+ # "error(0).parameter(0)"=>["X-PAYPAL-APPLICATION-ID"]
26
+
27
+ @error_message = @parsed_response["error(0).message"][0]
28
+ @error_code = @parsed_response["error(0).errorId"][0]
29
+ @paypal_error_field = @parsed_response["error(0).parameter(0)"][0]
30
+ end
31
+ end
32
+
33
+ protected
34
+
35
+ def symbol_to_key(symbol)
36
+ case symbol
37
+ when :correlation_id
38
+ return "responseEnvelope.correlationId"
39
+ else
40
+ return Paypal::Api.symbol_to_lower_camel(symbol)
41
+ end
42
+ end
43
+ end
44
+
45
+ class AdaptivePaymentsRequest < Request
46
+ # customize validation here
47
+ attr_accessor :ip_address
48
+
49
+ def self.api_endpoint
50
+ "https://svcs.paypal.com/AdaptivePayments/#{api_method.capitalize}"
51
+ end
52
+
53
+ def self.api_sandbox_endpoint
54
+ "https://svcs.sandbox.paypal.com/AdaptivePayments/#{api_method.capitalize}"
55
+ end
56
+
57
+ def headers
58
+ {
59
+ "X-PAYPAL-SECURITY-USERID" => user,
60
+ "X-PAYPAL-SECURITY-PASSWORD" => password,
61
+ "X-PAYPAL-SECURITY-SIGNATURE" => signature,
62
+ "X-PAYPAL-DEVICE-IPADDRESS" => @ip_address,
63
+ "X-PAYPAL-REQUEST-DATA-FORMAT" => "NV",
64
+ "X-PAYPAL-RESPONSE-DATA-FORMAT" => "NV",
65
+ "X-PAYPAL-APPLICATION-ID" => application_id
66
+ }
67
+ end
68
+
69
+ def process_response(response)
70
+ return AdaptivePaymentsResponse.new(response)
71
+ end
72
+
73
+ def make_request
74
+ if @ip_address.nil?
75
+ throw Paypal::InvalidRequest, "need an ip address"
76
+ else
77
+ super
78
+ end
79
+ end
80
+
81
+ def to_key(symbol)
82
+ if symbol.is_a?(String)
83
+ return symbol
84
+ else
85
+ case symbol
86
+ when :request_envelope_error_language then
87
+ return "requestEnvelope.errorLanguage"
88
+ # when :reverse_all_parallel_payments_on_error then
89
+ # return "reverseAllParallelPaymentsonError" # may just be a typo in docs, need to test
90
+ when :request_envelope_detail_level then
91
+ return "requestEnvelope.detailLevel"
92
+ when :sender_use_credentials
93
+ return "sender.useCredentials"
94
+ when :method
95
+ return "METHOD"
96
+ else
97
+ #camelcaps but first letter is lowercase
98
+ return Paypal::Api.symbol_to_lower_camel(symbol)
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ class AdaptivePayments < Paypal::Api
105
+
106
+ set_request_signature :pay, {
107
+ # standard params
108
+ :action_type => Enum.new({:pay => "PAY", :create => "CREATE", :pay_primary => "PAY_PRIMARY"}),
109
+ :receiver => Sequential.new({
110
+ :email => String,
111
+ :amount => Float,
112
+ :primary => Optional.new(bool_class),
113
+ :invoice_id => Optional.new(String), # max 127 char
114
+ :payment_type => Optional.new(Enum.new({
115
+ :goods => "GOODS",
116
+ :service => "SERVICE",
117
+ :personal => "PERSONAL",
118
+ :cash_advance => "CASHADVANCE",
119
+ :digital_goods => "DIGITALGOODS"
120
+ })),
121
+ :payment_sub_type => Optional.new(String),
122
+ :phone => Optional.new({
123
+ :country_code => String,
124
+ :phone_number => String,
125
+ :extension => Optional.new(String)
126
+ })
127
+ }, 6, lambda {|key, i| "receiverList.receiver(#{i}).#{Paypal::AdaptivePaymentsRequest.new.to_key(key)}" }),
128
+ :currency_code => Default.new("USD", String),
129
+ :cancel_url => String,
130
+ :return_url => String,
131
+
132
+ # parallel payments
133
+ # request is considered parallel if more than one receiver is added
134
+ :reverse_all_parallel_payments_on_error => Optional.new(bool_class),
135
+
136
+ # chained payments
137
+ # choose one of your receivers to be primary = true and the rest false to initiate a chained payment
138
+
139
+ # implicit payments
140
+ # if you are the api caller and you put in your own email address here, it is automatically approved
141
+ :sender_email => Optional.new(String),
142
+
143
+ # preapproval payments
144
+ # if you provide the following, and it is accurate, the payment will automatically be approved
145
+ :preapproval_key => Optional.new(String),
146
+ :pin => Optional.new(String),
147
+
148
+
149
+ :client_details => Optional.new({
150
+ :application_id => String,
151
+ :customer_id => String,
152
+ :customer_type => String,
153
+ :device_id => String,
154
+ :geo_location => String,
155
+ :ip_address => String,
156
+ :model => String,
157
+ :partner_name => String
158
+ }),
159
+ :fees_payer => Optional.new(Enum.new({
160
+ :sender => "SENDER",
161
+ :primary_receiver => "PRIMARYRECEIVER",
162
+ :each_receiver => "EACHRECEIVER",
163
+ :secondary_only => "SECONDARYONLY"
164
+ })),
165
+ # note: FundingConstraint is unavailable to API callers with standard permission levels; for more information, refer to the section Adaptive Payments Permission Levels.
166
+ # maybe collapse into a sequential w proc
167
+ :funding_constraint => Sequential.new({
168
+ :funding_type => Enum.new({:e_check => "ECHECK", :balance => "BALANCE", :credit_card => "CREDITCARD"})
169
+ }, nil, lambda {|key, i| "fundingConstraint.allowedFundingType(#{i}).fundingTypeInfo.#{Paypal::PaymentsPro.to_key(key)}"}),
170
+ :ipn_notification_url => Optional.new(String),
171
+ :memo => Optional.new(String), # max 1000 char
172
+
173
+ :request_envelope_error_language => "en_US",
174
+ :request_envelope_detail_level => Default.new("ReturnAll", Optional.new(String)),
175
+
176
+ :sender_user_credentials => Optional.new(bool_class),
177
+
178
+ :tracking_id => Optional.new(String)
179
+
180
+ }
181
+
182
+ end
183
+ end
@@ -4,10 +4,6 @@ module Paypal
4
4
  string = string.to_s
5
5
  return URI.escape(string, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
6
6
  end
7
-
8
- def to_key(symbol)
9
- return symbol.to_s.gsub(/[^a-z0-9]/i, "").upcase
10
- end
11
7
  end
12
8
 
13
9
  class Api
@@ -102,11 +98,33 @@ module Paypal
102
98
  return symbol.to_s.downcase.split("_").map(&:capitalize).join
103
99
  end
104
100
 
101
+ def self.symbol_to_lower_camel(symbol)
102
+ cameled = symbol_to_camel(symbol)
103
+ return cameled[0].downcase + cameled.split(/./, 2).join
104
+ end
105
+
105
106
  def self.set_request_signature(name, hash)
107
+
108
+ # create Request subclass if not already created
109
+ subname = self.to_s.split("::")[1]
110
+ if !Paypal.const_defined?("#{subname}Request")
111
+ Paypal.class_eval <<-EOS
112
+ class #{subname}Request < Request
113
+ def self.parent_api
114
+ return #{self}
115
+ end
116
+ end
117
+ EOS
118
+ end
119
+
106
120
  # create request object
107
121
  class_name = "#{self.symbol_to_camel name}Request"
108
- self.class.class_eval <<-EOS
109
- class Paypal::#{class_name} < Request; end
122
+ Paypal.class_eval <<-EOS
123
+ class #{class_name} < #{subname}Request
124
+ def self.api_method
125
+ return "#{name}"
126
+ end
127
+ end
110
128
  EOS
111
129
  klass = Kernel.const_get("Paypal").const_get(class_name)
112
130
 
@@ -138,6 +156,11 @@ module Paypal
138
156
  EOS
139
157
  end
140
158
 
159
+ # hack for no Bool class :'(
160
+ def self.bool_class
161
+ return Enum.new("true", "false")
162
+ end
163
+
141
164
  # TODO: make this useful :'(
142
165
  # def set_response_signature(hash)
143
166
  # hash.each do |k,v|
@@ -0,0 +1,5 @@
1
+ module Paypal
2
+ class ButtonManager < Paypal::Api
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Paypal
2
+ class ExpressCheckout < Paypal::Api
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Paypal
2
+ class Invoicing < Paypal::Api
3
+
4
+ end
5
+ end
@@ -1,5 +1,16 @@
1
1
  module Paypal
2
- class MassPay < Paypal::Api
2
+ class MassPayApiRequest < Request
3
+
4
+ protected
5
+ def validate!
6
+ if @payee.length == 0
7
+ raise Paypal::InvalidRequest, "you pust provide at least one payee"
8
+ end
9
+ end
10
+ end
11
+
12
+ # named with api because of method overlap
13
+ class MassPayApi < Paypal::Api
3
14
  set_request_signature :mass_pay, {
4
15
  :method => "MassPay",
5
16
  :email_subject => Optional.new(String), # max 255 char
@@ -12,14 +23,4 @@ module Paypal
12
23
  }, 250, lambda {|key, i| "L_#{key.to_s.gsub("_","").upcase}#{i}"})
13
24
  }
14
25
  end
15
-
16
- class MassPayRequest
17
-
18
- protected
19
- def validate!
20
- if @payee.length == 0
21
- raise Paypal::InvalidRequest, "you pust provide at least one payee"
22
- end
23
- end
24
- end
25
26
  end
@@ -151,5 +151,81 @@ module Paypal
151
151
  :profile_id => String # max 19 char
152
152
  }
153
153
 
154
+ set_request_signature :address_verify, {
155
+
156
+ }
157
+
158
+ set_request_signature :bill_outstanding_amount, {
159
+
160
+ }
161
+
162
+ set_request_signature :callback, {
163
+
164
+ }
165
+
166
+ set_request_signature :create_recurring_payments_profile, {
167
+
168
+ }
169
+
170
+ set_request_signature :do_authorization, {
171
+
172
+ }
173
+
174
+ set_request_signature :do_express_checkout_payment, {
175
+
176
+ }
177
+
178
+ set_request_signature :do_nonreferenced_credit, {
179
+
180
+ }
181
+
182
+ set_request_signature :do_reauthorization, {
183
+
184
+ }
185
+
186
+ set_request_signature :get_balance, {
187
+
188
+ }
189
+
190
+ set_request_signature :get_billing_agreement_customer_details, {
191
+
192
+ }
193
+
194
+ set_request_signature :get_express_checkout_details, {
195
+
196
+ }
197
+
198
+ set_request_signature :get_transaction_details, {
199
+
200
+ }
201
+
202
+ set_request_signature :manage_pending_transaction_status, {
203
+
204
+ }
205
+
206
+ set_request_signature :manage_recurring_payments_profile_status, {
207
+
208
+ }
209
+
210
+ set_request_signature :refund_transaction, {
211
+
212
+ }
213
+
214
+ set_request_signature :set_customer_billing_agreement, {
215
+
216
+ }
217
+
218
+ set_request_signature :set_express_checkout, {
219
+
220
+ }
221
+
222
+ set_request_signature :transaction_search, {
223
+
224
+ }
225
+
226
+ set_request_signature :update_recurring_payments_profile, {
227
+
228
+ }
229
+
154
230
  end
155
231
  end
@@ -0,0 +1,5 @@
1
+ module Paypal
2
+ class Permissions < Paypal::Api
3
+
4
+ end
5
+ end
@@ -1,7 +1,7 @@
1
1
  module Paypal
2
2
  class Request
3
3
 
4
- cattr_accessor :environment, :user, :pwd, :signature, :version
4
+ cattr_accessor :environment, :user, :pwd, :signature, :version, :application_id
5
5
 
6
6
  PAYPAL_VERSION = "84.0"
7
7
  @@paypal_info = nil
@@ -12,7 +12,7 @@ module Paypal
12
12
  @required = []
13
13
  @sequential = []
14
14
 
15
- attr_accessor :payload, :error_message
15
+ attr_accessor :payload, :error_message, :test_request
16
16
 
17
17
  def initialize(payload = {})
18
18
  config
@@ -42,17 +42,25 @@ module Paypal
42
42
  @sequential
43
43
  end
44
44
 
45
+ def request_host
46
+ URI.parse(@@paypal_endpoint).host
47
+ end
48
+
45
49
  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
+ return "#{@@paypal_endpoint}?PWD=#{password}" +
51
+ "&USER=#{user}" +
52
+ "&SIGNATURE=#{signature}" +
53
+ "&VERSION=#{version}"
50
54
  end
51
55
 
52
56
  def sequentials_string
53
57
  self.class.sequential_keys.map{|k| self.send(k).to_query_string }.join
54
58
  end
55
59
 
60
+ def to_key(symbol)
61
+ return symbol.to_s.gsub(/[^a-z0-9]/i, "").upcase
62
+ end
63
+
56
64
  def request_string
57
65
  (@payload.keys | self.class.required_keys).inject(paypal_endpoint_with_defaults + sequentials_string) do |acc, key|
58
66
  # if key signature is hash or optional...
@@ -62,32 +70,66 @@ module Paypal
62
70
 
63
71
  # separated out so as not to stub Kernel.open in tests
64
72
  def make_request
65
- response = open(request_string)
66
- return Paypal::Response.new(response)
73
+ response = nil
74
+ if self.respond_to? :headers
75
+ uri = URI.parse(request_string)
76
+
77
+ http = Net::HTTP.new(uri.host, uri.port)
78
+ http.set_debug_output $stderr if @test_request
79
+ http.use_ssl = true
80
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
81
+
82
+ request = Net::HTTP::Get.new(uri.request_uri)
83
+ headers.each do |k,v|
84
+ request[k] = v
85
+ end
86
+
87
+ response = http.request(request).body
88
+ else
89
+ $stderr.puts(request_string) if @test_request
90
+
91
+ response = open(request_string)
92
+ end
93
+
94
+ return process_response(response)
95
+ end
96
+
97
+ def process_response(response)
98
+ Paypal::Response.new(response)
67
99
  end
68
100
 
69
101
  def make(&block)
70
102
  params_fulfilled?
71
103
  validate!
72
104
 
73
- begin
74
- response = make_request
105
+ response = make_request
75
106
 
76
- if block
77
- yield response
78
- else
79
- return response
107
+ if block
108
+ yield response
109
+ else
110
+ return response
111
+ end
112
+
113
+ do_request = lambda {
114
+
115
+ }
116
+
117
+ if @test_request
118
+ do_request.call
119
+ else
120
+ begin
121
+ do_request.call
122
+ rescue OpenURI::HTTPError => error
123
+ # status_code = error.io.status[0]
124
+ # Rails.logger.info "[ERROR][Paypal] #{error.message } : #{error.backtrace} " if @@rails
125
+ raise $!
126
+ # rescue Timeout::Error => time_out_error
127
+ # Rails.logger.info "[ERROR][Timeout Error] #{time_out_error.message} : #{time_out_error.backtrace}" if @@rails
128
+ # raise $!
129
+ rescue => err
130
+ # Rails.logger.info "[ERROR][Something went wrong] #{err.message} : #{err.backtrace}" if @@rails
131
+ raise $!
80
132
  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
133
  end
92
134
  end
93
135
 
@@ -95,18 +137,55 @@ module Paypal
95
137
 
96
138
  include Paypal::Formatters
97
139
 
140
+ def user
141
+ @@paypal_info["username"] || self.class.user
142
+ end
143
+
144
+ def signature
145
+ @@paypal_info["signature"] || self.class.signature
146
+ end
147
+
148
+ def password
149
+ @@paypal_info["password"] || self.class.pwd
150
+ end
151
+
152
+ def application_id
153
+ @@paypal_info["application_id"] || self.class.application_id
154
+ end
155
+
156
+ def version
157
+ self.class.version || PAYPAL_VERSION
158
+ end
159
+
98
160
  # override for custom request validation
99
161
  def validate!
100
162
  return true
101
163
  end
102
164
 
165
+ # for completeness
166
+ def self.parent_api
167
+ return nil
168
+ end
169
+
170
+ def self.api_method
171
+ ""
172
+ end
173
+
174
+ def self.api_endpoint
175
+ "https://api-3t.paypal.com/nvp"
176
+ end
177
+
178
+ def self.api_sandbox_endpoint
179
+ "https://api-3t.sandbox.paypal.com/nvp"
180
+ end
181
+
103
182
  def config
104
183
 
105
184
  @@paypal_info = {}
106
185
 
107
- @@paypal_info = get_info if Module.const_defined?("Rails")
186
+ @@paypal_info = get_info if Module.const_defined?("Rails") && (Module.const_get("Rails").respond_to?(:root) && !Module.const_get("Rails").root.nil?)
108
187
 
109
- @@paypal_endpoint = (@@paypal_info["environment"] == "production" || Paypal::Request.environment == "production") ? "https://api-3t.paypal.com/nvp" : "https://api-3t.sandbox.paypal.com/nvp"
188
+ @@paypal_endpoint = (@@paypal_info["environment"] == "production" || Paypal::Request.environment == "production") ? self.class.api_endpoint : self.class.api_sandbox_endpoint
110
189
 
111
190
  end
112
191
 
@@ -1,7 +1,7 @@
1
1
  module Paypal
2
2
  class Response
3
3
 
4
- attr_accessor :raw_response, :parsed_response, :error_code
4
+ attr_accessor :raw_response, :parsed_response, :error_code, :paypal_error_field
5
5
 
6
6
  def initialize(stringio)
7
7
  @raw_response = stringio.class == StringIO ? stringio.read : stringio
@@ -36,14 +36,14 @@ module Paypal
36
36
  end
37
37
 
38
38
  def error_field
39
- @@error_codes[@error_code] ? @@human_readable[@@error_codes[@error_code]] : nil
39
+ @paypal_error_field || (@@error_codes[@error_code] ? @@human_readable[@@error_codes[@error_code]] : nil)
40
40
  end
41
41
 
42
42
  def error_message
43
43
  @error_message + "[#{@error_code}]"
44
44
  end
45
45
 
46
- private
46
+ protected
47
47
 
48
48
  def symbol_to_key(symbol)
49
49
  return symbol.to_s.gsub(/[^0-9a-z]/i, "").upcase
@@ -1,3 +1,3 @@
1
1
  module Paypal
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.4"
3
3
  end
data/lib/paypal_api.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "cgi"
2
2
  require "open-uri"
3
3
  require 'active_support/core_ext/class/attribute_accessors'
4
+ require "net/http"
4
5
 
5
6
  module Paypal
6
7
  class InvalidRequest < StandardError; end
@@ -14,8 +15,18 @@ $:.push File.expand_path("../../lib", __FILE__)
14
15
 
15
16
  require "paypal_api/version"
16
17
  require "paypal_api/apis/api"
18
+
17
19
  require "paypal_api/support/parameter"
18
20
  require "paypal_api/support/response"
19
21
  require "paypal_api/support/request"
22
+
23
+ require "generators/paypal/ipn_message_generator"
24
+
20
25
  require "paypal_api/apis/payments_pro"
21
- require "paypal_api/apis/mass_pay"
26
+ require "paypal_api/apis/express_checkout"
27
+ require "paypal_api/apis/adaptive_payments"
28
+ require "paypal_api/apis/adaptive_accounts"
29
+ require "paypal_api/apis/invoicing"
30
+ require "paypal_api/apis/mass_pay"
31
+ require "paypal_api/apis/button_manager"
32
+ require "paypal_api/apis/permissions"
data/paypal_api.gemspec CHANGED
@@ -18,9 +18,11 @@ Gem::Specification.new do |s|
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
19
  s.require_paths = ["lib"]
20
20
 
21
- s.add_dependency "activesupport", "3.2.1"
21
+ # s.add_dependency "activesupport", "~> 3.1"
22
+ s.add_dependency "rails", "~> 3.1"
22
23
 
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"
24
+ s.add_development_dependency "ruby-debug19"
25
+ s.add_development_dependency "rspec-rails", "~> 2.6"
26
+ s.add_development_dependency "generator_spec", "0.8.5"
27
+ # s.add_development_dependency "rake", "0.8.7"
26
28
  end
@@ -0,0 +1,43 @@
1
+ require "rails/all"
2
+ require "spec_helper"
3
+ require "generator_spec/test_case"
4
+
5
+ describe Paypal::Generators::IpnMessageGenerator do
6
+ include GeneratorSpec::TestCase
7
+ destination File.expand_path("../../tmp", __FILE__)
8
+
9
+ before(:all) do
10
+ prepare_destination
11
+ run_generator
12
+ end
13
+
14
+ it "should add model file" do
15
+ assert_file "app/models/ipn_message.rb"
16
+ end
17
+
18
+ specify do
19
+ destination_root.should have_structure {
20
+ no_file "test.rb"
21
+ directory "app" do
22
+ directory "models" do
23
+ file "ipn_message.rb" do
24
+ contains "def self.create_from_message"
25
+ contains "def unique_ids"
26
+ end
27
+ end
28
+ end
29
+ directory "db" do
30
+ directory "migrate" do
31
+ migration "add_ipn_messages" do
32
+ contains "class CreateIpnMessages"
33
+ contains ":message"
34
+ contains ":correlation_id"
35
+ contains ":transaction_id"
36
+ contains ":tracking_id"
37
+ contains ":success"
38
+ end
39
+ end
40
+ end
41
+ }
42
+ end
43
+ end
@@ -1,3 +1,4 @@
1
+ require "rails/all"
1
2
  require "spec_helper"
2
3
 
3
4
  describe Paypal::Api::Parameter do
@@ -67,13 +68,13 @@ describe Paypal::Api::Parameter do
67
68
 
68
69
  describe Paypal::Api::Sequential do
69
70
  before do
70
- class Test < @api
71
+ class Testical < @api
71
72
  set_request_signature :tester, {
72
73
  :sequential => @api::Sequential.new({:l_string => String, :l_fixnum => Fixnum, :l_set_category => Optional.new(String)})
73
74
  }
74
75
  end
75
76
 
76
- @request = Test.tester
77
+ @request = Testical.tester
77
78
  @request.sequential.push({:l_string => "sasdf", :l_fixnum => 23, :l_set_category => "asdfasdf"})
78
79
 
79
80
 
@@ -44,6 +44,7 @@ describe Paypal::Request do
44
44
  describe "for production" do
45
45
  before do
46
46
  Module.should_receive(:const_defined?).and_return(true)
47
+ Module.should_receive(:const_get).twice.and_return(stub(:root => true))
47
48
 
48
49
  Paypal::Request.any_instance.should_receive(:get_info).and_return({"environment" => "production" })
49
50
  end
@@ -72,7 +73,7 @@ describe Paypal::Request do
72
73
  before do
73
74
  @api = Paypal::Api
74
75
 
75
- class Test < @api
76
+ class Testical < @api
76
77
  set_request_signature :tester, {
77
78
  :test_field => "something",
78
79
  :optional => @api::Optional.new(String),
@@ -84,7 +85,7 @@ describe Paypal::Request do
84
85
  }
85
86
  end
86
87
 
87
- @request = Test.tester({
88
+ @request = Testical.tester({
88
89
  :string => "adsafasdf",
89
90
  :fixnum => 23,
90
91
  :enum => :one
@@ -0,0 +1,122 @@
1
+ require "spec_helper"
2
+
3
+ describe Paypal::AdaptivePayments do
4
+
5
+ before :all do
6
+ Paypal::Request.application_id = "APP-80W284485P519543T"
7
+ end
8
+
9
+ describe :to_key do
10
+ it "should camel but not first letter" do
11
+ string = Paypal::AdaptivePaymentsRequest.new.to_key(:som_symbole_here)
12
+ string.should eq("somSymboleHere")
13
+ end
14
+ end
15
+
16
+ describe :pay do
17
+ before(:each) do
18
+ @request = Paypal::AdaptivePayments.pay
19
+ end
20
+
21
+ it "should be a PayRequest" do
22
+ @request.should be_a(Paypal::PayRequest)
23
+ end
24
+
25
+ it "should be invalid without a lot of stuff" do
26
+ @request.valid?.should be_false
27
+ @request.error_message.should_not be_nil
28
+ end
29
+
30
+ context "with some information filled in" do
31
+ before(:each) do
32
+ @request.receiver.push({
33
+ :email => "test@dude.com",
34
+ :amount => 10.22
35
+ })
36
+ @request.cancel_url = "http://www.test.com/cancel"
37
+ @request.return_url = "http://www.test.com/return"
38
+ @request.ip_address = "168.212.226.204"
39
+ end
40
+
41
+ shared_examples_for "a good pay request" do
42
+ it "should look like a successful request" do
43
+ @request.request_string.should include("&receiverList.receiver(0).email=test%40dude.com&receiverList.receiver(0).amount=10.22&actionType=")
44
+ @request.request_string.should include("&currencyCode=USD&cancelUrl=http%3A%2F%2Fwww.test.com%2Fcancel&returnUrl=http%3A%2F%2Fwww.test.com%2Freturn&requestEnvelope.errorLanguage=en_US&requestEnvelope.detailLevel=ReturnAll")
45
+ end
46
+
47
+ it "should not raise exception, response should be success" do
48
+ Paypal::PayRequest.any_instance.stub(:make_request).and_return("")
49
+ expect {
50
+ response = @request.make
51
+ }.to_not raise_exception(Paypal::InvalidRequest)
52
+ end
53
+
54
+ it "should get a response from paypal", :slow_paypal => true do
55
+ # @request.test_request = true
56
+ response = @request.make
57
+ response.success?.should be_true
58
+ end
59
+
60
+ describe "with stubbed response" do
61
+ before(:each) do
62
+ Paypal::PayRequest.any_instance.stub(:make_request).and_return(Paypal::AdaptivePaymentsResponse.new("responseEnvelope.timestamp=2012-02-29T13%3A40%3A17.074-08%3A00&responseEnvelope.ack=Success&responseEnvelope.correlationId=722689b427b7c&responseEnvelope.build=2486531&payKey=AP-1W763567RM6393227&paymentExecStatus=CREATED"))
63
+ @response = @request.make
64
+ end
65
+
66
+ it "should be an adative payments response" do
67
+ @response.should be_a(Paypal::AdaptivePaymentsResponse)
68
+ end
69
+
70
+ it "should have a correlation_id" do
71
+ @response[:correlation_id].should eq("722689b427b7c")
72
+ end
73
+
74
+ it "should have a pay key and exec status" do
75
+ @response[:pay_key].should eq("AP-1W763567RM6393227")
76
+ @response[:payment_exec_status].should eq("CREATED")
77
+ end
78
+ end
79
+
80
+ describe "with stubbed failure" do
81
+ before(:each) do
82
+ Paypal::PayRequest.any_instance.stub(:make_request).and_return(Paypal::AdaptivePaymentsResponse.new("responseEnvelope.timestamp=2012-02-29T13%3A35%3A28.528-08%3A00&responseEnvelope.ack=Failure&responseEnvelope.correlationId=ca0befbd1fe0b&responseEnvelope.build=2486531&error(0).errorId=560022&error(0).domain=PLATFORM&error(0).subdomain=Application&error(0).severity=Error&error(0).category=Application&error(0).message=The+X-PAYPAL-APPLICATION-ID+header+contains+an+invalid+value&error(0).parameter(0)=X-PAYPAL-APPLICATION-ID"))
83
+ @response = @request.make
84
+ end
85
+
86
+ it "should have a correlation id" do
87
+ @response[:correlation_id].should eq("ca0befbd1fe0b")
88
+ end
89
+
90
+ it "should have an error message" do
91
+ @response.error_message.should_not be_nil
92
+ end
93
+
94
+ it "should not be success" do
95
+ @response.success?.should be_false
96
+ end
97
+
98
+ it "should have an error field and code" do
99
+ @response.error_code.should eq("560022")
100
+ @response.error_field.should eq("X-PAYPAL-APPLICATION-ID")
101
+ end
102
+ end
103
+ end
104
+
105
+ describe "when creating" do
106
+ before(:each) do
107
+ @request.action_type = :create
108
+ end
109
+
110
+ it_behaves_like "a good pay request"
111
+ end
112
+
113
+ describe "when paying" do
114
+ before(:each) do
115
+ @request.action_type = :pay
116
+ end
117
+
118
+ it_behaves_like "a good pay request"
119
+ end
120
+ end
121
+ end
122
+ end
@@ -8,7 +8,7 @@ describe Paypal::Api do
8
8
  describe :set_request_signature do
9
9
  it "should be callable from a class body definition" do
10
10
  expect {
11
- class Test < @api
11
+ class Testical < @api
12
12
  set_request_signature :test, {}
13
13
  end
14
14
  }.to_not raise_exception
@@ -16,7 +16,7 @@ describe Paypal::Api do
16
16
 
17
17
  context "with field descriptions" do
18
18
  before do
19
- class Test < @api
19
+ class Testical < @api
20
20
  set_request_signature :tester, {
21
21
  :test_field => "something",
22
22
  :optional => @api::Optional.new(String),
@@ -30,27 +30,27 @@ describe Paypal::Api do
30
30
  end
31
31
 
32
32
  it "should add getters for all keys" do
33
- Test.tester.payload.each do |k,v|
34
- Test.tester.respond_to?(k).should be_true
33
+ Testical.tester.payload.each do |k,v|
34
+ Testical.tester.respond_to?(k).should be_true
35
35
  end
36
36
  end
37
37
 
38
38
  it "should return a dynamic request object" do
39
- Test.tester.class.should eq(Paypal::TesterRequest)
39
+ Testical.tester.class.should eq(Paypal::TesterRequest)
40
40
  end
41
41
 
42
42
  it "should make the class respond to class method" do
43
- Test.respond_to?(:tester).should be_true
43
+ Testical.respond_to?(:tester).should be_true
44
44
  end
45
45
 
46
46
  it "should add constant getters and not setters for value types" do
47
- Test.tester.respond_to?(:test_field).should be_true
48
- Test.tester.send(:test_field).should eq("something")
49
- Test.tester.respond_to?(:test_field=).should be_false
47
+ Testical.tester.respond_to?(:test_field).should be_true
48
+ Testical.tester.send(:test_field).should eq("something")
49
+ Testical.tester.respond_to?(:test_field=).should be_false
50
50
  end
51
51
 
52
52
  it "should add setters for all non-constant types" do
53
- request = Test.tester
53
+ request = Testical.tester
54
54
  request.payload.each do |k,v|
55
55
  if [String, Fixnum, Float].include?(v.class)
56
56
  request.respond_to?(k).should be_false
@@ -61,7 +61,7 @@ describe Paypal::Api do
61
61
  end
62
62
 
63
63
  it "should raise an InvalidRequest error when missing required fields" do
64
- request = Test.tester
64
+ request = Testical.tester
65
65
  expect {
66
66
  request.make
67
67
  }.to raise_exception(Paypal::InvalidRequest)
@@ -69,7 +69,7 @@ describe Paypal::Api do
69
69
 
70
70
  describe "setters" do
71
71
  before do
72
- @request = Test.tester
72
+ @request = Testical.tester
73
73
  end
74
74
 
75
75
  it "should handle sequential types" do
@@ -1,10 +1,10 @@
1
1
  require "spec_helper"
2
2
 
3
- describe Paypal::MassPay do
3
+ describe Paypal::MassPayApi do
4
4
 
5
5
  describe :mass_pay do
6
6
  before(:each) do
7
- @request = Paypal::MassPay.mass_pay
7
+ @request = Paypal::MassPayApi.mass_pay
8
8
  end
9
9
 
10
10
  it "should be invalid without a payee" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paypal_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,33 +9,33 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-27 00:00:00.000000000Z
12
+ date: 2012-03-01 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: activesupport
16
- requirement: &70128187644320 !ruby/object:Gem::Requirement
15
+ name: rails
16
+ requirement: &70244225979560 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
- - - =
19
+ - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 3.2.1
21
+ version: '3.1'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70128187644320
24
+ version_requirements: *70244225979560
25
25
  - !ruby/object:Gem::Dependency
26
- name: rspec
27
- requirement: &70128187643520 !ruby/object:Gem::Requirement
26
+ name: ruby-debug19
27
+ requirement: &70244225978140 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
- - - ~>
30
+ - - ! '>='
31
31
  - !ruby/object:Gem::Version
32
- version: '2.6'
32
+ version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70128187643520
35
+ version_requirements: *70244225978140
36
36
  - !ruby/object:Gem::Dependency
37
- name: rspec-mocks
38
- requirement: &70128187642680 !ruby/object:Gem::Requirement
37
+ name: rspec-rails
38
+ requirement: &70244225976180 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,18 +43,18 @@ dependencies:
43
43
  version: '2.6'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70128187642680
46
+ version_requirements: *70244225976180
47
47
  - !ruby/object:Gem::Dependency
48
- name: rake
49
- requirement: &70128187642120 !ruby/object:Gem::Requirement
48
+ name: generator_spec
49
+ requirement: &70244225974780 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - =
53
53
  - !ruby/object:Gem::Version
54
- version: 0.8.7
54
+ version: 0.8.5
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70128187642120
57
+ version_requirements: *70244225974780
58
58
  description: alpha - currently covers part of payments pro and all of mass pay
59
59
  email:
60
60
  - matt.handler@gmail.com
@@ -65,22 +65,34 @@ files:
65
65
  - .gitignore
66
66
  - .rspec
67
67
  - Gemfile
68
+ - MIT-LICENSE
68
69
  - README.markdown
69
70
  - Rakefile
70
71
  - init.rb
72
+ - lib/generators/paypal/ipn_message_generator.rb
73
+ - lib/generators/templates/ipn_message.rb
74
+ - lib/generators/templates/migration.rb
71
75
  - lib/paypal_api.rb
76
+ - lib/paypal_api/apis/adaptive_accounts.rb
77
+ - lib/paypal_api/apis/adaptive_payments.rb
72
78
  - lib/paypal_api/apis/api.rb
79
+ - lib/paypal_api/apis/button_manager.rb
80
+ - lib/paypal_api/apis/express_checkout.rb
81
+ - lib/paypal_api/apis/invoicing.rb
73
82
  - lib/paypal_api/apis/mass_pay.rb
74
83
  - lib/paypal_api/apis/payments_pro.rb
84
+ - lib/paypal_api/apis/permissions.rb
75
85
  - lib/paypal_api/support/parameter.rb
76
86
  - lib/paypal_api/support/request.rb
77
87
  - lib/paypal_api/support/response.rb
78
88
  - lib/paypal_api/version.rb
79
89
  - paypal_api.gemspec
90
+ - spec/generators/ipn_message_generator_spec.rb
80
91
  - spec/spec_helper.rb
81
92
  - spec/support/parameter_spec.rb
82
93
  - spec/support/request_spec.rb
83
94
  - spec/support/response_spec.rb
95
+ - spec/unit/adaptive_payments_spec.rb
84
96
  - spec/unit/api_spec.rb
85
97
  - spec/unit/mass_pay_spec.rb
86
98
  - spec/unit/payments_pro_spec.rb
@@ -109,10 +121,12 @@ signing_key:
109
121
  specification_version: 3
110
122
  summary: an interface to paypals api
111
123
  test_files:
124
+ - spec/generators/ipn_message_generator_spec.rb
112
125
  - spec/spec_helper.rb
113
126
  - spec/support/parameter_spec.rb
114
127
  - spec/support/request_spec.rb
115
128
  - spec/support/response_spec.rb
129
+ - spec/unit/adaptive_payments_spec.rb
116
130
  - spec/unit/api_spec.rb
117
131
  - spec/unit/mass_pay_spec.rb
118
132
  - spec/unit/payments_pro_spec.rb