diff --git a/lib/active_merchant/billing/gateways/cyber_source.rb b/lib/active_merchant/billing/gateways/cyber_source.rb index 336fb398eb2..720b05b0e07 100644 --- a/lib/active_merchant/billing/gateways/cyber_source.rb +++ b/lib/active_merchant/billing/gateways/cyber_source.rb @@ -370,6 +370,7 @@ def build_auth_request(money, creditcard_or_reference, options) add_threeds_services(xml, options) add_business_rules_data(xml, creditcard_or_reference, options) add_airline_data(xml, options) + add_merchant_category_code(xml, options) add_sales_slip_number(xml, options) add_payment_network_token(xml, creditcard_or_reference, options) add_payment_solution(xml, creditcard_or_reference) @@ -413,6 +414,7 @@ def build_capture_request(money, authorization, options) add_mdd_fields(xml, options) add_capture_service(xml, request_id, request_token, options) add_business_rules_data(xml, authorization, options) + add_merchant_category_code(xml, options) add_tax_management_indicator(xml, options) add_issuer_additional_data(xml, options) add_merchant_description(xml, options) @@ -433,6 +435,7 @@ def build_purchase_request(money, payment_method_or_reference, options) if (!payment_method_or_reference.is_a?(String) && card_brand(payment_method_or_reference) == 'check') || reference_is_a_check?(payment_method_or_reference) add_check_service(xml) add_airline_data(xml, options) + add_merchant_category_code(xml, options) add_sales_slip_number(xml, options) add_tax_management_indicator(xml, options) add_issuer_additional_data(xml, options) @@ -443,6 +446,7 @@ def build_purchase_request(money, payment_method_or_reference, options) add_threeds_services(xml, options) add_business_rules_data(xml, payment_method_or_reference, options) add_airline_data(xml, options) + add_merchant_category_code(xml, options) add_sales_slip_number(xml, options) add_payment_network_token(xml, payment_method_or_reference, options) add_payment_solution(xml, payment_method_or_reference) @@ -477,6 +481,7 @@ def build_void_request(identification, options) add_mdd_fields(xml, options) add_auth_reversal_service(xml, request_id, request_token) end + add_merchant_category_code(xml, options) add_issuer_additional_data(xml, options) add_partner_solution_id(xml) @@ -492,6 +497,7 @@ def build_refund_request(money, identification, options) add_credit_service(xml, request_id: request_id, request_token: request_token, use_check_service: reference_is_a_check?(identification)) + add_merchant_category_code(xml, options) add_partner_solution_id(xml) xml.target! @@ -1035,6 +1041,10 @@ def add_subscription_retrieve_service(xml, options) xml.tag! 'paySubscriptionRetrieveService', { 'run' => 'true' } end + def add_merchant_category_code(xml, options) + xml.tag! 'merchantCategoryCode', options[:merchant_category_code] if options[:merchant_category_code] + end + def add_subscription(xml, options, reference = nil) options[:subscription] ||= {} diff --git a/lib/active_merchant/billing/gateways/cyber_source_rest.rb b/lib/active_merchant/billing/gateways/cyber_source_rest.rb index 658b245e98b..90413eeaa52 100644 --- a/lib/active_merchant/billing/gateways/cyber_source_rest.rb +++ b/lib/active_merchant/billing/gateways/cyber_source_rest.rb @@ -78,7 +78,7 @@ def credit(money, payment, options = {}) def void(authorization, options = {}) payment, amount = authorization.split('|') - post = build_void_request(amount) + post = build_void_request(options, amount) commit("payments/#{payment}/reversals", post) end @@ -148,7 +148,7 @@ def add_three_ds(post, payment_method, options) post end - def build_void_request(amount = nil) + def build_void_request(options, amount = nil) { reversalInformation: { amountDetails: { totalAmount: nil } } }.tap do |post| add_reversal_amount(post, amount.to_i) if amount.present? end.compact @@ -164,6 +164,7 @@ def build_auth_request(amount, payment, 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_merchant_category_code(post, options) add_partner_solution_id(post) add_stored_credentials(post, payment, options) add_three_ds(post, payment, options) @@ -177,6 +178,7 @@ def build_reference_request(amount, options) add_code(post, options) add_mdd_fields(post, options) add_amount(post, amount, options) + add_merchant_category_code(post, options) add_partner_solution_id(post) end.compact end @@ -187,6 +189,7 @@ def build_credit_request(amount, payment, options) add_credit_card(post, payment) add_mdd_fields(post, options) add_amount(post, amount, options) + add_merchant_category_code(post, options) add_address(post, payment, options[:billing_address], options, :billTo) add_merchant_description(post, options) end.compact @@ -330,6 +333,11 @@ def add_merchant_description(post, options) merchant[:locality] = options[:merchant_descriptor_locality] if options[:merchant_descriptor_locality] end + def add_merchant_category_code(post, options) + merchant_mcc = post[:merchantInformation] ||= {} + merchant_mcc[:categoryCode] = options[:merchant_category_code] if options[:merchant_category_code] + end + def add_stored_credentials(post, payment, options) return unless options[:stored_credential] diff --git a/test/remote/gateways/remote_cyber_source_rest_test.rb b/test/remote/gateways/remote_cyber_source_rest_test.rb index cba393e2de9..d6f61965138 100644 --- a/test/remote/gateways/remote_cyber_source_rest_test.rb +++ b/test/remote/gateways/remote_cyber_source_rest_test.rb @@ -242,6 +242,17 @@ def test_successful_refund assert response.params['_links']['void'].present? end + def test_successful_refund_with_merchant_category_code + purchase = @gateway.purchase(@amount, @visa_card, @options) + response = @gateway.refund(@amount, purchase.authorization, @options.merge(merchant_category_code: '1111')) + + assert_success response + assert response.test? + assert_equal 'PENDING', response.message + assert response.params['id'].present? + assert response.params['_links']['void'].present? + end + def test_failure_refund purchase = @gateway.purchase(@amount, @card_without_funds, @options) response = @gateway.refund(@amount, purchase.authorization, @options) @@ -293,6 +304,16 @@ def test_successful_credit assert_nil response.params['_links']['capture'] end + def test_successful_credit_with_merchant_category_code + response = @gateway.credit(@amount, @visa_card, @options.merge(merchant_category_code: '1111')) + + assert_success response + assert response.test? + assert_equal 'PENDING', response.message + assert response.params['id'].present? + assert_nil response.params['_links']['capture'] + end + def test_failure_credit response = @gateway.credit(@amount, @card_without_funds, @options) @@ -311,6 +332,15 @@ def test_successful_void assert_nil response.params['_links']['capture'] end + def test_successful_void_with_merchant_category_code + authorize = @gateway.authorize(@amount, @visa_card, @options) + response = @gateway.void(authorize.authorization, @options.merge(merchant_category_code: '1111')) + assert_success response + assert response.params['id'].present? + assert_equal 'REVERSED', response.message + assert_nil response.params['_links']['capture'] + end + def test_failure_void_using_card_without_funds authorize = @gateway.authorize(@amount, @card_without_funds, @options) response = @gateway.void(authorize.authorization, @options) @@ -599,6 +629,14 @@ def test_successful_purchase_with_level_2_data assert_nil response.params['_links']['capture'] end + def test_successful_purchase_with_merchant_catefory_code + response = @gateway.purchase(@amount, @visa_card, @options.merge(merchant_category_code: '1111')) + assert_success response + assert response.test? + assert_equal 'AUTHORIZED', response.message + assert_nil response.params['_links']['capture'] + end + def test_successful_purchase_with_level_2_and_3_data options = { purchase_order_number: '6789', diff --git a/test/remote/gateways/remote_cyber_source_test.rb b/test/remote/gateways/remote_cyber_source_test.rb index d421d1f21ca..c6b2cba6926 100644 --- a/test/remote/gateways/remote_cyber_source_test.rb +++ b/test/remote/gateways/remote_cyber_source_test.rb @@ -171,6 +171,13 @@ def test_successful_authorization assert !response.authorization.blank? end + def test_successful_authorization_with_merchant_catefory_code + options = @options.merge(merchant_category_code: '1111') + assert response = @gateway.authorize(@amount, @credit_card, options) + assert_successful_response(response) + assert !response.authorization.blank? + end + def test_successful_authorization_with_reconciliation_id options = @options.merge(reconciliation_id: '1936831') assert response = @gateway.authorize(@amount, @credit_card, options) @@ -316,6 +323,13 @@ def test_successful_purchase_with_single_element_from_other_tax assert !response.authorization.blank? end + def test_successful_purchase_with_merchant_catefory_code + options = @options.merge(merchant_category_code: '1111') + assert response = @gateway.purchase(@amount, @credit_card, options) + assert_successful_response(response) + assert !response.authorization.blank? + end + def test_successful_auth_with_gratuity_amount options = @options.merge(gratuity_amount: '7.50') @@ -371,6 +385,15 @@ def test_purchase_and_void assert_successful_response(void) end + def test_purchase_and_void_with_merchant_category_code + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_successful_response(purchase) + + void_options = @options.merge(merchant_category_code: '1111') + assert void = @gateway.void(purchase.authorization, void_options) + assert_successful_response(void) + end + # Note: This test will only pass with test account credentials which # have asynchronous adjustments enabled. def test_successful_asynchronous_adjust @@ -694,6 +717,16 @@ def test_successful_capture_with_issuer_additional_data assert !response.authorization.blank? end + def test_successful_capture_with_merchant_category_code + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_successful_response(auth) + + capture_options = @options.merge(merchant_category_code: '1111') + assert response = @gateway.capture(@amount, auth.authorization, capture_options) + assert_successful_response(response) + assert !response.authorization.blank? + end + def test_successful_capture_with_solution_id ActiveMerchant::Billing::CyberSourceGateway.application_id = 'A1000000' assert auth = @gateway.authorize(@amount, @credit_card, @options) @@ -752,6 +785,15 @@ def test_successful_refund_with_solution_id ActiveMerchant::Billing::CyberSourceGateway.application_id = nil end + def test_successful_refund_with_merchant_category_code + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_successful_response(response) + + refund_options = @options.merge(merchant_category_code: '1111') + assert response = @gateway.refund(@amount, response.authorization, refund_options) + assert_successful_response(response) + end + def test_successful_refund_with_bank_account_follow_on bank_account = check({ account_number: '4100', routing_number: '011000015' }) assert response = @gateway.purchase(10000, bank_account, @options) diff --git a/test/unit/gateways/cyber_source_rest_test.rb b/test/unit/gateways/cyber_source_rest_test.rb index 08772bdc1b9..eb70207575c 100644 --- a/test/unit/gateways/cyber_source_rest_test.rb +++ b/test/unit/gateways/cyber_source_rest_test.rb @@ -463,6 +463,15 @@ def test_credit_includes_mdd_fields end.respond_with(successful_credit_response) end + def test_successful_credit_with_merchant_category_code + stub_comms do + @gateway.credit(@amount, @credit_card, @options.merge(merchant_category_code: '1111')) + end.check_request do |_endpoint, data, _headers| + json_data = JSON.parse(data) + assert_equal json_data['merchantInformation']['categoryCode'], '1111' + end.respond_with(successful_credit_response) + end + def test_authorize_includes_reconciliation_id stub_comms do @gateway.authorize(100, @credit_card, order_id: '1', reconciliation_id: '181537') @@ -490,6 +499,15 @@ def test_purchase_includes_invoice_number end.respond_with(successful_purchase_response) end + def test_successful_purchase_with_merchant_category_code + stub_comms do + @gateway.purchase(100, @credit_card, @options.merge(merchant_category_code: '1111')) + end.check_request do |_endpoint, data, _headers| + json_data = JSON.parse(data) + assert_equal json_data['merchantInformation']['categoryCode'], '1111' + end.respond_with(successful_purchase_response) + end + def test_mastercard_purchase_with_3ds2 @options[:three_d_secure] = { version: '2.2.0', diff --git a/test/unit/gateways/cyber_source_test.rb b/test/unit/gateways/cyber_source_test.rb index dafabf44cf2..82a7881893c 100644 --- a/test/unit/gateways/cyber_source_test.rb +++ b/test/unit/gateways/cyber_source_test.rb @@ -105,6 +105,14 @@ def test_successful_purchase_with_other_tax_fields end.respond_with(successful_purchase_response) end + def test_successful_purchase_with_merchant_category_code + stub_comms do + @gateway.purchase(100, @credit_card, @options.merge!(merchant_category_code: '1111')) + end.check_request do |_endpoint, data, _headers| + assert_match(/1111<\/merchantCategoryCode>/, data) + end.respond_with(successful_purchase_response) + end + def test_successful_purchase_with_purchase_totals_data stub_comms do @gateway.purchase(100, @credit_card, @options.merge(discount_management_indicator: 'T', purchase_tax_amount: 7.89, original_amount: 1.23, invoice_amount: 1.23)) @@ -122,6 +130,14 @@ def test_successful_authorize_with_national_tax_indicator end.respond_with(successful_authorization_response) end + def test_successful_authorize_with_merchant_category_code + stub_comms do + @gateway.authorize(100, @credit_card, @options.merge(merchant_category_code: '1111')) + end.check_request do |_endpoint, data, _headers| + assert_match(/1111<\/merchantCategoryCode>/, data) + end.respond_with(successful_authorization_response) + end + def test_successful_authorize_with_cc_auth_service_fields stub_comms do @gateway.authorize(100, @credit_card, @options.merge(mobile_remote_payment_type: 'T')) @@ -739,6 +755,14 @@ def test_capture_includes_mdd_fields end.respond_with(successful_capture_response) end + def test_successful_capture_with_merchant_category_code + stub_comms do + @gateway.capture(100, '1846925324700976124593', @options.merge!(merchant_category_code: '1111')) + end.check_request do |_endpoint, data, _headers| + assert_match(/1111<\/merchantCategoryCode>/, data) + end.respond_with(successful_capture_response) + end + def test_successful_credit_card_purchase_request @gateway.stubs(:ssl_post).returns(successful_capture_response) assert response = @gateway.purchase(@amount, @credit_card, @options) @@ -846,6 +870,14 @@ def test_successful_refund_with_elo_request assert_success(@gateway.refund(@amount, response.authorization)) end + def test_successful_refund_with_merchant_category_code + stub_comms do + @gateway.refund(100, 'test;12345', @options.merge!(merchant_category_code: '1111')) + end.check_request do |_endpoint, data, _headers| + assert_match(/1111<\/merchantCategoryCode>/, data) + end.respond_with(successful_refund_response) + end + def test_successful_credit_to_card_request @gateway.stubs(:ssl_post).returns(successful_card_credit_response)