paypal_api 0.3.0 → 0.3.4

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.
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