From eb782f11d52d9c9e8db7442e5c7063f71fef6eda Mon Sep 17 00:00:00 2001 From: Will Bradley Date: Sat, 24 Aug 2013 03:40:34 -0700 Subject: [PATCH] Adding IPN validation --- app/controllers/ipns_controller.rb | 12 ++++++- app/models/ipn.rb | 58 ++++++++++++++++++++++++------ app/views/ipns/index.html.erb | 9 ++++- app/views/ipns/new.html.erb | 2 +- app/views/payments/show.html.erb | 2 +- config/routes.rb | 1 + 6 files changed, 69 insertions(+), 15 deletions(-) diff --git a/app/controllers/ipns_controller.rb b/app/controllers/ipns_controller.rb index a392dd8..6018abd 100644 --- a/app/controllers/ipns_controller.rb +++ b/app/controllers/ipns_controller.rb @@ -15,11 +15,21 @@ class IpnsController < ApplicationController end def create - #TODO: ensure the request is actually from paypal @ipn = Ipn.new_from_dynamic_params(params) @ipn.data = params.to_json @ipn.save render :nothing => true + unless @ipn.validate! + Rails.logger.error "Unable to validate IPN: #{@ipn.inspect}" + end + end + + def validate + if @ipn.validate! + redirect_to ipns_url, :notice => 'Valid!' + else + redirect_to ipns_url, :notice => 'INVALID' + end end def link diff --git a/app/models/ipn.rb b/app/models/ipn.rb index 3643066..2b37017 100644 --- a/app/models/ipn.rb +++ b/app/models/ipn.rb @@ -1,3 +1,4 @@ +require 'net/http' class Ipn < ActiveRecord::Base attr_accessible :data belongs_to :payment @@ -16,6 +17,32 @@ class Ipn < ActiveRecord::Base return ipn end + # Post back to Paypal to make sure it's valid + def validate! + uri = URI.parse('https://www.paypal.com/cgi-bin/webscr?cmd=_notify-validate') + + http = Net::HTTP.new(uri.host, uri.port) + http.open_timeout = 60 + http.read_timeout = 60 + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + http.use_ssl = true + response = http.post(uri.request_uri, self.data, + 'Content-Length' => "#{self.data.size}", + 'User-Agent' => "Ruby on Rails" + ).body + + unless ["VERIFIED", "INVALID"].include?(response) + Rails.logger.error "Faulty paypal result: #{response}" + return false + end + unless response == "VERIFIED" + Rails.logger.error "Invalid IPN: #{response}" + return false + end + + return true + end + def link_payment create_payment end @@ -26,25 +53,34 @@ class Ipn < ActiveRecord::Base user = User.find_by_email(self.payer_email) user = User.find_by_payee(self.payer_email) if user.nil? && self.payer_email.present? - # Only create payments if the amount matches a member level + # Only create payments if the IPN matches a member if user.present? - if User.member_levels[self.payment_gross.to_i].present? - payment = Payment.new - payment.date = self.payment_date - payment.user_id = user.id - payment.amount = self.payment_gross - if payment.save - self.payment_id = payment.id - self.save! + # And is a payment (not a cancellation, etc) + payment_types = ["subscr_payment","send_money"] + if payment_types.include?(self.txn_type) + # And a member level + if User.member_levels[self.payment_gross.to_i].present? + payment = Payment.new + payment.date = Date.strptime(self.payment_date, "%H:%M:%S %b %e, %Y %Z") + payment.user_id = user.id + payment.amount = self.payment_gross + if payment.save + self.payment_id = payment.id + self.save! + else + return [false, "Unable to link payment. Payment error: #{payment.errors.full_messages.first}"] + end else - return [false, "Unable to link payment. Payment error: #{payment.errors.full_messages.first}"] + return [false, "Unable to link payment. Couldn't find membership level '#{self.payment_gross.to_i}'."] end else - return [false, "Unable to link payment. Couldn't find membership level '#{self.payment_gross.to_i}'."] + return [false, "Unable to link payment. Transaction is a '#{self.txn_type}' instead of '#{payment_types.inspect}'."] end else return [false, "Unable to link payment. Couldn't find user/payee '#{self.payer_email}'."] end + + return [true] end end diff --git a/app/views/ipns/index.html.erb b/app/views/ipns/index.html.erb index 9a6afff..724c670 100644 --- a/app/views/ipns/index.html.erb +++ b/app/views/ipns/index.html.erb @@ -11,7 +11,13 @@ <%= ipn.payment_date %> <%= ipn.first_name %> <%= ipn.last_name %> <%= ipn.item_name %> - <%= ipn.payment_gross %> + + <% if ipn.payment_gross.blank? %> + <%= ipn.txn_type %> + <% else %> + <%= ipn.payment_gross %> + <% end %> + <% if ipn.payment.present? %> <%= link_to "Linked Payment", ipn.payment %> @@ -20,6 +26,7 @@ <% end %> <%= link_to "Details", ipn %> + <%= link_to "Validate", validate_ipn_path(ipn) %> <% end %> diff --git a/app/views/ipns/new.html.erb b/app/views/ipns/new.html.erb index 3322347..8839fb6 100644 --- a/app/views/ipns/new.html.erb +++ b/app/views/ipns/new.html.erb @@ -35,7 +35,7 @@
<%= label_tag :payment_date %> - <%= text_field_tag :payment_date, Date.today.to_s %> + <%= text_field_tag :payment_date, "20:46:54 Jun 20, 2013 PDT" %>
<%= label_tag :txn_type %> diff --git a/app/views/payments/show.html.erb b/app/views/payments/show.html.erb index c735823..316d2ec 100644 --- a/app/views/payments/show.html.erb +++ b/app/views/payments/show.html.erb @@ -1,6 +1,6 @@

User: - <%= @payment.user.name_with_payee_and_member_level unless @payment.user.blank? %> + <%= link_to @payment.user.name_with_payee_and_member_level, @payment.user unless @payment.user.blank? %>

diff --git a/config/routes.rb b/config/routes.rb index 6c64774..4a4f563 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,7 @@ Dooraccess::Application.routes.draw do resources :ipns match 'ipns/:id/link' => 'ipns#link', :as => :link_ipn + match 'ipns/:id/validate' => 'ipns#validate', :as => :validate_ipn resources :payments