pippin 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ gem 'combustion',
6
+ :git => 'git://github.com/freelancing-god/combustion.git',
7
+ :ref => 'd7a6836269a8fb528bc3721e9b8c06b5a62ef3cc'
data/HISTORY ADDED
@@ -0,0 +1,2 @@
1
+ 0.1.0 - January 4th 2012
2
+ * First release
data/LICENCE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Pat Allan
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.textile ADDED
@@ -0,0 +1,35 @@
1
+ h1. Pippin
2
+
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
+
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.
6
+
7
+ h2. Installation and Usage
8
+
9
+ Just add it to your Gemfile:
10
+
11
+ <pre><code>gem 'pippin', '~> 0.1'</code></pre>
12
+
13
+ Then, in an initializer, add your listener:
14
+
15
+ <pre><code># config/initializers/pippin.rb
16
+ Pippin.listener = lambda { |ipn|
17
+ # use IPN data
18
+ ipn.params # => {'business' => 'email@domain.com', 'txn_type' => ...}
19
+ }</code></pre>
20
+
21
+ 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
+
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@.
24
+
25
+ h2. Contributing
26
+
27
+ Contributions are very much welcome - but keep in mind the following:
28
+
29
+ * Keep patches in a separate branch.
30
+ * Write tests for your patches.
31
+ * Don't mess with the version or history file. I'll take care of that when the patch is merged in.
32
+
33
+ h2. Credits
34
+
35
+ Copyright (c) 2012, Pippin is developed and maintained by Pat Allan, and is released under the open MIT Licence.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,16 @@
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
data/config/routes.rb ADDED
@@ -0,0 +1,5 @@
1
+ Rails.application.routes.draw do
2
+ namespace :pippin do
3
+ resources :ipns, :only => [:create]
4
+ end
5
+ end
data/config.ru ADDED
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ Bundler.require :default, :development
5
+
6
+ Combustion.initialize! :action_controller
7
+ run Combustion::Application
@@ -0,0 +1,4 @@
1
+ class Pippin::Engine < Rails::Engine
2
+ paths['app/controllers'] << 'app/controllers'
3
+ paths['config'] << 'config'
4
+ end
data/lib/pippin/ipn.rb ADDED
@@ -0,0 +1,43 @@
1
+ require 'open-uri'
2
+
3
+ class Pippin::IPN
4
+ attr_accessor :params, :body
5
+
6
+ def initialize(params = {}, body = '')
7
+ @params = parse_dates(params)
8
+ @body = body
9
+ end
10
+
11
+ def valid?
12
+ open(paypal_uri).read == 'VERIFIED'
13
+ end
14
+
15
+ private
16
+
17
+ def parse_dates(params)
18
+ params.each do |key, value|
19
+ next if key.to_s[/_date$/].nil?
20
+
21
+ params[key] = parse_date value
22
+ end
23
+ end
24
+
25
+ def parse_date(string)
26
+ return nil if string.blank?
27
+
28
+ match = string.match(/(\d+):(\d+):(\d+) (\w{3}) (\d+), (\d{4}) (\w\w\w)/)
29
+ time = Time.zone.local match[6].to_i, match[4], match[5].to_i,
30
+ match[1].to_i, match[2].to_i, match[3].to_i
31
+
32
+ case match[7]
33
+ when /PDT/
34
+ time + 7.hours + Time.zone.utc_offset
35
+ when /PST/
36
+ time + 8.hours + Time.zone.utc_offset
37
+ end
38
+ end
39
+
40
+ def paypal_uri
41
+ "https://www.paypal.com/cgi-bin/webscr?cmd=_notify-validate&#{body}"
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ module Pippin
2
+ VERSION = '0.1.0'
3
+ end
data/lib/pippin.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'rails'
2
+
3
+ module Pippin
4
+ def self.listener
5
+ @listener
6
+ end
7
+
8
+ def self.listener=(listener)
9
+ @listener = listener
10
+ end
11
+ end
12
+
13
+ require 'pippin/engine'
14
+ require 'pippin/ipn'
15
+ require 'pippin/version'
data/pippin.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'pippin/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'pippin'
7
+ s.version = Pippin::VERSION
8
+ s.authors = ['Pat Allan']
9
+ s.email = ['pat@freelancing-gods.com']
10
+ s.homepage = ''
11
+ s.summary = 'A PayPal Rails Engine that handles IPNs'
12
+ s.description = 'Accepts IPN requests, validates them and passes them on to a defined listener.'
13
+
14
+ s.rubyforge_project = 'pippin'
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ['lib']
20
+
21
+ s.add_runtime_dependency 'rails', '3.1.3'
22
+
23
+ s.add_development_dependency 'fakeweb', '1.3.0'
24
+ s.add_development_dependency 'fakeweb-matcher', '1.2.2'
25
+ s.add_development_dependency 'rspec-rails', '2.7.0'
26
+ s.add_development_dependency 'sqlite3', '1.3.5'
27
+ end
@@ -0,0 +1,49 @@
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
@@ -0,0 +1 @@
1
+ *.log
File without changes
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pippin::IPN do
4
+ describe '#params' do
5
+ it "should parse dates in params" do
6
+ ipn = Pippin::IPN.new :subscr_date => '20:36:25 Jan 02, 2012 PST'
7
+ ipn.params[:subscr_date].should == Time.zone.local(2012, 1, 3, 4, 36, 25)
8
+ end
9
+ end
10
+
11
+ describe '#valid?' do
12
+ let(:ipn) { Pippin::IPN.new }
13
+
14
+ it "checks the validity with PayPal and the given request body" do
15
+ FakeWeb.register_uri :get, /^https:\/\/www\.paypal\.com\/cgi-bin\/webscr/,
16
+ :body => 'VERIFIED'
17
+
18
+ ipn = Pippin::IPN.new({}, 'foo=bar')
19
+ ipn.valid?
20
+
21
+ FakeWeb.should have_requested(:get,
22
+ 'https://www.paypal.com/cgi-bin/webscr?cmd=_notify-validate&foo=bar')
23
+ end
24
+
25
+ it "returns true if PayPal confirms the validity of the data" do
26
+ FakeWeb.register_uri :get, /^https:\/\/www\.paypal\.com\/cgi-bin\/webscr/,
27
+ :body => 'VERIFIED'
28
+
29
+ ipn.should be_valid
30
+ end
31
+
32
+ it "returns false if PayPal revokes the validity of the data" do
33
+ FakeWeb.register_uri :get, /^https:\/\/www\.paypal\.com\/cgi-bin\/webscr/,
34
+ :body => 'INVALID'
35
+
36
+ ipn.should_not be_valid
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'IPN Submission' do
4
+ context 'valid IPN' do
5
+ before :each do
6
+ FakeWeb.register_uri :get, /^https:\/\/www\.paypal\.com\/cgi-bin\/webscr/,
7
+ :body => 'VERIFIED'
8
+ end
9
+
10
+ it "responds with a 200 status" do
11
+ post '/pippin/ipns'
12
+
13
+ response.status.should == 200
14
+ end
15
+
16
+ it "calls the provided listener" do
17
+ ipn_lodged = false
18
+ Pippin.listener = lambda { |ipn| ipn_lodged = true }
19
+
20
+ post '/pippin/ipns'
21
+
22
+ ipn_lodged.should be_true
23
+ end
24
+ end
25
+
26
+ context 'invalid IPN' do
27
+ before :each do
28
+ FakeWeb.register_uri :get, /^https:\/\/www\.paypal\.com\/cgi-bin\/webscr/,
29
+ :body => 'INVALID'
30
+ end
31
+
32
+ it "responds with a 400 status" do
33
+ post '/pippin/ipns'
34
+
35
+ response.status.should == 400
36
+ end
37
+
38
+ it "does not call the provided listener" do
39
+ ipn_lodged = false
40
+ Pippin.listener = lambda { |ipn| ipn_lodged = true }
41
+
42
+ post '/pippin/ipns'
43
+
44
+ ipn_lodged.should be_false
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ Bundler.require :default, :development
5
+
6
+ Combustion.initialize! :action_controller
7
+
8
+ require 'rspec/rails'
9
+ require 'fakeweb_matcher'
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pippin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Pat Allan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: &70216495368700 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - =
20
+ - !ruby/object:Gem::Version
21
+ version: 3.1.3
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70216495368700
25
+ - !ruby/object:Gem::Dependency
26
+ name: fakeweb
27
+ requirement: &70216491700040 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - =
31
+ - !ruby/object:Gem::Version
32
+ version: 1.3.0
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70216491700040
36
+ - !ruby/object:Gem::Dependency
37
+ name: fakeweb-matcher
38
+ requirement: &70216491698460 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - =
42
+ - !ruby/object:Gem::Version
43
+ version: 1.2.2
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70216491698460
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec-rails
49
+ requirement: &70216491697500 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - =
53
+ - !ruby/object:Gem::Version
54
+ version: 2.7.0
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70216491697500
58
+ - !ruby/object:Gem::Dependency
59
+ name: sqlite3
60
+ requirement: &70216491696780 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - =
64
+ - !ruby/object:Gem::Version
65
+ version: 1.3.5
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70216491696780
69
+ description: Accepts IPN requests, validates them and passes them on to a defined
70
+ listener.
71
+ email:
72
+ - pat@freelancing-gods.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - .gitignore
78
+ - Gemfile
79
+ - HISTORY
80
+ - LICENCE
81
+ - README.textile
82
+ - Rakefile
83
+ - app/controllers/pippin/ipns_controller.rb
84
+ - config.ru
85
+ - config/routes.rb
86
+ - lib/pippin.rb
87
+ - lib/pippin/engine.rb
88
+ - lib/pippin/ipn.rb
89
+ - lib/pippin/version.rb
90
+ - pippin.gemspec
91
+ - spec/controllers/pippin/ipns_controller_spec.rb
92
+ - spec/internal/log/.gitignore
93
+ - spec/internal/public/favicon.ico
94
+ - spec/pippin/ipn_spec.rb
95
+ - spec/requests/ipn_submission_spec.rb
96
+ - spec/spec_helper.rb
97
+ homepage: ''
98
+ licenses: []
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ! '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ! '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubyforge_project: pippin
117
+ rubygems_version: 1.8.10
118
+ signing_key:
119
+ specification_version: 3
120
+ summary: A PayPal Rails Engine that handles IPNs
121
+ test_files:
122
+ - spec/controllers/pippin/ipns_controller_spec.rb
123
+ - spec/internal/log/.gitignore
124
+ - spec/internal/public/favicon.ico
125
+ - spec/pippin/ipn_spec.rb
126
+ - spec/requests/ipn_submission_spec.rb
127
+ - spec/spec_helper.rb
128
+ has_rdoc: