paypal 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE +22 -0
- data/README +83 -0
- data/Rakefile +221 -0
- data/lib/helper.rb +86 -0
- data/lib/notification.rb +160 -0
- data/lib/paypal.rb +36 -0
- data/misc/PayPal - Instant Payment Notification - Technical Overview.pdf +0 -0
- data/misc/paypal.psd +0 -0
- metadata +54 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2005 Tobias Luetke
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
data/README
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
== Welcome to Paypal/ruby
|
2
|
+
|
3
|
+
This library is here to aid with integrating Paypal payments into ruby on rails
|
4
|
+
applications or similar. To set this up you will need to log into your paypal
|
5
|
+
business account and tell paypal where to send the IPN ( Instant payment notifications ).
|
6
|
+
|
7
|
+
== Download
|
8
|
+
|
9
|
+
* Preferred method of installation is using rubygems. gem install --source http://dist.leetsoft.com paypal
|
10
|
+
* Alternatively you can get the library packaged at http://dist.leetsoft.com/pkg/
|
11
|
+
|
12
|
+
== Requirements
|
13
|
+
|
14
|
+
* Valid paypal business account.
|
15
|
+
* The money library from http://dist.leetsoft.com/api/money
|
16
|
+
|
17
|
+
== Installation
|
18
|
+
|
19
|
+
1) Either install the rubygem for this library ( rake install from source
|
20
|
+
or gem install --source <url> paypal using gems directly )
|
21
|
+
|
22
|
+
2) Create a new controller which handels the paypal related tasks.
|
23
|
+
script/generate controller payment
|
24
|
+
|
25
|
+
3) Add the Paypal::Helpers to the newly created controller.
|
26
|
+
module PaymentHelper
|
27
|
+
include Paypal::Helpers
|
28
|
+
end
|
29
|
+
4) Create a paypal_ipn ( or similar ) action like the one in the "Example rails controller" appendix.
|
30
|
+
|
31
|
+
Within the new payment controller you can now create pages from which users can be sent to paypal. You always
|
32
|
+
have to sent users to paypal using a HTTP Post so a standard link won't work (well OK but you need some javascript for that). The +Paypal::Helper+ namespace has some examples of how such a forward page may look.
|
33
|
+
|
34
|
+
== Testing the integration
|
35
|
+
|
36
|
+
Under https://developer.paypal.com/ you can signup for a paypal developer account.
|
37
|
+
This allows you to set up "sandboxed" accounts which work and act like real accounts
|
38
|
+
with the difference that no money is exchanged. Its a good idea to sign up for a
|
39
|
+
sandbox account to use while the application is running in development mode.
|
40
|
+
|
41
|
+
|
42
|
+
== Example rails controller
|
43
|
+
|
44
|
+
class BackendController < ApplicationController
|
45
|
+
def paypal_ipn
|
46
|
+
notify = PaypalNotification.new(request.raw_post)
|
47
|
+
|
48
|
+
order = Order.find(notify.item_id)
|
49
|
+
|
50
|
+
if notify.acknowledge
|
51
|
+
begin
|
52
|
+
|
53
|
+
if notify.complete? and order.total == notify.amount
|
54
|
+
order.status = 'success'
|
55
|
+
|
56
|
+
shop.ship(order)
|
57
|
+
else
|
58
|
+
logger.error("Failed to verify Paypal's notification, please investigate")
|
59
|
+
end
|
60
|
+
|
61
|
+
rescue => e
|
62
|
+
order.status = 'failed'
|
63
|
+
raise
|
64
|
+
ensure
|
65
|
+
order.save
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
render :nothing
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
== Example paypal forward page
|
75
|
+
|
76
|
+
<%= paypal_form_tag %>
|
77
|
+
<%= paypal_setup "Item 500", Money.us_dollar(50000), "bob@bigbusiness.com" %>
|
78
|
+
|
79
|
+
Please press here to pay $500US using paypal. <br/>
|
80
|
+
<%= submit_tag "Go to paypal >>" %>
|
81
|
+
|
82
|
+
<% end_form_tag %>
|
83
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
require 'rake/contrib/rubyforgepublisher'
|
7
|
+
|
8
|
+
PKG_VERSION = "0.5.0"
|
9
|
+
PKG_NAME = "paypal"
|
10
|
+
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
11
|
+
|
12
|
+
PKG_FILES = FileList[
|
13
|
+
"lib/**/*",
|
14
|
+
"test/*",
|
15
|
+
"misc/*",
|
16
|
+
"[A-Z]*",
|
17
|
+
"MIT-LICENSE",
|
18
|
+
"Rakefile"
|
19
|
+
].exclude(/\bCVS\b|~$/)
|
20
|
+
|
21
|
+
desc "Default Task"
|
22
|
+
task :default => [ :test ]
|
23
|
+
|
24
|
+
desc "Delete tar.gz / zip / rdoc"
|
25
|
+
task :cleanup => [ :rm_packages, :clobber_rdoc ]
|
26
|
+
|
27
|
+
# Run the unit tests
|
28
|
+
Rake::TestTask.new("test") { |t|
|
29
|
+
t.libs << "test"
|
30
|
+
t.pattern = 'test/*_test.rb'
|
31
|
+
t.verbose = false
|
32
|
+
}
|
33
|
+
|
34
|
+
desc "Create a rubygem and install it. Might need root rights"
|
35
|
+
task :install => [:package] do
|
36
|
+
`gem install pkg/#{PKG_FILE_NAME}.gem`
|
37
|
+
end
|
38
|
+
|
39
|
+
# Genereate the RDoc documentation
|
40
|
+
|
41
|
+
Rake::RDocTask.new { |rdoc|
|
42
|
+
rdoc.rdoc_dir = 'doc'
|
43
|
+
rdoc.title = "Paypal library"
|
44
|
+
rdoc.options << '--line-numbers --inline-source'
|
45
|
+
rdoc.rdoc_files.include('README')
|
46
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
47
|
+
}
|
48
|
+
|
49
|
+
task :lines do
|
50
|
+
lines = 0
|
51
|
+
codelines = 0
|
52
|
+
Dir.foreach("lib") { |file_name|
|
53
|
+
next unless file_name =~ /.*rb/
|
54
|
+
|
55
|
+
f = File.open("lib/" + file_name)
|
56
|
+
|
57
|
+
while line = f.gets
|
58
|
+
lines += 1
|
59
|
+
next if line =~ /^\s*$/
|
60
|
+
next if line =~ /^\s*#/
|
61
|
+
codelines += 1
|
62
|
+
end
|
63
|
+
}
|
64
|
+
puts "Lines #{lines}, LOC #{codelines}"
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
desc "Publish the gem on leetsoft"
|
69
|
+
task :publish => [:rdoc, :package] do
|
70
|
+
Rake::SshFilePublisher.new("leetsoft.com", "dist/pkg", "pkg", "#{PKG_FILE_NAME}.zip").upload
|
71
|
+
Rake::SshFilePublisher.new("leetsoft.com", "dist/pkg", "pkg", "#{PKG_FILE_NAME}.tgz").upload
|
72
|
+
Rake::SshFilePublisher.new("leetsoft.com", "dist/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
73
|
+
`ssh tobi@leetsoft.com "mkdir -p dist/api/#{PKG_NAME}"`
|
74
|
+
Rake::SshDirPublisher.new("leetsoft.com", "dist/api/#{PKG_NAME}", "doc").upload
|
75
|
+
`ssh tobi@leetsoft.com './gemupdate'`
|
76
|
+
end
|
77
|
+
|
78
|
+
spec = Gem::Specification.new do |s|
|
79
|
+
s.name = PKG_NAME
|
80
|
+
s.version = PKG_VERSION
|
81
|
+
s.summary = "Paypal IPN integration for rails apps and similar"
|
82
|
+
s.has_rdoc = true
|
83
|
+
|
84
|
+
s.files = %w(README Rakefile MIT-LICENSE) + Dir['lib/**/*'] + Dir['misc/*'] + Dir['tests/*']
|
85
|
+
|
86
|
+
s.require_path = 'lib'
|
87
|
+
s.autorequire = 'paypal'
|
88
|
+
s.author = "Tobias Luetke"
|
89
|
+
s.email = "tobi@leetsoft.com"
|
90
|
+
s.homepage = "http://dist.leetsoft.com/api/paypal"
|
91
|
+
|
92
|
+
s.add_dependency('money')
|
93
|
+
end
|
94
|
+
|
95
|
+
Rake::GemPackageTask.new(spec) do |p|
|
96
|
+
p.gem_spec = spec
|
97
|
+
p.need_tar = true
|
98
|
+
p.need_zip = true
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
# --- Ruby forge release manager by florian gross -------------------------------------------------
|
103
|
+
|
104
|
+
RUBY_FORGE_PROJECT = 'paypal'
|
105
|
+
RUBY_FORGE_USER = 'xal'
|
106
|
+
RELEASE_NAME = "REL #{PKG_VERSION}"
|
107
|
+
|
108
|
+
desc "Publish the release files to RubyForge."
|
109
|
+
task :release => [:gem] do
|
110
|
+
files = ["gem"].map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" }
|
111
|
+
|
112
|
+
if RUBY_FORGE_PROJECT then
|
113
|
+
require 'net/http'
|
114
|
+
require 'open-uri'
|
115
|
+
|
116
|
+
project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/"
|
117
|
+
project_data = open(project_uri) { |data| data.read }
|
118
|
+
group_id = project_data[/[?&]group_id=(\d+)/, 1]
|
119
|
+
raise "Couldn't get group id" unless group_id
|
120
|
+
|
121
|
+
# This echos password to shell which is a bit sucky
|
122
|
+
if ENV["RUBY_FORGE_PASSWORD"]
|
123
|
+
password = ENV["RUBY_FORGE_PASSWORD"]
|
124
|
+
else
|
125
|
+
print "#{RUBY_FORGE_USER}@rubyforge.org's password: "
|
126
|
+
password = STDIN.gets.chomp
|
127
|
+
end
|
128
|
+
|
129
|
+
login_response = Net::HTTP.start("rubyforge.org", 80) do |http|
|
130
|
+
data = [
|
131
|
+
"login=1",
|
132
|
+
"form_loginname=#{RUBY_FORGE_USER}",
|
133
|
+
"form_pw=#{password}"
|
134
|
+
].join("&")
|
135
|
+
http.post("/account/login.php", data)
|
136
|
+
end
|
137
|
+
|
138
|
+
cookie = login_response["set-cookie"]
|
139
|
+
raise "Login failed" unless cookie
|
140
|
+
headers = { "Cookie" => cookie }
|
141
|
+
|
142
|
+
release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}"
|
143
|
+
release_data = open(release_uri, headers) { |data| data.read }
|
144
|
+
package_id = release_data[/[?&]package_id=(\d+)/, 1]
|
145
|
+
raise "Couldn't get package id" unless package_id
|
146
|
+
|
147
|
+
first_file = true
|
148
|
+
release_id = ""
|
149
|
+
|
150
|
+
files.each do |filename|
|
151
|
+
basename = File.basename(filename)
|
152
|
+
file_ext = File.extname(filename)
|
153
|
+
file_data = File.open(filename, "rb") { |file| file.read }
|
154
|
+
|
155
|
+
puts "Releasing #{basename}..."
|
156
|
+
|
157
|
+
release_response = Net::HTTP.start("rubyforge.org", 80) do |http|
|
158
|
+
release_date = Time.now.strftime("%Y-%m-%d %H:%M")
|
159
|
+
type_map = {
|
160
|
+
".zip" => "3000",
|
161
|
+
".tgz" => "3110",
|
162
|
+
".gz" => "3110",
|
163
|
+
".gem" => "1400"
|
164
|
+
}; type_map.default = "9999"
|
165
|
+
type = type_map[file_ext]
|
166
|
+
boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor"
|
167
|
+
|
168
|
+
query_hash = if first_file then
|
169
|
+
{
|
170
|
+
"group_id" => group_id,
|
171
|
+
"package_id" => package_id,
|
172
|
+
"release_name" => RELEASE_NAME,
|
173
|
+
"release_date" => release_date,
|
174
|
+
"type_id" => type,
|
175
|
+
"processor_id" => "8000", # Any
|
176
|
+
"release_notes" => "",
|
177
|
+
"release_changes" => "",
|
178
|
+
"preformatted" => "1",
|
179
|
+
"submit" => "1"
|
180
|
+
}
|
181
|
+
else
|
182
|
+
{
|
183
|
+
"group_id" => group_id,
|
184
|
+
"release_id" => release_id,
|
185
|
+
"package_id" => package_id,
|
186
|
+
"step2" => "1",
|
187
|
+
"type_id" => type,
|
188
|
+
"processor_id" => "8000", # Any
|
189
|
+
"submit" => "Add This File"
|
190
|
+
}
|
191
|
+
end
|
192
|
+
|
193
|
+
query = "?" + query_hash.map do |(name, value)|
|
194
|
+
[name, URI.encode(value)].join("=")
|
195
|
+
end.join("&")
|
196
|
+
|
197
|
+
data = [
|
198
|
+
"--" + boundary,
|
199
|
+
"Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"",
|
200
|
+
"Content-Type: application/octet-stream",
|
201
|
+
"Content-Transfer-Encoding: binary",
|
202
|
+
"", file_data, ""
|
203
|
+
].join("\x0D\x0A")
|
204
|
+
|
205
|
+
release_headers = headers.merge(
|
206
|
+
"Content-Type" => "multipart/form-data; boundary=#{boundary}"
|
207
|
+
)
|
208
|
+
|
209
|
+
target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php"
|
210
|
+
http.post(target + query, data, release_headers)
|
211
|
+
end
|
212
|
+
|
213
|
+
if first_file then
|
214
|
+
release_id = release_response.body[/release_id=(\d+)/, 1]
|
215
|
+
raise("Couldn't get release id") unless release_id
|
216
|
+
end
|
217
|
+
|
218
|
+
first_file = false
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
data/lib/helper.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
module Paypal
|
2
|
+
# This is a collection of helpers which aid in the creation of paypal buttons
|
3
|
+
#
|
4
|
+
# Example:
|
5
|
+
#
|
6
|
+
# <%= form_tag Paypal::Notification.ipn_url %>
|
7
|
+
#
|
8
|
+
# <%= paypal_setup "Item 500", Money.us_dollar(50000), "bob@bigbusiness.com" %>
|
9
|
+
# Please press here to pay $500US using paypal. <%= submit_tag %>
|
10
|
+
#
|
11
|
+
# <% end_form_tag %>
|
12
|
+
#
|
13
|
+
# For this to work you have to include these methods as helpers in your rails application.
|
14
|
+
# One way is to add "include Paypal::Helpers" in your application_helper.rb
|
15
|
+
# See Paypal::Notification for information on how to catch payment events.
|
16
|
+
module Helpers
|
17
|
+
|
18
|
+
# Convenience helper. Can replace <%= form_tag Paypal::Notification.ipn_url %>
|
19
|
+
def paypal_form_tag
|
20
|
+
form_tag(Paypal::Notification.ipn_url)
|
21
|
+
end
|
22
|
+
|
23
|
+
# This helper creates the hidden form data which is needed for a paypal purchase.
|
24
|
+
#
|
25
|
+
# * <tt>item_number</tt> -- The first parameter is the item number. This is for your personal organization and can
|
26
|
+
# be arbitrary. Paypal will sent the item number back with the IPN so its a great place to
|
27
|
+
# store a user ID or a order ID or something like this.
|
28
|
+
#
|
29
|
+
# * <tt>amount</tt> -- should be a parameter of type Money ( see http://leetsoft.com/api/money ) but can also
|
30
|
+
# be a string of type "50.00" for 50$. If you use the string syntax make sure you set the current
|
31
|
+
# currency as part of the options hash. The default is USD
|
32
|
+
#
|
33
|
+
# * <tt>business</tt> -- This is your paypal account name ( a email ). This needs to be a valid paypal business account.
|
34
|
+
#
|
35
|
+
# The last parameter is a options hash. You can override several things:
|
36
|
+
#
|
37
|
+
# * <tt>:return_url</tt> -- default is nil. If provided paypal will redirect a user back to your application after a
|
38
|
+
# successful purchase. Useful for a kind of thankyou page.
|
39
|
+
# * <tt>:item_name</tt> -- default is 'Store purchase'. This is the name of the purchase which will be displayed
|
40
|
+
# on the paypal page.
|
41
|
+
# * <tt>:no_shipping</tt> -- default is '1'. By default we tell paypal that no shipping is required. Usually
|
42
|
+
# the shipping address should be collected in our application, not by paypal.
|
43
|
+
# * <tt>:no_note</tt> -- default is '1'
|
44
|
+
# * <tt>:currency</tt> -- default is 'USD'
|
45
|
+
#
|
46
|
+
# Examples:
|
47
|
+
#
|
48
|
+
# <%= paypal_setup @order.id, Money.us_dollar(50000), "bob@bigbusiness.com" %>
|
49
|
+
# <%= paypal_setup @order.id, 50.00, "bob@bigbusiness.com", :currency => 'USD' %>
|
50
|
+
# <%= paypal_setup @order.id, Money.ca_dollar(50000), "bob@bigbusiness.com", :item_name => 'Snowdevil shop purchase', :return_url => url_for(:only_path => false, :action => 'thankyou') %>
|
51
|
+
#
|
52
|
+
def paypal_setup(item_number, amount, business, options = {})
|
53
|
+
|
54
|
+
params = {
|
55
|
+
:item_name => 'Store purchase',
|
56
|
+
:no_shipping => '1',
|
57
|
+
:no_note => '1',
|
58
|
+
:currency => 'USD',
|
59
|
+
:return_url => nil
|
60
|
+
}.merge(options)
|
61
|
+
|
62
|
+
# We accept both, strings and money objects as amount
|
63
|
+
amount = amount.cents / 100 if amount.respond_to?(:cents)
|
64
|
+
amount = sprintf("%.2f", amount)
|
65
|
+
|
66
|
+
# Build the form
|
67
|
+
returning button = [] do
|
68
|
+
button << form_tag(Paypal::Notification.ipn_url)
|
69
|
+
|
70
|
+
button << tag(:input, :type => 'hidden', :name => 'cmd', :value => "_xclick")
|
71
|
+
button << tag(:input, :type => 'hidden', :name => 'business', :value => business)
|
72
|
+
button << tag(:input, :type => 'hidden', :name => 'amount', :value => amount)
|
73
|
+
button << tag(:input, :type => 'hidden', :name => 'item_number', :value => item_number)
|
74
|
+
button << tag(:input, :type => 'hidden', :name => 'item_name', :value => params[:item_name])
|
75
|
+
button << tag(:input, :type => 'hidden', :name => 'no_shipping', :value => params[:no_shipping])
|
76
|
+
button << tag(:input, :type => 'hidden', :name => 'no_note', :value => params[:no_note])
|
77
|
+
button << tag(:input, :type => 'hidden', :name => 'return', :value => params[:return_url]) if params[:return_url]
|
78
|
+
|
79
|
+
# if amount was a object of type money or something compatible we will use its currency,
|
80
|
+
# otherwise get the currency from the options. default is USD
|
81
|
+
button << tag(:input, :type => 'hidden', :name => 'currency_code', :value => amount.respond_to?(:currency) ? amount.currency : params[:currency])
|
82
|
+
end.join("\n")
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
data/lib/notification.rb
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
module Paypal
|
2
|
+
# Parser and handler for incoming Instant payment notifications from paypal.
|
3
|
+
# The Example shows a typical handler in a rails application.
|
4
|
+
#
|
5
|
+
# Example
|
6
|
+
#
|
7
|
+
# class BackendController < ApplicationController
|
8
|
+
#
|
9
|
+
# def paypal_ipn
|
10
|
+
# notify = PaypalNotification.new(request.raw_post)
|
11
|
+
#
|
12
|
+
# order = Order.find(notify.item_id)
|
13
|
+
#
|
14
|
+
# if notify.acknowledge
|
15
|
+
# begin
|
16
|
+
#
|
17
|
+
# if notify.complete? and order.total == notify.amount
|
18
|
+
# order.status = 'success'
|
19
|
+
#
|
20
|
+
# shop.ship(order)
|
21
|
+
# else
|
22
|
+
# logger.error("Failed to verify Paypal's notification, please investigate")
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# rescue => e
|
26
|
+
# order.status = 'failed'
|
27
|
+
# raise
|
28
|
+
# ensure
|
29
|
+
# order.save
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# render :nothing
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
class Notification
|
37
|
+
attr_accessor :params
|
38
|
+
attr_accessor :raw
|
39
|
+
|
40
|
+
# Overwrite this url. It points to the Paypal sandbox by default.
|
41
|
+
# Please note that the Paypal technical overview (doc directory)
|
42
|
+
# speaks of a https:// address for production use. In my tests
|
43
|
+
# this https address does not in fact work.
|
44
|
+
#
|
45
|
+
# Example:
|
46
|
+
# Paypal::Notification.ipn_url = http://www.paypal.com/cgi-bin/webscr
|
47
|
+
#
|
48
|
+
cattr_accessor :ipn_url
|
49
|
+
@@ipn_url = 'http://www.sandbox.paypal.com/cgi-bin/webscr'
|
50
|
+
|
51
|
+
# Creates a new paypal object. Pass the raw html you got from paypal in.
|
52
|
+
# In a rails application this looks something like this
|
53
|
+
#
|
54
|
+
# def paypal_ipn
|
55
|
+
# paypal = Paypal::Notification.new(request.raw_post)
|
56
|
+
# ...
|
57
|
+
# end
|
58
|
+
def initialize(post)
|
59
|
+
empty!
|
60
|
+
parse(post)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Was the transaction complete?
|
64
|
+
def complete?
|
65
|
+
status == "Completed"
|
66
|
+
end
|
67
|
+
|
68
|
+
# When was this payment received by the client.
|
69
|
+
# sometimes it can happen that we get the notification much later.
|
70
|
+
# One possible scenario is that our web application was down. In this case paypal tries several
|
71
|
+
# times an hour to inform us about the notification
|
72
|
+
def recieved_at
|
73
|
+
Time.parse params['payment_date']
|
74
|
+
end
|
75
|
+
|
76
|
+
# Whats the status of this transaction?
|
77
|
+
def status
|
78
|
+
params['payment_status']
|
79
|
+
end
|
80
|
+
|
81
|
+
# Id of this transaction (paypal number)
|
82
|
+
def transaction_id
|
83
|
+
params['txn_id']
|
84
|
+
end
|
85
|
+
|
86
|
+
# What type of transaction are we dealing with?
|
87
|
+
# "cart" "send_money" "web_accept" are possible here.
|
88
|
+
def type
|
89
|
+
params['txn_type']
|
90
|
+
end
|
91
|
+
|
92
|
+
# the money amount we received in X.2 decimal.
|
93
|
+
def gross
|
94
|
+
params['mc_gross']
|
95
|
+
end
|
96
|
+
|
97
|
+
# the markup paypal charges for the transaction
|
98
|
+
def fee
|
99
|
+
params['mc_fee']
|
100
|
+
end
|
101
|
+
|
102
|
+
# What currency have we been dealing with
|
103
|
+
def currency
|
104
|
+
params['mc_currency']
|
105
|
+
end
|
106
|
+
|
107
|
+
# This is the item number which we submitted to paypal
|
108
|
+
def item_id
|
109
|
+
params['item_number']
|
110
|
+
end
|
111
|
+
|
112
|
+
# This combines the gross and currency and returns a proper Money object.
|
113
|
+
# this requires the money library located at http://dist.leetsoft.com/api/money
|
114
|
+
def amount
|
115
|
+
amount = gross.sub(/[^\d]/, '').to_i
|
116
|
+
Money.new(amount, currency)
|
117
|
+
end
|
118
|
+
|
119
|
+
# reset the notification.
|
120
|
+
def empty!
|
121
|
+
@params = Hash.new
|
122
|
+
@raw = ""
|
123
|
+
end
|
124
|
+
|
125
|
+
# Acknowledge the transaction to paypal. This method has to be called after a new
|
126
|
+
# ipn arrives. Paypal will verify that all the information we received are correct and will return a
|
127
|
+
# ok or a fail.
|
128
|
+
#
|
129
|
+
# Example:
|
130
|
+
#
|
131
|
+
# def paypal_ipn
|
132
|
+
# notify = PaypalNotification.new(request.raw_post)
|
133
|
+
#
|
134
|
+
# if notify.acknowledge
|
135
|
+
# ... process order ... if notify.complete?
|
136
|
+
# else
|
137
|
+
# ... log possible hacking attempt ...
|
138
|
+
# end
|
139
|
+
def acknowledge
|
140
|
+
uri = URI.parse(self.class.ipn_url)
|
141
|
+
status = nil
|
142
|
+
Net::HTTP.start(uri.host, uri.port) do |request|
|
143
|
+
status = request.post(uri.path, raw + "&cmd=_notify-validate").body
|
144
|
+
end
|
145
|
+
status == "VERIFIED"
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
# Take the posted data and move the relevant data into a hash
|
151
|
+
def parse(post)
|
152
|
+
@raw = post
|
153
|
+
for line in post.split('&')
|
154
|
+
key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten
|
155
|
+
params[key] = CGI.unescape(value)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
data/lib/paypal.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2005 Tobias Luetke
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require 'cgi'
|
25
|
+
require 'net/http'
|
26
|
+
require 'net/https'
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'money'
|
30
|
+
rescue LoadError
|
31
|
+
require 'rubygems'
|
32
|
+
require_gem 'money'
|
33
|
+
end
|
34
|
+
|
35
|
+
require File.dirname(__FILE__) + '/notification'
|
36
|
+
require File.dirname(__FILE__) + '/helper'
|
Binary file
|
data/misc/paypal.psd
ADDED
Binary file
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.10
|
3
|
+
specification_version: 1
|
4
|
+
name: paypal
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.5.0
|
7
|
+
date: 2005-06-08
|
8
|
+
summary: Paypal IPN integration for rails apps and similar
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: tobi@leetsoft.com
|
12
|
+
homepage: http://dist.leetsoft.com/api/paypal
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire: paypal
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
-
|
22
|
+
- ">"
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.0.0
|
25
|
+
version:
|
26
|
+
platform: ruby
|
27
|
+
authors:
|
28
|
+
- Tobias Luetke
|
29
|
+
files:
|
30
|
+
- README
|
31
|
+
- Rakefile
|
32
|
+
- MIT-LICENSE
|
33
|
+
- lib/helper.rb
|
34
|
+
- lib/notification.rb
|
35
|
+
- lib/paypal.rb
|
36
|
+
- "misc/PayPal - Instant Payment Notification - Technical Overview.pdf"
|
37
|
+
- misc/paypal.psd
|
38
|
+
test_files: []
|
39
|
+
rdoc_options: []
|
40
|
+
extra_rdoc_files: []
|
41
|
+
executables: []
|
42
|
+
extensions: []
|
43
|
+
requirements: []
|
44
|
+
dependencies:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: money
|
47
|
+
version_requirement:
|
48
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
49
|
+
requirements:
|
50
|
+
-
|
51
|
+
- ">"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.0.0
|
54
|
+
version:
|