Skip to content

Instantly share code, notes, and snippets.

@manuelmeurer
Created March 8, 2014 10:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save manuelmeurer/9428199 to your computer and use it in GitHub Desktop.
Save manuelmeurer/9428199 to your computer and use it in GitHub Desktop.
Paypal Recurring stuff
class Admin::PaypalController < AdminController
skip_load_and_authorize_resource
before_filter :load_current_account, except: :ipn
skip_before_filter :authenticate_admin_user!, :verify_authenticity_token, :redirect_to_dashboard_if_account_is_disabled, :redirect_to_dashboard_if_maintenance, only: :ipn
rescue_from PaypalNotification::RecordInvalid, with: :notify_airbrake_and_render_nothing
rescue_from PaypalNotification::ResponseInvalid, with: :notify_airbrake_and_render_nothing
rescue_from PaypalNotification::HandlingFailed, with: :notify_airbrake_and_render_nothing
def setup_recurring
redirect_to PaypalRecurring.new(@account).checkout_url
end
def confirm_recurring
raise StandardError, 'No PayerID supplied.' if params[:PayerID].blank?
@account.paypal_customer_token = params[:PayerID]
@account.paypal_payment_token = params[:token]
response = PaypalRecurring.new(@account).make_recurring
@account.paypal_recurring_profile_token = response.profile_id
@account.save!
redirect_to admin_my_account_path, notice: 'Successfully set up Paypal subscription.'
end
def ipn
response = RestClient.post(
PayPal::Recurring.site_endpoint,
['cmd=_notify-validate', request.raw_post].join('&')
)
begin
paypal_notification = PaypalNotification.create!(params: params.except('controller', 'action'))
rescue PaypalNotification::DuplicateRecord
# Duplicate
render nothing: true and return
rescue ActiveRecord::RecordInvalid => e
raise PaypalNotification::RecordInvalid, e.message
end
if response.code == 200
case response.body
when 'VERIFIED'
paypal_notification.handle! if paypal_notification.persisted?
when 'INVALID'
raise PaypalNotification::ResponseInvalid, "Paypal said notification validation request was invalid: #{response.inspect}"
else
raise PaypalNotification::ResponseInvalid, "Unexpected response from Paypal: #{response.inspect}"
end
else
raise PaypalNotification::ResponseInvalid, "Unexpected response code from Paypal: #{response.inspect}"
end
render nothing: true
end
private
def notify_airbrake_and_render_nothing(exception)
notify_airbrake exception
render nothing: true
end
def load_current_account
@account = current_admin_user.account
end
end
# == Schema Information
#
# Table name: paypal_notifications
#
# id :integer(4) not null, primary key
# status :string(255)
# transaction_id :string(255)
# params :text
# account_id :integer(4)
# created_at :datetime
# updated_at :datetime
# payment_id :integer(4)
#
class PaypalNotification < ActiveRecord::Base
belongs_to :account
belongs_to :payment
validates :account, presence: true
validates :params, presence: true
validates :transaction_id,
uniqueness: true,
allow_blank: true
serialize :params, Hash
before_validation :set_defaults, on: :create
before_validation :check_for_duplicate, on: :create
class RecordInvalid < StandardError; end
class ResponseInvalid < StandardError; end
class DuplicateRecord < StandardError; end
class HandlingFailed < StandardError; end
def handle!
return unless self.status == 'Completed'
raise StandardError, 'Paypal notification has already been handled.' if self.payment.present?
payment = self.account.payments.build(
title: self.params['item_name'],
amount: self.params['mc_gross'].gsub(/[.,]/, '').to_i
)
if self.params.has_key?('item_number')
payment.product_package = ProductPackage.from_template(self.params['item_number'].to_i)
payment.product_package.account = self.account
else
Airbrake.notify \
error_class: 'PaypalNotificationWithoutItemNumberReceived',
error_message: "Paypal notification #{self.id} does not have a item number so no product package has been created."
end
payment.save!
self.payment = payment
self.save!
rescue StandardError => e
raise HandlingFailed, e.message
end
private
def set_defaults
self.account ||= Account.find(self.params['custom']) if self.params.has_key?('custom')
self.transaction_id ||= self.params['txn_id'] if self.params.has_key?('txn_id')
self.status ||= self.params['payment_status'] if self.params.has_key?('payment_status')
end
def check_for_duplicate
raise DuplicateRecord if self.transaction_id.present? && self.class.exists?(transaction_id: self.transaction_id)
end
end
class PaypalRecurring
include ApplicationHelper
def initialize(account)
@account = account
end
def checkout_url
process(:checkout).checkout_url
end
def make_recurring
process :request_payment
process :create_recurring_profile,
period: :monthly,
frequency: 1,
start_at: @account.trial_end_date.advance(days: 1)
end
private
def process(action, options = {})
opts = options.reverse_merge(
return_url: Rails.application.routes.url_helpers.admin_paypal_confirm_recurring_url(host: "admin.#{AppConfig.host}"),
cancel_url: Rails.application.routes.url_helpers.admin_my_account_url(host: "admin.#{AppConfig.host}"),
ipn_url: Rails.application.routes.url_helpers.admin_paypal_ipn_url(host: "admin.#{AppConfig.host}"),
token: @account.paypal_payment_token,
payer_id: @account.paypal_customer_token,
description: I18n.t(:paypal, amount: format_price(@account.current_plan.amount), date: @account.current_billing_period.next.start_date.to_s(:long)),
amount: '%.2f' % @account.current_plan.amount,
currency: 'USD',
locale: :us
)
PayPal::Recurring.new(opts).send(action).tap do |response|
raise response.errors.inspect if response.errors.present?
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment