module ActiveMerchant #:nodoc: module Billing #:nodoc: class PayArcGateway < Gateway self.test_url = 'https://testapi.payarc.net/v1' self.live_url = 'https://api.payarc.net/v1' self.supported_countries = ['US'] self.default_currency = 'usd' self.supported_cardtypes = %i[visa master american_express discover jcb] self.homepage_url = 'https://www.payarc.net/' self.display_name = 'PAYARC Gateway' STANDARD_ERROR_CODE_MAPPING = {} STANDARD_ACTIONS = { token: { end_point: 'tokens', allowed_fields: %i[card_source card_number exp_month exp_year cvv card_holder_name address_line1 address_line2 city state zip country] }, capture: { end_point: 'charges', allowed_fields: %i[amount statement_description card_id currency customer_id token_id card_source tip_amount card_level sales_tax purchase_order supplier_reference_number customer_ref_id ship_to_zip amex_descriptor customer_vat_number summary_commodity_code shipping_charges duty_charges ship_from_zip destination_country_code vat_invoice order_date tax_category tax_type tax_amount tax_rate address_line1 zip terminal_id surcharge description email receipt_phone statement_descriptor ] }, void: { end_point: 'charges/{{chargeID}}/void', allowed_fields: %i[reason void_description] }, refund: { end_point: 'charges/{{charge_id}}/refunds', allowed_fields: %i[amount reason description] }, credit: { end_point: 'refunds/wo_reference', allowed_fields: %i[amount charge_description statement_description terminal_id card_source card_number exp_month exp_year cvv card_holder_name address_line1 address_line2 city state zip country currency reason receipt_phone receipt_email ] } } SUCCESS_STATUS = %w[ submitted_for_settlement authorized partially_submitted_for_settlement credit partial_refund void refunded settled ] FAILURE_STATUS = %w[not_processed failed_by_gateway invalid_track_data authorization_expired] # The gateway must be configured with Bearer token. # # :api_key PAYARC's Bearer token must be passsed to initialise the gateway. def initialize(options = {}) requires!(options, :api_key) super end # # Purchase API through PAYARC. # # :money A positive integer in cents representing how much to charge. The minimum amount is 50c USD. # # :creditcard CreditCard object with card details. # # :options Other information like address, card source etc can be passed in options # # ==== Options # # * :card_source -- Source of payment (REQUIRED) ( INTERNET, SWIPE, PHONE, MAIL, MANUAL ) # * :currency -- Three-letter ISO currency code, in lowercase (REQUIRED) # * :card_holder_name --Name of the Card Holder (OPTIONAL) # * :address_line1 -- Set in payment method's billing address (OPTIONAL) # * :address_line2 -- Set in payment method's billing address (OPTIONAL) # * :state -- State (OPTIONAL) # * :country -- Country (OPTIONAL) # * :statement_description -- An arbitrary string to be displayed on your costomer's credit card statement. This may be up to 22 characters. (OPTIONAL) # * :card_level -- Commercial card level - "LEVEL2" OR "LEVEL3" (OPTIONAL) # * :sales_tax -- A positive integer in cents representing sales tax. (OPTIONAL) # * :terminal_id -- Optional terminal id. (OPTIONAL) # * :tip_amount -- A positive integer in cents representing tip amount. (OPTIONAL) # * :sales_tax -- Applicable for LEVEL2 or LEVEL3 Charge. A positive integer in cents representing sales tax. (REQUIRED for LEVEL2 0r LEVEL3) # * :purchase_order -- Applicable for Level2 or Level3 Charge. The value used by the customer to identify an order. Issued by the buyer to the seller. (REQUIRED for LEVEL2 0r LEVEL3) # * :order_date -- Applicable for Level2 Charge for AMEX card only or Level3 Charge. The date the order was processed. Format: Alphanumeric and Special Character |Min Length=0 Max Length=10|Allowed format: MM/DD/YYYY For example: 12/01/2016 # * :customer_ref_id -- Applicable for Level2 Charge for AMEX card only or Level3 Charge. The reference identifier supplied by the Commercial Card cardholder. Format: Alphanumeric and Special Character |Min Length=0 Max Length=17| a-z A-Z 0-9 Space <> # * :ship_to_zip -- Applicable for Level2 Charge for AMEX card only or Level3 Charge. The postal code for the address to which the goods are being shipped. Format: Alphanumeric |Min Length=2 Max Length=10 # * :amex_descriptor -- Applicable for Level2 Charge for AMEX card only. The value of the Transaction Advice Addendum field, displays descriptive information about a transactions on a customer's AMEX card statement. Format: Alphanumeric and Special Character |Min Length=0 Max Length=40|a-z A-Z 0-9 Space <> # * :supplier_reference_number -- Applicable for Level2 Charge for AMEX card only or Level3 charge. The value used by the customer to identify an order. Issued by the buyer to the seller. # * :tax_amount -- Applicable for Level3 Charge. The tax amount. Format: Numeric|Max Length=12|Allowed characters: 0-9 .(dot) Note: If a decimal point is included, the amount reflects a dollar value. If a decimal point is not included, the amount reflects a cent value. # * :tax_category -- Applicable for Level3 Charge. The type of tax. Formerly established through TaxCategory messages. Allowed values: SERVICE, DUTY, VAT, ALTERNATE, NATIONAL, TAX_EXEMPT # * :customer_vat_number -- Applicable for Level3 Charge. Indicates the customer's government assigned tax identification number or the identification number assigned to their purchasing company by the tax authorities. Format: Alphanumeric and Special Character|Min Length=0 Max Length=13| a-z A-Z 0-9 Space <> # * :summary_commodity_code -- Applicable for Level3 Charge. The international description code of the overall goods or services being supplied. Format: Alphanumeric and Special Character |Min Length=0 Max Length=4|Allowed character: a-z A-Z 0-9 Space <> # * :shipping_charges -- Applicable for Level3 Charge. The dollar amount for shipping or freight charges applied to a product or transaction. Format: Numeric |Max Length=12|Allowed characters: 0-9 .(dot) Note: If a decimal point is included, the amount reflects a dollar value. If a decimal point is not included, the amount reflects a cent value. # * :duty_charges -- Applicable for Level3 Charge. Indicates the total charges for any import or export duties included in the order. Format: Numeric |Max Length=12|Allowed characters: 0-9 . (dot) Note: If a decimal point is included, the amount reflects a dollar value. If a decimal point is not included, the amount reflects a cent value. # * :ship_from_zip -- Applicable for Level3 Charge. The postal code for the address to which the goods are being shipped. Format: Alphanumeric |Min Length=2 Max Length=10 # * :destination_country_code -- Applicable for Level3 Charge. The destination country code indicator. Format: Alphanumeric. # * :tax_type -- Applicable for Level3 Charge. The type of tax. For example, VAT, NATIONAL, Service Tax. Format: Alphanumeric and Special Character # * :vat_invoice -- Applicable for Level3 Charge. The Value Added Tax (VAT) invoice number associated with the transaction. Format: Alphanumeric and Special Character |Min Length=0 Max Length=15|Allowed character: a-z A-Z 0-9 Space <> # * :tax_rate -- Applicable for Level3 Charge. The type of tax rate. This field is used if taxCategory is not used. Default sale tax rate in percentage Must be between 0.1% - 22% ,Applicable only Level 2 AutoFill. Format: Decimal Number |Max Length=4|Allowed characters: 0-9 .(dot) Allowed range: 0.01 - 100 # * :email -- Customer's email address sent with payment method. def purchase(money, creditcard, options = {}) options[:capture] = 1 MultiResponse.run do |r| r.process { token(creditcard, options) } r.process { charge(money, r.authorization, options) } end end # # Authorize the payment API through PAYARC. # # :money A positive integer in cents representing how much to charge. The minimum amount is 50c USD. # # :creditcard CreditCard object with card details. # # :options Other information like address, card source etc can be passed in options # # ==== Options # # * :card_source -- Source of payment (REQUIRED) ( INTERNET, SWIPE, PHONE, MAIL, MANUAL ) # * :currency -- Three-letter ISO currency code, in lowercase (REQUIRED) # * :card_holder_name --Name of the Card Holder (OPTIONAL) # * :address_line1 -- Set in payment method's billing address (OPTIONAL) # * :address_line2 -- Set in payment method's billing address (OPTIONAL) # * :state -- State (OPTIONAL) # * :country -- Country (OPTIONAL) # * :statement_description -- An arbitrary string to be displayed on your costomer's credit card statement. This may be up to 22 characters. (OPTIONAL) # * :card_level -- Commercial card level - "LEVEL2" OR "LEVEL3" (OPTIONAL) # * :sales_tax -- A positive integer in cents representing sales tax. (OPTIONAL) # * :terminal_id -- Optional terminal id. (OPTIONAL) # * :tip_amount -- A positive integer in cents representing tip amount. (OPTIONAL) # * :sales_tax -- Applicable for LEVEL2 or LEVEL3 Charge. A positive integer in cents representing sales tax. (REQUIRED for LEVEL2 0r LEVEL3) # * :purchase_order -- Applicable for Level2 or Level3 Charge. The value used by the customer to identify an order. Issued by the buyer to the seller. (REQUIRED for LEVEL2 0r LEVEL3) # * :order_date -- Applicable for Level2 Charge for AMEX card only or Level3 Charge. The date the order was processed. Format: Alphanumeric and Special Character |Min Length=0 Max Length=10|Allowed format: MM/DD/YYYY For example: 12/01/2016 # * :customer_ref_id -- Applicable for Level2 Charge for AMEX card only or Level3 Charge. The reference identifier supplied by the Commercial Card cardholder. Format: Alphanumeric and Special Character |Min Length=0 Max Length=17| a-z A-Z 0-9 Space <> # * :ship_to_zip -- Applicable for Level2 Charge for AMEX card only or Level3 Charge. The postal code for the address to which the goods are being shipped. Format: Alphanumeric |Min Length=2 Max Length=10 # * :amex_descriptor -- Applicable for Level2 Charge for AMEX card only. The value of the Transaction Advice Addendum field, displays descriptive information about a transactions on a customer's AMEX card statement. Format: Alphanumeric and Special Character |Min Length=0 Max Length=40|a-z A-Z 0-9 Space <> # * :supplier_reference_number -- Applicable for Level2 Charge for AMEX card only or Level3 charge. The value used by the customer to identify an order. Issued by the buyer to the seller. # * :tax_amount -- Applicable for Level3 Charge. The tax amount. Format: Numeric|Max Length=12|Allowed characters: 0-9 .(dot) Note: If a decimal point is included, the amount reflects a dollar value. If a decimal point is not included, the amount reflects a cent value. # * :tax_category -- Applicable for Level3 Charge. The type of tax. Formerly established through TaxCategory messages. Allowed values: SERVICE, DUTY, VAT, ALTERNATE, NATIONAL, TAX_EXEMPT # * :customer_vat_number -- Applicable for Level3 Charge. Indicates the customer's government assigned tax identification number or the identification number assigned to their purchasing company by the tax authorities. Format: Alphanumeric and Special Character|Min Length=0 Max Length=13| a-z A-Z 0-9 Space <> # * :summary_commodity_code -- Applicable for Level3 Charge. The international description code of the overall goods or services being supplied. Format: Alphanumeric and Special Character |Min Length=0 Max Length=4|Allowed character: a-z A-Z 0-9 Space <> # * :shipping_charges -- Applicable for Level3 Charge. The dollar amount for shipping or freight charges applied to a product or transaction. Format: Numeric |Max Length=12|Allowed characters: 0-9 .(dot) Note: If a decimal point is included, the amount reflects a dollar value. If a decimal point is not included, the amount reflects a cent value. # * :duty_charges -- Applicable for Level3 Charge. Indicates the total charges for any import or export duties included in the order. Format: Numeric |Max Length=12|Allowed characters: 0-9 . (dot) Note: If a decimal point is included, the amount reflects a dollar value. If a decimal point is not included, the amount reflects a cent value. # * :ship_from_zip -- Applicable for Level3 Charge. The postal code for the address to which the goods are being shipped. Format: Alphanumeric |Min Length=2 Max Length=10 # * :destination_country_code -- Applicable for Level3 Charge. The destination country code indicator. Format: Alphanumeric. # * :tax_type -- Applicable for Level3 Charge. The type of tax. For example, VAT, NATIONAL, Service Tax. Format: Alphanumeric and Special Character # * :vat_invoice -- Applicable for Level3 Charge. The Value Added Tax (VAT) invoice number associated with the transaction. Format: Alphanumeric and Special Character |Min Length=0 Max Length=15|Allowed character: a-z A-Z 0-9 Space <> # * :tax_rate -- Applicable for Level3 Charge. The type of tax rate. This field is used if taxCategory is not used. Default sale tax rate in percentage Must be between 0.1% - 22% ,Applicable only Level 2 AutoFill. Format: Decimal Number |Max Length=4|Allowed characters: 0-9 .(dot) Allowed range: 0.01 - 100 # * :email -- Customer's email address. def authorize(money, creditcard, options = {}) options[:capture] = '0' MultiResponse.run do |r| r.process { token(creditcard, options) } r.process { charge(money, r.authorization, options) } end end # # Capture the payment of an existing, uncaptured, charge. # This is the second half of the two-step payment flow, where first you created / authorized a charge # with the capture option set to false. # # :money A positive integer in cents representing how much to charge. The minimum amount is 50c USD. # # :tx_reference charge_id from previously created / authorized a charge # # :options Other information like address, card source etc can be passed in options def capture(money, tx_reference, options = {}) post = {} add_money(post, money, options) action = "#{STANDARD_ACTIONS[:capture][:end_point]}/#{tx_reference}/capture" post = filter_gateway_fields(post, options, STANDARD_ACTIONS[:capture][:allowed_fields]) commit(action, post) end # # Voids the transaction / charge. # # :tx_reference charge_id from previously created charge # # :options Other information like address, card source etc can be passed in options # # ==== Options # # * :reason -- Reason for voiding transaction (REQUIRED) ( requested_by_customer, duplicate, fraudulent, other ) def void(tx_reference, options = {}) post = {} post['reason'] = options[:reason] action = STANDARD_ACTIONS[:void][:end_point].gsub(/{{chargeID}}/, tx_reference) post = filter_gateway_fields(post, options, STANDARD_ACTIONS[:void][:allowed_fields]) commit(action, post) end # # Refund full / partial payment of an successful charge / capture / purchase. # # :money A positive integer in cents representing how much to charge. The minimum amount is 50c USD. # # :tx_reference charge_id from previously created / authorized a charge # # :options Other information like address, card source etc can be passed in options def refund(money, tx_reference, options = {}) post = {} add_money(post, money, options) action = STANDARD_ACTIONS[:refund][:end_point].gsub(/{{charge_id}}/, tx_reference) post = filter_gateway_fields(post, options, STANDARD_ACTIONS[:refund][:allowed_fields]) commit(action, post) end def credit(money, creditcard, options = {}) post = {} add_money(post, money, options) add_creditcard(post, creditcard, options) add_address(post, options) add_phone(post, options) post['receipt_email'] = options[:email] if options[:email] action = STANDARD_ACTIONS[:credit][:end_point] post = filter_gateway_fields(post, options, STANDARD_ACTIONS[:credit][:allowed_fields]) commit(action, post) end # # Verify the creditcard API through PAYARC. # # :creditcard CreditCard object with card details. # # :options Other information like address, card source etc can be passed in options # # ==== Options # # * :card_source -- Source of payment (REQUIRED) ( INTERNET, SWIPE, PHONE, MAIL, MANUAL ) # * :card_holder_name --Name of the Card Holder (OPTIONAL) # * :address_line1 -- Set in payment method's billing address (OPTIONAL) # * :address_line2 -- Set in payment method's billing address (OPTIONAL) # * :state -- State (OPTIONAL) # * :country -- Country (OPTIONAL) def verify(creditcard, options = {}) token(creditcard, options) end #:nodoc: def token(creditcard, options = {}) post = {} post['authorize_card'] = 1 post['card_source'] = options[:card_source] add_creditcard(post, creditcard, options) add_address(post, options) post = filter_gateway_fields(post, options, STANDARD_ACTIONS[:token][:allowed_fields]) commit(STANDARD_ACTIONS[:token][:end_point], post) end def supports_scrubbing? #:nodoc: true end def scrub(transcript) #:nodoc: transcript. gsub(%r((Authorization: Bearer )[^\s]+\s)i, '\1[FILTERED]\2'). gsub(%r((&?card_number=)[^&]*)i, '\1[FILTERED]'). gsub(%r((&?cvv=)[^&]*)i, '\1[BLANK]') end private def charge(money, authorization, options = {}) post = {} post['token_id'] = authorization post['capture'] = options[:capture] || 1 add_money(post, money, options) add_phone(post, options) post = filter_gateway_fields(post, options, STANDARD_ACTIONS[:capture][:allowed_fields]) commit(STANDARD_ACTIONS[:capture][:end_point], post) end def add_creditcard(post, creditcard, options) post['card_number'] = creditcard.number post['exp_month'] = format(creditcard.month, :two_digits) post['exp_year'] = creditcard.year post['cvv'] = creditcard.verification_value unless creditcard.verification_value.nil? post['card_holder_name'] = options[:card_holder_name] || "#{creditcard.first_name} #{creditcard.last_name}" end def add_address(post, options) return unless billing_address = options[:billing_address] post['address_line1'] = billing_address[:address1] post['address_line2'] = billing_address[:address2] post['city'] = billing_address[:city] post['state'] = billing_address[:state] post['zip'] = billing_address[:zip] post['country'] = billing_address[:country] end def add_phone(post, options) post['phone_number'] = options[:billing_address][:phone] if options.dig(:billing_address, :phone) end def add_money(post, money, options) post['amount'] = money post['currency'] = currency(money) unless options[:currency] post['statement_description'] = options[:statement_description] end def headers(api_key) { 'Authorization' => 'Bearer ' + api_key.strip, 'Accept' => 'application/json', 'User-Agent' => "PayArc ActiveMerchantBindings/#{ActiveMerchant::VERSION}" } end def parse(body) JSON.parse(body) rescue JSON::ParserError body end def filter_gateway_fields(post, options, gateway_fields) filtered_options = options.slice(*gateway_fields).compact post.update(filtered_options) post end def commit(action, parameters) url = (test? ? test_url : live_url) headers = headers(@options[:api_key]) end_point = "#{url}/#{action}" begin response = ssl_post(end_point, post_data(parameters), headers) parsed_response = parse(response) Response.new( success_from(parsed_response, action), message_from(parsed_response, action), parsed_response, test: test?, authorization: parse_response_id(parsed_response), error_code: error_code_from(parsed_response, action) ) rescue ResponseError => e parsed_response = parse(e.response.body) Response.new( false, message_from(parsed_response, action), parsed_response, test: test?, authorization: nil, error_code: error_code_from(parsed_response, action) ) end end def success_from(response, action) if action == STANDARD_ACTIONS[:token][:end_point] token = parse_response_id(response) (!token.nil? && !token.empty?) elsif response return SUCCESS_STATUS.include? response['data']['status'] if response['data'] end end def message_from(response, action) if success_from(response, action) if action == STANDARD_ACTIONS[:token][:end_point] return response['data']['id'] else return response['data']['status'] end else return response['message'] end end def parse_response_id(response) response['data']['id'] if response && response['data'] end def post_data(params) params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') end def error_code_from(response, action) response['status_code'] unless success_from(response, action) end end end end