diff --git a/CHANGELOG.md b/CHANGELOG.md index bcaa031eb..edb8d9556 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## next release +- Added a test for multiple reservations on the same space slot - Fix a security issue: updated moment to 2.29.4 to fix [CVE-2022-31129](https://cve.mitre.org/cgi-bin/cvename.cgi?CVE-2022-31129) ## v5.4.13 2022 July 27 diff --git a/test/integration/reservations/space_seats_test.rb b/test/integration/reservations/space_seats_test.rb new file mode 100644 index 000000000..6afeff6b1 --- /dev/null +++ b/test/integration/reservations/space_seats_test.rb @@ -0,0 +1,218 @@ +# frozen_string_literal: true + +require 'test_helper' + +module Reservations; end + +class Reservations::SpaceSeatsTest < ActionDispatch::IntegrationTest + setup do + @admin = User.admins.first + @user1 = User.find(5) + @user2 = User.find(2) + @user3 = User.find(4) + end + + test 'create a space availability and reserve it multiple times' do + login_as(@admin, scope: :user) + + space = Space.first + + date = (DateTime.current + 1.day).change(hour: 8, min: 0, sec: 0) + + post '/api/availabilities', + params: { + availability: { + start_at: date.iso8601, + end_at: (date + 1.hour).iso8601, + available_type: 'space', + tag_ids: [], + is_recurrent: false, + slot_duration: 60, + space_ids: [space.id], + nb_total_places: 2, + occurrences: [ + { start_at: date.iso8601, end_at: (date + 1.hour).iso8601 }] + } + } + + # Check response format & status + assert_equal 201, response.status + assert_equal Mime[:json], response.content_type + + # Check the availability + res = json_response(response.body) + availability = Availability.find(res[:id]) + + ### FIRST RESERVATION + + reservations_count = Reservation.count + invoice_count = Invoice.count + invoice_items_count = InvoiceItem.count + users_credit_count = UsersCredit.count + subscriptions_count = Subscription.count + + post '/api/local_payment/confirm_payment', + params: { + customer_id: @user1.id, + items: [ + { + reservation: { + reservable_id: space.id, + reservable_type: space.class.name, + slots_reservations_attributes: [ + { + slot_id: availability.slots.first.id + } + ] + } + } + ] + }.to_json, headers: default_headers + + # general assertions + assert_equal 201, response.status + assert_equal reservations_count + 1, Reservation.count + assert_equal invoice_count + 1, Invoice.count + assert_equal invoice_items_count + 1, InvoiceItem.count + assert_equal users_credit_count, UsersCredit.count + assert_equal subscriptions_count, Subscription.count + + # subscription assertions + assert_equal 0, @user1.subscriptions.count + assert_nil @user1.subscribed_plan + + # reservation assertions + reservation = Reservation.last + + assert reservation.original_invoice + assert_equal 1, reservation.original_invoice.invoice_items.count + + # invoice_items assertions + invoice_item = InvoiceItem.last + + assert_equal space.prices.find_by(group_id: @user1.group_id, plan_id: nil).amount, invoice_item.amount + assert invoice_item.check_footprint + + # invoice assertions + item = InvoiceItem.find_by(object: reservation) + invoice = item.invoice + assert_invoice_pdf invoice + assert_not_nil invoice.debug_footprint + + assert invoice.payment_gateway_object.blank? + assert_not invoice.total.blank? + assert invoice.check_footprint + + # notification + assert_not_empty Notification.where(attached_object: reservation) + + + ### SECOND RESERVATION + login_as(@user2, scope: :user) + + VCR.use_cassette('reservations_space_seats_user2') do + post '/api/stripe/confirm_payment', + params: { + payment_method_id: stripe_payment_method, + cart_items: { + items: [ + { + reservation: { + reservable_id: space.id, + reservable_type: space.class.name, + slots_reservations_attributes: [ + { + slot_id: availability.slots.first.id + } + ] + } + } + ] + } + }.to_json, headers: default_headers + end + + # general assertions + assert_equal 201, response.status + assert_equal reservations_count + 2, Reservation.count + assert_equal invoice_count + 2, Invoice.count + assert_equal invoice_items_count + 2, InvoiceItem.count + assert_equal users_credit_count, UsersCredit.count + assert_equal subscriptions_count, Subscription.count + + # subscription assertions + assert_equal 0, @user2.subscriptions.count + assert_nil @user2.subscribed_plan + + # reservation assertions + reservation = Reservation.last + + assert reservation.original_invoice + assert_equal 1, reservation.original_invoice.invoice_items.count + + # invoice_items assertions + invoice_item = InvoiceItem.last + + assert_equal space.prices.find_by(group_id: @user2.group_id, plan_id: nil).amount, invoice_item.amount + assert invoice_item.check_footprint + + # invoice assertions + item = InvoiceItem.find_by(object: reservation) + invoice = item.invoice + assert_invoice_pdf invoice + assert_not_nil invoice.debug_footprint + + assert_not invoice.payment_gateway_object.blank? + assert_not invoice.total.blank? + assert invoice.check_footprint + + # notification + assert_not_empty Notification.where(attached_object: reservation) + + ### THIRD RESERVATION + login_as(@user3, scope: :user) + + VCR.use_cassette('reservations_space_seats_user3') do + post '/api/stripe/confirm_payment', + params: { + payment_method_id: stripe_payment_method, + cart_items: { + items: [ + { + reservation: { + reservable_id: space.id, + reservable_type: space.class.name, + slots_reservations_attributes: [ + { + slot_id: availability.slots.first.id + } + ] + } + } + ] + } + }.to_json, headers: default_headers + end + + # general assertions + assert_equal 422, response.status + assert_equal reservations_count + 2, Reservation.count + assert_equal invoice_count + 2, Invoice.count + assert_equal invoice_items_count + 2, InvoiceItem.count + assert_equal users_credit_count, UsersCredit.count + assert_equal subscriptions_count, Subscription.count + + # subscription assertions + assert_equal 1, @user3.subscriptions.count + assert_not_nil @user3.subscribed_plan + + # assert nothing was created + reservation = Reservation.last + invoice = Invoice.last + invoice_item = InvoiceItem.last + + assert_not_equal reservation.user.id, @user3.id + assert_not_equal invoice.user.id, @user3.id + assert_not_equal space.prices.find_by(group_id: @user3.group_id, plan_id: nil).amount, invoice_item.amount + end +end diff --git a/test/vcr_cassettes/reservations_space_seats_user2.yml b/test/vcr_cassettes/reservations_space_seats_user2.yml new file mode 100644 index 000000000..9440dcd66 --- /dev/null +++ b/test/vcr_cassettes/reservations_space_seats_user2.yml @@ -0,0 +1,560 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.stripe.com/v1/payment_methods + body: + encoding: UTF-8 + string: type=card&card[number]=4242424242424242&card[exp_month]=4&card[exp_year]=2023&card[cvc]=314 + headers: + User-Agent: + - Stripe/v1 RubyBindings/5.29.0 + Authorization: + - Bearer sk_test_testfaketestfaketestfake + Content-Type: + - application/x-www-form-urlencoded + Stripe-Version: + - '2019-08-14' + X-Stripe-Client-User-Agent: + - '{"bindings_version":"5.29.0","lang":"ruby","lang_version":"2.6.10 p210 (2022-04-12)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 5.18.10-arch1-1 (linux@archlinux) (gcc (GCC) 12.1.0, GNU ld (GNU Binutils) + 2.38) #1 SMP PREEMPT_DYNAMIC Thu, 07 Jul 2022 17:18:13 +0000","hostname":"Sylvain-laptop"}' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Wed, 27 Jul 2022 15:17:01 GMT + Content-Type: + - application/json + Content-Length: + - '930' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Expose-Headers: + - Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Idempotency-Key: + - b2e1a527-8f47-4a44-9e30-ae875a15dbf2 + Original-Request: + - req_D8BOhF6zV284Qf + Request-Id: + - req_D8BOhF6zV284Qf + Stripe-Should-Retry: + - 'false' + Stripe-Version: + - '2019-08-14' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains; preload + body: + encoding: UTF-8 + string: |- + { + "id": "pm_1LQByr2sOmf47Nz9Hl3qCzNX", + "object": "payment_method", + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": null, + "name": null, + "phone": null + }, + "card": { + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": null, + "cvc_check": "unchecked" + }, + "country": "US", + "exp_month": 4, + "exp_year": 2023, + "fingerprint": "o52jybR7bnmNn6AT", + "funding": "credit", + "generated_from": null, + "last4": "4242", + "networks": { + "available": [ + "visa" + ], + "preferred": null + }, + "three_d_secure_usage": { + "supported": true + }, + "wallet": null + }, + "created": 1658935021, + "customer": null, + "livemode": false, + "metadata": {}, + "type": "card" + } + recorded_at: Wed, 27 Jul 2022 15:17:01 GMT +- request: + method: post + uri: https://api.stripe.com/v1/payment_intents + body: + encoding: UTF-8 + string: payment_method=pm_1LQByr2sOmf47Nz9Hl3qCzNX&amount=2000¤cy=usd&confirmation_method=manual&confirm=true&customer=cus_8Di1wjdVktv5kt + headers: + User-Agent: + - Stripe/v1 RubyBindings/5.29.0 + Authorization: + - Bearer sk_test_testfaketestfaketestfake + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-Telemetry: + - '{"last_request_metrics":{"request_id":"req_D8BOhF6zV284Qf","request_duration_ms":1261}}' + Stripe-Version: + - '2019-08-14' + X-Stripe-Client-User-Agent: + - '{"bindings_version":"5.29.0","lang":"ruby","lang_version":"2.6.10 p210 (2022-04-12)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 5.18.10-arch1-1 (linux@archlinux) (gcc (GCC) 12.1.0, GNU ld (GNU Binutils) + 2.38) #1 SMP PREEMPT_DYNAMIC Thu, 07 Jul 2022 17:18:13 +0000","hostname":"Sylvain-laptop"}' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Wed, 27 Jul 2022 15:17:04 GMT + Content-Type: + - application/json + Content-Length: + - '4430' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Expose-Headers: + - Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Idempotency-Key: + - 74b3cf6c-69a2-4cec-abe4-69d6b170d7c3 + Original-Request: + - req_6Mz1jjCYQWTLMO + Request-Id: + - req_6Mz1jjCYQWTLMO + Stripe-Should-Retry: + - 'false' + Stripe-Version: + - '2019-08-14' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains; preload + body: + encoding: UTF-8 + string: |- + { + "id": "pi_3LQBys2sOmf47Nz90Bu55CC1", + "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_3LQBys2sOmf47Nz901xP6fgK", + "object": "charge", + "amount": 2000, + "amount_captured": 2000, + "amount_refunded": 0, + "application": null, + "application_fee": null, + "application_fee_amount": null, + "balance_transaction": "txn_3LQBys2sOmf47Nz90248q1tS", + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": null, + "name": null, + "phone": null + }, + "calculated_statement_descriptor": "Stripe", + "captured": true, + "created": 1658935022, + "currency": "usd", + "customer": "cus_8Di1wjdVktv5kt", + "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": 58, + "seller_message": "Payment complete.", + "type": "authorized" + }, + "paid": true, + "payment_intent": "pi_3LQBys2sOmf47Nz90Bu55CC1", + "payment_method": "pm_1LQByr2sOmf47Nz9Hl3qCzNX", + "payment_method_details": { + "card": { + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": null, + "cvc_check": "pass" + }, + "country": "US", + "exp_month": 4, + "exp_year": 2023, + "fingerprint": "o52jybR7bnmNn6AT", + "funding": "credit", + "installments": null, + "last4": "4242", + "mandate": null, + "network": "visa", + "three_d_secure": null, + "wallet": null + }, + "type": "card" + }, + "receipt_email": null, + "receipt_number": null, + "receipt_url": "https://pay.stripe.com/receipts/acct_103rE62sOmf47Nz9/ch_3LQBys2sOmf47Nz901xP6fgK/rcpt_M8SuF32X4OCCbk1wVP4b75lFFHrA5za", + "refunded": false, + "refunds": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/charges/ch_3LQBys2sOmf47Nz901xP6fgK/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_3LQBys2sOmf47Nz90Bu55CC1" + }, + "client_secret": "pi_3LQBys2sOmf47Nz90Bu55CC1_secret_RYLYFK1TOMVC69SeOlyczqhd1", + "confirmation_method": "manual", + "created": 1658935022, + "currency": "usd", + "customer": "cus_8Di1wjdVktv5kt", + "description": null, + "invoice": null, + "last_payment_error": null, + "livemode": false, + "metadata": {}, + "next_action": null, + "on_behalf_of": null, + "payment_method": "pm_1LQByr2sOmf47Nz9Hl3qCzNX", + "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 + } + recorded_at: Wed, 27 Jul 2022 15:17:04 GMT +- request: + method: post + uri: https://api.stripe.com/v1/payment_intents/pi_3LQBys2sOmf47Nz90Bu55CC1 + body: + encoding: UTF-8 + string: description=Invoice+reference%3A+2207002%2FVL + headers: + User-Agent: + - Stripe/v1 RubyBindings/5.29.0 + Authorization: + - Bearer sk_test_testfaketestfaketestfake + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-Telemetry: + - '{"last_request_metrics":{"request_id":"req_6Mz1jjCYQWTLMO","request_duration_ms":2526}}' + Stripe-Version: + - '2019-08-14' + X-Stripe-Client-User-Agent: + - '{"bindings_version":"5.29.0","lang":"ruby","lang_version":"2.6.10 p210 (2022-04-12)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 5.18.10-arch1-1 (linux@archlinux) (gcc (GCC) 12.1.0, GNU ld (GNU Binutils) + 2.38) #1 SMP PREEMPT_DYNAMIC Thu, 07 Jul 2022 17:18:13 +0000","hostname":"Sylvain-laptop"}' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Wed, 27 Jul 2022 15:17:05 GMT + Content-Type: + - application/json + Content-Length: + - '4457' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Expose-Headers: + - Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Idempotency-Key: + - 6d5f0d63-ed5b-44f5-bf02-c62b655072bf + Original-Request: + - req_TLScQWYYL5dzB0 + Request-Id: + - req_TLScQWYYL5dzB0 + Stripe-Should-Retry: + - 'false' + Stripe-Version: + - '2019-08-14' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains; preload + body: + encoding: UTF-8 + string: |- + { + "id": "pi_3LQBys2sOmf47Nz90Bu55CC1", + "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_3LQBys2sOmf47Nz901xP6fgK", + "object": "charge", + "amount": 2000, + "amount_captured": 2000, + "amount_refunded": 0, + "application": null, + "application_fee": null, + "application_fee_amount": null, + "balance_transaction": "txn_3LQBys2sOmf47Nz90248q1tS", + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": null, + "name": null, + "phone": null + }, + "calculated_statement_descriptor": "Stripe", + "captured": true, + "created": 1658935022, + "currency": "usd", + "customer": "cus_8Di1wjdVktv5kt", + "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": 58, + "seller_message": "Payment complete.", + "type": "authorized" + }, + "paid": true, + "payment_intent": "pi_3LQBys2sOmf47Nz90Bu55CC1", + "payment_method": "pm_1LQByr2sOmf47Nz9Hl3qCzNX", + "payment_method_details": { + "card": { + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": null, + "cvc_check": "pass" + }, + "country": "US", + "exp_month": 4, + "exp_year": 2023, + "fingerprint": "o52jybR7bnmNn6AT", + "funding": "credit", + "installments": null, + "last4": "4242", + "mandate": null, + "network": "visa", + "three_d_secure": null, + "wallet": null + }, + "type": "card" + }, + "receipt_email": null, + "receipt_number": null, + "receipt_url": "https://pay.stripe.com/receipts/acct_103rE62sOmf47Nz9/ch_3LQBys2sOmf47Nz901xP6fgK/rcpt_M8SuF32X4OCCbk1wVP4b75lFFHrA5za", + "refunded": false, + "refunds": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/charges/ch_3LQBys2sOmf47Nz901xP6fgK/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_3LQBys2sOmf47Nz90Bu55CC1" + }, + "client_secret": "pi_3LQBys2sOmf47Nz90Bu55CC1_secret_RYLYFK1TOMVC69SeOlyczqhd1", + "confirmation_method": "manual", + "created": 1658935022, + "currency": "usd", + "customer": "cus_8Di1wjdVktv5kt", + "description": "Invoice reference: 2207002/VL", + "invoice": null, + "last_payment_error": null, + "livemode": false, + "metadata": {}, + "next_action": null, + "on_behalf_of": null, + "payment_method": "pm_1LQByr2sOmf47Nz9Hl3qCzNX", + "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 + } + recorded_at: Wed, 27 Jul 2022 15:17:05 GMT +recorded_with: VCR 6.0.0 diff --git a/test/vcr_cassettes/reservations_space_seats_user3.yml b/test/vcr_cassettes/reservations_space_seats_user3.yml new file mode 100644 index 000000000..152d5bbac --- /dev/null +++ b/test/vcr_cassettes/reservations_space_seats_user3.yml @@ -0,0 +1,118 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.stripe.com/v1/payment_methods + body: + encoding: UTF-8 + string: type=card&card[number]=4242424242424242&card[exp_month]=4&card[exp_year]=2023&card[cvc]=314 + headers: + User-Agent: + - Stripe/v1 RubyBindings/5.29.0 + Authorization: + - Bearer sk_test_testfaketestfaketestfake + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-Telemetry: + - '{"last_request_metrics":{"request_id":"req_TLScQWYYL5dzB0","request_duration_ms":2}}' + Stripe-Version: + - '2019-08-14' + X-Stripe-Client-User-Agent: + - '{"bindings_version":"5.29.0","lang":"ruby","lang_version":"2.6.10 p210 (2022-04-12)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 5.18.10-arch1-1 (linux@archlinux) (gcc (GCC) 12.1.0, GNU ld (GNU Binutils) + 2.38) #1 SMP PREEMPT_DYNAMIC Thu, 07 Jul 2022 17:18:13 +0000","hostname":"Sylvain-laptop"}' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Wed, 27 Jul 2022 15:21:15 GMT + Content-Type: + - application/json + Content-Length: + - '930' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Expose-Headers: + - Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Idempotency-Key: + - fb9a01ef-f2e0-4c17-aa2c-77565e5bef21 + Original-Request: + - req_ivPywFk7vszgb8 + Request-Id: + - req_ivPywFk7vszgb8 + Stripe-Should-Retry: + - 'false' + Stripe-Version: + - '2019-08-14' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains; preload + body: + encoding: UTF-8 + string: |- + { + "id": "pm_1LQC2x2sOmf47Nz9bvxgcgS3", + "object": "payment_method", + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": null, + "name": null, + "phone": null + }, + "card": { + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": null, + "cvc_check": "unchecked" + }, + "country": "US", + "exp_month": 4, + "exp_year": 2023, + "fingerprint": "o52jybR7bnmNn6AT", + "funding": "credit", + "generated_from": null, + "last4": "4242", + "networks": { + "available": [ + "visa" + ], + "preferred": null + }, + "three_d_secure_usage": { + "supported": true + }, + "wallet": null + }, + "created": 1658935275, + "customer": null, + "livemode": false, + "metadata": {}, + "type": "card" + } + recorded_at: Wed, 27 Jul 2022 15:21:15 GMT +recorded_with: VCR 6.0.0