require File.dirname(__FILE__) + '/paypal_common_api' module ActiveMerchant #:nodoc: module Billing #:nodoc: # This module is included in both PaypalGateway and PaypalExpressGateway module PaypalRecurringApi PAYPAL_NAMESPACE = ActiveMerchant::Billing::PaypalCommonAPI::PAYPAL_NAMESPACE API_VERSION = ActiveMerchant::Billing::PaypalCommonAPI::API_VERSION EBAY_NAMESPACE = ActiveMerchant::Billing::PaypalCommonAPI::EBAY_NAMESPACE # Create a recurring payment. # # This transaction creates a recurring payment profile # ==== Parameters # # * money -- The amount to be charged to the customer at each interval as an Integer value in cents. # * credit_card -- The CreditCard details for the transaction. # * options -- A hash of parameters. # # ==== Options # # * :period -- [Day, Week, SemiMonth, Month, Year] default: Month # * :frequency -- a number # * :cycles -- Limit to certain # of cycles (OPTIONAL) # * :start_date -- When does the charging starts (REQUIRED) # * :description -- The description to appear in the profile (REQUIRED) def recurring(amount, credit_card, options = {}) options[:credit_card] = credit_card options[:amount] = amount requires!(options, :description, :start_date, :period, :frequency, :amount) commit 'CreateRecurringPaymentsProfile', build_create_profile_request(options) end # Update a recurring payment's details. # # This transaction updates an existing Recurring Billing Profile # and the subscription must have already been created previously # by calling +recurring()+. The ability to change certain # details about a recurring payment is dependent on transaction history # and the type of plan you're subscribed with paypal. Web Payment Pro # seems to have the ability to update the most field. # # ==== Parameters # # * options -- A hash of parameters. # # ==== Options # # * :profile_id -- A string containing the :profile_id # of the recurring payment already in place for a given credit card. (REQUIRED) def update_recurring(options={}) requires!(options, :profile_id) opts = options.dup commit 'UpdateRecurringPaymentsProfile', build_change_profile_request(opts.delete(:profile_id), opts) end # Cancel a recurring payment. # # This transaction cancels an existing recurring billing profile. Your account must have recurring billing enabled # and the subscription must have already been created previously by calling recurring() # # ==== Parameters # # * profile_id -- A string containing the +profile_id+ of the # recurring payment already in place for a given credit card. (REQUIRED) # * options -- A hash with extra info ('note' for ex.) def cancel_recurring(profile_id, options = {}) raise_error_if_blank('profile_id', profile_id) commit 'ManageRecurringPaymentsProfileStatus', build_manage_profile_request(profile_id, 'Cancel', options) end # Get Subscription Status of a recurring payment profile. # # ==== Parameters # # * profile_id -- A string containing the +profile_id+ of the # recurring payment already in place for a given credit card. (REQUIRED) def status_recurring(profile_id) raise_error_if_blank('profile_id', profile_id) commit 'GetRecurringPaymentsProfileDetails', build_get_profile_details_request(profile_id) end # Suspends a recurring payment profile. # # ==== Parameters # # * profile_id -- A string containing the +profile_id+ of the # recurring payment already in place for a given credit card. (REQUIRED) def suspend_recurring(profile_id, options = {}) raise_error_if_blank('profile_id', profile_id) commit 'ManageRecurringPaymentsProfileStatus', build_manage_profile_request(profile_id, 'Suspend', options) end # Reactivates a suspended recurring payment profile. # # ==== Parameters # # * profile_id -- A string containing the +profile_id+ of the # recurring payment already in place for a given credit card. (REQUIRED) def reactivate_recurring(profile_id, options = {}) raise_error_if_blank('profile_id', profile_id) commit 'ManageRecurringPaymentsProfileStatus', build_manage_profile_request(profile_id, 'Reactivate', options) end # Bills outstanding amount to a recurring payment profile. # # ==== Parameters # # * profile_id -- A string containing the +profile_id+ of the # recurring payment already in place for a given credit card. (REQUIRED) def bill_outstanding_amount(profile_id, options = {}) raise_error_if_blank('profile_id', profile_id) commit 'BillOutstandingAmount', build_bill_outstanding_amount(profile_id, options) end private def raise_error_if_blank(field_name, field) raise ArgumentError.new("Missing required parameter: #{field_name}") if field.blank? end def build_create_profile_request(options) xml = Builder::XmlMarkup.new :indent => 2 xml.tag! 'CreateRecurringPaymentsProfileReq', 'xmlns' => PAYPAL_NAMESPACE do xml.tag! 'CreateRecurringPaymentsProfileRequest', 'xmlns:n2' => EBAY_NAMESPACE do xml.tag! 'n2:Version', API_VERSION xml.tag! 'n2:CreateRecurringPaymentsProfileRequestDetails' do xml.tag! 'Token', options[:token] unless options[:token].blank? if options[:credit_card] add_credit_card(xml, options[:credit_card], (options[:billing_address] || options[:address]), options) end xml.tag! 'n2:RecurringPaymentsProfileDetails' do xml.tag! 'n2:BillingStartDate', (options[:start_date].is_a?(Date) ? options[:start_date].to_time : options[:start_date]).utc.iso8601 xml.tag! 'n2:ProfileReference', options[:profile_reference] unless options[:profile_reference].blank? end xml.tag! 'n2:ScheduleDetails' do xml.tag! 'n2:Description', options[:description] xml.tag! 'n2:PaymentPeriod' do xml.tag! 'n2:BillingPeriod', options[:period] || 'Month' xml.tag! 'n2:BillingFrequency', options[:frequency] xml.tag! 'n2:TotalBillingCycles', options[:total_billing_cycles] unless options[:total_billing_cycles].blank? xml.tag! 'n2:Amount', amount(options[:amount]), 'currencyID' => options[:currency] || 'USD' xml.tag! 'n2:TaxAmount', amount(options[:tax_amount] || 0), 'currencyID' => options[:currency] || 'USD' unless options[:tax_amount].blank? xml.tag! 'n2:ShippingAmount', amount(options[:shipping_amount] || 0), 'currencyID' => options[:currency] || 'USD' unless options[:shipping_amount].blank? end if !options[:trial_amount].blank? xml.tag! 'n2:TrialPeriod' do xml.tag! 'n2:BillingPeriod', options[:trial_period] || 'Month' xml.tag! 'n2:BillingFrequency', options[:trial_frequency] xml.tag! 'n2:TotalBillingCycles', options[:trial_cycles] || 1 xml.tag! 'n2:Amount', amount(options[:trial_amount]), 'currencyID' => options[:currency] || 'USD' xml.tag! 'n2:TaxAmount', amount(options[:trial_tax_amount] || 0), 'currencyID' => options[:currency] || 'USD' unless options[:trial_tax_amount].blank? xml.tag! 'n2:ShippingAmount', amount(options[:trial_shipping_amount] || 0), 'currencyID' => options[:currency] || 'USD' unless options[:trial_shipping_amount].blank? end end if !options[:initial_amount].blank? xml.tag! 'n2:ActivationDetails' do xml.tag! 'n2:InitialAmount', amount(options[:initial_amount]), 'currencyID' => options[:currency] || 'USD' xml.tag! 'n2:FailedInitialAmountAction', options[:continue_on_failure] ? 'ContinueOnFailure' : 'CancelOnFailure' end end xml.tag! 'n2:MaxFailedPayments', options[:max_failed_payments] unless options[:max_failed_payments].blank? xml.tag! 'n2:AutoBillOutstandingAmount', options[:auto_bill_outstanding] ? 'AddToNextBilling' : 'NoAutoBill' end end end end xml.target! end def build_get_profile_details_request(profile_id) xml = Builder::XmlMarkup.new :indent => 2 xml.tag! 'GetRecurringPaymentsProfileDetailsReq', 'xmlns' => PAYPAL_NAMESPACE do xml.tag! 'GetRecurringPaymentsProfileDetailsRequest', 'xmlns:n2' => EBAY_NAMESPACE do xml.tag! 'n2:Version', API_VERSION xml.tag! 'ProfileID', profile_id end end xml.target! end def build_change_profile_request(profile_id, options) xml = Builder::XmlMarkup.new :indent => 2 xml.tag! 'UpdateRecurringPaymentsProfileReq', 'xmlns' => PAYPAL_NAMESPACE do xml.tag! 'UpdateRecurringPaymentsProfileRequest', 'xmlns:n2' => EBAY_NAMESPACE do xml.tag! 'n2:Version', API_VERSION xml.tag! 'n2:UpdateRecurringPaymentsProfileRequestDetails' do xml.tag! 'ProfileID', profile_id if options[:credit_card] add_credit_card(xml, options[:credit_card], options[:address], options) end xml.tag! 'n2:Note', options[:note] unless options[:note].blank? xml.tag! 'n2:Description', options[:description] unless options[:description].blank? xml.tag! 'n2:ProfileReference', options[:reference] unless options[:reference].blank? xml.tag! 'n2:AdditionalBillingCycles', options[:additional_billing_cycles] unless options[:additional_billing_cycles].blank? xml.tag! 'n2:MaxFailedPayments', options[:max_failed_payments] unless options[:max_failed_payments].blank? xml.tag! 'n2:AutoBillOutstandingAmount', options[:auto_bill_outstanding] ? 'AddToNextBilling' : 'NoAutoBill' if options.has_key?(:amount) xml.tag! 'n2:Amount', amount(options[:amount]), 'currencyID' => options[:currency] || 'USD' end if options.has_key?(:tax_amount) xml.tag! 'n2:TaxAmount', amount(options[:tax_amount] || 0), 'currencyID' => options[:currency] || 'USD' end if options.has_key?(:start_date) xml.tag! 'n2:BillingStartDate', (options[:start_date].is_a?(Date) ? options[:start_date].to_time : options[:start_date]).utc.iso8601 end end end end xml.target! end def build_manage_profile_request(profile_id, action, options) xml = Builder::XmlMarkup.new :indent => 2 xml.tag! 'ManageRecurringPaymentsProfileStatusReq', 'xmlns' => PAYPAL_NAMESPACE do xml.tag! 'ManageRecurringPaymentsProfileStatusRequest', 'xmlns:n2' => EBAY_NAMESPACE do xml.tag! 'n2:Version', API_VERSION xml.tag! 'n2:ManageRecurringPaymentsProfileStatusRequestDetails' do xml.tag! 'ProfileID', profile_id xml.tag! 'n2:Action', action xml.tag! 'n2:Note', options[:note] unless options[:note].blank? end end end xml.target! end def build_bill_outstanding_amount(profile_id, options) xml = Builder::XmlMarkup.new :indent => 2 xml.tag! 'BillOutstandingAmountReq', 'xmlns' => PAYPAL_NAMESPACE do xml.tag! 'BillOutstandingAmountRequest', 'xmlns:n2' => EBAY_NAMESPACE do xml.tag! 'n2:Version', API_VERSION xml.tag! 'n2:BillOutstandingAmountRequestDetails' do xml.tag! 'ProfileID', profile_id if options.has_key?(:amount) xml.tag! 'n2:Amount', amount(options[:amount]), 'currencyID' => options[:currency] || 'USD' end xml.tag! 'n2:Note', options[:note] unless options[:note].blank? end end end xml.target! end end end end