Skip to content
1 change: 1 addition & 0 deletions lib/active_merchant/billing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
require 'active_merchant/billing/check'
require 'active_merchant/billing/payment_token'
require 'active_merchant/billing/apple_pay_payment_token'
require 'active_merchant/billing/accept_js_token'
require 'active_merchant/billing/response'
require 'active_merchant/billing/gateways'
require 'active_merchant/billing/gateway'
Expand Down
17 changes: 17 additions & 0 deletions lib/active_merchant/billing/accept_js_token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module ActiveMerchant
module Billing
class AcceptJsToken < PaymentToken
def type
'accept_js'
end

def opaque_data
payment_data[:opaque_data]
end

def display_number
@metadata[:card_number]
end
end
end
end
48 changes: 24 additions & 24 deletions lib/active_merchant/billing/gateways/authorize_net.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class AuthorizeNetGateway < Gateway
}.freeze

APPLE_PAY_DATA_DESCRIPTOR = 'COMMON.APPLE.INAPP.PAYMENT'
ACCEPT_JS_DATA_DESCRIPTOR = 'COMMON.ACCEPT.INAPP.PAYMENT'

PAYMENT_METHOD_NOT_SUPPORTED_ERROR = '155'
INELIGIBLE_FOR_ISSUING_CREDIT_ERROR = '54'
Expand Down Expand Up @@ -176,18 +177,18 @@ def credit(amount, payment, options = {})
end
end

def verify(credit_card, options = {})
def verify(payment, options = {})
MultiResponse.run(:use_first_response) do |r|
r.process { authorize(100, credit_card, options) }
r.process { authorize(100, payment, options) }
r.process(:ignore_result) { void(r.authorization, options) }
end
end

def store(credit_card, options = {})
def store(payment, options = {})
if options[:customer_profile_id]
create_customer_payment_profile(credit_card, options)
create_customer_payment_profile(payment, options)
else
create_customer_profile(credit_card, options)
create_customer_profile(payment, options)
end
end

Expand Down Expand Up @@ -399,6 +400,8 @@ def add_payment_source(xml, source, options, action = nil)
add_check(xml, source)
elsif card_brand(source) == 'apple_pay'
add_apple_pay_payment_token(xml, source)
elsif card_brand(source) == 'accept_js'
add_accept_js_token(xml, source)
else
add_credit_card(xml, source, action)
end
Expand Down Expand Up @@ -518,8 +521,17 @@ def add_apple_pay_payment_token(xml, apple_pay_payment_token)
end
end

def add_accept_js_token(xml, accept_js_token)
xml.payment do
xml.opaqueData do
xml.dataDescriptor(accept_js_token.opaque_data[:data_descriptor])
xml.dataValue(accept_js_token.opaque_data[:data_value])
end
end
end

def add_market_type_device_type(xml, payment, options)
return if payment.is_a?(String) || card_brand(payment) == 'check' || card_brand(payment) == 'apple_pay'
return if payment.is_a?(String) || %w[check apple_pay accept_js].include?(card_brand(payment))

if valid_track_data
xml.retail do
Expand Down Expand Up @@ -731,23 +743,17 @@ def add_subsequent_auth_information(xml, options)
end
end

def create_customer_payment_profile(credit_card, options)
def create_customer_payment_profile(payment_source, options)
commit(:cim_store_update, options) do |xml|
xml.customerProfileId options[:customer_profile_id]
xml.paymentProfile do
add_billing_address(xml, credit_card, options)
xml.payment do
xml.creditCard do
xml.cardNumber(truncate(credit_card.number, 16))
xml.expirationDate(format(credit_card.year, :four_digits) + '-' + format(credit_card.month, :two_digits))
xml.cardCode(credit_card.verification_value) if credit_card.verification_value
end
end
add_billing_address(xml, payment_source, options)
add_payment_source(xml, payment_source, options)
end
end
end

def create_customer_profile(credit_card, options)
def create_customer_profile(payment_source, options)
commit(:cim_store, options) do |xml|
xml.profile do
xml.merchantCustomerId(truncate(options[:merchant_customer_id], 20) || SecureRandom.hex(10))
Expand All @@ -756,15 +762,9 @@ def create_customer_profile(credit_card, options)

xml.paymentProfiles do
xml.customerType('individual')
add_billing_address(xml, credit_card, options)
add_billing_address(xml, payment_source, options)
add_shipping_address(xml, options, 'shipToList')
xml.payment do
xml.creditCard do
xml.cardNumber(truncate(credit_card.number, 16))
xml.expirationDate(format(credit_card.year, :four_digits) + '-' + format(credit_card.month, :two_digits))
xml.cardCode(credit_card.verification_value) if credit_card.verification_value
end
end
add_payment_source(xml, payment_source, options)
end
end
end
Expand Down
13 changes: 12 additions & 1 deletion lib/active_merchant/billing/gateways/authorize_net_cim.rb
Original file line number Diff line number Diff line change
Expand Up @@ -758,8 +758,10 @@ def add_payment_profile(xml, payment_profile)
xml.tag!('payment') do
add_credit_card(xml, payment_profile[:payment][:credit_card]) if payment_profile[:payment].has_key?(:credit_card)
add_bank_account(xml, payment_profile[:payment][:bank_account]) if payment_profile[:payment].has_key?(:bank_account)
add_drivers_license(xml, payment_profile[:payment][:drivers_license]) if payment_profile[:payment].has_key?(:drivers_license)
# This element is only required for Wells Fargo SecureSource eCheck.Net merchants
add_drivers_license(xml, payment_profile[:payment][:drivers_license]) if payment_profile[:payment].has_key?(:drivers_license)
add_opaque_data(xml, payment_profile[:payment][:opaque_data]) if payment_profile[:payment].has_key?(:opaque_data)

# The customer's Social Security Number or Tax ID
xml.tag!('taxId', payment_profile[:payment]) if payment_profile[:payment].has_key?(:tax_id)
end
Expand Down Expand Up @@ -850,6 +852,15 @@ def add_drivers_license(xml, drivers_license)
end
end

def add_opaque_data(xml, opaque_data)
return unless opaque_data
# The generic payment data type used to process tokenized payment information
xml.tag!('opaqueData') do
xml.tag!('dataDescriptor', opaque_data[:data_descriptor])
xml.tag!('dataValue', opaque_data[:data_value])
end
end

def commit(action, request)
url = test? ? test_url : live_url
xml = ssl_post(url, request, 'Content-Type' => 'text/xml')
Expand Down
66 changes: 65 additions & 1 deletion test/remote/gateways/remote_authorize_net_cim_test.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
require 'test_helper'
require 'support/authorize_helper'
require 'pp'

class AuthorizeNetCimTest < Test::Unit::TestCase
include AuthorizeHelper

def setup
Base.mode = :test

@gateway = AuthorizeNetCimGateway.new(fixtures(:authorize_net))
@amount = 100
@customer_profile_id = nil
@credit_card = credit_card('4242424242424242')
@payment = {
credit_card: @credit_card
}
@address = address
@profile = {
merchant_customer_id: 'Up to 20 chars', # Optional
description: 'Up to 255 Characters', # Optional
Expand Down Expand Up @@ -72,10 +77,38 @@ def test_successful_profile_create_get_update_and_delete
assert response.test?
assert_success response
assert_nil response.authorization
assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id)
assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id)
assert_nil response.params['profile']['merchant_customer_id']
assert_equal 'Up to 255 Characters', response.params['profile']['description']
assert_equal 'new email address', response.params['profile']['email']
end

def test_successful_profile_create_with_acceptjs
@options[:profile][:payment_profiles].delete(:payment)
token = get_sandbox_acceptjs_token_for_credit_card(credit_card)
@options[:profile][:payment_profiles][:payment] = token.payment_data

assert response = @gateway.create_customer_profile(@options)
@customer_profile_id = response.authorization

assert_success response
assert response.test?

assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id)
assert response.test?
assert_success response
assert_equal @customer_profile_id, response.authorization
assert_equal 'Successful.', response.message
assert response.params['profile']['payment_profiles']['customer_payment_profile_id'] =~ /\d+/, 'The customer_payment_profile_id should be a number'
assert_equal "XXXX#{@credit_card.last_digits}", response.params['profile']['payment_profiles']['payment']['credit_card']['card_number'], "The card number should contain the last 4 digits of the card we passed in #{@credit_card.last_digits}"
assert_equal @profile[:merchant_customer_id], response.params['profile']['merchant_customer_id']
assert_equal @profile[:description], response.params['profile']['description']
assert_equal @profile[:email], response.params['profile']['email']
assert_equal @profile[:payment_profiles][:customer_type], response.params['profile']['payment_profiles']['customer_type']
assert_equal @profile[:ship_to_list][:phone_number], response.params['profile']['ship_to_list']['phone_number']
assert_equal @profile[:ship_to_list][:company], response.params['profile']['ship_to_list']['company']
end

def test_get_customer_profile_with_unmasked_exp_date_and_issuer_info
assert response = @gateway.create_customer_profile(@options)
@customer_profile_id = response.authorization
Expand All @@ -88,6 +121,7 @@ def test_get_customer_profile_with_unmasked_exp_date_and_issuer_info
unmask_expiration_date: true,
include_issuer_info: true
)

assert response.test?
assert_success response
assert_equal @customer_profile_id, response.authorization
Expand Down Expand Up @@ -196,6 +230,36 @@ def test_successful_create_customer_payment_profile_request
payment_profile: payment_profile
)

assert response.test?
assert_success response
assert customer_payment_profile_id = response.params['customer_payment_profile_id']
assert customer_payment_profile_id =~ /\d+/, "The customerPaymentProfileId should be numeric. It was #{customer_payment_profile_id}"
end

def test_get_token_for_credit_card
assert token = get_sandbox_acceptjs_token_for_credit_card(credit_card)
assert_not_nil token.opaque_data[:data_value]
assert token.opaque_data[:data_descriptor] == 'COMMON.ACCEPT.INAPP.PAYMENT'
end

def test_successful_create_customer_payment_profile_request_with_acceptjs
@options[:profile].delete(:payment_profiles)
assert response = @gateway.create_customer_profile(@options)
@customer_profile_id = response.authorization

assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id)
assert_nil response.params['profile']['payment_profiles']

token = get_sandbox_acceptjs_token_for_credit_card(credit_card)

assert response = @gateway.create_customer_payment_profile(
:customer_profile_id => @customer_profile_id,
:payment_profile => {
:customer_type => 'individual',
:payment => token.payment_data
}
)

assert response.test?
assert_success response
assert_equal @customer_profile_id, response.authorization
Expand Down
51 changes: 51 additions & 0 deletions test/remote/gateways/remote_authorize_net_test.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
require 'test_helper'
require 'support/authorize_helper'

class RemoteAuthorizeNetTest < Test::Unit::TestCase
include AuthorizeHelper

def setup
@gateway = AuthorizeNetGateway.new(fixtures(:authorize_net))

Expand Down Expand Up @@ -150,6 +153,13 @@ def test_successful_purchase_with_customer
assert_equal 'This transaction has been approved', response.message
end

def test_successful_purchase_with_acceptjs_token
acceptjs_token = get_sandbox_acceptjs_token_for_credit_card(@credit_card)
response = @gateway.purchase(@amount, acceptjs_token, @options.merge(description: 'Accept.js Store Purchase'))
assert_success response
assert_equal 'This transaction has been approved', response.message
end

def test_failed_purchase
response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
Expand Down Expand Up @@ -383,6 +393,16 @@ def test_successful_authorization_with_moto_retail_type
assert response.authorization
end

def test_authorization_and_void_acceptjs
acceptjs_token = get_sandbox_acceptjs_token_for_credit_card(@credit_card)
assert authorization = @gateway.authorize(@amount, acceptjs_token, @options)
assert_success authorization

assert void = @gateway.void(authorization.authorization)
assert_success void
assert_equal 'This transaction has been approved', void.message
end

def test_successful_verify
response = @gateway.verify(@credit_card, @options)
assert_success response
Expand All @@ -405,6 +425,15 @@ def test_successful_store
assert_equal '1', response.params['message_code']
end

def test_successful_store_acceptjs
acceptjs_token = get_sandbox_acceptjs_token_for_credit_card(@credit_card)
assert response = @gateway.store(acceptjs_token)
assert_success response
assert response.authorization
assert_equal 'Successful', response.message
assert_equal '1', response.params['message_code']
end

def test_successful_store_new_payment_profile
assert store = @gateway.store(@credit_card)
assert_success store
Expand All @@ -419,6 +448,20 @@ def test_successful_store_new_payment_profile
assert_equal '1', response.params['message_code']
end

def test_successful_store_new_payment_profile_acceptjs
assert store = @gateway.store(@credit_card)
assert_success store
assert store.authorization

customer_profile_id, _, _ = store.authorization.split('#')
acceptjs_token = get_sandbox_acceptjs_token_for_credit_card(@credit_card)

assert response = @gateway.store(acceptjs_token, customer_profile_id: customer_profile_id)
assert_success response
assert_equal 'Successful', response.message
assert_equal '1', response.params['message_code']
end

def test_failed_store_new_payment_profile
assert store = @gateway.store(@credit_card)
assert_success store
Expand Down Expand Up @@ -722,6 +765,14 @@ def test_successful_credit
assert response.authorization
end

def test_successful_credit_acceptjs
acceptjs_token = get_sandbox_acceptjs_token_for_credit_card(@credit_card)
response = @gateway.credit(@amount, acceptjs_token, @options.merge(description: 'Accept.js Store Refund'))
assert_success response
assert_equal 'This transaction has been approved', response.message
assert response.authorization
end

def test_successful_echeck_credit
response = @gateway.credit(@amount, @check, @options)
assert_equal 'The transaction is currently under review', response.message
Expand Down
Loading