lib/active_merchant/billing/gateways/payflow.rb in activemerchant-1.1.0 vs lib/active_merchant/billing/gateways/payflow.rb in activemerchant-1.2.0

- old
+ new

@@ -1,82 +1,151 @@ require File.dirname(__FILE__) + '/payflow/payflow_common_api' +require File.dirname(__FILE__) + '/payflow/payflow_response' +require File.dirname(__FILE__) + '/payflow_express' module ActiveMerchant #:nodoc: module Billing #:nodoc: class PayflowGateway < Gateway include PayflowCommonAPI - def authorize(money, credit_card, options = {}) - if result = test_result_from_cc_number(credit_card.number) - return result - end - - request = build_sale_or_authorization_request('Authorization', money, credit_card, options) + RECURRING_ACTIONS = Set.new([:add, :modify, :cancel, :inquiry, :reactivate, :payment]) + + self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :discover, :diners_club] + self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=_payflow-pro-overview-outside' + self.display_name = 'PayPal Payflow Pro' + + def authorize(money, credit_card_or_reference, options = {}) + request = build_sale_or_authorization_request(:authorization, money, credit_card_or_reference, options) + commit(request) end - def purchase(money, credit_card, options = {}) - if result = test_result_from_cc_number(credit_card.number) - return result + def purchase(money, credit_card_or_reference, options = {}) + request = build_sale_or_authorization_request(:purchase, money, credit_card_or_reference, options) + + commit(request) + end + + def credit(money, identification_or_credit_card, options = {}) + if identification_or_credit_card.is_a?(String) + # Perform referenced credit + request = build_reference_request(:credit, money, identification_or_credit_card, options) + else + # Perform non-referenced credit + request = build_credit_card_request(:credit, money, identification_or_credit_card, options) end - request = build_sale_or_authorization_request('Sale', money, credit_card, options) commit(request) - end + end + # Adds or modifies a recurring Payflow profile. See the Payflow Pro Recurring Billing Guide for more details: + # https://www.paypal.com/en_US/pdf/PayflowPro_RecurringBilling_Guide.pdf + # + # 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) if credit_card + end + commit(request, :recurring) + end + + def cancel_recurring(profile_id) + request = build_recurring_request(:cancel, 0, :profile_id => profile_id) + commit(request, :recurring) + end + + def recurring_inquiry(profile_id) + request = build_recurring_request(:inquiry, nil, :profile_id => profile_id) + commit(request, :recurring) + end + def express @express ||= PayflowExpressGateway.new(@options) end - def self.supported_cardtypes - [:visa, :master, :american_express, :jcb, :discover, :diners_club] + private + def build_sale_or_authorization_request(action, money, credit_card_or_reference, options) + if credit_card_or_reference.is_a?(String) + build_reference_sale_or_authorization_request(action, money, credit_card_or_reference, options) + else + build_credit_card_request(action, money, credit_card_or_reference, options) + end end - private - def build_sale_or_authorization_request(action, money, credit_card, options) + def build_reference_sale_or_authorization_request(action, money, reference, options) xml = Builder::XmlMarkup.new :indent => 2 - xml.tag! action do + xml.tag! TRANSACTIONS[action] do xml.tag! 'PayData' do xml.tag! 'Invoice' do + xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money) + end + xml.tag! 'Tender' do + xml.tag! 'Card' do + xml.tag! 'ExtData', 'Name' => 'ORIGID', 'Value' => reference + end + end + end + end + xml.target! + end + + def build_credit_card_request(action, money, credit_card, options) + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! TRANSACTIONS[action] do + xml.tag! 'PayData' do + xml.tag! 'Invoice' do xml.tag! 'CustIP', options[:ip] unless options[:ip].blank? xml.tag! 'InvNum', options[:order_id] unless options[:order_id].blank? xml.tag! 'Description', options[:description] unless options[:description].blank? billing_address = options[:billing_address] || options[:address] shipping_address = options[:shipping_address] || billing_address add_address(xml, 'BillTo', billing_address, options) add_address(xml, 'ShipTo', shipping_address, options) - xml.tag! 'TotalAmt', amount(money), 'Currency' => currency(money) - - + xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money) end xml.tag! 'Tender' do add_credit_card(xml, credit_card) end end end xml.target! end - + def add_credit_card(xml, credit_card) xml.tag! 'Card' do - xml.tag! 'CardType', CARD_MAPPING[credit_card.type.to_sym] + xml.tag! 'CardType', credit_card_type(credit_card) xml.tag! 'CardNum', credit_card.number xml.tag! 'ExpDate', expdate(credit_card) xml.tag! 'NameOnCard', credit_card.name xml.tag! 'CVNum', credit_card.verification_value if credit_card.verification_value? - if [ 'switch', 'solo' ].include?(credit_card.type.to_s) + if requires_start_date_or_issue_number?(credit_card) xml.tag!('ExtData', 'Name' => 'CardStart', 'Value' => startdate(credit_card)) unless credit_card.start_month.blank? || credit_card.start_year.blank? - xml.tag!('ExtData', 'Name' => 'CardIssue', 'Value' => credit_card.issue_number) unless credit_card.issue_number.blank? + xml.tag!('ExtData', 'Name' => 'CardIssue', 'Value' => format(credit_card.issue_number, :two_digits)) unless credit_card.issue_number.blank? end end end + def credit_card_type(credit_card) + return '' if credit_card.type.blank? + + CARD_MAPPING[credit_card.type.to_sym] + end + def expdate(creditcard) year = sprintf("%.4i", creditcard.year) month = sprintf("%.2i", creditcard.month) "#{year}#{month}" @@ -87,11 +156,80 @@ month = format(creditcard.start_month, :two_digits) "#{month}#{year}" end - def build_response(success, message, response, options = {}) - Response.new(success, message, response, options) + 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 + xml.tag! 'RecurringProfiles' do + xml.tag! 'RecurringProfile' do + xml.tag! action.to_s.capitalize do + unless [:cancel, :inquiry].include?(action) + xml.tag! 'RPData' do + xml.tag! 'Name', options[:name] unless options[:name].nil? + xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money) + xml.tag! 'PayPeriod', get_pay_period(options) + xml.tag! 'Term', options[:payments] unless options[:payments].nil? + xml.tag! 'Comment', options[:comment] unless options[:comment].nil? + + + if initial_tx = options[:initial_transaction] + requires!(initial_tx, [:type, :authorization, :purchase]) + requires!(initial_tx, :amount) if initial_tx[:type] == :purchase + + xml.tag! 'OptionalTrans', TRANSACTIONS[initial_tx[:type]] + xml.tag! 'OptionalTransAmt', amount(initial_tx[:amount]) unless initial_tx[:amount].blank? + end + + xml.tag! 'Start', format_rp_date(options[:starting_at] || Date.today + 1 ) + xml.tag! 'EMail', options[:email] unless options[:email].nil? + + billing_address = options[:billing_address] || options[:address] + shipping_address = options[:shipping_address] || billing_address + + add_address(xml, 'BillTo', billing_address, options) + add_address(xml, 'ShipTo', shipping_address, options) + end + xml.tag! 'Tender' do + yield xml + end + end + if action != :add + xml.tag! "ProfileID", options[:profile_id] + end + 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 'Weekly' + when :biweekly then 'Bi-weekly' + when :semimonthly then 'Semi-monthly' + when :quadweekly then 'Every four weeks' + when :monthly then 'Monthly' + when :quarterly then 'Quarterly' + when :semiyearly then 'Semi-yearly' + when :yearly then 'Yearly' + end + end + + def format_rp_date(time) + case time + when Time, Date then time.strftime("%m%d%Y") + else + time.to_s + end + end + + def build_response(success, message, response, options = {}) + PayflowResponse.new(success, message, response, options) end end end end