Active Merchant is a simple payment abstraction library extracted from Shopify. The aim of the project is to feel natural to Ruby users and to abstract as many parts as possible away from the user to offer a consistent interface across all supported gateways.
This is the worst possible outcome, as it can result in a merchant providing service/product when they are not paid for it.
The root cause is authorize.net's API returning "success" for a card charge attempt if the account is in "Card Present" mode (it should be "Card Not Present" for use via the API). This is an easy mistake for new merchants to make.
Steps to reproduce:
have an account in Card Present mode
for AIM just charge a card, or for CIM, store a customer, store a payment profile, then charge the profile
note that the return (see below for CIM example) says resultCode=Ok and code=I00001 (success) and text="Successful."
no money was captured
So, currently, all users of ActiveMerchant (at least those that let other people enter their own authorize.net account) must add protection against this issue.
Desired change:
have ActiveMerchant test for this condition and instead return failure
Below is a raw response for charging a stored CIM profile on a Card Present account:
<createCustomerProfileTransactionResponse xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"AnetApi/xml/v1/schema/AnetApiSchema.xsd\">OkI00001Successful.1.0,3,87,Transactions of this market type cannot be processed on this system.,,P,,0,74078B398622E4FBDC0FB5469D7A2054,,,,,,,,,,,,,,,,,,
And the resulting ActiveMerchant::Billing::Response, note that it even provides a (blank) transaction_id field, making it easy to store this field and consider the result a success:
=> #<ActiveMerchant::Billing::Response:0x0055a0a0b8bd20 @params={"messages"=>{"result_code"=>"Ok", "message"=>{"code"=>"I00001", "text"=>"Successful."}}, "direct_response"=>{"raw"=>"1.0,3,87,Transactions of this market type cannot be processed on this system.,,P,,0,74078B398622E4FBDC0FB5469D7A2054,,,,,,,,,,,,,,,,,,", "response_code"=>"1.0", "response_subcode"=>"3", "response_reason_code"=>"87", "message"=>"Transactions of this market type cannot be processed on this system.", "approval_code"=>"", "avs_response"=>"P", "transaction_id"=>"", "invoice_number"=>"0", "order_description"=>"74078B398622E4FBDC0FB5469D7A2054", "amount"=>nil, "method"=>nil, "transaction_type"=>nil, "customer_id"=>nil, "first_name"=>nil, "last_name"=>nil, "company"=>nil, "address"=>nil, "city"=>nil, "state"=>nil, "zip_code"=>nil, "country"=>nil, "phone"=>nil, "fax"=>nil, "email_address"=>nil, "ship_to_first_name"=>nil, "ship_to_last_name"=>nil, "ship_to_company"=>nil, "ship_to_address"=>nil, "ship_to_city"=>nil, "ship_to_state"=>nil, "ship_to_zip_code"=>nil, "ship_to_country"=>nil, "tax"=>nil, "duty"=>nil, "freight"=>nil, "tax_exempt"=>nil, "purchase_order_number"=>nil, "md5_hash"=>nil, "card_code"=>nil, "cardholder_authentication_verification_response"=>nil, "account_number"=>"", "card_type"=>"", "split_tender_id"=>"", "requested_amount"=>"", "balance_on_card"=>""}}, @message="Successful.", @success=true, @test=false, @authorization="", @fraud_review=nil, @error_code=nil, @emv_authorization=nil, @avs_result={"code"=>nil, "message"=>nil, "street_match"=>nil, "postal_match"=>nil}, @cvv_result={"code"=>nil, "message"=>nil}>
This is the worst possible outcome, as it can result in a merchant providing service/product when they are not paid for it.
The root cause is authorize.net's API returning "success" for a card charge attempt if the account is in "Card Present" mode (it should be "Card Not Present" for use via the API). This is an easy mistake for new merchants to make.
Steps to reproduce:
So, currently, all users of ActiveMerchant (at least those that let other people enter their own authorize.net account) must add protection against this issue.
Desired change:
Below is a raw response for charging a stored CIM profile on a Card Present account: <createCustomerProfileTransactionResponse xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"AnetApi/xml/v1/schema/AnetApiSchema.xsd\">Ok Successful. 1.0,3,87,Transactions of this market type cannot be processed on this system.,,P,,0,74078B398622E4FBDC0FB5469D7A2054,,,,,,,,,,,,,,,,,,
I00001
And the resulting ActiveMerchant::Billing::Response, note that it even provides a (blank) transaction_id field, making it easy to store this field and consider the result a success: => #<ActiveMerchant::Billing::Response:0x0055a0a0b8bd20 @params={"messages"=>{"result_code"=>"Ok", "message"=>{"code"=>"I00001", "text"=>"Successful."}}, "direct_response"=>{"raw"=>"1.0,3,87,Transactions of this market type cannot be processed on this system.,,P,,0,74078B398622E4FBDC0FB5469D7A2054,,,,,,,,,,,,,,,,,,", "response_code"=>"1.0", "response_subcode"=>"3", "response_reason_code"=>"87", "message"=>"Transactions of this market type cannot be processed on this system.", "approval_code"=>"", "avs_response"=>"P", "transaction_id"=>"", "invoice_number"=>"0", "order_description"=>"74078B398622E4FBDC0FB5469D7A2054", "amount"=>nil, "method"=>nil, "transaction_type"=>nil, "customer_id"=>nil, "first_name"=>nil, "last_name"=>nil, "company"=>nil, "address"=>nil, "city"=>nil, "state"=>nil, "zip_code"=>nil, "country"=>nil, "phone"=>nil, "fax"=>nil, "email_address"=>nil, "ship_to_first_name"=>nil, "ship_to_last_name"=>nil, "ship_to_company"=>nil, "ship_to_address"=>nil, "ship_to_city"=>nil, "ship_to_state"=>nil, "ship_to_zip_code"=>nil, "ship_to_country"=>nil, "tax"=>nil, "duty"=>nil, "freight"=>nil, "tax_exempt"=>nil, "purchase_order_number"=>nil, "md5_hash"=>nil, "card_code"=>nil, "cardholder_authentication_verification_response"=>nil, "account_number"=>"", "card_type"=>"", "split_tender_id"=>"", "requested_amount"=>"", "balance_on_card"=>""}}, @message="Successful.", @success=true, @test=false, @authorization="", @fraud_review=nil, @error_code=nil, @emv_authorization=nil, @avs_result={"code"=>nil, "message"=>nil, "street_match"=>nil, "postal_match"=>nil}, @cvv_result={"code"=>nil, "message"=>nil}>