class ActiveMerchant::Billing::CyberSourceRestGateway
Constants
- CREDIT_CARD_CODES
- NT_PAYMENT_SOLUTION
- WALLET_PAYMENT_SOLUTION
Public Class Methods
new(options = {})
click to toggle source
Calls superclass method
ActiveMerchant::Billing::Gateway::new
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 45 def initialize(options = {}) requires!(options, :merchant_id, :public_key, :private_key) super end
Public Instance Methods
capture(money, authorization, options = {})
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 61 def capture(money, authorization, options = {}) payment = authorization.split('|').first post = build_reference_request(money, options) commit("payments/#{payment}/captures", post, options) end
credit(money, payment, options = {})
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 74 def credit(money, payment, options = {}) post = build_credit_request(money, payment, options) commit('credits', post) end
purchase(money, payment, options = {})
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 50 def purchase(money, payment, options = {}) authorize(money, payment, options, true) end
refund(money, authorization, options = {})
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 68 def refund(money, authorization, options = {}) payment = authorization.split('|').first post = build_reference_request(money, options) commit("payments/#{payment}/refunds", post, options) end
scrub(transcript)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 97 def scrub(transcript) transcript. gsub(/(\\?"number\\?":\\?")\d+/, '\1[FILTERED]'). gsub(/(\\?"routingNumber\\?":\\?")\d+/, '\1[FILTERED]'). gsub(/(\\?"securityCode\\?":\\?")\d+/, '\1[FILTERED]'). gsub(/(\\?"cryptogram\\?":\\?")[^<]+/, '\1[FILTERED]'). gsub(/(signature=")[^"]*/, '\1[FILTERED]'). gsub(/(keyid=")[^"]*/, '\1[FILTERED]'). gsub(/(Digest: SHA-256=)[\w\/\+=]*/, '\1[FILTERED]') end
supports_scrubbing?()
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 93 def supports_scrubbing? true end
verify(credit_card, options = {})
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 85 def verify(credit_card, options = {}) amount = eligible_for_zero_auth?(credit_card, options) ? 0 : 100 MultiResponse.run(:use_first_response) do |r| r.process { authorize(amount, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } end end
void(authorization, options = {})
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 79 def void(authorization, options = {}) payment, amount = authorization.split('|') post = build_void_request(amount) commit("payments/#{payment}/reversals", post) end
Private Instance Methods
add_ach(post, payment)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 223 def add_ach(post, payment) post[:paymentInformation][:bank] = { account: { type: payment.account_type == 'checking' ? 'C' : 'S', number: payment.account_number }, routingNumber: payment.routing_number } end
add_address(post, payment_method, address, options, address_type)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 282 def add_address(post, payment_method, address, options, address_type) return unless address.present? first_name, last_name = address_names(address[:name], payment_method) post[:orderInformation][address_type] = { firstName: first_name, lastName: last_name, address1: address[:address1], address2: address[:address2], locality: address[:city], administrativeArea: address[:state], postalCode: address[:zip], country: lookup_country_code(address[:country])&.value, email: options[:email].presence || 'null@cybersource.com', phoneNumber: address[:phone] # merchantTaxID: ship_to ? options[:merchant_tax_id] : nil, # company: address[:company], # companyTaxID: address[:companyTaxID], # ipAddress: options[:ip], # driversLicenseNumber: options[:drivers_license_number], # driversLicenseState: options[:drivers_license_state], }.compact end
add_amount(post, amount, options)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 215 def add_amount(post, amount, options) currency = options[:currency] || currency(amount) post[:orderInformation][:amountDetails] = { totalAmount: localized_amount(amount, currency), currency: currency } end
add_business_rules_data(post, payment, options)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 457 def add_business_rules_data(post, payment, options) post[:processingInformation][:authorizationOptions] = {} post[:processingInformation][:authorizationOptions][:ignoreAvsResult] = 'true' if options[:ignore_avs].to_s == 'true' post[:processingInformation][:authorizationOptions][:ignoreCvResult] = 'true' if options[:ignore_cvv].to_s == 'true' end
add_code(post, options)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 195 def add_code(post, options) return unless options[:order_id].present? post[:clientReferenceInformation][:code] = options[:order_id] end
add_credit_card(post, creditcard)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 272 def add_credit_card(post, creditcard) post[:paymentInformation][:card] = { number: creditcard.number, expirationMonth: format(creditcard.month, :two_digits), expirationYear: format(creditcard.year, :four_digits), securityCode: creditcard.verification_value, type: CREDIT_CARD_CODES[card_brand(creditcard).to_sym] } end
add_customer_id(post, options)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 201 def add_customer_id(post, options) return unless options[:customer_id].present? post[:paymentInformation][:customer] = { customerId: options[:customer_id] } end
add_invoice_number(post, options)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 484 def add_invoice_number(post, options) return unless options[:invoice_number].present? post[:orderInformation][:invoiceDetails] ||= {} post[:orderInformation][:invoiceDetails][:invoiceNumber] = options[:invoice_number] end
add_level_2_data(post, options)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 110 def add_level_2_data(post, options) return unless options[:purchase_order_number] post[:orderInformation][:invoiceDetails] ||= {} post[:orderInformation][:invoiceDetails][:purchaseOrderNumber] = options[:purchase_order_number] end
add_level_3_data(post, options)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 117 def add_level_3_data(post, options) return unless options[:line_items] post[:orderInformation][:lineItems] = options[:line_items] post[:processingInformation][:purchaseLevel] = '3' post[:orderInformation][:shipping_details] = { shipFromPostalCode: options[:ships_from_postal_code] } post[:orderInformation][:amountDetails] ||= {} post[:orderInformation][:amountDetails][:discountAmount] = options[:discount_amount] end
add_mdd_fields(post, options)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 463 def add_mdd_fields(post, options) mdd_fields = options.select { |k, v| k.to_s.start_with?('mdd_field') && v.present? } return unless mdd_fields.present? post[:merchantDefinedInformation] = mdd_fields.map do |key, value| { key: key, value: value } end end
add_merchant_description(post, options)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 307 def add_merchant_description(post, options) return unless options[:merchant_descriptor_name] || options[:merchant_descriptor_address1] || options[:merchant_descriptor_locality] merchant = post[:merchantInformation][:merchantDescriptor] = {} merchant[:name] = options[:merchant_descriptor_name] if options[:merchant_descriptor_name] merchant[:address1] = options[:merchant_descriptor_address1] if options[:merchant_descriptor_address1] merchant[:locality] = options[:merchant_descriptor_locality] if options[:merchant_descriptor_locality] end
add_network_tokenization_card(post, payment, options)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 244 def add_network_tokenization_card(post, payment, options) post[:processingInformation][:commerceIndicator] = 'internet' unless options[:stored_credential] || card_brand(payment) == 'jcb' post[:paymentInformation][:tokenizedCard] = { number: payment.number, expirationMonth: payment.month, expirationYear: payment.year, cryptogram: payment.payment_cryptogram, type: CREDIT_CARD_CODES[card_brand(payment).to_sym], transactionType: payment.source == :network_token ? '3' : '1' } if payment.source == :network_token && NT_PAYMENT_SOLUTION[payment.brand] post[:processingInformation][:paymentSolution] = NT_PAYMENT_SOLUTION[payment.brand] else # Apple Pay / Google Pay post[:processingInformation][:paymentSolution] = WALLET_PAYMENT_SOLUTION[payment.source] if card_brand(payment) == 'master' post[:consumerAuthenticationInformation] = { ucafAuthenticationData: payment.payment_cryptogram, ucafCollectionIndicator: '2' } else post[:consumerAuthenticationInformation] = { cavv: payment.payment_cryptogram } end end end
add_partner_solution_id(post)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 491 def add_partner_solution_id(post) return unless application_id post[:clientReferenceInformation][:partner] = { solutionId: application_id } end
add_payment(post, payment, options)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 233 def add_payment(post, payment, options) post[:processingInformation] = {} if payment.is_a?(NetworkTokenizationCreditCard) add_network_tokenization_card(post, payment, options) elsif payment.is_a?(Check) add_ach(post, payment) else add_credit_card(post, payment) end end
add_reconciliation_id(post, options)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 472 def add_reconciliation_id(post, options) return unless options[:reconciliation_id].present? post[:clientReferenceInformation][:reconciliationId] = options[:reconciliation_id] end
add_reversal_amount(post, amount)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 207 def add_reversal_amount(post, amount) currency = options[:currency] || currency(amount) post[:reversalInformation][:amountDetails] = { totalAmount: localized_amount(amount, currency) } end
add_sec_code(post, options)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 478 def add_sec_code(post, options) return unless options[:sec_code].present? post[:processingInformation][:bankTransferOptions] = { secCode: options[:sec_code] } end
add_stored_credentials(post, payment, options)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 316 def add_stored_credentials(post, payment, options) return unless options[:stored_credential] post[:processingInformation][:commerceIndicator] = commerce_indicator(options.dig(:stored_credential, :reason_type)) add_authorization_options(post, payment, options) end
add_three_ds(post, payment_method, options)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 127 def add_three_ds(post, payment_method, options) return unless three_d_secure = options[:three_d_secure] post[:consumerAuthenticationInformation] ||= {} if payment_method.brand == 'master' post[:consumerAuthenticationInformation][:ucafAuthenticationData] = three_d_secure[:cavv] post[:consumerAuthenticationInformation][:ucafCollectionIndicator] = '2' else post[:consumerAuthenticationInformation][:cavv] = three_d_secure[:cavv] end post[:consumerAuthenticationInformation][:cavvAlgorithm] = three_d_secure[:cavv_algorithm] if three_d_secure[:cavv_algorithm] post[:consumerAuthenticationInformation][:paSpecificationVersion] = three_d_secure[:version] if three_d_secure[:version] post[:consumerAuthenticationInformation][:directoryServerTransactionID] = three_d_secure[:ds_transaction_id] if three_d_secure[:ds_transaction_id] post[:consumerAuthenticationInformation][:eciRaw] = three_d_secure[:eci] if three_d_secure[:eci] if three_d_secure[:xid].present? post[:consumerAuthenticationInformation][:xid] = three_d_secure[:xid] else post[:consumerAuthenticationInformation][:xid] = three_d_secure[:cavv] end post[:consumerAuthenticationInformation][:veresEnrolled] = three_d_secure[:enrolled] if three_d_secure[:enrolled] post[:consumerAuthenticationInformation][:paresStatus] = three_d_secure[:authentication_response_status] if three_d_secure[:authentication_response_status] post end
auth_headers(action, options, post, http_method = 'post')
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 442 def auth_headers(action, options, post, http_method = 'post') digest = "SHA-256=#{Digest::SHA256.base64digest(post.to_json)}" if post.present? date = Time.now.httpdate { 'Accept' => 'application/hal+json;charset=utf-8', 'Content-Type' => 'application/json;charset=utf-8', 'V-C-Merchant-Id' => options[:merchant_id] || @options[:merchant_id], 'Date' => date, 'Host' => host, 'Signature' => get_http_signature(action, digest, http_method, date), 'Digest' => digest } end
build_auth_request(amount, payment, options)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 157 def build_auth_request(amount, payment, options) { clientReferenceInformation: {}, paymentInformation: {}, orderInformation: {} }.tap do |post| add_customer_id(post, options) add_code(post, options) add_payment(post, payment, options) add_mdd_fields(post, options) add_amount(post, amount, options) add_address(post, payment, options[:billing_address], options, :billTo) add_address(post, payment, options[:shipping_address], options, :shipTo) add_business_rules_data(post, payment, options) add_partner_solution_id(post) add_stored_credentials(post, payment, options) add_three_ds(post, payment, options) add_level_2_data(post, options) add_level_3_data(post, options) end.compact end
build_credit_request(amount, payment, options)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 184 def build_credit_request(amount, payment, options) { clientReferenceInformation: {}, paymentInformation: {}, orderInformation: {} }.tap do |post| add_code(post, options) add_credit_card(post, payment) add_mdd_fields(post, options) add_amount(post, amount, options) add_address(post, payment, options[:billing_address], options, :billTo) add_merchant_description(post, options) end.compact end
build_reference_request(amount, options)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 175 def build_reference_request(amount, options) { clientReferenceInformation: {}, orderInformation: {} }.tap do |post| add_code(post, options) add_mdd_fields(post, options) add_amount(post, amount, options) add_partner_solution_id(post) end.compact end
build_void_request(amount = nil)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 151 def build_void_request(amount = nil) { reversalInformation: { amountDetails: { totalAmount: nil } } }.tap do |post| add_reversal_amount(post, amount.to_i) if amount.present? end.compact end
commerce_indicator(reason_type)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 323 def commerce_indicator(reason_type) case reason_type when 'recurring' 'recurring' when 'installment' 'install' else 'internet' end end
commit(action, post, options = {})
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 372 def commit(action, post, options = {}) add_reconciliation_id(post, options) add_sec_code(post, options) add_invoice_number(post, options) response = parse(ssl_post(url(action), post.to_json, auth_headers(action, options, post))) Response.new( success_from(response), message_from(response), response, authorization: authorization_from(response), avs_result: AVSResult.new(code: response.dig('processorInformation', 'avs', 'code')), # cvv_result: CVVResult.new(response['some_cvv_response_key']), network_transaction_id: network_transaction_id_from(response), test: test?, error_code: error_code_from(response) ) rescue ActiveMerchant::ResponseError => e response = e.response.body.present? ? parse(e.response.body) : { 'response' => { 'rmsg' => e.response.msg } } message = response.dig('response', 'rmsg') || response.dig('message') Response.new(false, message, response, test: test?) end
error_code_from(response)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 414 def error_code_from(response) response['errorInformation']['reason'] unless success_from(response) end
get_http_signature(resource, digest, http_method = 'post', gmtdatetime = Time.now.httpdate)
click to toggle source
This implementation follows the Cybersource guide on how create the request signature, see: developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/GenerateHeader/httpSignatureAuthentication.html
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 420 def get_http_signature(resource, digest, http_method = 'post', gmtdatetime = Time.now.httpdate) string_to_sign = { host: host, date: gmtdatetime, "request-target": "#{http_method} /pts/v2/#{resource}", digest: digest, "v-c-merchant-id": @options[:merchant_id] }.map { |k, v| "#{k}: #{v}" }.join("\n").force_encoding(Encoding::UTF_8) { keyid: @options[:public_key], algorithm: 'HmacSHA256', headers: "host date request-target#{digest.present? ? ' digest' : ''} v-c-merchant-id", signature: sign_payload(string_to_sign) }.map { |k, v| %{#{k}="#{v}"} }.join(', ') end
host()
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 364 def host URI.parse(url('')).host end
message_from(response)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 398 def message_from(response) return response['status'] if success_from(response) response['errorInformation']['message'] || response['message'] end
network_transaction_id_from(response)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 356 def network_transaction_id_from(response) response.dig('processorInformation', 'networkTransactionId') end
parse(body)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 368 def parse(body) JSON.parse(body) end
sign_payload(payload)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 437 def sign_payload(payload) decoded_key = Base64.decode64(@options[:private_key]) Base64.strict_encode64(OpenSSL::HMAC.digest('sha256', decoded_key, payload)) end
success_from(response)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 394 def success_from(response) %w(AUTHORIZED PENDING REVERSED).include?(response['status']) end
url(action)
click to toggle source
# File lib/active_merchant/billing/gateways/cyber_source_rest.rb, line 360 def url(action) "#{test? ? test_url : live_url}/pts/v2/#{action}" end