Created
January 7, 2009 20:59
-
-
Save pkieltyka/44430 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
### Recurring Billing implementation was originally written by Chris Cera | |
### http://blog.vuzit.com/2008/08/01/paypal-website-payments-pro-us-with-recurring-billing-and-activemerchant/ | |
# put the below in an init.rb file into a Rails plugin | |
#require 'paypal_recurring_billing' | |
#ActiveMerchant::Billing::PaypalGateway.send :include, AmExt::PaypalRecurringBilling | |
# | |
module AmExt | |
module PaypalRecurringBilling | |
@@API_VERSION = '52.0' | |
PAYPAL_NAMESPACE = ActiveMerchant::Billing::PaypalCommonAPI::PAYPAL_NAMESPACE | |
EBAY_NAMESPACE = ActiveMerchant::Billing::PaypalCommonAPI::EBAY_NAMESPACE | |
# I invented the :suspend action, and this doesn't appear in payflow.rb | |
RECURRING_ACTIONS = Set.new([:add, :cancel, :inquiry, :suspend]) | |
# Several options are available to customize the recurring profile: | |
# | |
# * <tt>profile_id</tt> - is only required for editing a recurring profile | |
# * <tt>starting_at</tt> - takes a Date, Time, or string in mmddyyyy format. The date must be in the future. | |
# * <tt>name</tt> - The name of the customer to be billed. If not specified, the name from the credit card is used. | |
# * <tt>periodicity</tt> - The frequency that the recurring payments will occur at. Can be one of | |
# :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily, :semimonthly, :quadweekly, :quarterly, :semiyearly | |
# * <tt>payments</tt> - The term, or number of payments that will be made | |
# * <tt>comment</tt> - A comment associated with the profile | |
def recurring(money, credit_card, options = {}) | |
options[:name] = credit_card.name if options[:name].blank? && credit_card | |
request = build_recurring_request(options[:profile_id] ? :modify : :add, money, options) do |xml| | |
add_credit_card(xml, credit_card, options[:billing_address], options) if credit_card | |
end | |
commit('CreateRecurringPaymentsProfile', request) | |
end | |
# cancels an existing recurring profile | |
def cancel_recurring(profile_id) | |
request = build_recurring_request(:cancel, 0, :profile_id => profile_id) {} | |
commit('ManageRecurringPaymentsProfileStatus', request) | |
end | |
# retrieves information about a recurring profile | |
def recurring_inquiry(profile_id, options = {}) | |
request = build_recurring_request(:inquiry, nil, options.update(:profile_id => profile_id)) | |
commit('GetRecurringPaymentsProfileDetails', request) | |
end | |
# suspends a recurring profile | |
def suspend_recurring(profile_id) | |
request = build_recurring_request(:suspend, 0, :profile_id => profile_id) {} | |
commit('ManageRecurringPaymentsProfileStatus', request) | |
end | |
private | |
def build_recurring_request(action, money, options) | |
unless RECURRING_ACTIONS.include?(action) | |
raise StandardError, "Invalid Recurring Profile Action: #{action}" | |
end | |
xml = Builder::XmlMarkup.new :indent => 2 | |
ns2 = 'n2:' | |
if [:add].include?(action) | |
xml.tag! 'CreateRecurringPaymentsProfileReq', 'xmlns' => PAYPAL_NAMESPACE do | |
xml.tag! 'CreateRecurringPaymentsProfileRequest' do | |
xml.tag! 'Version', @@API_VERSION, 'xmlns' => EBAY_NAMESPACE | |
# NOTE: namespace prefix here is critical! | |
xml.tag! ns2 + 'CreateRecurringPaymentsProfileRequestDetails ', 'xmlns:n2' => EBAY_NAMESPACE do | |
# credit card and other information goes here | |
yield xml | |
xml.tag! ns2 + 'RecurringPaymentsProfileDetails' do | |
xml.tag! ns2 + 'BillingStartDate', options[:starting_at] | |
end | |
xml.tag! ns2 + 'ScheduleDetails' do | |
xml.tag! ns2 + 'Description', options[:comment] | |
unless options[:initial_payment].nil? | |
xml.tag! ns2 + 'TrialPeriod' do | |
xml.tag! ns2 + 'BillingPeriod', 'Month' | |
xml.tag! ns2 + 'BillingFrequency', 1 | |
xml.tag! ns2 + 'TotalBillingCycles', 1 | |
xml.tag! ns2 + 'Amount', amount(options[:initial_payment]), 'currencyID' => options[:currency] || currency(options[:initial_payment]) | |
end | |
end | |
frequency, period = get_pay_period(options) | |
xml.tag! ns2 + 'PaymentPeriod' do | |
xml.tag! ns2 + 'BillingPeriod', period | |
xml.tag! ns2 + 'BillingFrequency', frequency.to_s | |
xml.tag! ns2 + 'TotalBillingCycles', options[:payments] unless options[:payments].nil? || options[:payments] == 0 | |
xml.tag! ns2 + 'Amount', amount(money), 'currencyID' => options[:currency] || currency(money) | |
end | |
xml.tag! ns2 + 'AutoBillOutstandingAmount', 'AddToNextBilling' | |
end | |
end | |
end | |
end | |
elsif [:cancel, :suspend].include?(action) | |
xml.tag! 'ManageRecurringPaymentsProfileStatusReq', 'xmlns' => PAYPAL_NAMESPACE do | |
xml.tag! 'ManageRecurringPaymentsProfileStatusRequest', 'xmlns:n2' => EBAY_NAMESPACE do | |
xml.tag! ns2 + 'Version', @@API_VERSION | |
xml.tag! ns2 + 'ManageRecurringPaymentsProfileStatusRequestDetails' do | |
xml.tag! 'ProfileID', options[:profile_id] | |
xml.tag! ns2 + 'Action', action == :cancel ? 'Cancel' : 'Suspend' | |
xml.tag! ns2 + 'Note', 'Canceling the action, no real comment here' | |
end | |
end | |
end | |
elsif [:inquiry].include?(action) | |
xml.tag! 'GetRecurringPaymentsProfileDetailsReq', 'xmlns' => PAYPAL_NAMESPACE do | |
xml.tag! 'GetRecurringPaymentsProfileDetailsRequest', 'xmlns:n2' => EBAY_NAMESPACE do | |
xml.tag! ns2 + 'Version', @@API_VERSION | |
xml.tag! 'ProfileID', options[:profile_id] | |
end | |
end | |
end | |
end | |
def get_pay_period(options) | |
requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily, :semimonthly, :quadweekly, :quarterly, :semiyearly]) | |
case options[:periodicity] | |
when :weekly then [1, 'Week'] | |
when :biweekly then [2, 'Week'] | |
when :semimonthly then [1, 'SemiMonth'] | |
when :quadweekly then [4, 'Week'] | |
when :monthly then [1, 'Month'] | |
when :quarterly then [3, 'Month'] | |
when :semiyearly then [6, 'Month'] # broken! i think | |
when :yearly then [1, 'Year'] | |
end | |
end | |
end | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment