sinatra-paypal 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +97 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/sinatra/paypal.rb +89 -0
- data/lib/sinatra/paypal/_payment.erb +25 -0
- data/lib/sinatra/paypal/paypal-helper.rb +22 -0
- data/lib/sinatra/paypal/paypal-request.rb +109 -0
- data/lib/sinatra/paypal/version.rb +5 -0
- data/sinatra-paypal.gemspec +26 -0
- metadata +128 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ffeb452cdbca569c58812c31d78e17710f934e56
|
4
|
+
data.tar.gz: 0de4a6bd12fb5739d09449ee50056fa46b5738b7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1fe847202845a369bf73bdc01599e265ca5d0e486e3db82b1bf6f33c78d4115792aa0762998ac341193027707acd239240d1da0fe5ceeead6e19f1184e6da29d
|
7
|
+
data.tar.gz: 7f227512ad14d5b6dfe88d0f611c48a9f771ea11a5fdc5ff2616c8ee85719b67e00c9158a88fa10b18e7daf9d42f73d72b2432af9f7ea918321036425cd41e94
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Nathan Reed
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# Sinatra::Paypal
|
2
|
+
|
3
|
+
This gem makes it easy to validate and process Paypal IPN payments.
|
4
|
+
|
5
|
+
It automatically adds a route at `/payment/validate` to use as the IPN endpoint, and then adds a number of new methods to the sinatra DSL allow payment events to be captured and processed
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
Install the gem and then add it to your project with:
|
10
|
+
|
11
|
+
require 'sinatra-paypal'
|
12
|
+
|
13
|
+
Then set the email of the paypal account where you want the payments to be sent:
|
14
|
+
|
15
|
+
configure do
|
16
|
+
settings.paypal.email = 'my.company@gmail.com'
|
17
|
+
end
|
18
|
+
|
19
|
+
There are other configuration options you could use, but only setting the destination email is required.
|
20
|
+
|
21
|
+
Add the following to your app. It will get called only when the payment is complete:
|
22
|
+
|
23
|
+
# if this gets called, then you can be sure that the payment has passed validation
|
24
|
+
# with paypal, but you will still have to do your own validation - users can fiddle
|
25
|
+
# with the form fields before it gets sent to paypal - the only thing you can be sure
|
26
|
+
# of it the $ amount - if the payment is complete then this is the amount in your
|
27
|
+
# account.
|
28
|
+
#
|
29
|
+
# all the payment 'routes' get called with the payment object ('p')s containing all
|
30
|
+
# the data for the transaction
|
31
|
+
payment :complete do |p|
|
32
|
+
#
|
33
|
+
# check that the price for the item matches ours - it's possible for users to
|
34
|
+
# change the price to whatever they want, and there is no way for paypal to check
|
35
|
+
# this
|
36
|
+
#
|
37
|
+
# You could also do this in the 'payment :validate' method
|
38
|
+
#
|
39
|
+
halt 500, 'invalid price' if p.amount != ITEM_PRICE
|
40
|
+
|
41
|
+
# you can put a json string in the custom_data property of the payment
|
42
|
+
# form, and it will be deserialized into the data object - this is very
|
43
|
+
# useful for tracking user names etc, but remember that the contents of this
|
44
|
+
# field are not secure - a user could set it to whatever they want
|
45
|
+
upgrade_account p.data[:user_id]
|
46
|
+
end
|
47
|
+
|
48
|
+
Other routes that you can use for processing:
|
49
|
+
|
50
|
+
# validate! is called for every transaction - not just complete ones
|
51
|
+
#
|
52
|
+
# if it fails validation, then 'halt' with the error message - the
|
53
|
+
# return value is ignored
|
54
|
+
payment :validate! do |p|
|
55
|
+
# we need a 'user_id' field in the data, otherwise its not valid
|
56
|
+
halt 500, 'user_id required' if p.data[:user_id].nil?
|
57
|
+
end
|
58
|
+
|
59
|
+
# finsh is called last after all other processing is complete. It is called for
|
60
|
+
# every transaction type. It is useful for doing logging etc.
|
61
|
+
payment :finish do |p|
|
62
|
+
log_payment p.id, p.amount
|
63
|
+
end
|
64
|
+
|
65
|
+
# repeated should return true if this transaction has been seen before - we want
|
66
|
+
# to make sure we don't process it more than once
|
67
|
+
payment :repeated? do |p|
|
68
|
+
return true if transaction_list.include? p.id
|
69
|
+
|
70
|
+
transaction_list.push p.id
|
71
|
+
return false
|
72
|
+
end
|
73
|
+
|
74
|
+
## The Payment Object
|
75
|
+
|
76
|
+
Each payment route gets passed a payment object containing all the data from paypal for the transaction. It has several useful methods to make it easier to process the payment.
|
77
|
+
|
78
|
+
p.id # unique sha1 for the payment notification
|
79
|
+
p.transaction_id # paypal transaction_id
|
80
|
+
p.item_number # the item number set in the payment form
|
81
|
+
p.amount # payment amount
|
82
|
+
p.payment_fee # paypal fee amount
|
83
|
+
p.profit # payment amount minus any fees
|
84
|
+
p.data # custom data sent through as JSON
|
85
|
+
p.status # payment status - COMPLETE, PENDING, REFUNDED etc
|
86
|
+
p.complete? # true if the payment status is COMPLETE
|
87
|
+
p.is_accountable? # true if this notification has caused you paypal balance to change
|
88
|
+
p.reason_code # if the transaction is a refund the reason code will appear here
|
89
|
+
p.fields # object with the raw paypal notification data
|
90
|
+
|
91
|
+
## Contributing
|
92
|
+
|
93
|
+
1. Fork it ( https://github.com/reednj/sinatra-paypal/fork )
|
94
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
95
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
96
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
97
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "sinatra/paypal"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
|
2
|
+
require 'sinatra/base'
|
3
|
+
require 'sinatra/paypal/version'
|
4
|
+
require 'sinatra/paypal/paypal-helper'
|
5
|
+
require 'sinatra/paypal/paypal-request'
|
6
|
+
|
7
|
+
PAYPAL_BLOCKS = {}
|
8
|
+
|
9
|
+
module PayPal
|
10
|
+
|
11
|
+
module Helpers
|
12
|
+
def paypal_block(name)
|
13
|
+
return Proc.new{} if !PAYPAL_BLOCKS.key? name
|
14
|
+
PAYPAL_BLOCKS[name]
|
15
|
+
end
|
16
|
+
|
17
|
+
def paypal_form_url
|
18
|
+
PaypalHelper.form_url(settings.paypal.sandbox?)
|
19
|
+
end
|
20
|
+
|
21
|
+
def html_payment_form(offer_data, data = nil)
|
22
|
+
data ||= {}
|
23
|
+
data[:username] = session[:reddit_user] if !data.nil? && data[:username].nil?
|
24
|
+
data[:offer_code] = offer_data.code if !offer_data.nil?
|
25
|
+
|
26
|
+
raise 'cannot generate a payment form without settings.paypal.email' if settings.paypal.email.nil?
|
27
|
+
|
28
|
+
erb :_payment, :views => File.join(File.dirname(__FILE__), '/paypal'), :locals => {
|
29
|
+
:custom_data => data,
|
30
|
+
:offer_data => offer_data
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.registered(app)
|
36
|
+
app.helpers PayPal::Helpers
|
37
|
+
|
38
|
+
app.set :paypal, OpenStruct.new({
|
39
|
+
:return_url => '/payment/complete',
|
40
|
+
:notify_url => '/payment/validate',
|
41
|
+
:sandbox? => app.settings.development?,
|
42
|
+
:email => nil
|
43
|
+
})
|
44
|
+
|
45
|
+
app.post '/payment/validate' do
|
46
|
+
paypal_helper = PaypalHelper.new(app.settings.paypal.sandbox?)
|
47
|
+
paypal_request = PaypalRequest.new(params)
|
48
|
+
|
49
|
+
# first we check the request against paypal to make sure it is ok
|
50
|
+
if settings.production?
|
51
|
+
halt 400, 'request could not be validated' if !paypal_helper.ipn_valid? params
|
52
|
+
end
|
53
|
+
|
54
|
+
halt 400, 'no username provided' if paypal_request.username.nil?
|
55
|
+
|
56
|
+
# check transaction log to make sure this not a replay attack
|
57
|
+
if instance_exec(paypal_request, &paypal_block(:repeated?))
|
58
|
+
# we want to log this, so we know about it, but we also want to return 200, because
|
59
|
+
# if it is paypal sending this, it will send it again and again until it gets a 200
|
60
|
+
# back
|
61
|
+
log_error 'already processed' if respond_to? :log_error
|
62
|
+
halt 200, 'already processed'
|
63
|
+
else
|
64
|
+
instance_exec(paypal_request, &paypal_block(:save))
|
65
|
+
end
|
66
|
+
|
67
|
+
instance_exec(paypal_request, &paypal_block(:validate!))
|
68
|
+
|
69
|
+
# check that the payment is complete. we still return 200 if not, but
|
70
|
+
# we don't need to do anymore processing (except for marking it as accountable, if it is)
|
71
|
+
if paypal_request.complete?
|
72
|
+
instance_exec(paypal_request, &paypal_block(:complete))
|
73
|
+
end
|
74
|
+
|
75
|
+
instance_exec(paypal_request, &paypal_block(:finish))
|
76
|
+
|
77
|
+
return 200
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def payment(name, &block)
|
82
|
+
PAYPAL_BLOCKS[name] = block
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
module Sinatra
|
88
|
+
register PayPal
|
89
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
<form id='paypal-form' action="<%= paypal_form_url %>" method="post" style='display:none'>
|
3
|
+
<input type="hidden" name="cmd" value="_xclick">
|
4
|
+
|
5
|
+
<input type="hidden" name="business" value="<%= settings.paypal.email %>">
|
6
|
+
<input type='hidden' name='custom' value='<%= custom_data.to_json %>'>
|
7
|
+
<input type="hidden" name="return" value="<%= url(settings.paypal.return_url) %>">
|
8
|
+
<input type="hidden" name="notify_url" value="<%= url(settings.paypal.notify_url) %>">
|
9
|
+
|
10
|
+
<% if offer_data.nil? %>
|
11
|
+
<input type="hidden" name="item_number" id='paypal-item-number' value="0">
|
12
|
+
<input type="hidden" name="item_name" id='paypal-item-name' value="0">
|
13
|
+
<input type="hidden" name="amount" id='paypal-amount' value="1.00">
|
14
|
+
<% else %>
|
15
|
+
<input type="hidden" name="item_number" id='paypal-item-number' value="<%= offer_data.offer.item_code %>">
|
16
|
+
<input type="hidden" name="item_name" id='paypal-item-name' value="<%= offer_data.item.name %>">
|
17
|
+
<input type="hidden" name="amount" id='paypal-amount' value="<%= offer_data.offer.amount %>">
|
18
|
+
<% end %>
|
19
|
+
|
20
|
+
<input type="hidden" name="no_shipping" value="1">
|
21
|
+
<input type="hidden" name="no_note" value="1">
|
22
|
+
<input type="hidden" name="currency_code" value="USD">
|
23
|
+
<input type="hidden" name="lc" value="US">
|
24
|
+
<input type="hidden" name="bn" value="PP-BuyNowBF">
|
25
|
+
</form>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
|
3
|
+
class PaypalHelper
|
4
|
+
def initialize(use_sandbox)
|
5
|
+
@use_sandbox = use_sandbox
|
6
|
+
end
|
7
|
+
|
8
|
+
def form_url
|
9
|
+
@use_sandbox ? 'https://www.sandbox.paypal.com/cgi-bin/webscr' : 'https://www.paypal.com/cgi-bin/webscr'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.form_url(use_sandbox)
|
13
|
+
new(use_sandbox).form_url
|
14
|
+
end
|
15
|
+
|
16
|
+
def ipn_valid?(params)
|
17
|
+
return false if params.nil?
|
18
|
+
|
19
|
+
params[:cmd] = '_notify-validate'
|
20
|
+
return RestClient.post(self.form_url, params) == 'VERIFIED'
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
|
2
|
+
class PaypalRequest
|
3
|
+
def initialize(params)
|
4
|
+
if params.is_a? String
|
5
|
+
params = JSON.parse(params, {:symbolize_names => true})
|
6
|
+
end
|
7
|
+
|
8
|
+
@fields = params
|
9
|
+
@custom_data = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def id
|
14
|
+
self.transaction_hash
|
15
|
+
end
|
16
|
+
|
17
|
+
# the same transaction id can come in mulitple times with different statuses
|
18
|
+
# so we need to check both of them in order to see if the txn is unquie
|
19
|
+
def transaction_hash
|
20
|
+
"#{self.transaction_id}-#{@fields[:payment_status]}".sha1
|
21
|
+
end
|
22
|
+
|
23
|
+
def transaction_id
|
24
|
+
@fields[:txn_id]
|
25
|
+
end
|
26
|
+
|
27
|
+
def item_valid?(item_list, offer_data = nil)
|
28
|
+
if item_list[self.item_number] != nil
|
29
|
+
if item_list[self.item_number][:amount] == self.amount
|
30
|
+
# this is a regular purchase at the regular price
|
31
|
+
return true
|
32
|
+
elsif self.uses_offer?(offer_data)
|
33
|
+
# the price comes from a special offer, its valid
|
34
|
+
return true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
return false
|
39
|
+
end
|
40
|
+
|
41
|
+
def uses_offer?(offer_data)
|
42
|
+
# to check if the offer is valid on this purchase we need to check the item code and the price matches
|
43
|
+
return !offer_data.nil? && offer_data[:offer][:item_code] == self.item_number && offer_data[:offer][:amount] == self.amount
|
44
|
+
end
|
45
|
+
|
46
|
+
def item_number
|
47
|
+
@fields[:item_number]
|
48
|
+
end
|
49
|
+
|
50
|
+
def amount
|
51
|
+
Float(@fields[:mc_gross] || 0)
|
52
|
+
end
|
53
|
+
|
54
|
+
def payment_fee
|
55
|
+
Float(@fields[:mc_fee] || 0)
|
56
|
+
end
|
57
|
+
|
58
|
+
# defined as the gross amount, minus transaction fees
|
59
|
+
# could also subtract shipping and tax here as well, but we don't have to deal with
|
60
|
+
# any of that yet
|
61
|
+
def profit
|
62
|
+
self.amount - self.payment_fee
|
63
|
+
end
|
64
|
+
|
65
|
+
def username
|
66
|
+
self.custom_data[:username]
|
67
|
+
end
|
68
|
+
|
69
|
+
def custom_data
|
70
|
+
if @custom_data.nil?
|
71
|
+
if @fields[:custom].strip.start_with? '{'
|
72
|
+
# we could get a json object through, in which case it needs to be parsed...
|
73
|
+
@custom_data = JSON.parse(@fields[:custom], {:symbolize_names => true})
|
74
|
+
else
|
75
|
+
# ...or would just have a string (which we assume is the target username) in that
|
76
|
+
# case we need to normalize it into an object
|
77
|
+
@custom_data = {:username => @fields[:custom]}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
return @custom_data
|
82
|
+
end
|
83
|
+
|
84
|
+
def data
|
85
|
+
custom_data
|
86
|
+
end
|
87
|
+
|
88
|
+
def status
|
89
|
+
@fields[:payment_status]
|
90
|
+
end
|
91
|
+
|
92
|
+
def complete?
|
93
|
+
self.status == 'Completed'
|
94
|
+
end
|
95
|
+
|
96
|
+
# these are payment statues that actually result in the paypal balance changing, so we should set them as
|
97
|
+
# accountable in the payment_log
|
98
|
+
def is_accountable?
|
99
|
+
(self.complete? || self.status == 'Refunded' || self.status == 'Reversed' || self.status == 'Canceled_Reversal')
|
100
|
+
end
|
101
|
+
|
102
|
+
def reason_code
|
103
|
+
@fields[:reason_code] || @fields[:pending_reason]
|
104
|
+
end
|
105
|
+
|
106
|
+
def fields
|
107
|
+
@fields
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'sinatra/paypal/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "sinatra-paypal"
|
8
|
+
spec.version = Sinatra::Paypal::VERSION
|
9
|
+
spec.authors = ["Nathan Reed"]
|
10
|
+
spec.email = ["reednj@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Easy validation and processing of Paypal IPN payments}
|
13
|
+
spec.homepage = "http://github.com/reednj/sinatra-paypal"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.9"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "test-unit"
|
24
|
+
spec.add_runtime_dependency "sinatra"
|
25
|
+
spec.add_runtime_dependency "rest-client"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sinatra-paypal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nathan Reed
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.9'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: test-unit
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sinatra
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rest-client
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- reednj@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".travis.yml"
|
92
|
+
- Gemfile
|
93
|
+
- LICENSE.txt
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- bin/console
|
97
|
+
- bin/setup
|
98
|
+
- lib/sinatra/paypal.rb
|
99
|
+
- lib/sinatra/paypal/_payment.erb
|
100
|
+
- lib/sinatra/paypal/paypal-helper.rb
|
101
|
+
- lib/sinatra/paypal/paypal-request.rb
|
102
|
+
- lib/sinatra/paypal/version.rb
|
103
|
+
- sinatra-paypal.gemspec
|
104
|
+
homepage: http://github.com/reednj/sinatra-paypal
|
105
|
+
licenses:
|
106
|
+
- MIT
|
107
|
+
metadata: {}
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options: []
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
requirements: []
|
123
|
+
rubyforge_project:
|
124
|
+
rubygems_version: 2.4.5
|
125
|
+
signing_key:
|
126
|
+
specification_version: 4
|
127
|
+
summary: Easy validation and processing of Paypal IPN payments
|
128
|
+
test_files: []
|