diff --git a/CHANGELOG b/CHANGELOG index 470b512e7c0..dfd6aa6fe21 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -39,6 +39,7 @@ * Fat Zebra: Add multi-currency support [nagash] * Fat Zebra: Add auth/capture capability [nagash] * Fat Zebra: Add soft descriptor support [nagash] +* Stripe: Add EMV "chip & sign", "chip & offline PIN" and Maestro support [bizla] == Version 1.47.0 (February 25, 2015) diff --git a/lib/active_merchant/billing/credit_card.rb b/lib/active_merchant/billing/credit_card.rb index 04f7cb51c52..b7c3b37a03b 100644 --- a/lib/active_merchant/billing/credit_card.rb +++ b/lib/active_merchant/billing/credit_card.rb @@ -138,6 +138,17 @@ def brand=(value) # @return [String] attr_accessor :track_data + # Returns or sets the ICC/ASN1 credit card data for a EMV transaction, typically this is a BER-encoded TLV string. + # + # @return [String] + attr_accessor :icc_data + + # Returns or sets a fallback reason for a EMV transaction whereby the customer's card entered a fallback scenario. + # This can be an arbitrary string. + # + # @return [String] + attr_accessor :fallback_reason + def type ActiveMerchant.deprecated "CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead." brand @@ -253,6 +264,10 @@ def self.requires_name? require_name end + def emv? + icc_data.present? + end + private def validate_essential_attributes #:nodoc: diff --git a/lib/active_merchant/billing/credit_card_methods.rb b/lib/active_merchant/billing/credit_card_methods.rb index ab4cb02806e..d74323386b0 100644 --- a/lib/active_merchant/billing/credit_card_methods.rb +++ b/lib/active_merchant/billing/credit_card_methods.rb @@ -3,7 +3,7 @@ module Billing #:nodoc: # Convenience methods that can be included into a custom Credit Card object, such as an ActiveRecord based Credit Card object. module CreditCardMethods CARD_COMPANIES = { - 'visa' => /^4\d{12}(\d{3})?$/, + 'visa' => /^4\d{12}(\d{3})?(\d{3})?$/, 'master' => /^(5[1-5]\d{4}|677189)\d{10}$/, 'discover' => /^(6011|65\d{2}|64[4-9]\d)\d{12}|(62\d{14})$/, 'american_express' => /^3[47]\d{13}$/, @@ -49,11 +49,11 @@ def valid_start_year?(year) def valid_card_verification_value?(cvv, brand) cvv.to_s =~ /^\d{#{card_verification_value_length(brand)}}$/ end - + def card_verification_value_length(brand) brand == 'american_express' ? 4 : 3 end - + def valid_issue_number?(number) (number.to_s =~ /^\d{1,2}$/) end diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb index 3cfb968f495..ee19036efed 100644 --- a/lib/active_merchant/billing/gateways/stripe.rb +++ b/lib/active_merchant/billing/gateways/stripe.rb @@ -27,7 +27,7 @@ class StripeGateway < Gateway self.supported_countries = %w(AT AU BE CA CH DE DK ES FI FR GB IE IT LU NL NO SE US) self.default_currency = 'USD' self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro] self.homepage_url = 'https://stripe.com/' self.display_name = 'Stripe' @@ -62,7 +62,7 @@ def authorize(money, payment, options = {}) end r.process do post = create_post_for_auth_or_purchase(money, payment, options) - post[:capture] = "false" + post[:capture] = "false" unless emv_payment?(payment) commit(:post, 'charges', post, options) end end.responses.last @@ -90,10 +90,16 @@ def purchase(money, payment, options = {}) def capture(money, authorization, options = {}) post = {} + add_amount(post, money, options) add_application_fee(post, options) - commit(:post, "charges/#{CGI.escape(authorization)}/capture", post, options) + if emv_tc_response = options.delete(:icc_data) + post[:card] = { emv_approval_data: emv_tc_response } + commit(:post, "charges/#{CGI.escape(authorization)}", post, options) + else + commit(:post, "charges/#{CGI.escape(authorization)}/capture", post, options) + end end def void(identification, options = {}) @@ -226,23 +232,22 @@ def type def create_post_for_auth_or_purchase(money, payment, options) post = {} - add_amount(post, money, options, true) + if payment.is_a?(StripePaymentToken) add_payment_token(post, payment, options) else add_creditcard(post, payment, options) end - add_customer(post, payment, options) - add_customer_data(post, options) - post[:description] = options[:description] - post[:statement_description] = options[:statement_description] - - post[:metadata] = options[:metadata] || {} - post[:metadata][:email] = options[:email] if options[:email] - post[:metadata][:order_id] = options[:order_id] if options[:order_id] - post.delete(:metadata) if post[:metadata].empty? + unless emv_payment?(payment) + add_amount(post, money, options, true) + add_customer_data(post, options) + add_metadata(post, options) + post[:description] = options[:description] + post[:statement_description] = options[:statement_description] + add_customer(post, payment, options) + add_flags(post, options) + end - add_flags(post, options) add_application_fee(post, options) post end @@ -283,9 +288,12 @@ def add_address(post, options) def add_creditcard(post, creditcard, options) card = {} - if creditcard.respond_to?(:number) + if emv_payment?(creditcard) + add_emv_creditcard(post, creditcard.icc_data) + elsif creditcard.respond_to?(:number) if creditcard.respond_to?(:track_data) && creditcard.track_data.present? card[:swipe_data] = creditcard.track_data + post[:card] = { fallback_reason: creditcard.fallback_reason } if creditcard.fallback_reason else card[:number] = creditcard.number card[:exp_month] = creditcard.month @@ -293,7 +301,6 @@ def add_creditcard(post, creditcard, options) card[:cvc] = creditcard.verification_value if creditcard.verification_value? card[:name] = creditcard.name if creditcard.name end - post[:card] = card if creditcard.is_a?(NetworkTokenizationCreditCard) @@ -314,6 +321,10 @@ def add_creditcard(post, creditcard, options) end end + def add_emv_creditcard(post, icc_data, options = {}) + post[:card] = { emv_auth_data: icc_data } + end + def add_payment_token(post, token, options = {}) post[:card] = token.payment_data["id"] end @@ -327,6 +338,13 @@ def add_flags(post, options) post[:recurring] = true if (options[:eci] == 'recurring' || options[:recurring]) end + def add_metadata(post, options = {}) + post[:metadata] = options[:metadata] || {} + post[:metadata][:email] = options[:email] if options[:email] + post[:metadata][:order_id] = options[:order_id] if options[:order_id] + post.delete(:metadata) if post[:metadata].empty? + end + def fetch_application_fees(identification, options = {}) options.merge!(:key => @fee_refund_api_key) @@ -386,11 +404,11 @@ def api_request(method, endpoint, parameters = nil, options = {}) def commit(method, url, parameters = nil, options = {}) add_expand_parameters(parameters, options) if parameters - response = api_request(method, url, parameters, options) + success = !response.key?("error") - card = response["card"] || response["active_card"] || {} + card = response["card"] || response["active_card"] || response["source"] || {} avs_code = AVS_CODE_TRANSLATOR["line1: #{card["address_line1_check"]}, zip: #{card["address_zip_check"]}"] cvc_code = CVC_CODE_TRANSLATOR[card["cvc_check"]] @@ -401,6 +419,7 @@ def commit(method, url, parameters = nil, options = {}) :authorization => success ? response["id"] : response["error"]["charge"], :avs_result => { :code => avs_code }, :cvv_result => cvc_code, + :emv_authorization => card["emv_auth_data"], :error_code => success ? nil : STANDARD_ERROR_CODE_MAPPING[response["error"]["code"]] ) end @@ -426,6 +445,10 @@ def json_error(raw_response) def non_fractional_currency?(currency) CURRENCIES_WITHOUT_FRACTIONS.include?(currency.to_s) end + + def emv_payment?(payment) + payment.respond_to?(:emv?) && payment.emv? + end end end end diff --git a/lib/active_merchant/billing/response.rb b/lib/active_merchant/billing/response.rb index 1fb34e11d8b..a59174ceb7c 100644 --- a/lib/active_merchant/billing/response.rb +++ b/lib/active_merchant/billing/response.rb @@ -4,7 +4,7 @@ class Error < ActiveMerchantError #:nodoc: end class Response - attr_reader :params, :message, :test, :authorization, :avs_result, :cvv_result, :error_code + attr_reader :params, :message, :test, :authorization, :avs_result, :cvv_result, :error_code, :emv_authorization def success? @success @@ -24,6 +24,7 @@ def initialize(success, message, params = {}, options = {}) @authorization = options[:authorization] @fraud_review = options[:fraud_review] @error_code = options[:error_code] + @emv_authorization = options[:emv_authorization] @avs_result = if options[:avs_result].kind_of?(AVSResult) options[:avs_result].to_hash @@ -79,7 +80,7 @@ def success? (primary_response ? primary_response.success? : true) end - %w(params message test authorization avs_result cvv_result error_code test? fraud_review?).each do |m| + %w(params message test authorization avs_result cvv_result error_code emv_authorization test? fraud_review?).each do |m| class_eval %( def #{m} (@responses.empty? ? nil : primary_response.#{m}) diff --git a/test/fixtures.yml b/test/fixtures.yml index 3a270a54afa..12dcea9d30f 100644 --- a/test/fixtures.yml +++ b/test/fixtures.yml @@ -799,6 +799,16 @@ stripe: login: fee_refund_login: +# Used only for remote tests that use EMV credit card mocks +stripe_emv_uk: + login: + fee_refund_login: + +# Used only for remote tests that use EMV credit card mocks +stripe_emv_us: + login: + fee_refund_login: + # Working credentials, no need to replace swipe_checkout: login: 2077103073D8B5 diff --git a/test/remote/gateways/remote_stripe_test.rb b/test/remote/gateways/remote_stripe_test.rb index 1b64953c96b..2864734a0e5 100644 --- a/test/remote/gateways/remote_stripe_test.rb +++ b/test/remote/gateways/remote_stripe_test.rb @@ -12,6 +12,11 @@ def setup @declined_card = credit_card('4000000000000002') @new_credit_card = credit_card('5105105105105100') + @emv_credit_cards = { + uk: ActiveMerchant::Billing::CreditCard.new(icc_data: '500B56495341204352454449545F201A56495341204143515549524552205445535420434152442030315F24031512315F280208405F2A0208265F300202015F34010182025C008407A0000000031010950502000080009A031408259B02E8009C01009F02060000000734499F03060000000000009F0607A00000000310109F0902008C9F100706010A03A080009F120F4352454449544F20444520564953419F1A0208269F1C0831373030303437309F1E0831373030303437309F2608EB2EC0F472BEA0A49F2701809F3303E0B8C89F34031E03009F3501229F360200C39F37040A27296F9F4104000001319F4502DAC5DFAE5711476173FFFFFF0119D15122011758989389DFAE5A08476173FFFFFF011957114761739001010119D151220117589893895A084761739001010119'), + us: ActiveMerchant::Billing::CreditCard.new(icc_data: '50074D41455354524F571167999989000018123D25122200835506065A0967999989000018123F5F20134D54495032362D204D41455354524F203132415F24032512315F280200565F2A0208405F300202205F340101820278008407A0000000043060950500000080009A031504219B02E8009C01009F02060000000010009F03060000000000009F0607A00000000430609F090200029F10120210A7800F040000000000000000000000FF9F12074D61657374726F9F1A0208409F1C0831303030333331369F1E0831303030333331369F2608460245B808BCA1369F2701809F3303E0B8C89F34034403029F3501229F360200279F3704EA2C3A7A9F410400000094DF280104DFAE5711679999FFFFFFF8123D2512220083550606DFAE5A09679999FFFFFFF8123F') + } + @options = { :currency => @currency, :description => 'ActiveMerchant Test Purchase', @@ -43,6 +48,28 @@ def test_successful_purchase assert response.params["paid"] assert_equal "ActiveMerchant Test Purchase", response.params["description"] assert_equal "wow@example.com", response.params["metadata"]["email"] + end + + # for EMV contact transactions, it's advised to do a separate auth + capture + # to satisfy the EMV chip's transaction flow, but this works as a legal + # API call. You shouldn't use it in a real EMV implementation, though. + def test_successful_purchase_with_emv_credit_card_in_uk + @gateway = StripeGateway.new(fixtures(:stripe_emv_uk)) + assert response = @gateway.purchase(@amount, @emv_credit_cards[:uk], @options) + assert_success response + assert_equal "charge", response.params["object"] + assert response.params["paid"] + assert_match CHARGE_ID_REGEX, response.authorization + end + + # perform separate auth & capture rather than a purchase in practice for the + # reasons mentioned above. + def test_successful_purchase_with_emv_credit_card_in_us + @gateway = StripeGateway.new(fixtures(:stripe_emv_us)) + assert response = @gateway.purchase(@amount, @emv_credit_cards[:us], @options) + assert_success response + assert_equal "charge", response.params["object"] + assert response.params["paid"] assert_match CHARGE_ID_REGEX, response.authorization end @@ -77,7 +104,7 @@ def test_unsuccessful_purchase def test_authorization_and_capture assert authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization - assert !authorization.params["captured"] + refute authorization.params["captured"] assert_equal "ActiveMerchant Test Purchase", authorization.params["description"] assert_equal "wow@example.com", authorization.params["metadata"]["email"] @@ -85,10 +112,34 @@ def test_authorization_and_capture assert_success capture end + def test_authorization_and_capture_with_emv_credit_card_in_uk + @gateway = StripeGateway.new(fixtures(:stripe_emv_uk)) + assert authorization = @gateway.authorize(@amount, @emv_credit_cards[:uk], @options) + assert_success authorization + assert authorization.emv_authorization, "Authorization should contain emv_authorization containing the EMV ARPC" + refute authorization.params["captured"] + + assert capture = @gateway.capture(@amount, authorization.authorization) + assert_success capture + assert capture.emv_authorization, "Capture should contain emv_authorization containing the EMV TC" + end + + def test_authorization_and_capture_with_emv_credit_card_in_us + @gateway = StripeGateway.new(fixtures(:stripe_emv_us)) + assert authorization = @gateway.authorize(@amount, @emv_credit_cards[:us], @options) + assert_success authorization + assert authorization.emv_authorization, "Authorization should contain emv_authorization containing the EMV ARPC" + refute authorization.params["captured"] + + assert capture = @gateway.capture(@amount, authorization.authorization) + assert_success capture + assert capture.emv_authorization, "Capture should contain emv_authorization containing the EMV TC" + end + def test_authorization_and_capture_with_apple_pay_payment_token assert authorization = @gateway.authorize(@amount, @apple_pay_payment_token, @options) assert_success authorization - assert !authorization.params["captured"] + refute authorization.params["captured"] assert_equal "ActiveMerchant Test Purchase", authorization.params["description"] assert_equal "wow@example.com", authorization.params["metadata"]["email"] @@ -99,7 +150,29 @@ def test_authorization_and_capture_with_apple_pay_payment_token def test_authorization_and_void assert authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization - assert !authorization.params["captured"] + refute authorization.params["captured"] + + assert void = @gateway.void(authorization.authorization) + assert_success void + end + + def test_authorization_and_void_with_emv_credit_card_in_us + @gateway = StripeGateway.new(fixtures(:stripe_emv_us)) + assert authorization = @gateway.authorize(@amount, @emv_credit_cards[:us], @options) + assert_success authorization + assert authorization.emv_authorization, "Authorization should contain emv_authorization containing the EMV ARPC" + refute authorization.params["captured"] + + assert void = @gateway.void(authorization.authorization) + assert_success void + end + + def test_authorization_and_void_with_emv_credit_card_in_uk + @gateway = StripeGateway.new(fixtures(:stripe_emv_uk)) + assert authorization = @gateway.authorize(@amount, @emv_credit_cards[:uk], @options) + assert_success authorization + assert authorization.emv_authorization, "Authorization should contain emv_authorization containing the EMV ARPC" + refute authorization.params["captured"] assert void = @gateway.void(authorization.authorization) assert_success void @@ -108,7 +181,7 @@ def test_authorization_and_void def test_authorization_and_void_with_apple_pay_payment_token assert authorization = @gateway.authorize(@amount, @apple_pay_payment_token, @options) assert_success authorization - assert !authorization.params["captured"] + refute authorization.params["captured"] assert void = @gateway.void(authorization.authorization) assert_success void @@ -294,7 +367,7 @@ def test_card_present_authorize_and_capture @credit_card.track_data = '%B378282246310005^LONGSON/LONGBOB^1705101130504392?' assert authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization - assert !authorization.params["captured"] + refute authorization.params["captured"] assert capture = @gateway.capture(@amount, authorization.authorization) assert_success capture diff --git a/test/unit/credit_card_test.rb b/test/unit/credit_card_test.rb index ed66abe3afe..2738b879f49 100644 --- a/test/unit/credit_card_test.rb +++ b/test/unit/credit_card_test.rb @@ -432,4 +432,12 @@ def test_month_and_year_are_immediately_converted_to_integers card.start_year = "1" assert_equal 1, card.start_year end + + def test_should_report_as_emv_if_icc_data_present + assert CreditCard.new(icc_data: "E480").emv? + end + + def test_should_not_report_as_emv_if_icc_data_not_present + refute CreditCard.new.emv? + end end diff --git a/test/unit/gateways/stripe_test.rb b/test/unit/gateways/stripe_test.rb index 9c50614acd2..61ca7303d84 100644 --- a/test/unit/gateways/stripe_test.rb +++ b/test/unit/gateways/stripe_test.rb @@ -16,6 +16,7 @@ def setup } @apple_pay_payment_token = apple_pay_payment_token + @emv_credit_card = credit_card_with_icc_data end def test_successful_new_customer_with_card @@ -42,6 +43,18 @@ def test_successful_new_customer_with_apple_pay_payment_token assert response.test? end + def test_successful_new_customer_with_emv_credit_card + @gateway.expects(:ssl_request).returns(successful_new_customer_response) + + assert response = @gateway.store(@emv_credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'cus_3sgheFxeBgTQ3M', response.authorization + assert response.test? + end + + def test_successful_new_card @gateway.expects(:ssl_request).returns(successful_new_card_response) @gateway.expects(:add_creditcard) @@ -66,6 +79,18 @@ def test_successful_new_card_via_apple_pay_payment_token assert response.test? end + def test_successful_new_card_with_emv_credit_card + @gateway.expects(:ssl_request).returns(successful_new_card_response) + @gateway.expects(:add_creditcard) + + assert response = @gateway.store(@emv_credit_card, :customer => 'cus_3sgheFxeBgTQ3M') + assert_instance_of MultiResponse, response + assert_success response + + assert_equal 'card_483etw4er9fg4vF3sQdrt3FG', response.authorization + assert response.test? + end + def test_successful_new_card_and_customer_update @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) @gateway.expects(:add_creditcard) @@ -96,6 +121,20 @@ def test_successful_new_card_and_customer_update_via_apple_pay_payment_token assert response.test? end + def test_successful_new_card_and_customer_update_with_emv_credit_card + @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) + + assert response = @gateway.store(@emv_credit_card, :customer => 'cus_3sgheFxeBgTQ3M', :email => 'test@test.com') + assert_instance_of MultiResponse, response + assert_success response + + assert_equal 'card_483etw4er9fg4vF3sQdrt3FG', response.authorization + assert_equal 2, response.responses.size + assert_equal 'card_483etw4er9fg4vF3sQdrt3FG', response.responses[0].authorization + assert_equal 'cus_3sgheFxeBgTQ3M', response.responses[1].authorization + assert response.test? + end + def test_successful_new_default_card @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) @gateway.expects(:add_creditcard) @@ -126,6 +165,20 @@ def test_successful_new_default_card_via_apple_pay_payment_token assert response.test? end + def test_successful_new_default_card_with_emv_credit_card + @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) + + assert response = @gateway.store(@emv_credit_card, @options.merge(:customer => 'cus_3sgheFxeBgTQ3M', :set_default => true)) + assert_instance_of MultiResponse, response + assert_success response + + assert_equal 'card_483etw4er9fg4vF3sQdrt3FG', response.authorization + assert_equal 2, response.responses.size + assert_equal 'card_483etw4er9fg4vF3sQdrt3FG', response.responses[0].authorization + assert_equal 'cus_3sgheFxeBgTQ3M', response.responses[1].authorization + assert response.test? + end + def test_successful_authorization @gateway.expects(:add_creditcard) @gateway.expects(:ssl_request).returns(successful_authorization_response) @@ -150,6 +203,17 @@ def test_successful_authorization_with_apple_pay_token_exchange assert response.test? end + def test_successful_authorization_with_emv_credit_card + @gateway.expects(:ssl_request).returns(successful_authorization_response_with_icc_data) + + assert response = @gateway.authorize(@amount, @emv_credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'ch_test_emv_charge', response.authorization + assert response.emv_authorization, "Response should include emv_authorization containing the EMV ARPC" + end + def test_successful_capture @gateway.expects(:ssl_request).returns(successful_capture_response) @@ -158,6 +222,14 @@ def test_successful_capture assert response.test? end + def test_successful_capture_with_emv_credit_card_tc + @gateway.expects(:ssl_request).returns(successful_capture_response_with_icc_data) + + assert response = @gateway.capture(@amount, "ch_test_emv_charge") + assert_success response + assert response.emv_authorization, "Response should include emv_authorization containing the EMV TC" + end + def test_successful_purchase @gateway.expects(:add_creditcard) @gateway.expects(:ssl_request).returns(successful_purchase_response) @@ -387,6 +459,13 @@ def test_add_creditcard_with_token_and_track_data assert_equal "Tracking data", post[:card][:swipe_data] end + def test_add_creditcard_with_emv_credit_card + post = {} + @gateway.send(:add_creditcard, post, @emv_credit_card, {}) + + assert_equal @emv_credit_card.icc_data, post[:card][:emv_auth_data] + end + def test_add_customer post = {} @gateway.send(:add_customer, post, nil, {:customer => "test_customer"}) @@ -663,6 +742,12 @@ def test_successful_purchase_with_network_tokenization private + # this mock is only useful with unit tests, as cryptograms generated by an EMV terminal + # are specific to the target acquirer, so remote tests using this mock will fail elsewhere. + def credit_card_with_icc_data + ActiveMerchant::Billing::CreditCard.new(icc_data: '500B56495341204352454449545F201A56495341204143515549524552205445535420434152442030315F24031512315F280208405F2A0208265F300202015F34010182025C008407A0000000031010950502000080009A031408259B02E8009C01009F02060000000734499F03060000000000009F0607A00000000310109F0902008C9F100706010A03A080009F120F4352454449544F20444520564953419F1A0208269F1C0831373030303437309F1E0831373030303437309F2608EB2EC0F472BEA0A49F2701809F3303E0B8C89F34031E03009F3501229F360200C39F37040A27296F9F4104000001319F4502DAC5DFAE5711476173FFFFFF0119D15122011758989389DFAE5A08476173FFFFFF011957114761739001010119D151220117589893895A084761739001010119') + end + def pre_scrubbed <<-PRE_SCRUBBED opening connection to api.stripe.com:443... @@ -827,6 +912,71 @@ def successful_authorization_response RESPONSE end + def successful_authorization_response_with_icc_data + <<-RESPONSE + { + "id": "ch_test_emv_charge", + "object": "charge", + "created": 1429642948, + "livemode": true, + "paid": true, + "status": "succeeded", + "amount": 1000, + "currency": "usd", + "refunded": false, + "source": { + "id": "card_15u6dcHMpVh8I77hUfAVAfsK", + "object": "card", + "last4": "8123", + "brand": "MasterCard", + "funding": "unknown", + "exp_month": 12, + "exp_year": 2025, + "fingerprint": "tdVpM3XDe3H4juSD", + "country": "US", + "name": null, + "address_line1": null, + "address_line2": null, + "address_city": null, + "address_state": null, + "address_zip": null, + "address_country": null, + "cvc_check": null, + "address_line1_check": null, + "address_zip_check": null, + "dynamic_last4": null, + "metadata": {}, + "customer": null, + "emv_auth_data": "8A023835910AF7F7BA77D7ACCFAB0012710F860D8424000008C1EFF627EAE08933" + }, + "captured": false, + "balance_transaction": null, + "failure_message": null, + "failure_code": null, + "amount_refunded": 0, + "customer": null, + "invoice": null, + "description": null, + "dispute": null, + "metadata": {}, + "statement_descriptor": null, + "fraud_details": {}, + "receipt_email": null, + "receipt_number": null, + "authorization_code": "816826", + "shipping": null, + "application_fee": null, + "refunds": { + "object": "list", + "total_count": 0, + "has_more": false, + "url": "/v1/charges/ch_15u6dcHMpVh8I77hdIKNQ1jH/refunds", + "data": [] + } + } + RESPONSE + end + def successful_capture_response <<-RESPONSE { @@ -857,6 +1007,84 @@ def successful_capture_response RESPONSE end + def successful_capture_response_with_icc_data + <<-RESPONSE + { + "id": "ch_test_emv_charge", + "object": "charge", + "created": 1429643380, + "livemode": true, + "paid": true, + "status": "succeeded", + "amount": 1000, + "currency": "usd", + "refunded": false, + "source": { + "id": "card_15u6kaHMpVh8I77htEt6tgX4", + "object": "card", + "last4": "8123", + "brand": "MasterCard", + "funding": "unknown", + "exp_month": 12, + "exp_year": 2025, + "fingerprint": "tdVpM3XDe3H4juSD", + "country": "US", + "name": null, + "address_line1": null, + "address_line2": null, + "address_city": null, + "address_state": null, + "address_zip": null, + "address_country": null, + "cvc_check": null, + "address_line1_check": null, + "address_zip_check": null, + "dynamic_last4": null, + "metadata": {}, + "customer": null, + "emv_auth_data": "8A023835910AF7F7BA77D7ACCFAB0012710F860D8424000008C1EFF627EAE08933" + }, + "captured": true, + "balance_transaction": "txn_15u6kbHMpVh8I77hA79CanC2", + "failure_message": null, + "failure_code": null, + "amount_refunded": 900, + "customer": null, + "invoice": null, + "description": null, + "dispute": null, + "metadata": {}, + "statement_descriptor": null, + "fraud_details": {}, + "receipt_email": null, + "receipt_number": null, + "authorization_code": "662021", + "shipping": null, + "application_fee": null, + "refunds": { + "object": "list", + "total_count": 1, + "has_more": false, + "url": "/v1/charges/ch_15u6kaHMpVh8I77hrF9XY8bG/refunds", + "data": [ + { + "id": "re_15u6kbHMpVh8I77h2o6RsdQq", + "amount": 900, + "currency": "usd", + "created": 1429643381, + "object": "refund", + "balance_transaction": "txn_15u6kbHMpVh8I77hXqAYL6kZ", + "metadata": {}, + "charge": "ch_15u6kaHMpVh8I77hrF9XY8bG", + "receipt_number": null, + "reason": null + } + ] + } + } + RESPONSE + end + def successful_purchase_response(refunded=false) <<-RESPONSE {