lib/active_merchant/billing/gateways/data_cash.rb in activemerchant-1.3.2 vs lib/active_merchant/billing/gateways/data_cash.rb in activemerchant-1.4.0

- old
+ new

@@ -1,42 +1,45 @@ -# Author:: MoneySpyder, http://moneyspyder.co.uk +# Authors:: MoneySpyder, http://moneyspyder.co.uk and E-consultancy, http://www.e-consultancy.com module ActiveMerchant module Billing class DataCashGateway < Gateway self.default_currency = 'GBP' self.supported_countries = ['GB'] - + # From the DataCash docs; Page 13, the following cards are # usable: # American Express, ATM, Carte Blanche, Diners Club, Discover, # EnRoute, GE Capital, JCB, Laser, Maestro, Mastercard, Solo, - # Switch, Visa, Visa Delta, VISA Electron, Visa Purchasing - self.supported_cardtypes = [ :visa, :master, :american_express, :discover, :diners_club, :jcb, - :maestro, :switch, :solo, :laser ] - + # Switch, Visa, Visa Delta, VISA Electron, Visa Purchasing + # + # Note continuous authority is only supported for :visa, :master and :american_express card types + self.supported_cardtypes = [ :visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro, :switch, :solo, :laser ] + self.homepage_url = 'http://www.datacash.com/' self.display_name = 'DataCash' - + # Datacash server URLs TEST_URL = 'https://testserver.datacash.com/Transaction' LIVE_URL = 'https://mars.transaction.datacash.com/Transaction' - + # Different Card Transaction Types AUTH_TYPE = 'auth' CANCEL_TYPE = 'cancel' FULFILL_TYPE = 'fulfill' PRE_TYPE = 'pre' - + REFUND_TYPE = 'refund' + TRANSACTION_REFUND_TYPE = 'txn_refund' + # Constant strings for use in the ExtendedPolicy complex element for # CV2 checks POLICY_ACCEPT = 'accept' POLICY_REJECT = 'reject' - + # Datacash success code DATACASH_SUCCESS = '1' - + # Creates a new DataCashGateway # # The gateway requires that a valid login and password be passed # in the +options+ hash. # @@ -49,60 +52,110 @@ def initialize(options = {}) requires!(options, :login, :password) @options = options super end - + # Perform a purchase, which is essentially an authorization and capture in a single operation. # # ==== Parameters + # * <tt>money</tt> The amount to be authorized. Either an Integer value in cents or a Money object. + # * <tt>authorization_or_credit_card</tt>:: The continuous authority reference or CreditCard details for the transaction. + # * <tt>options</tt> A hash of optional parameters. + # * <tt>:order_id</tt> A unique reference for this order (corresponds to merchantreference in datacash documentation) + # * <tt>:set_up_continuous_authority</tt> + # Set to true to set up a recurring historic transaction account be set up. + # Only supported for :visa, :master and :american_express card types + # See http://www.datacash.com/services/recurring/historic.php for more details of historic transactions. + # * <tt>:address</tt>:: billing address for card # - # * <tt>money</tt> -- The amount to be purchased. Either an Integer value in cents or a Money object. - # * <tt>credit_card</tt> -- The CreditCard details for the transaction. - # * <tt>options</tt> -- A hash of optional parameters. - def purchase(money, credit_card, options = {}) - commit(build_purchase_or_authorization_request(AUTH_TYPE, money, credit_card, options)) + # The continuous authority reference will be available in response#params['ca_referece'] if you have requested one + def purchase(money, authorization_or_credit_card, options = {}) + requires!(options, :order_id) + + if authorization_or_credit_card.is_a?(String) + request = build_purchase_or_authorization_request_with_continuous_authority_reference_request(AUTH_TYPE, money, authorization_or_credit_card, options) + else + request = build_purchase_or_authorization_request_with_credit_card_request(AUTH_TYPE, money, authorization_or_credit_card, options) + end + + commit(request) end - + # Performs an authorization, which reserves the funds on the customer's credit card, but does not # charge the card. - # + # # ==== Parameters # - # * <tt>money</tt> -- The amount to be authorized. Either an Integer value in cents or a Money object. - # * <tt>credit_card</tt> -- The CreditCard details for the transaction. - # * <tt>options</tt> -- A hash of optional parameters. - def authorize(money, credit_card, options = {}) - commit(build_purchase_or_authorization_request(PRE_TYPE, money, credit_card, options)) + # * <tt>money</tt> The amount to be authorized. Either an Integer value in cents or a Money object. + # * <tt>authorization_or_credit_card</tt>:: The continuous authority reference or CreditCard details for the transaction. + # * <tt>options</tt> A hash of optional parameters. + # * <tt>:order_id</tt> A unique reference for this order (corresponds to merchantreference in datacash documentation) + # * <tt>:set_up_continuous_authority</tt>:: + # Set to true to set up a recurring historic transaction account be set up. + # Only supported for :visa, :master and :american_express card types + # See http://www.datacash.com/services/recurring/historic.php for more details of historic transactions. + # * <tt>:address</tt>:: billing address for card + # + # The continuous authority reference will be available in response#params['ca_referece'] if you have requested one + def authorize(money, authorization_or_credit_card, options = {}) + requires!(options, :order_id) + + if authorization_or_credit_card.is_a?(String) + request = build_purchase_or_authorization_request_with_continuous_authority_reference_request(AUTH_TYPE, money, authorization_or_credit_card, options) + else + request = build_purchase_or_authorization_request_with_credit_card_request(PRE_TYPE, money, authorization_or_credit_card, options) + end + + commit(request) end - + # Captures the funds from an authorized transaction. # # ==== Parameters # # * <tt>money</tt> -- The amount to be captured. Either an Integer value in cents or a Money object. # * <tt>authorization</tt> -- The authorization returned from the previous authorize request. def capture(money, authorization, options = {}) commit(build_void_or_capture_request(FULFILL_TYPE, money, authorization, options)) end - + # Void a previous transaction # # ==== Parameters # # * <tt>authorization</tt> - The authorization returned from the previous authorize request. def void(authorization, options = {}) request = build_void_or_capture_request(CANCEL_TYPE, nil, authorization, options) - + commit(request) end - + + # Refund to a card + # + # ==== Parameters + # + # * <tt>money</tt> The amount to be refunded. Either an Integer value in cents or a Money object. Set to nil for a full refund on existing transaction. + # * <tt>reference_or_credit_card</tt> The credit card you want to refund OR the datacash_reference for the existing transaction you are refunding + # * <tt>options</tt> Are ignored when refunding via reference to an existing transaction, otherwise + # * <tt>:order_id</tt> A unique reference for this order (corresponds to merchantreference in datacash documentation) + # * <tt>:address</tt>:: billing address for card + def credit(money, reference_or_credit_card, options = {}) + if reference_or_credit_card.is_a?(String) + request = build_transaction_refund_request(money, reference_or_credit_card) + else + request = build_refund_request(money, reference_or_credit_card, options) + end + + commit(request) + end + # Is the gateway running in test mode? def test? @options[:test] || super end - + private # Create the xml document for a 'cancel' or 'fulfill' transaction. # # Final XML should look like: # <Request> @@ -121,23 +174,22 @@ # </HistoricTxn> # </Transaction> # </Request> # # Parameters: - # -type must be FULFILL_TYPE or CANCEL_TYPE - # -money - optional - A money object with the price and currency - # -authorization - the Datacash reference number from a previous - # succesful authorize transaction - # -authcode - the Datacash authcode - # -order_id - The merchants reference + # * <tt>type</tt> must be FULFILL_TYPE or CANCEL_TYPE + # * <tt>money</tt> - optional - Money object or value in cents + # * <tt>authorization</tt> - the Datacash authorization from a previous succesful authorize transaction + # * <tt>options</tt> + # * <tt>order_id</tt> - A unique reference for the transaction # # Returns: # -Builder xml document # def build_void_or_capture_request(type, money, authorization, options) - reference, auth_code = authorization.to_s.split(';') - + reference, auth_code, ca_reference = authorization.to_s.split(';') + xml = Builder::XmlMarkup.new :indent => 2 xml.instruct! xml.tag! :Request do add_authentication(xml) @@ -156,12 +208,12 @@ end end end xml.target! end - - # Create the xml document for an 'auth' or 'pre' transaction. + + # Create the xml document for an 'auth' or 'pre' transaction with a credit card # # Final XML should look like: # # <Request> # <Authentication> @@ -201,11 +253,11 @@ # notmatched="reject" # partialmatch="accept"/> # </ExtendedPolicy> # </Cv2Avs> # </Card> - # <method>auth, </method> + # <method>auth</method> # </CardTxn> # </Transaction> # </Request> # # Parameters: @@ -218,31 +270,175 @@ # :address is the delivery address # # Returns: # -xml: Builder document containing the markup # - def build_purchase_or_authorization_request(type, money, credit_card, options) + def build_purchase_or_authorization_request_with_credit_card_request(type, money, credit_card, options) xml = Builder::XmlMarkup.new :indent => 2 xml.instruct! xml.tag! :Request do add_authentication(xml) xml.tag! :Transaction do + if options[:set_up_continuous_authority] + xml.tag! :ContAuthTxn, :type => 'setup' + end xml.tag! :CardTxn do xml.tag! :method, type - - add_credit_card(xml, credit_card, options[:billing_address] || options[:address]) + add_credit_card(xml, credit_card, options[:billing_address]) end xml.tag! :TxnDetails do xml.tag! :merchantreference, format_reference_number(options[:order_id]) xml.tag! :amount, amount(money), :currency => options[:currency] || currency(money) end end end xml.target! end - + + # Create the xml document for an 'auth' or 'pre' transaction with + # continuous authorization + # + # Final XML should look like: + # + # <Request> + # <Transaction> + # <ContAuthTxn type="historic" /> + # <TxnDetails> + # <merchantreference>3851231</merchantreference> + # <capturemethod>cont_auth</capturemethod> + # <amount currency="GBP">18.50</amount> + # </TxnDetails> + # <HistoricTxn> + # <reference>4500200040925092</reference> + # <method>auth</method> + # </HistoricTxn> + # </Transaction> + # <Authentication> + # <client>99000001</client> + # <password>mypasswd</password> + # </Authentication> + # </Request> + # + # Parameters: + # -type must be 'auth' or 'pre' + # -money - A money object with the price and currency + # -authorization - The authorization containing a continuous authority reference previously set up on a credit card + # -options: + # :order_id is the merchant reference number + # + # Returns: + # -xml: Builder document containing the markup + # + def build_purchase_or_authorization_request_with_continuous_authority_reference_request(type, money, authorization, options) + reference, auth_code, ca_reference = authorization.to_s.split(';') + raise ArgumentError, "The continuous authority reference is required for continuous authority transactions" if ca_reference.blank? + + xml = Builder::XmlMarkup.new :indent => 2 + xml.instruct! + xml.tag! :Request do + add_authentication(xml) + xml.tag! :Transaction do + xml.tag! :ContAuthTxn, :type => 'historic' + xml.tag! :HistoricTxn do + xml.tag! :reference, ca_reference + xml.tag! :method, type + end + xml.tag! :TxnDetails do + xml.tag! :merchantreference, format_reference_number(options[:order_id]) + xml.tag! :amount, amount(money), :currency => options[:currency] || currency(money) + xml.tag! :capturemethod, 'cont_auth' + end + end + end + xml.target! + end + + # Create the xml document for a full or partial refund transaction with + # + # Final XML should look like: + # + # <Request> + # <Authentication> + # <client>99000001</client> + # <password>*******</password> + # </Authentication> + # <Transaction> + # <HistoricTxn> + # <method>txn_refund</method> + # <reference>12345678</reference> + # </HistoricTxn> + # <TxnDetails> + # <amount>10.00</amount> + # </TxnDetails> + # </Transaction> + # </Request> + # + def build_transaction_refund_request(money, reference) + xml = Builder::XmlMarkup.new :indent => 2 + xml.instruct! + xml.tag! :Request do + add_authentication(xml) + xml.tag! :Transaction do + xml.tag! :HistoricTxn do + xml.tag! :reference, reference + xml.tag! :method, TRANSACTION_REFUND_TYPE + end + unless money.nil? + xml.tag! :TxnDetails do + xml.tag! :amount, amount(money) + end + end + end + end + xml.target! + end + + # Create the xml document for a full or partial refund with + # + # Final XML should look like: + # + # <Request> + # <Authentication> + # <client>99000001</client> + # <password>*****</password> + # </Authentication> + # <Transaction> + # <CardTxn> + # <Card> + # <pan>633300*********1</pan> + # <expirydate>04/06</expirydate> + # <startdate>01/04</startdate> + # </Card> + # <method>refund</method> + # </CardTxn> + # <TxnDetails> + # <merchantreference>1000001</merchantreference> + # <amount currency="GBP">95.99</amount> + # </TxnDetails> + # </Transaction> + # </Request> + def build_refund_request(money, credit_card, options) + xml = Builder::XmlMarkup.new :indent => 2 + xml.instruct! + xml.tag! :Request do + add_authentication(xml) + xml.tag! :Transaction do + xml.tag! :CardTxn do + xml.tag! :method, REFUND_TYPE + add_credit_card(xml, credit_card, options[:billing_address]) + end + xml.tag! :TxnDetails do + xml.tag! :merchantreference, format_reference_number(options[:order_id]) + xml.tag! :amount, amount(money) + end + end + end + xml.target! + end + + # Adds the authentication element to the passed builder xml doc # # Parameters: # -xml: Builder document that is being built up # @@ -253,11 +449,11 @@ xml.tag! :Authentication do xml.tag! :client, @options[:login] xml.tag! :password, @options[:password] end end - + # Add credit_card detals to the passed XML Builder doc # # Parameters: # -xml: Builder document that is being built up # -credit_card: ActiveMerchant::Billing::CreditCard object @@ -267,35 +463,35 @@ # -none: The results is stored in the passed xml document # def add_credit_card(xml, credit_card, address) xml.tag! :Card do - + # DataCash calls the CC number 'pan' xml.tag! :pan, credit_card.number xml.tag! :expirydate, format_date(credit_card.month, credit_card.year) - + # optional values - for Solo etc if [ 'switch', 'solo' ].include?(card_brand(credit_card).to_s) xml.tag! :issuenumber, credit_card.issue_number unless credit_card.issue_number.blank? - + if !credit_card.start_month.blank? && !credit_card.start_year.blank? xml.tag! :startdate, format_date(credit_card.start_month, credit_card.start_year) end end - + xml.tag! :Cv2Avs do xml.tag! :cv2, credit_card.verification_value if credit_card.verification_value? if address xml.tag! :street_address1, address[:address1] unless address[:address1].blank? xml.tag! :street_address2, address[:address2] unless address[:address2].blank? xml.tag! :street_address3, address[:address3] unless address[:address3].blank? xml.tag! :street_address4, address[:address4] unless address[:address4].blank? xml.tag! :postcode, address[:zip] unless address[:zip].blank? end - + # The ExtendedPolicy defines what to do when the passed data # matches, or not... # # All of the following elements MUST be present for the # xml to be valid (or can drop the ExtendedPolicy and use @@ -321,11 +517,11 @@ :partialmatch => POLICY_ACCEPT end end end end - + # Send the passed data to DataCash for processing # # Parameters: # -request: The XML data that is to be sent to Datacash # @@ -335,14 +531,14 @@ def commit(request) response = parse(ssl_post(test? ? TEST_URL : LIVE_URL, request)) Response.new(response[:status] == DATACASH_SUCCESS, response[:reason], response, :test => test?, - :authorization => "#{response[:datacash_reference]};#{response[:authcode]}" + :authorization => "#{response[:datacash_reference]};#{response[:authcode]};#{response[:ca_reference]}" ) end - + # Returns a date string in the format Datacash expects # # Parameters: # -month: integer, the month # -year: integer, the year @@ -351,32 +547,32 @@ # -String: date in MM/YY format # def format_date(month, year) "#{format(month,:two_digits)}/#{format(year, :two_digits)}" end - + # Parse the datacash response and create a Response object # # Parameters: # -body: The XML returned from Datacash # # Returns: # -a hash with all of the values returned in the Datacash XML response # def parse(body) - + response = {} xml = REXML::Document.new(body) root = REXML::XPath.first(xml, "//Response") - + root.elements.to_a.each do |node| parse_element(response, node) end - + response end - + # Parse an xml element # # Parameters: # -response: The hash that the values are being returned in # -node: The node that is currently being read @@ -388,10 +584,10 @@ node.elements.each{|e| parse_element(response, e) } else response[node.name.underscore.to_sym] = node.text end end - + def format_reference_number(number) number.to_s.gsub(/[^A-Za-z0-9]/, '').rjust(6, "0").first(30) end end end