Skip to content

Commit

Permalink
Checkout v2: add l2/l3
Browse files Browse the repository at this point in the history
Summary
----------------
This adds the L2/L3 fields required in purchase, authorize and capture transactions

[SER-1554](https://spreedly.atlassian.net/browse/SER-1554)

Unit Tests
----------------
Finished in 0.282324 seconds.
70 tests, 481 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed

Remote Tests
----------------
Finished in 164.547288 seconds.
116 tests, 280 assertions, 7 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
93.9655% passed

Rubocop
----------------
806 files inspected, no offenses detected
  • Loading branch information
gasb150 committed Jan 21, 2025
1 parent 3a3a067 commit f5df0d9
Show file tree
Hide file tree
Showing 3 changed files with 246 additions and 0 deletions.
63 changes: 63 additions & 0 deletions lib/active_merchant/billing/gateways/checkout_v2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def capture(amount, authorization, options = {})
add_customer_data(post, options)
add_shipping_address(post, options)
add_metadata(post, options)
add_level_two_three_data(post, options)

commit(:capture, post, options, authorization)
end
Expand Down Expand Up @@ -151,6 +152,7 @@ def build_auth_or_purchase(post, amount, payment_method, options)
add_processing_data(post, options)
add_payment_sender_data(post, options)
add_risk_data(post, options)
add_level_two_three_data(post, options)
truncate_amex_reference_id(post, options, payment_method)
end

Expand Down Expand Up @@ -505,6 +507,67 @@ def add_marketplace_data(post, options)
end
end

def add_level_two_three_data(post, options)
post[:processing] ||= {}
post[:customer] ||= {}

# American Express only supports Level 2 data.
# Only is required add items info in lvl2 data for amex
add_items(post, options)
add_level_two_data(post, options)
add_level_three_data(post, options)
add_shipping_data(post, options)
end

def add_items(post, options)
items = build_items(options[:line_items] || [])
post[:items] = items unless items.empty?
end

def add_level_two_data(post, options)
post[:customer][:tax_number] = options[:tax_number] # field no require for amex

post[:processing].merge!(
{
order_id: options[:invoice_id],
tax_amount: options[:tax_amount]
}.compact
)
end

def add_level_three_data(post, options)
post[:processing].merge!(
{
discount_amount: options[:discount_amount],
shipping_amount: options[:shipping_amount],
duty_amount: options[:duty_amount]
}.compact
)
end

def add_shipping_data(post, options)
post[:shipping] ||= {}
post[:shipping][:from_address_zip] = options[:from_address_zip]
end

def build_items(line_items = [])
line_items.map do |item|
{
# for lvl 2 amex and lvl 3 visa/master
name: item[:name],
quantity: item[:quantity],
unit_price: item[:unit_price],
# for lvl3 visa/master
reference: item[:reference],
tax_amount: item[:tax_amount],
discount_amount: item[:discount_amount],
total_amount: item[:total_amount],
commodity_code: item[:commodity_code],
unit_of_measure: item[:unit_of_measure]
}.compact
end
end

def access_token_header
{
'Authorization' => "Basic #{Base64.encode64("#{@options[:client_id]}:#{@options[:client_secret]}").delete("\n")}",
Expand Down
60 changes: 60 additions & 0 deletions test/remote/gateways/remote_checkout_v2_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ def setup
}
}
)
@minimun_level2_options = @options.merge({
order_data: {
tax_amount: 130
}
})
end

def test_failed_access_token
Expand Down Expand Up @@ -1186,4 +1191,59 @@ def test_non_truncate_id_for_non_amex_transactions
assert_equal 31, response.params['reference'].length
assert_equal 'Visa', response.params['source']['scheme']
end

def test_successful_purchase_with_minimun_level_2_data
response = @gateway.purchase(@amount, @credit_card, @minimun_level2_options)
assert_success response
end

def test_successful_authorize_and_capture_with_minimun_level_2_data
authorize = @gateway.authorize(@amount, @credit_card, @options)
assert_success authorize

response = @gateway.capture(@amount, authorize.authorization, @minimun_level2_options)
assert_success response
end

def test_successful_purchase_with_minimun_level_2_data_for_amex
amex_options = {
currency: 'USD',
line_items: [{ item_name: 'Paint', quantity: '1', unit_cost: '1270' }]
}
amex_card = credit_card('345678901234564', brand: 'american_express', verification_value: '1000', month: '12', year: Time.now.year)

response = @gateway.purchase(1500, amex_card, @minimun_level2_options.merge(amex_options))
assert_success response
end

def test_successful_purchase_with_minimun_level_3_data
order_data = {
processing: { order_id: '01234' },
tax_number: '123456',
from_address_zip: '000123456',
tax_amount: 30,
discount_amount: 0,
shipping_amount: 200,
duty_amount: 0
}
line_items = {
line_items: [
{
commodity_code: '123',
name: 'Paint',
quantity: 1,
unit_price: 1270,
tax_amount: 30,
discount_amount: 0,
total_amount: 1270,
reference: 'Paint123',
unit_of_measure: 'Liters'
}
]
}
options = @options.merge({ currency: 'USD' }).merge(order_data).merge(line_items)

response = @gateway.purchase(1500, @credit_card, options)
assert_success response
end
end
123 changes: 123 additions & 0 deletions test/unit/gateways/checkout_v2_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,46 @@ def setup
@credit_card = credit_card
@amount = 100
@token = '2MPedsuenG2o8yFfrsdOBWmOuEf'

@lvl_2_3_options = {
order_id: '1',
billing_address: address,
shipping_address: address,
description: 'Purchase',
email: '[email protected]',
processing_channel_id: 'pc_lxgl7aqahkzubkundd2l546hdm',
invoice_id: 12462,
tax_number: 123456,
from_address_zip: 12345,
tax_amount: 30,
shipping_amount: 20,
discount_amount: 10,
duty_amount: 5,
line_items: [
{ # only for American Express in level 2 or any lvl 3
commodity_code: 123,
name: 'glass',
quantity: 1,
unit_price: 200,
tax_amount: 12,
discount_amount: 12,
total_amount: 200,
reference: 'glass123',
unit_of_measure: 'Centimeters'
},
{
commodity_code: 456,
name: 'water',
quantity: 2,
unit_price: 100,
tax_amount: 6,
discount_amount: 6,
total_amount: 100,
reference: 'water123',
unit_of_measure: 'Liters'
}
]
}
end

def test_supported_card_types
Expand Down Expand Up @@ -1115,6 +1155,89 @@ def test_authorize_supports_alternate_credit_card_implementation
end.respond_with(successful_authorize_response)
end

def test_authorize_with_level_2_3_data
response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@amount, @credit_card, @lvl_2_3_options)
end.check_request do |_method, _endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request.dig('customer', 'tax_number'), 123456
assert_equal request.dig('processing', 'order_id'), 12462
assert_equal request.dig('processing', 'tax_amount'), 30
assert_equal request.dig('processing', 'discount_amount'), 10
assert_equal request.dig('processing', 'shipping_amount'), 20
assert_equal request.dig('processing', 'duty_amount'), 5
assert_equal request.dig('shipping', 'from_address_zip'), 12345

item_one = request['items'][0]
item_two = request['items'][1]

assert_equal item_one['reference'], 'glass123'
assert_equal item_one['name'], 'glass'
assert_equal item_one['quantity'], 1
assert_equal item_one['unit_price'], 200
assert_equal item_one['tax_amount'], 12
assert_equal item_one['discount_amount'], 12
assert_equal item_one['total_amount'], 200
assert_equal item_one['commodity_code'], 123
assert_equal item_one['unit_of_measure'], 'Centimeters'

assert_equal item_two['reference'], 'water123'
assert_equal item_two['name'], 'water'
assert_equal item_two['quantity'], 2
assert_equal item_two['unit_price'], 100
assert_equal item_two['tax_amount'], 6
assert_equal item_two['discount_amount'], 6
assert_equal item_two['total_amount'], 100
assert_equal item_two['commodity_code'], 456
assert_equal item_two['unit_of_measure'], 'Liters'
end.respond_with(successful_authorize_response)

assert_success response
assert_equal 'Succeeded', response.message
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
end

def test_capture_with_level_2_3_data
response = stub_comms(@gateway, :ssl_request) do
@gateway.capture(@amount, 'some_value', @lvl_2_3_options)
end.check_request do |_method, _endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request.dig('customer', 'tax_number'), 123456
assert_equal request.dig('processing', 'order_id'), 12462
assert_equal request.dig('processing', 'tax_amount'), 30
assert_equal request.dig('processing', 'discount_amount'), 10
assert_equal request.dig('processing', 'duty_amount'), 5
assert_equal request.dig('processing', 'shipping_amount'), 20
assert_equal request.dig('shipping', 'from_address_zip'), 12345

item_one = request['items'][0]
item_two = request['items'][1]

assert_equal item_one['name'], 'glass'
assert_equal item_one['quantity'], 1
assert_equal item_one['unit_price'], 200
assert_equal item_one['reference'], 'glass123'
assert_equal item_one['commodity_code'], 123
assert_equal item_one['unit_of_measure'], 'Centimeters'
assert_equal item_one['total_amount'], 200
assert_equal item_one['tax_amount'], 12
assert_equal item_one['discount_amount'], 12

assert_equal item_two['reference'], 'water123'
assert_equal item_two['name'], 'water'
assert_equal item_two['quantity'], 2
assert_equal item_two['unit_price'], 100
assert_equal item_two['tax_amount'], 6
assert_equal item_two['discount_amount'], 6
assert_equal item_two['total_amount'], 100
assert_equal item_two['commodity_code'], 456
assert_equal item_two['unit_of_measure'], 'Liters'
end.respond_with(successful_capture_response)

assert_success response
assert_equal 'Succeeded', response.message
end

private

def pre_scrubbed
Expand Down

0 comments on commit f5df0d9

Please sign in to comment.