pippin 0.1.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ script: bundle exec rspec spec
3
+ rvm:
4
+ - 1.8.7
5
+ - 1.9.2
6
+ - 1.9.3
data/Gemfile CHANGED
@@ -3,5 +3,5 @@ source :rubygems
3
3
  gemspec
4
4
 
5
5
  gem 'combustion',
6
- :git => 'git://github.com/freelancing-god/combustion.git',
6
+ :git => 'git://github.com/pat/combustion.git',
7
7
  :ref => 'd7a6836269a8fb528bc3721e9b8c06b5a62ef3cc'
data/HISTORY CHANGED
@@ -1,3 +1,8 @@
1
+ 1.0.0 - September 25th 2012
2
+ * Rails/ActiveSupport 3.1 or better.
3
+ * No listener - use ActiveSupport::Notifications instead.
4
+ * No controller - just as easy running things as a simple Rack app.
5
+
1
6
  0.1.1 - January 4th 2012
2
7
  * Don't require a specific version of Rails, just 3.0 or better.
3
8
 
@@ -2,25 +2,37 @@ h1. Pippin
2
2
 
3
3
  Pippin is a Rails Engine for processing PayPal IPN requests. It automatically adds a route to your Rails application that validates and processes IPNs.
4
4
 
5
- If you want to do something with those IPN objects (and I recommend you do), then all you need to do is attach a listener, and that'll fire as each valid IPN is received.
5
+ If you want to do something with those IPN objects (and I recommend you do), then all you need to do is attach a subscriber to the notification, and that'll fire as each valid IPN is received.
6
+
7
+ *Using Rails 3.0?* Then you better jump back to the "0.1.1 release":https://github.com/pat/pippin/tree/v0.1.1 of this library, which does not use ActiveSupport::Notifications.
6
8
 
7
9
  h2. Installation and Usage
8
10
 
9
11
  Just add it to your Gemfile:
10
12
 
11
- <pre><code>gem 'pippin', '~> 0.1'</code></pre>
13
+ <pre><code>gem 'pippin', '~> 1.0.0'</code></pre>
12
14
 
13
- Then, in an initializer, add your listener:
15
+ Then somewhere (perhaps in an initializer), subscribe to the notifications:
14
16
 
15
- <pre><code># config/initializers/pippin.rb
16
- Pippin.listener = lambda { |ipn|
17
+ <pre><code>ActiveSupport::Notifications.subscribe('received.ipn') do |*args|
18
+ event = ActiveSupport::Notifications::Event.new(*args)
19
+ ipn = event.payload[:ipn]
17
20
  # use IPN data
18
21
  ipn.params # => {'business' => 'email@domain.com', 'txn_type' => ...}
19
- }</code></pre>
22
+ end</code></pre>
23
+
24
+ There are also notifications fired using the txn_type parameter as the prefix, if you wish to subscribe to certain types:
25
+
26
+ <pre><code>ActiveSupport::Notifications.subscribe('subscr_signup.ipn') do |*args|
27
+ event = ActiveSupport::Notifications::Event.new(*args)
28
+ ipn = event.payload[:ipn]
29
+
30
+ # A subscription sign up has happened. Do something!
31
+ end</code></pre>
20
32
 
21
33
  Any parameters with the suffix '_date' in their name will automatically be translated from PayPal's ugly date string to an appropriate Time object.
22
34
 
23
- When constructing your form that redirects people to PayPal, you'll want to set the @notify_url@ parameter to use Pippin's URL that has automatically been added to your routes: @pippin_ipns_url@.
35
+ When constructing your form that redirects people to PayPal, you'll want to set the @notify_url@ parameter to use Pippin's URL that has automatically been added to your routes: @'pippin/ipns'@.
24
36
 
25
37
  h2. Contributing
26
38
 
@@ -1,5 +1,3 @@
1
1
  Rails.application.routes.draw do
2
- namespace :pippin do
3
- resources :ipns, :only => [:create]
4
- end
2
+ mount Pippin::App.new => '/pippin/ipns'
5
3
  end
@@ -1,15 +1,11 @@
1
- require 'rails'
1
+ require 'active_support/notifications'
2
+ require 'active_support/core_ext/module/delegation'
2
3
 
3
4
  module Pippin
4
- def self.listener
5
- @listener
6
- end
7
-
8
- def self.listener=(listener)
9
- @listener = listener
10
- end
5
+ #
11
6
  end
12
7
 
13
- require 'pippin/engine'
8
+ require 'pippin/app'
14
9
  require 'pippin/ipn'
15
- require 'pippin/version'
10
+
11
+ require 'pippin/engine' if defined?(Rails)
@@ -0,0 +1,17 @@
1
+ class Pippin::App
2
+ delegate :instrument, :to => ActiveSupport::Notifications
3
+
4
+ def call(env)
5
+ request = Rack::Request.new(env)
6
+ ipn = Pippin::IPN.new request.params, request.body.read
7
+
8
+ if ipn.valid?
9
+ instrument 'received.ipn', :ipn => ipn
10
+ instrument "#{ipn.params['txn_type']}.ipn", :ipn => ipn
11
+
12
+ [200, {}, [' ']]
13
+ else
14
+ [400, {}, [' ']]
15
+ end
16
+ end
17
+ end
@@ -1,4 +1,3 @@
1
1
  class Pippin::Engine < Rails::Engine
2
- paths['app/controllers'] << 'app/controllers'
3
2
  paths['config'] << 'config'
4
3
  end
@@ -38,6 +38,9 @@ class Pippin::IPN
38
38
  end
39
39
 
40
40
  def paypal_uri
41
- "https://www.paypal.com/cgi-bin/webscr?cmd=_notify-validate&#{body}"
41
+ domain = 'www.paypal.com'
42
+ domain = 'www.sandbox.paypal.com' if params['test_ipn'] == '1'
43
+
44
+ "https://#{domain}/cgi-bin/webscr?cmd=_notify-validate&#{body}"
42
45
  end
43
46
  end
@@ -1,10 +1,9 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  $:.push File.expand_path('../lib', __FILE__)
3
- require 'pippin/version'
4
3
 
5
4
  Gem::Specification.new do |s|
6
5
  s.name = 'pippin'
7
- s.version = Pippin::VERSION
6
+ s.version = '1.0.0'
8
7
  s.authors = ['Pat Allan']
9
8
  s.email = ['pat@freelancing-gods.com']
10
9
  s.homepage = ''
@@ -18,7 +17,7 @@ Gem::Specification.new do |s|
18
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
18
  s.require_paths = ['lib']
20
19
 
21
- s.add_runtime_dependency 'rails', '>= 3.0'
20
+ s.add_runtime_dependency 'activesupport', '>= 3.1'
22
21
 
23
22
  s.add_development_dependency 'fakeweb', '1.3.0'
24
23
  s.add_development_dependency 'fakeweb-matcher', '1.2.2'
@@ -22,6 +22,18 @@ describe Pippin::IPN do
22
22
  'https://www.paypal.com/cgi-bin/webscr?cmd=_notify-validate&foo=bar')
23
23
  end
24
24
 
25
+ it "checks the validity in the sandbox if it is a test IPN" do
26
+ FakeWeb.register_uri :get,
27
+ /^https:\/\/www\.sandbox\.paypal\.com\/cgi-bin\/webscr/,
28
+ :body => 'VERIFIED'
29
+
30
+ ipn = Pippin::IPN.new({'test_ipn' => '1'}, 'foo=bar')
31
+ ipn.valid?
32
+
33
+ FakeWeb.should have_requested(:get,
34
+ 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_notify-validate&foo=bar')
35
+ end
36
+
25
37
  it "returns true if PayPal confirms the validity of the data" do
26
38
  FakeWeb.register_uri :get, /^https:\/\/www\.paypal\.com\/cgi-bin\/webscr/,
27
39
  :body => 'VERIFIED'
@@ -13,12 +13,16 @@ describe 'IPN Submission' do
13
13
  response.status.should == 200
14
14
  end
15
15
 
16
- it "calls the provided listener" do
16
+ it "calls the provided subscriber" do
17
17
  ipn_lodged = false
18
- Pippin.listener = lambda { |ipn| ipn_lodged = true }
18
+ sub = ActiveSupport::Notifications.subscribe('received.ipn') { |payload|
19
+ ipn_lodged = true
20
+ }
19
21
 
20
22
  post '/pippin/ipns'
21
23
 
24
+ ActiveSupport::Notifications.unsubscribe sub
25
+
22
26
  ipn_lodged.should be_true
23
27
  end
24
28
  end
@@ -37,10 +41,14 @@ describe 'IPN Submission' do
37
41
 
38
42
  it "does not call the provided listener" do
39
43
  ipn_lodged = false
40
- Pippin.listener = lambda { |ipn| ipn_lodged = true }
44
+ sub = ActiveSupport::Notifications.subscribe('received.ipn') { |payload|
45
+ ipn_lodged = true
46
+ }
41
47
 
42
48
  post '/pippin/ipns'
43
49
 
50
+ ActiveSupport::Notifications.unsubscribe sub
51
+
44
52
  ipn_lodged.should be_false
45
53
  end
46
54
  end
@@ -1,5 +1,6 @@
1
1
  require 'rubygems'
2
2
  require 'bundler'
3
+ require 'rails'
3
4
 
4
5
  Bundler.require :default, :development
5
6
 
metadata CHANGED
@@ -1,125 +1,164 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: pippin
3
- version: !ruby/object:Gem::Version
4
- version: 0.1.1
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
5
  prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
6
11
  platform: ruby
7
- authors:
12
+ authors:
8
13
  - Pat Allan
9
14
  autorequire:
10
15
  bindir: bin
11
16
  cert_chain: []
12
- date: 2012-01-04 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: rails
16
- requirement: &70102267855040 !ruby/object:Gem::Requirement
17
+
18
+ date: 2012-09-25 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: activesupport
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
17
24
  none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: '3.0'
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 5
29
+ segments:
30
+ - 3
31
+ - 1
32
+ version: "3.1"
22
33
  type: :runtime
23
- prerelease: false
24
- version_requirements: *70102267855040
25
- - !ruby/object:Gem::Dependency
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
26
36
  name: fakeweb
27
- requirement: &70102277736820 !ruby/object:Gem::Requirement
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
28
39
  none: false
29
- requirements:
30
- - - =
31
- - !ruby/object:Gem::Version
40
+ requirements:
41
+ - - "="
42
+ - !ruby/object:Gem::Version
43
+ hash: 27
44
+ segments:
45
+ - 1
46
+ - 3
47
+ - 0
32
48
  version: 1.3.0
33
49
  type: :development
34
- prerelease: false
35
- version_requirements: *70102277736820
36
- - !ruby/object:Gem::Dependency
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
37
52
  name: fakeweb-matcher
38
- requirement: &70102277735200 !ruby/object:Gem::Requirement
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
39
55
  none: false
40
- requirements:
41
- - - =
42
- - !ruby/object:Gem::Version
56
+ requirements:
57
+ - - "="
58
+ - !ruby/object:Gem::Version
59
+ hash: 27
60
+ segments:
61
+ - 1
62
+ - 2
63
+ - 2
43
64
  version: 1.2.2
44
65
  type: :development
45
- prerelease: false
46
- version_requirements: *70102277735200
47
- - !ruby/object:Gem::Dependency
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
48
68
  name: rspec-rails
49
- requirement: &70102277734220 !ruby/object:Gem::Requirement
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
50
71
  none: false
51
- requirements:
52
- - - =
53
- - !ruby/object:Gem::Version
72
+ requirements:
73
+ - - "="
74
+ - !ruby/object:Gem::Version
75
+ hash: 19
76
+ segments:
77
+ - 2
78
+ - 7
79
+ - 0
54
80
  version: 2.7.0
55
81
  type: :development
56
- prerelease: false
57
- version_requirements: *70102277734220
58
- - !ruby/object:Gem::Dependency
82
+ version_requirements: *id004
83
+ - !ruby/object:Gem::Dependency
59
84
  name: sqlite3
60
- requirement: &70102277733580 !ruby/object:Gem::Requirement
85
+ prerelease: false
86
+ requirement: &id005 !ruby/object:Gem::Requirement
61
87
  none: false
62
- requirements:
63
- - - =
64
- - !ruby/object:Gem::Version
88
+ requirements:
89
+ - - "="
90
+ - !ruby/object:Gem::Version
91
+ hash: 17
92
+ segments:
93
+ - 1
94
+ - 3
95
+ - 5
65
96
  version: 1.3.5
66
97
  type: :development
67
- prerelease: false
68
- version_requirements: *70102277733580
69
- description: Accepts IPN requests, validates them and passes them on to a defined
70
- listener.
71
- email:
98
+ version_requirements: *id005
99
+ description: Accepts IPN requests, validates them and passes them on to a defined listener.
100
+ email:
72
101
  - pat@freelancing-gods.com
73
102
  executables: []
103
+
74
104
  extensions: []
105
+
75
106
  extra_rdoc_files: []
76
- files:
107
+
108
+ files:
77
109
  - .gitignore
110
+ - .travis.yml
78
111
  - Gemfile
79
112
  - HISTORY
80
113
  - LICENCE
81
114
  - README.textile
82
115
  - Rakefile
83
- - app/controllers/pippin/ipns_controller.rb
84
116
  - config.ru
85
117
  - config/routes.rb
86
118
  - lib/pippin.rb
119
+ - lib/pippin/app.rb
87
120
  - lib/pippin/engine.rb
88
121
  - lib/pippin/ipn.rb
89
- - lib/pippin/version.rb
90
122
  - pippin.gemspec
91
- - spec/controllers/pippin/ipns_controller_spec.rb
92
123
  - spec/internal/log/.gitignore
93
124
  - spec/internal/public/favicon.ico
94
125
  - spec/pippin/ipn_spec.rb
95
126
  - spec/requests/ipn_submission_spec.rb
96
127
  - spec/spec_helper.rb
97
- homepage: ''
128
+ homepage: ""
98
129
  licenses: []
130
+
99
131
  post_install_message:
100
132
  rdoc_options: []
101
- require_paths:
133
+
134
+ require_paths:
102
135
  - lib
103
- required_ruby_version: !ruby/object:Gem::Requirement
136
+ required_ruby_version: !ruby/object:Gem::Requirement
104
137
  none: false
105
- requirements:
106
- - - ! '>='
107
- - !ruby/object:Gem::Version
108
- version: '0'
109
- required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ hash: 3
142
+ segments:
143
+ - 0
144
+ version: "0"
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
146
  none: false
111
- requirements:
112
- - - ! '>='
113
- - !ruby/object:Gem::Version
114
- version: '0'
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ hash: 3
151
+ segments:
152
+ - 0
153
+ version: "0"
115
154
  requirements: []
155
+
116
156
  rubyforge_project: pippin
117
- rubygems_version: 1.8.10
157
+ rubygems_version: 1.8.16
118
158
  signing_key:
119
159
  specification_version: 3
120
160
  summary: A PayPal Rails Engine that handles IPNs
121
- test_files:
122
- - spec/controllers/pippin/ipns_controller_spec.rb
161
+ test_files:
123
162
  - spec/internal/log/.gitignore
124
163
  - spec/internal/public/favicon.ico
125
164
  - spec/pippin/ipn_spec.rb
@@ -1,16 +0,0 @@
1
- class Pippin::IpnsController < ActionController::Base
2
- def create
3
- if ipn.valid?
4
- Pippin.listener.call ipn if Pippin.listener
5
- head :ok
6
- else
7
- head :bad_request
8
- end
9
- end
10
-
11
- private
12
-
13
- def ipn
14
- @ipn ||= Pippin::IPN.new params, request.body.read
15
- end
16
- end
@@ -1,3 +0,0 @@
1
- module Pippin
2
- VERSION = '0.1.1'
3
- end
@@ -1,49 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Pippin::IpnsController do
4
- describe '#create' do
5
- let(:ipn) { double('IPN') }
6
- let(:block) { double('block', :call => true) }
7
-
8
- before :each do
9
- Pippin::IPN.stub :new => ipn
10
- Pippin.stub :listener => block
11
- end
12
-
13
- context 'valid IPN' do
14
- before :each do
15
- ipn.stub :valid? => true
16
- end
17
-
18
- it "responds with 200" do
19
- post :create
20
-
21
- response.status.should == 200
22
- end
23
-
24
- it "calls the listener with the IPN object" do
25
- block.should_receive(:call).with(ipn)
26
-
27
- post :create
28
- end
29
- end
30
-
31
- context 'invalid IPN' do
32
- before :each do
33
- ipn.stub :valid? => false
34
- end
35
-
36
- it "responds with a 400" do
37
- post :create
38
-
39
- response.status.should == 400
40
- end
41
-
42
- it "does not call the listener" do
43
- block.should_not_receive(:call)
44
-
45
- post :create
46
- end
47
- end
48
- end
49
- end