diff --git a/CHANGELOG b/CHANGELOG index db808176284..c1d5da622d7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ * Adyen: Add the store field [yunnydang] #4878 * Stripe Payment Intents: Expand balance txns for regular transactions [yunnydang] #4882 * CyberSource (SOAP): Added support for 3DS exemption request fields [BritneyS] #4881 +* StripePI: Adding network tokenization fields to Stripe PaymentIntents [BritneyS] #4867 == Version 1.135.0 (August 24, 2023) * PaymentExpress: Correct endpoints [steveh] #4827 diff --git a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb index 5a0daeec936..f423ff39aa5 100644 --- a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +++ b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb @@ -14,7 +14,7 @@ class StripePaymentIntentsGateway < StripeGateway def create_intent(money, payment_method, options = {}) MultiResponse.run do |r| - if payment_method.is_a?(NetworkTokenizationCreditCard) + if payment_method.is_a?(NetworkTokenizationCreditCard) && digital_wallet_payment_method?(payment_method) r.process { tokenize_apple_google(payment_method, options) } payment_method = (r.params['token']['id']) if r.success? end @@ -28,6 +28,7 @@ def create_intent(money, payment_method, options = {}) result = add_payment_method_token(post, payment_method, options) return result if result.is_a?(ActiveMerchant::Billing::Response) + add_network_token_cryptogram_and_eci(post, payment_method) add_external_three_d_secure_auth_data(post, options) add_metadata(post, options) add_return_url(post, options) @@ -85,18 +86,18 @@ def add_payment_method_data(payment_method, options = {}) post = { type: 'card', card: { - number: payment_method.number, exp_month: payment_method.month, exp_year: payment_method.year } } - + post[:card][:number] = payment_method.number unless adding_network_token_card_data?(payment_method) post[:card][:cvc] = payment_method.verification_value if payment_method.verification_value if billing = options[:billing_address] || options[:address] post[:billing_details] = add_address(billing, options) end add_name_only(post, payment_method) if post[:billing_details].nil? + add_network_token_data(post, payment_method, options) post end @@ -268,8 +269,22 @@ def setup_purchase(money, options = {}) commit(:post, 'payment_intents', post, options) end + def supports_network_tokenization? + true + end + private + def digital_wallet_payment_method?(payment_method) + payment_method.source == :google_pay || payment_method.source == :apple_pay + end + + def adding_network_token_card_data?(payment_method) + return true if payment_method.is_a?(ActiveMerchant::Billing::NetworkTokenizationCreditCard) && payment_method.source == :network_token + + false + end + def off_session_request?(options = {}) (options[:off_session] || options[:setup_future_usage]) && options[:confirm] == true end @@ -343,10 +358,36 @@ def add_payment_method_token(post, payment_method, options, responses = []) when ActiveMerchant::Billing::CreditCard return create_payment_method_and_extract_token(post, payment_method, options, responses) if options[:verify] + get_payment_method_data_from_card(post, payment_method, options, responses) + when ActiveMerchant::Billing::NetworkTokenizationCreditCard get_payment_method_data_from_card(post, payment_method, options, responses) end end + def add_network_token_data(post_data, payment_method, options) + return unless adding_network_token_card_data?(payment_method) + + post_data[:card] ||= {} + post_data[:card][:last4] = options[:last_4] + post_data[:card][:network_token] = {} + post_data[:card][:network_token][:number] = payment_method.number + post_data[:card][:network_token][:exp_month] = payment_method.month + post_data[:card][:network_token][:exp_year] = payment_method.year + post_data[:card][:network_token][:payment_account_reference] = options[:payment_account_reference] if options[:payment_account_reference] + + post_data + end + + def add_network_token_cryptogram_and_eci(post, payment_method) + return unless adding_network_token_card_data?(payment_method) + + post[:payment_method_options] ||= {} + post[:payment_method_options][:card] ||= {} + post[:payment_method_options][:card][:network_token] ||= {} + post[:payment_method_options][:card][:network_token][:cryptogram] = payment_method.payment_cryptogram if payment_method.payment_cryptogram + post[:payment_method_options][:card][:network_token][:electronic_commerce_indicator] = payment_method.eci if payment_method.eci + end + def extract_token_from_string_and_maybe_add_customer_id(post, payment_method) if payment_method.include?('|') customer_id, payment_method = payment_method.split('|') @@ -385,7 +426,7 @@ def tokenize_apple_google(payment, options = {}) end def get_payment_method_data_from_card(post, payment_method, options, responses) - return create_payment_method_and_extract_token(post, payment_method, options, responses) unless off_session_request?(options) + return create_payment_method_and_extract_token(post, payment_method, options, responses) unless off_session_request?(options) || adding_network_token_card_data?(payment_method) post[:payment_method_data] = add_payment_method_data(payment_method, options) end diff --git a/test/remote/gateways/remote_stripe_payment_intents_test.rb b/test/remote/gateways/remote_stripe_payment_intents_test.rb index e0277f47122..14811db6d95 100644 --- a/test/remote/gateways/remote_stripe_payment_intents_test.rb +++ b/test/remote/gateways/remote_stripe_payment_intents_test.rb @@ -72,6 +72,17 @@ def setup last_name: 'Longsen' ) + @network_token_credit_card = network_tokenization_credit_card( + '4000056655665556', + payment_cryptogram: 'AAEBAwQjSQAAXXXXXXXJYe0BbQA=', + source: :network_token, + brand: 'visa', + month: '09', + year: '2030', + first_name: 'Longbob', + last_name: 'Longsen' + ) + @destination_account = fixtures(:stripe_destination)[:stripe_user_id] end @@ -212,6 +223,18 @@ def test_successful_purchase_with_google_pay assert_match('google_pay', purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['wallet']['type']) end + def test_successful_purchase_with_tokenized_visa + options = { + currency: 'USD', + last_4: '4242' + } + + purchase = @gateway.purchase(@amount, @network_token_credit_card, options) + assert_equal(nil, purchase.responses.first.params.dig('token', 'card', 'tokenization_method')) + assert purchase.success? + assert_not_nil(purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['network_token']) + end + def test_successful_purchase_with_google_pay_when_sending_the_billing_address options = { currency: 'GBP', @@ -1029,6 +1052,24 @@ def test_create_a_payment_intent_and_manually_capture assert_equal 'Payment complete.', capture_response.params.dig('charges', 'data')[0].dig('outcome', 'seller_message') end + def test_create_a_payment_intent_and_manually_capture_with_network_token + options = { + currency: 'GBP', + customer: @customer, + confirmation_method: 'manual', + capture_method: 'manual', + confirm: true, + last_4: '4242' + } + assert create_response = @gateway.create_intent(@amount, @network_token_credit_card, options) + intent_id = create_response.params['id'] + assert_equal 'requires_capture', create_response.params['status'] + + assert capture_response = @gateway.capture(@amount, intent_id, options) + assert_equal 'succeeded', capture_response.params['status'] + assert_equal 'Payment complete.', capture_response.params.dig('charges', 'data')[0].dig('outcome', 'seller_message') + end + def test_failed_create_a_payment_intent_with_set_error_on_requires_action options = { currency: 'GBP', diff --git a/test/unit/gateways/stripe_payment_intents_test.rb b/test/unit/gateways/stripe_payment_intents_test.rb index 320c7d52b4a..175b6d16211 100644 --- a/test/unit/gateways/stripe_payment_intents_test.rb +++ b/test/unit/gateways/stripe_payment_intents_test.rb @@ -47,6 +47,18 @@ def setup first_name: 'Longbob', last_name: 'Longsen' ) + + @network_token_credit_card = network_tokenization_credit_card( + '4000056655665556', + verification_value: '123', + payment_cryptogram: 'dGVzdGNyeXB0b2dyYW1YWFhYWFhYWFhYWFg9PQ==', + source: :network_token, + brand: 'visa', + month: '09', + year: '2030', + first_name: 'Longbob', + last_name: 'Longsen' + ) end def test_successful_create_and_confirm_intent @@ -78,6 +90,19 @@ def test_successful_create_and_capture_intent assert_equal 'Payment complete.', capture.params.dig('charges', 'data')[0].dig('outcome', 'seller_message') end + def test_successful_create_and_capture_intent_with_network_token + options = @options.merge(capture_method: 'manual', confirm: true) + @gateway.expects(:ssl_request).twice.returns(successful_create_intent_manual_capture_response_with_network_token_fields, successful_manual_capture_of_payment_intent_response_with_network_token_fields) + assert create = @gateway.create_intent(@amount, @network_token_credit_card, options) + assert_success create + assert_equal 'requires_capture', create.params['status'] + + assert capture = @gateway.capture(@amount, create.params['id'], options) + assert_success capture + assert_equal 'succeeded', capture.params['status'] + assert_equal 'Payment complete.', capture.params.dig('charges', 'data')[0].dig('outcome', 'seller_message') + end + def test_successful_create_and_update_intent @gateway.expects(:ssl_request).twice.returns(successful_create_intent_response, successful_update_intent_response) assert create = @gateway.create_intent(@amount, @visa_token, @options.merge(capture_method: 'manual')) @@ -502,6 +527,28 @@ def test_purchase_with_google_pay_with_billing_address end.respond_with(successful_create_intent_response_with_google_pay_and_billing_address) end + def test_purchase_with_network_token_card + options = { + currency: 'USD', + last_4: '4242' + } + + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @network_token_credit_card, options) + end.check_request do |_method, endpoint, data, _headers| + assert_match(%r{/payment_intents}, endpoint) + assert_match('confirm=true', data) + assert_match('payment_method_data[type]=card', data) + assert_match('[card][exp_month]=9', data) + assert_match('[card][exp_year]=2030', data) + assert_match('[card][last4]=4242', data) + assert_match('[card][network_token][number]=4000056655665556', data) + assert_match("[card][network_token][cryptogram]=#{URI.encode_www_form_component('dGVzdGNyeXB0b2dyYW1YWFhYWFhYWFhYWFg9PQ==')}", data) + assert_match('[card][network_token][exp_month]=9', data) + assert_match('[card][network_token][exp_year]=2030', data) + end.respond_with(successful_create_intent_response_with_network_token_fields) + end + def test_purchase_with_shipping_options options = { currency: 'GBP', @@ -912,6 +959,534 @@ def successful_create_intent_response RESPONSE end + def successful_create_intent_response_with_network_token_fields + <<~RESPONSE + { + "id": "pi_3NfRruAWOtgoysog1FxgDwtf", + "object": "payment_intent", + "amount": 2000, + "amount_capturable": 0, + "amount_details": { + "tip": { + } + }, + "amount_received": 2000, + "application": null, + "application_fee_amount": null, + "automatic_payment_methods": null, + "canceled_at": null, + "cancellation_reason": null, + "capture_method": "automatic", + "charges": { + "object": "list", + "data": [ + { + "id": "ch_3NfRruAWOtgoysog1ptwVNHx", + "object": "charge", + "amount": 2000, + "amount_captured": 2000, + "amount_refunded": 0, + "application": null, + "application_fee": null, + "application_fee_amount": null, + "balance_transaction": "txn_3NfRruAWOtgoysog1mtFHzZr", + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": null, + "name": "Longbob Longsen", + "phone": null + }, + "calculated_statement_descriptor": "SPREEDLY", + "captured": true, + "created": 1692123686, + "currency": "usd", + "customer": null, + "description": null, + "destination": null, + "dispute": null, + "disputed": false, + "failure_balance_transaction": null, + "failure_code": null, + "failure_message": null, + "fraud_details": { + }, + "invoice": null, + "livemode": false, + "metadata": { + }, + "on_behalf_of": null, + "order": null, + "outcome": { + "network_status": "approved_by_network", + "reason": null, + "risk_level": "normal", + "risk_score": 34, + "seller_message": "Payment complete.", + "type": "authorized" + }, + "paid": true, + "payment_intent": "pi_3NfRruAWOtgoysog1FxgDwtf", + "payment_method": "pm_1NfRruAWOtgoysogjdx336vt", + "payment_method_details": { + "card": { + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": null, + "cvc_check": "pass" + }, + "country": "US", + "ds_transaction_id": null, + "exp_month": 9, + "exp_year": 2030, + "fingerprint": null, + "funding": "debit", + "installments": null, + "last4": "4242", + "mandate": null, + "moto": null, + "network": "visa", + "network_token": { + "exp_month": 9, + "exp_year": 2030, + "fingerprint": "OdTRtGskBulROtqa", + "last4": "5556", + "used": false + }, + "network_transaction_id": "791008482116711", + "three_d_secure": null, + "wallet": null + }, + "type": "card" + }, + "receipt_email": null, + "receipt_number": null, + "receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xNjBEWDZBV090Z295c29nKKeE76YGMgbjse9I0TM6LBZ6z9Y1XXMETb-LDQ5oyLVXQhIMltBU0qwDkNKpNvrIGvXOhYmhorDkkE36", + "refunded": false, + "refunds": { + "object": "list", + "data": [ + ], + "has_more": false, + "total_count": 0, + "url": "/v1/charges/ch_3NfRruAWOtgoysog1ptwVNHx/refunds" + }, + "review": null, + "shipping": null, + "source": null, + "source_transfer": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "succeeded", + "transfer_data": null, + "transfer_group": null + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/charges?payment_intent=pi_3NfRruAWOtgoysog1FxgDwtf" + }, + "client_secret": "pi_3NfRruAWOtgoysog1FxgDwtf_secret_f4ke", + "confirmation_method": "automatic", + "created": 1692123686, + "currency": "usd", + "customer": null, + "description": null, + "invoice": null, + "last_payment_error": null, + "latest_charge": "ch_3NfRruAWOtgoysog1ptwVNHx", + "level3": null, + "livemode": false, + "metadata": { + }, + "next_action": null, + "on_behalf_of": null, + "payment_method": "pm_1NfRruAWOtgoysogjdx336vt", + "payment_method_options": { + "card": { + "installments": null, + "mandate_options": null, + "network": null, + "request_three_d_secure": "automatic" + } + }, + "payment_method_types": [ + "card" + ], + "processing": null, + "receipt_email": null, + "review": null, + "setup_future_usage": null, + "shipping": null, + "source": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "succeeded", + "transfer_data": null, + "transfer_group": null + } + RESPONSE + end + + def successful_create_intent_manual_capture_response_with_network_token_fields + <<~RESPONSE + { + "id": "pi_3NfTpgAWOtgoysog1SqST5dL", + "object": "payment_intent", + "amount": 2000, + "amount_capturable": 2000, + "amount_details": { + "tip": { + } + }, + "amount_received": 0, + "application": null, + "application_fee_amount": null, + "automatic_payment_methods": null, + "canceled_at": null, + "cancellation_reason": null, + "capture_method": "manual", + "charges": { + "object": "list", + "data": [ + { + "id": "ch_3NfTpgAWOtgoysog1ZcuSdwZ", + "object": "charge", + "amount": 2000, + "amount_captured": 0, + "amount_refunded": 0, + "application": null, + "application_fee": null, + "application_fee_amount": null, + "balance_transaction": null, + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": null, + "name": "Longbob Longsen", + "phone": null + }, + "calculated_statement_descriptor": "SPREEDLY", + "captured": false, + "created": 1692131237, + "currency": "gbp", + "customer": "cus_OSOcijtQkDdBbF", + "description": null, + "destination": null, + "dispute": null, + "disputed": false, + "failure_balance_transaction": null, + "failure_code": null, + "failure_message": null, + "fraud_details": { + }, + "invoice": null, + "livemode": false, + "metadata": { + }, + "on_behalf_of": null, + "order": null, + "outcome": { + "network_status": "approved_by_network", + "reason": null, + "risk_level": "normal", + "risk_score": 24, + "seller_message": "Payment complete.", + "type": "authorized" + }, + "paid": true, + "payment_intent": "pi_3NfTpgAWOtgoysog1SqST5dL", + "payment_method": "pm_1NfTpgAWOtgoysogHnl1rNCf", + "payment_method_details": { + "card": { + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": null, + "cvc_check": "pass" + }, + "country": "US", + "ds_transaction_id": null, + "exp_month": 9, + "exp_year": 2030, + "fingerprint": null, + "funding": "debit", + "installments": null, + "last4": "4242", + "mandate": null, + "moto": null, + "network": "visa", + "network_token": { + "exp_month": 9, + "exp_year": 2030, + "fingerprint": "OdTRtGskBulROtqa", + "last4": "5556", + "used": false + }, + "network_transaction_id": "791008482116711", + "three_d_secure": null, + "wallet": null + }, + "type": "card" + }, + "receipt_email": null, + "receipt_number": null, + "receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xNjBEWDZBV090Z295c29nKKW_76YGMgZFk46uT_Y6LBZ51LZOrwdCQ0w176ShWIhNs2CXEh-L6A9pDYW33I_z6C6SenKNrWasw9Ie", + "refunded": false, + "refunds": { + "object": "list", + "data": [ + ], + "has_more": false, + "total_count": 0, + "url": "/v1/charges/ch_3NfTpgAWOtgoysog1ZcuSdwZ/refunds" + }, + "review": null, + "shipping": null, + "source": null, + "source_transfer": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "succeeded", + "transfer_data": null, + "transfer_group": null + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/charges?payment_intent=pi_3NfTpgAWOtgoysog1SqST5dL" + }, + "client_secret": "pi_3NfRruAWOtgoysog1FxgDwtf_secret_f4ke", + "confirmation_method": "manual", + "created": 1692131236, + "currency": "gbp", + "customer": "cus_OSOcijtQkDdBbF", + "description": null, + "invoice": null, + "last_payment_error": null, + "latest_charge": "ch_3NfTpgAWOtgoysog1ZcuSdwZ", + "level3": null, + "livemode": false, + "metadata": { + }, + "next_action": null, + "on_behalf_of": null, + "payment_method": "pm_1NfTpgAWOtgoysogHnl1rNCf", + "payment_method_options": { + "card": { + "installments": null, + "mandate_options": null, + "network": null, + "request_three_d_secure": "automatic" + } + }, + "payment_method_types": [ + "card" + ], + "processing": null, + "receipt_email": null, + "review": null, + "setup_future_usage": null, + "shipping": null, + "source": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "requires_capture", + "transfer_data": null, + "transfer_group": null + } + RESPONSE + end + + def successful_manual_capture_of_payment_intent_response_with_network_token_fields + <<-RESPONSE + { + "id": "pi_3NfTpgAWOtgoysog1SqST5dL", + "object": "payment_intent", + "amount": 2000, + "amount_capturable": 0, + "amount_details": { + "tip": { + } + }, + "amount_received": 2000, + "application": null, + "application_fee_amount": null, + "automatic_payment_methods": null, + "canceled_at": null, + "cancellation_reason": null, + "capture_method": "manual", + "charges": { + "object": "list", + "data": [ + { + "id": "ch_3NfTpgAWOtgoysog1ZcuSdwZ", + "object": "charge", + "amount": 2000, + "amount_captured": 2000, + "amount_refunded": 0, + "application": null, + "application_fee": null, + "application_fee_amount": null, + "balance_transaction": "txn_3NfTpgAWOtgoysog1ZTZXCvO", + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": null, + "name": "Longbob Longsen", + "phone": null + }, + "calculated_statement_descriptor": "SPREEDLY", + "captured": true, + "created": 1692131237, + "currency": "gbp", + "customer": "cus_OSOcijtQkDdBbF", + "description": null, + "destination": null, + "dispute": null, + "disputed": false, + "failure_balance_transaction": null, + "failure_code": null, + "failure_message": null, + "fraud_details": { + }, + "invoice": null, + "livemode": false, + "metadata": { + }, + "on_behalf_of": null, + "order": null, + "outcome": { + "network_status": "approved_by_network", + "reason": null, + "risk_level": "normal", + "risk_score": 24, + "seller_message": "Payment complete.", + "type": "authorized" + }, + "paid": true, + "payment_intent": "pi_3NfTpgAWOtgoysog1SqST5dL", + "payment_method": "pm_1NfTpgAWOtgoysogHnl1rNCf", + "payment_method_details": { + "card": { + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": null, + "cvc_check": "pass" + }, + "country": "US", + "ds_transaction_id": null, + "exp_month": 9, + "exp_year": 2030, + "fingerprint": null, + "funding": "debit", + "installments": null, + "last4": "4242", + "mandate": null, + "moto": null, + "network": "visa", + "network_token": { + "exp_month": 9, + "exp_year": 2030, + "fingerprint": "OdTRtGskBulROtqa", + "last4": "5556", + "used": false + }, + "network_transaction_id": "791008482116711", + "three_d_secure": null, + "wallet": null + }, + "type": "card" + }, + "receipt_email": null, + "receipt_number": null, + "receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xNjBEWDZBV090Z295c29nKKa_76YGMgZZ4Fl_Etg6LBYGcD6D2xFTlgp69zLDZz1ZToBrKKjxhRCpYcnLWInSmJZHcjcBdrhyAKGv", + "refunded": false, + "refunds": { + "object": "list", + "data": [ + ], + "has_more": false, + "total_count": 0, + "url": "/v1/charges/ch_3NfTpgAWOtgoysog1ZcuSdwZ/refunds" + }, + "review": null, + "shipping": null, + "source": null, + "source_transfer": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "succeeded", + "transfer_data": null, + "transfer_group": null + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/charges?payment_intent=pi_3NfTpgAWOtgoysog1SqST5dL" + }, + "client_secret": "pi_3NfRruAWOtgoysog1FxgDwtf_secret_f4ke", + "confirmation_method": "manual", + "created": 1692131236, + "currency": "gbp", + "customer": "cus_OSOcijtQkDdBbF", + "description": null, + "invoice": null, + "last_payment_error": null, + "latest_charge": "ch_3NfTpgAWOtgoysog1ZcuSdwZ", + "level3": null, + "livemode": false, + "metadata": { + }, + "next_action": null, + "on_behalf_of": null, + "payment_method": "pm_1NfTpgAWOtgoysogHnl1rNCf", + "payment_method_options": { + "card": { + "installments": null, + "mandate_options": null, + "network": null, + "request_three_d_secure": "automatic" + } + }, + "payment_method_types": [ + "card" + ], + "processing": null, + "receipt_email": null, + "review": null, + "setup_future_usage": null, + "shipping": null, + "source": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "succeeded", + "transfer_data": null, + "transfer_group": null + } + RESPONSE + end + def successful_create_intent_response_with_apple_pay_and_billing_address <<-RESPONSE {"id"=>"pi_3N0mqdAWOtgoysog1IQeiLiz", "object"=>"payment_intent", "amount"=>2000, "amount_capturable"=>0, "amount_details"=>{"tip"=>{}}, "amount_received"=>2000, "application"=>nil, "application_fee_amount"=>nil, "automatic_payment_methods"=>nil, "canceled_at"=>nil, "cancellation_reason"=>nil, "capture_method"=>"automatic", "charges"=>{"object"=>"list", "data"=>[{"id"=>"ch_3N0mqdAWOtgoysog1HddFSKg", "object"=>"charge", "amount"=>2000, "amount_captured"=>2000, "amount_refunded"=>0, "application"=>nil, "application_fee"=>nil, "application_fee_amount"=>nil, "balance_transaction"=>"txn_3N0mqdAWOtgoysog1EpiFDCD", "billing_details"=>{"address"=>{"city"=>"Ottawa", "country"=>"CA", "line1"=>"456 My Street", "line2"=>"Apt 1", "postal_code"=>"K1C2N6", "state"=>"ON"}, "email"=>nil, "name"=>nil, "phone"=>nil}, "calculated_statement_descriptor"=>"SPREEDLY", "captured"=>true, "created"=>1682432883, "currency"=>"gbp", "customer"=>nil, "description"=>nil, "destination"=>nil, "dispute"=>nil, "disputed"=>false, "failure_balance_transaction"=>nil, "failure_code"=>nil, "failure_message"=>nil, "fraud_details"=>{}, "invoice"=>nil, "livemode"=>false, "metadata"=>{}, "on_behalf_of"=>nil, "order"=>nil, "outcome"=>{"network_status"=>"approved_by_network", "reason"=>nil, "risk_level"=>"normal", "risk_score"=>15, "seller_message"=>"Payment complete.", "type"=>"authorized"}, "paid"=>true, "payment_intent"=>"pi_3N0mqdAWOtgoysog1IQeiLiz", "payment_method"=>"pm_1N0mqdAWOtgoysogloANIhUF", "payment_method_details"=>{"card"=>{"brand"=>"visa", "checks"=>{"address_line1_check"=>"pass", "address_postal_code_check"=>"pass", "cvc_check"=>nil}, "country"=>"US", "ds_transaction_id"=>nil, "exp_month"=>9, "exp_year"=>2030, "fingerprint"=>"hfaVNMiXc0dYSiC5", "funding"=>"credit", "installments"=>nil, "last4"=>"0000", "mandate"=>nil, "moto"=>nil, "network"=>"visa", "network_token"=>{"used"=>false}, "network_transaction_id"=>"104102978678771", "three_d_secure"=>nil, "wallet"=>{"apple_pay"=>{"type"=>"apple_pay"}, "dynamic_last4"=>"4242", "type"=>"apple_pay"}}, "type"=>"card"}, "receipt_email"=>nil, "receipt_number"=>nil, "receipt_url"=>"https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xNjBEWDZBV090Z295c29nKPTGn6IGMgZMGrHHLa46LBY0n2_9_Yar0wPTNukle4t28eKG0ZDZnxGYr6GyKn8VsKIEVjU4NkW8NHTL", "refunded"=>false, "refunds"=>{"object"=>"list", "data"=>[], "has_more"=>false, "total_count"=>0, "url"=>"/v1/charges/ch_3N0mqdAWOtgoysog1HddFSKg/refunds"}, "review"=>nil, "shipping"=>nil, "source"=>nil, "source_transfer"=>nil, "statement_descriptor"=>nil, "statement_descriptor_suffix"=>nil, "status"=>"succeeded", "transfer_data"=>nil, "transfer_group"=>nil}], "has_more"=>false, "total_count"=>1, "url"=>"/v1/charges?payment_intent=pi_3N0mqdAWOtgoysog1IQeiLiz"}, "client_secret"=>"pi_3N0mqdAWOtgoysog1IQeiLiz_secret_laDLUM6rVleLRqz0nMus9HktB", "confirmation_method"=>"automatic", "created"=>1682432883, "currency"=>"gbp", "customer"=>nil, "description"=>nil, "invoice"=>nil, "last_payment_error"=>nil, "latest_charge"=>"ch_3N0mqdAWOtgoysog1HddFSKg", "level3"=>nil, "livemode"=>false, "metadata"=>{}, "next_action"=>nil, "on_behalf_of"=>nil, "payment_method"=>"pm_1N0mqdAWOtgoysogloANIhUF", "payment_method_options"=>{"card"=>{"installments"=>nil, "mandate_options"=>nil, "network"=>nil, "request_three_d_secure"=>"automatic"}}, "payment_method_types"=>["card"], "processing"=>nil, "receipt_email"=>nil, "review"=>nil, "setup_future_usage"=>nil, "shipping"=>nil, "source"=>nil, "statement_descriptor"=>nil, "statement_descriptor_suffix"=>nil, "status"=>"succeeded", "transfer_data"=>nil, "transfer_group"=>nil}