diff --git a/app/models/payment.rb b/app/models/payment.rb index 757d3ca9219..388345345f8 100644 --- a/app/models/payment.rb +++ b/app/models/payment.rb @@ -91,6 +91,7 @@ def payment_request_succeeded # payable_type :string default("Invoice"), not null # payment_type :enum default("provider"), not null # provider_payment_data :jsonb +# provider_payment_method_data :jsonb not null # reference :string # status :string not null # created_at :datetime not null diff --git a/app/services/payment_providers/stripe/handle_event_service.rb b/app/services/payment_providers/stripe/handle_event_service.rb index 970ab8dbec1..4e926d8bfc8 100644 --- a/app/services/payment_providers/stripe/handle_event_service.rb +++ b/app/services/payment_providers/stripe/handle_event_service.rb @@ -5,15 +5,12 @@ module Stripe class HandleEventService < BaseService EVENT_MAPPING = { "setup_intent.succeeded" => PaymentProviders::Stripe::Webhooks::SetupIntentSucceededService, + "payment_intent.succeeded" => PaymentProviders::Stripe::Webhooks::PaymentIntentSucceededService, + "payment_intent.payment_failed" => PaymentProviders::Stripe::Webhooks::PaymentIntentPaymentFailedService, "customer.updated" => PaymentProviders::Stripe::Webhooks::CustomerUpdatedService, "charge.dispute.closed" => PaymentProviders::Stripe::Webhooks::ChargeDisputeClosedService }.freeze - PAYMENT_SERVICE_CLASS_MAP = { - "Invoice" => Invoices::Payments::StripeService, - "PaymentRequest" => PaymentRequests::Payments::StripeService - }.freeze - def initialize(organization:, event_json:) @organization = organization @event_json = event_json @@ -37,18 +34,6 @@ def call end case event.type - when "payment_intent.payment_failed", "payment_intent.succeeded" - status = (event.type == "payment_intent.succeeded") ? "succeeded" : "failed" - payment_service_klass(event) - .new.update_payment_status( - organization_id: organization.id, - status:, - stripe_payment: PaymentProviders::StripeProvider::StripePayment.new( - id: event.data.object.id, - status: event.data.object.status, - metadata: event.data.object.metadata.to_h.symbolize_keys - ) - ).raise_if_error! when "payment_method.detached" PaymentProviderCustomers::StripeService .new @@ -81,14 +66,6 @@ def call def event @event ||= ::Stripe::Event.construct_from(JSON.parse(event_json)) end - - def payment_service_klass(event) - payable_type = event.data.object.metadata.to_h[:lago_payable_type] || "Invoice" - - PAYMENT_SERVICE_CLASS_MAP.fetch(payable_type) do - raise NameError, "Invalid lago_payable_type: #{payable_type}" - end - end end end end diff --git a/app/services/payment_providers/stripe/webhooks/base_service.rb b/app/services/payment_providers/stripe/webhooks/base_service.rb index 75f84a246d9..55d61979b2c 100644 --- a/app/services/payment_providers/stripe/webhooks/base_service.rb +++ b/app/services/payment_providers/stripe/webhooks/base_service.rb @@ -15,6 +15,11 @@ def initialize(organization_id:, event:) attr_reader :organization, :event + PAYMENT_SERVICE_CLASS_MAP = { + "Invoice" => Invoices::Payments::StripeService, + "PaymentRequest" => PaymentRequests::Payments::StripeService + }.freeze + def metadata @metadata ||= event.data.object.metadata.to_h.symbolize_keys end @@ -30,6 +35,27 @@ def handle_missing_customer result.not_found_failure!(resource: "stripe_customer") end + + # TODO: Move this to a proper factory + def payment_service_klass + payable_type = metadata[:lago_payable_type] || "Invoice" + + PAYMENT_SERVICE_CLASS_MAP.fetch(payable_type) do + raise NameError, "Invalid lago_payable_type: #{payable_type}" + end + end + + def update_payment_status!(status) + payment_service_klass.new.update_payment_status( + organization_id: organization.id, + status:, + stripe_payment: PaymentProviders::StripeProvider::StripePayment.new( + id: event.data.object.id, + status: event.data.object.status, + metadata: + ) + ).raise_if_error! + end end end end diff --git a/app/services/payment_providers/stripe/webhooks/payment_intent_payment_failed_service.rb b/app/services/payment_providers/stripe/webhooks/payment_intent_payment_failed_service.rb new file mode 100644 index 00000000000..6b02da48f4e --- /dev/null +++ b/app/services/payment_providers/stripe/webhooks/payment_intent_payment_failed_service.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module PaymentProviders + module Stripe + module Webhooks + class PaymentIntentPaymentFailedService < BaseService + def call + update_payment_status! "failed" + end + end + end + end +end diff --git a/app/services/payment_providers/stripe/webhooks/payment_intent_succeeded_service.rb b/app/services/payment_providers/stripe/webhooks/payment_intent_succeeded_service.rb new file mode 100644 index 00000000000..a8a53870218 --- /dev/null +++ b/app/services/payment_providers/stripe/webhooks/payment_intent_succeeded_service.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module PaymentProviders + module Stripe + module Webhooks + class PaymentIntentSucceededService < BaseService + def call + @result = update_payment_status! "succeeded" + update_provider_payment_method_data + result + end + + private + + def update_provider_payment_method_data + latest_charge = event.data.object.charges.data.last + data = { + id: event.data.object.payment_method, + type: latest_charge.payment_method_details.type + } + if data[:type] == "card" + data[:brand] = latest_charge.payment_method_details.card.brand + data[:last4] = latest_charge.payment_method_details.card.last4 + end + + # NOTE: `result.payment was set by the service handling update_payment_status! + result.payment.update(provider_payment_method_data: data) + end + end + end + end +end diff --git a/db/migrate/20250220223944_add_provider_payment_method_data_to_payments.rb b/db/migrate/20250220223944_add_provider_payment_method_data_to_payments.rb new file mode 100644 index 00000000000..1f12e699c68 --- /dev/null +++ b/db/migrate/20250220223944_add_provider_payment_method_data_to_payments.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddProviderPaymentMethodDataToPayments < ActiveRecord::Migration[7.1] + def change + add_column :payments, :provider_payment_method_data, :jsonb, null: false, default: {} + end +end diff --git a/db/schema.rb b/db/schema.rb index 663b59c6e6d..7d2f01cb458 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2025_02_20_085848) do +ActiveRecord::Schema[7.1].define(version: 2025_02_20_223944) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -1187,6 +1187,7 @@ t.enum "payable_payment_status", enum_type: "payment_payable_payment_status" t.enum "payment_type", default: "provider", null: false, enum_type: "payment_type" t.string "reference" + t.jsonb "provider_payment_method_data", default: {}, null: false t.index ["invoice_id"], name: "index_payments_on_invoice_id" t.index ["payable_id", "payable_type"], name: "index_payments_on_payable_id_and_payable_type", unique: true, where: "((payable_payment_status = ANY (ARRAY['pending'::payment_payable_payment_status, 'processing'::payment_payable_payment_status])) AND (payment_type = 'provider'::payment_type))" t.index ["payable_type", "payable_id"], name: "index_payments_on_payable_type_and_payable_id" diff --git a/spec/fixtures/stripe/charge_event.json b/spec/fixtures/stripe/charge_event.json deleted file mode 100644 index 8b7bd944f12..00000000000 --- a/spec/fixtures/stripe/charge_event.json +++ /dev/null @@ -1,142 +0,0 @@ -{ - "id": "evt_123456", - "object": "event", - "api_version": "2020-08-27", - "created": 1689539649, - "data": { - "object": { - "id": "ch_123456", - "object": "charge", - "amount": 10000, - "amount_captured": 10000, - "amount_refunded": 0, - "application": null, - "application_fee": null, - "application_fee_amount": null, - "balance_transaction": "txn_123456", - "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": "GET LAGO CORP.", - "captured": true, - "created": 1689539648, - "currency": "usd", - "customer": "cus_123456", - "description": "Subscription update", - "destination": null, - "dispute": null, - "disputed": false, - "failure_balance_transaction": null, - "failure_code": null, - "failure_message": null, - "fraud_details": { - }, - "invoice": "in_123456", - "livemode": false, - "metadata": { - }, - "on_behalf_of": null, - "order": null, - "outcome": { - "network_status": "approved_by_network", - "reason": null, - "risk_level": "normal", - "risk_score": 6, - "seller_message": "Payment complete.", - "type": "authorized" - }, - "paid": true, - "payment_intent": "pi_123456", - "payment_method": "card_123456", - "payment_method_details": { - "card": { - "brand": "visa", - "checks": { - "address_line1_check": null, - "address_postal_code_check": null, - "cvc_check": null - }, - "country": "US", - "exp_month": 9, - "exp_year": 2029, - "fingerprint": "123456", - "funding": "credit", - "installments": null, - "last4": "4242", - "mandate": null, - "network": "visa", - "network_token": { - "used": false - }, - "three_d_secure": null, - "wallet": null - }, - "type": "card" - }, - "receipt_email": null, - "receipt_number": null, - "receipt_url": "https://pay.stripe.com/receipts/invoices/123456", - "refunded": false, - "refunds": { - "object": "list", - "data": [ - ], - "has_more": false, - "total_count": 0, - "url": "/v1/charges/ch_123456/refunds" - }, - "review": null, - "shipping": null, - "source": { - "id": "card_123456", - "object": "card", - "address_city": null, - "address_country": null, - "address_line1": null, - "address_line1_check": null, - "address_line2": null, - "address_state": null, - "address_zip": null, - "address_zip_check": null, - "brand": "Visa", - "country": "US", - "customer": "cus_123456", - "cvc_check": null, - "dynamic_last4": null, - "exp_month": 9, - "exp_year": 2029, - "fingerprint": "123456", - "funding": "credit", - "last4": "4242", - "metadata": { - }, - "name": null, - "tokenization_method": null, - "wallet": null - }, - "source_transfer": null, - "statement_descriptor": null, - "statement_descriptor_suffix": null, - "status": "succeeded", - "transfer_data": null, - "transfer_group": null - } - }, - "livemode": false, - "pending_webhooks": 1, - "request": { - "id": null, - "idempotency_key": "in_123456" - }, - "type": "charge.succeeded" -} diff --git a/spec/fixtures/stripe/payment_intent_event.json b/spec/fixtures/stripe/payment_intent_event.json index 6ff02cb9950..cdc0ebf0c80 100644 --- a/spec/fixtures/stripe/payment_intent_event.json +++ b/spec/fixtures/stripe/payment_intent_event.json @@ -1,18 +1,18 @@ { - "id": "evt_1LCflrDu4p87RxPw3rxrRLSc", + "id": "evt_3Qu0oXQ8iJWBZFaM2POll434", "object": "event", "api_version": "2020-08-27", - "created": 1655712943, + "created": 1739923617, "data": { "object": { - "id": "pi_1JKS2Y2VYugoKSBzNHPFBNj9", + "id": "pi_3Qu0oXQ8iJWBZFaM2cc2RG6D", "object": "payment_intent", - "amount": 1000, + "amount": 9995, "amount_capturable": 0, "amount_details": { "tip": {} }, - "amount_received": 0, + "amount_received": 9995, "application": null, "application_fee_amount": null, "automatic_payment_methods": null, @@ -21,53 +21,187 @@ "capture_method": "automatic", "charges": { "object": "list", - "data": [], + "data": [ + { + "id": "ch_3Qu0oXQ8iJWBZFaM2mejOQKg", + "object": "charge", + "amount": 9995, + "amount_captured": 9995, + "amount_refunded": 0, + "application": null, + "application_fee": null, + "application_fee_amount": null, + "balance_transaction": "txn_3Qu0oXQ8iJWBZFaM2dA7i3aQ", + "billing_details": { + "address": { + "city": null, + "country": "US", + "line1": null, + "line2": null, + "postal_code": "45678", + "state": null + }, + "email": "julienbourdeau@hey.com", + "name": "Testing Stripe", + "phone": null + }, + "calculated_statement_descriptor": "JULIEN", + "captured": true, + "created": 1739923617, + "currency": "usd", + "customer": "cus_RnWzeE8gaYbSve", + "description": "Hooli - Invoice HOO-CAAB-010-001", + "destination": null, + "dispute": null, + "disputed": false, + "failure_balance_transaction": null, + "failure_code": null, + "failure_message": null, + "fraud_details": {}, + "invoice": null, + "livemode": false, + "metadata": { + "invoice_type": "one_off", + "lago_customer_id": "475c46b6-b907-4e5e-8dc7-934377b1cb3c", + "invoice_issuing_date": "2025-02-19", + "lago_invoice_id": "294f0452-453c-430d-9d7f-6444149a8eae" + }, + "on_behalf_of": null, + "order": null, + "outcome": { + "advice_code": null, + "network_advice_code": null, + "network_decline_code": null, + "network_status": "approved_by_network", + "reason": null, + "risk_level": "normal", + "risk_score": 14, + "seller_message": "Payment complete.", + "type": "authorized" + }, + "paid": true, + "payment_intent": "pi_3Qu0oXQ8iJWBZFaM2cc2RG6D", + "payment_method": "pm_1Qu0lNQ8iJWBZFaMkKPH3KFv", + "payment_method_details": { + "card": { + "amount_authorized": 9995, + "authorization_code": null, + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": "pass", + "cvc_check": "pass" + }, + "country": "US", + "exp_month": 12, + "exp_year": 2028, + "extended_authorization": { + "status": "disabled" + }, + "fingerprint": "8TOiB4cGytYxCweY", + "funding": "credit", + "incremental_authorization": { + "status": "unavailable" + }, + "installments": null, + "last4": "4242", + "mandate": null, + "multicapture": { + "status": "unavailable" + }, + "network": "visa", + "network_token": { + "used": false + }, + "network_transaction_id": "568479105665299", + "overcapture": { + "maximum_amount_capturable": 9995, + "status": "unavailable" + }, + "regulated_status": "unregulated", + "three_d_secure": null, + "wallet": null + }, + "type": "card" + }, + "radar_options": {}, + "receipt_email": null, + "receipt_number": null, + "receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xUXNPODZROGlKV0JaRmFNKKLB1L0GMgZyY5dzQos6LBYmPDJ2CJS8Ofst88nIYdxk7dQPCLdbAkTKJVa3ly3jF3irJxsp03NFOUjj", + "refunded": false, + "refunds": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/charges/ch_3Qu0oXQ8iJWBZFaM2mejOQKg/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, - "url": "/v1/charges?payment_intent=pi_1JKS2Y2VYugoKSBzNHPFBNj9" + "total_count": 1, + "url": "/v1/charges?payment_intent=pi_3Qu0oXQ8iJWBZFaM2cc2RG6D" }, - "client_secret": "pi_1JKS2Y2VYugoKSBzNHPFBNj9_secret_niLMVIt33lBGf0z6Gt5WIGc3C", + "client_secret": "pi_3Qu0oXQ8iJWBZFaM2cc2RG6D_secret_wV73MVI9F0SNoZNeODtkLsOKc", "confirmation_method": "automatic", - "created": 1628014114, + "created": 1739923617, "currency": "usd", - "customer": null, - "description": "Created by stripe.com/docs demo", + "customer": "cus_RnWzeE8gaYbSve", + "description": "Hooli - Invoice HOO-CAAB-010-001", "invoice": null, "last_payment_error": null, + "latest_charge": "ch_3Qu0oXQ8iJWBZFaM2mejOQKg", "livemode": false, "metadata": { + "invoice_type": "one_off", + "lago_customer_id": "475c46b6-b907-4e5e-8dc7-934377b1cb3c", + "invoice_issuing_date": "2025-02-19", "lago_invoice_id": "a587e552-36bc-4334-81f2-abcbf034ad3f" }, "next_action": null, "on_behalf_of": null, - "payment_method": null, + "payment_method": "pm_1Qu0lNQ8iJWBZFaMkKPH3KFv", + "payment_method_configuration_details": null, "payment_method_options": { "card": { "installments": null, "mandate_options": null, "network": null, "request_three_d_secure": "automatic" - } + }, + "crypto": {} }, "payment_method_types": [ - "card" + "card", + "crypto" ], "processing": null, "receipt_email": null, "review": null, "setup_future_usage": null, "shipping": null, + "source": null, "statement_descriptor": null, "statement_descriptor_suffix": null, - "status": "success", + "status": "succeeded", "transfer_data": null, "transfer_group": null } }, "livemode": false, - "pending_webhooks": 0, + "pending_webhooks": 1, "request": { - "id": null, - "idempotency_key": null + "id": "req_STTo2yGuTiiuNq", + "idempotency_key": "payment-31e11fc4-60a4-467f-8661-1f24f410fbe0" }, "type": "payment_intent.succeeded" } diff --git a/spec/fixtures/stripe/payment_intent_event_failed.json b/spec/fixtures/stripe/payment_intent_event_failed.json new file mode 100644 index 00000000000..078ad3990b2 --- /dev/null +++ b/spec/fixtures/stripe/payment_intent_event_failed.json @@ -0,0 +1,207 @@ +{ + "id": "evt_3Qu0oXQ8iJWBZFaM2POll434", + "object": "event", + "api_version": "2020-08-27", + "created": 1739923617, + "data": { + "object": { + "id": "pi_3Qu0oXQ8iJWBZFaM2cc2RG6D", + "object": "payment_intent", + "amount": 9995, + "amount_capturable": 0, + "amount_details": { + "tip": {} + }, + "amount_received": 9995, + "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_3Qu0oXQ8iJWBZFaM2mejOQKg", + "object": "charge", + "amount": 9995, + "amount_captured": 9995, + "amount_refunded": 0, + "application": null, + "application_fee": null, + "application_fee_amount": null, + "balance_transaction": "txn_3Qu0oXQ8iJWBZFaM2dA7i3aQ", + "billing_details": { + "address": { + "city": null, + "country": "US", + "line1": null, + "line2": null, + "postal_code": "45678", + "state": null + }, + "email": "julienbourdeau@hey.com", + "name": "Testing Stripe", + "phone": null + }, + "calculated_statement_descriptor": "JULIEN", + "captured": true, + "created": 1739923617, + "currency": "usd", + "customer": "cus_RnWzeE8gaYbSve", + "description": "Hooli - Invoice HOO-CAAB-010-001", + "destination": null, + "dispute": null, + "disputed": false, + "failure_balance_transaction": null, + "failure_code": null, + "failure_message": null, + "fraud_details": {}, + "invoice": null, + "livemode": false, + "metadata": { + "invoice_type": "one_off", + "lago_customer_id": "475c46b6-b907-4e5e-8dc7-934377b1cb3c", + "invoice_issuing_date": "2025-02-19", + "lago_invoice_id": "294f0452-453c-430d-9d7f-6444149a8eae" + }, + "on_behalf_of": null, + "order": null, + "outcome": { + "advice_code": null, + "network_advice_code": null, + "network_decline_code": null, + "network_status": "approved_by_network", + "reason": null, + "risk_level": "normal", + "risk_score": 14, + "seller_message": "Payment complete.", + "type": "authorized" + }, + "paid": true, + "payment_intent": "pi_3Qu0oXQ8iJWBZFaM2cc2RG6D", + "payment_method": "pm_1Qu0lNQ8iJWBZFaMkKPH3KFv", + "payment_method_details": { + "card": { + "amount_authorized": 9995, + "authorization_code": null, + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": "pass", + "cvc_check": "pass" + }, + "country": "US", + "exp_month": 12, + "exp_year": 2028, + "extended_authorization": { + "status": "disabled" + }, + "fingerprint": "8TOiB4cGytYxCweY", + "funding": "credit", + "incremental_authorization": { + "status": "unavailable" + }, + "installments": null, + "last4": "4242", + "mandate": null, + "multicapture": { + "status": "unavailable" + }, + "network": "visa", + "network_token": { + "used": false + }, + "network_transaction_id": "568479105665299", + "overcapture": { + "maximum_amount_capturable": 9995, + "status": "unavailable" + }, + "regulated_status": "unregulated", + "three_d_secure": null, + "wallet": null + }, + "type": "card" + }, + "radar_options": {}, + "receipt_email": null, + "receipt_number": null, + "receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xUXNPODZROGlKV0JaRmFNKKLB1L0GMgZyY5dzQos6LBYmPDJ2CJS8Ofst88nIYdxk7dQPCLdbAkTKJVa3ly3jF3irJxsp03NFOUjj", + "refunded": false, + "refunds": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/charges/ch_3Qu0oXQ8iJWBZFaM2mejOQKg/refunds" + }, + "review": null, + "shipping": null, + "source": null, + "source_transfer": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "failed", + "transfer_data": null, + "transfer_group": null + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/charges?payment_intent=pi_3Qu0oXQ8iJWBZFaM2cc2RG6D" + }, + "client_secret": "pi_3Qu0oXQ8iJWBZFaM2cc2RG6D_secret_wV73MVI9F0SNoZNeODtkLsOKc", + "confirmation_method": "automatic", + "created": 1739923617, + "currency": "usd", + "customer": "cus_RnWzeE8gaYbSve", + "description": "Hooli - Invoice HOO-CAAB-010-001", + "invoice": null, + "last_payment_error": null, + "latest_charge": "ch_3Qu0oXQ8iJWBZFaM2mejOQKg", + "livemode": false, + "metadata": { + "invoice_type": "one_off", + "lago_customer_id": "475c46b6-b907-4e5e-8dc7-934377b1cb3c", + "invoice_issuing_date": "2025-02-19", + "lago_invoice_id": "a587e552-36bc-4334-81f2-abcbf034ad3f" + }, + "next_action": null, + "on_behalf_of": null, + "payment_method": "pm_1Qu0lNQ8iJWBZFaMkKPH3KFv", + "payment_method_configuration_details": null, + "payment_method_options": { + "card": { + "installments": null, + "mandate_options": null, + "network": null, + "request_three_d_secure": "automatic" + }, + "crypto": {} + }, + "payment_method_types": [ + "card", + "crypto" + ], + "processing": null, + "receipt_email": null, + "review": null, + "setup_future_usage": null, + "shipping": null, + "source": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "failed", + "transfer_data": null, + "transfer_group": null + } + }, + "livemode": false, + "pending_webhooks": 1, + "request": { + "id": "req_STTo2yGuTiiuNq", + "idempotency_key": "payment-31e11fc4-60a4-467f-8661-1f24f410fbe0" + }, + "type": "payment_intent.payment_failed" +} diff --git a/spec/fixtures/stripe/payment_intent_event_invalid_payable_type.json b/spec/fixtures/stripe/payment_intent_event_invalid_payable_type.json index a31dd6e5c65..b38deae2402 100644 --- a/spec/fixtures/stripe/payment_intent_event_invalid_payable_type.json +++ b/spec/fixtures/stripe/payment_intent_event_invalid_payable_type.json @@ -21,7 +21,130 @@ "capture_method": "automatic", "charges": { "object": "list", - "data": [], + "data": [ + { + "id": "ch_3Qu0oXQ8iJWBZFaM2mejOQKg", + "object": "charge", + "amount": 9995, + "amount_captured": 9995, + "amount_refunded": 0, + "application": null, + "application_fee": null, + "application_fee_amount": null, + "balance_transaction": "txn_3Qu0oXQ8iJWBZFaM2dA7i3aQ", + "billing_details": { + "address": { + "city": null, + "country": "US", + "line1": null, + "line2": null, + "postal_code": "45678", + "state": null + }, + "email": "julienbourdeau@hey.com", + "name": "Testing Stripe", + "phone": null + }, + "calculated_statement_descriptor": "JULIEN", + "captured": true, + "created": 1739923617, + "currency": "usd", + "customer": "cus_RnWzeE8gaYbSve", + "description": "Hooli - Invoice HOO-CAAB-010-001", + "destination": null, + "dispute": null, + "disputed": false, + "failure_balance_transaction": null, + "failure_code": null, + "failure_message": null, + "fraud_details": {}, + "invoice": null, + "livemode": false, + "metadata": { + "lago_payment_request_id": "a587e552-36bc-4334-81f2-abcbf034ad3f", + "lago_payable_type": "InvalidPayableTypeName" + }, + "on_behalf_of": null, + "order": null, + "outcome": { + "advice_code": null, + "network_advice_code": null, + "network_decline_code": null, + "network_status": "approved_by_network", + "reason": null, + "risk_level": "normal", + "risk_score": 14, + "seller_message": "Payment complete.", + "type": "authorized" + }, + "paid": true, + "payment_intent": "pi_3Qu0oXQ8iJWBZFaM2cc2RG6D", + "payment_method": "pm_1Qu0lNQ8iJWBZFaMkKPH3KFv", + "payment_method_details": { + "card": { + "amount_authorized": 9995, + "authorization_code": null, + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": "pass", + "cvc_check": "pass" + }, + "country": "US", + "exp_month": 12, + "exp_year": 2028, + "extended_authorization": { + "status": "disabled" + }, + "fingerprint": "8TOiB4cGytYxCweY", + "funding": "credit", + "incremental_authorization": { + "status": "unavailable" + }, + "installments": null, + "last4": "4242", + "mandate": null, + "multicapture": { + "status": "unavailable" + }, + "network": "visa", + "network_token": { + "used": false + }, + "network_transaction_id": "568479105665299", + "overcapture": { + "maximum_amount_capturable": 9995, + "status": "unavailable" + }, + "regulated_status": "unregulated", + "three_d_secure": null, + "wallet": null + }, + "type": "card" + }, + "radar_options": {}, + "receipt_email": null, + "receipt_number": null, + "receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xUXNPODZROGlKV0JaRmFNKKLB1L0GMgZyY5dzQos6LBYmPDJ2CJS8Ofst88nIYdxk7dQPCLdbAkTKJVa3ly3jF3irJxsp03NFOUjj", + "refunded": false, + "refunds": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/charges/ch_3Qu0oXQ8iJWBZFaM2mejOQKg/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, "url": "/v1/charges?payment_intent=pi_1JKS2Y2VYugoKSBzNHPFBNj9" }, diff --git a/spec/fixtures/stripe/payment_intent_event_payment_request.json b/spec/fixtures/stripe/payment_intent_event_payment_request.json index 8df56067404..9a838cb15ae 100644 --- a/spec/fixtures/stripe/payment_intent_event_payment_request.json +++ b/spec/fixtures/stripe/payment_intent_event_payment_request.json @@ -1,18 +1,18 @@ { - "id": "evt_1LCflrDu4p87RxPw3rxrRLSc", + "id": "evt_3Qu0oXQ8iJWBZFaM2POll434", "object": "event", "api_version": "2020-08-27", - "created": 1655712943, + "created": 1739923617, "data": { "object": { - "id": "pi_1JKS2Y2VYugoKSBzNHPFBNj9", + "id": "pi_3Qu0oXQ8iJWBZFaM2cc2RG6D", "object": "payment_intent", - "amount": 1000, + "amount": 9995, "amount_capturable": 0, "amount_details": { "tip": {} }, - "amount_received": 0, + "amount_received": 9995, "application": null, "application_fee_amount": null, "automatic_payment_methods": null, @@ -21,18 +21,143 @@ "capture_method": "automatic", "charges": { "object": "list", - "data": [], + "data": [ + { + "id": "ch_3Qu0oXQ8iJWBZFaM2mejOQKg", + "object": "charge", + "amount": 9995, + "amount_captured": 9995, + "amount_refunded": 0, + "application": null, + "application_fee": null, + "application_fee_amount": null, + "balance_transaction": "txn_3Qu0oXQ8iJWBZFaM2dA7i3aQ", + "billing_details": { + "address": { + "city": null, + "country": "US", + "line1": null, + "line2": null, + "postal_code": "45678", + "state": null + }, + "email": "julienbourdeau@hey.com", + "name": "Testing Stripe", + "phone": null + }, + "calculated_statement_descriptor": "JULIEN", + "captured": true, + "created": 1739923617, + "currency": "usd", + "customer": "cus_RnWzeE8gaYbSve", + "description": "Hooli - Invoice HOO-CAAB-010-001", + "destination": null, + "dispute": null, + "disputed": false, + "failure_balance_transaction": null, + "failure_code": null, + "failure_message": null, + "fraud_details": {}, + "invoice": null, + "livemode": false, + "metadata": { + "lago_payment_request_id": "a587e552-36bc-4334-81f2-abcbf034ad3f", + "lago_payable_type": "PaymentRequest" + }, + "on_behalf_of": null, + "order": null, + "outcome": { + "advice_code": null, + "network_advice_code": null, + "network_decline_code": null, + "network_status": "approved_by_network", + "reason": null, + "risk_level": "normal", + "risk_score": 14, + "seller_message": "Payment complete.", + "type": "authorized" + }, + "paid": true, + "payment_intent": "pi_3Qu0oXQ8iJWBZFaM2cc2RG6D", + "payment_method": "pm_1Qu0lNQ8iJWBZFaMkKPH3KFv", + "payment_method_details": { + "card": { + "amount_authorized": 9995, + "authorization_code": null, + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": "pass", + "cvc_check": "pass" + }, + "country": "US", + "exp_month": 12, + "exp_year": 2028, + "extended_authorization": { + "status": "disabled" + }, + "fingerprint": "8TOiB4cGytYxCweY", + "funding": "credit", + "incremental_authorization": { + "status": "unavailable" + }, + "installments": null, + "last4": "4242", + "mandate": null, + "multicapture": { + "status": "unavailable" + }, + "network": "visa", + "network_token": { + "used": false + }, + "network_transaction_id": "568479105665299", + "overcapture": { + "maximum_amount_capturable": 9995, + "status": "unavailable" + }, + "regulated_status": "unregulated", + "three_d_secure": null, + "wallet": null + }, + "type": "card" + }, + "radar_options": {}, + "receipt_email": null, + "receipt_number": null, + "receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xUXNPODZROGlKV0JaRmFNKKLB1L0GMgZyY5dzQos6LBYmPDJ2CJS8Ofst88nIYdxk7dQPCLdbAkTKJVa3ly3jF3irJxsp03NFOUjj", + "refunded": false, + "refunds": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/charges/ch_3Qu0oXQ8iJWBZFaM2mejOQKg/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, - "url": "/v1/charges?payment_intent=pi_1JKS2Y2VYugoKSBzNHPFBNj9" + "total_count": 1, + "url": "/v1/charges?payment_intent=pi_3Qu0oXQ8iJWBZFaM2cc2RG6D" }, - "client_secret": "pi_1JKS2Y2VYugoKSBzNHPFBNj9_secret_niLMVIt33lBGf0z6Gt5WIGc3C", + "client_secret": "pi_3Qu0oXQ8iJWBZFaM2cc2RG6D_secret_wV73MVI9F0SNoZNeODtkLsOKc", "confirmation_method": "automatic", - "created": 1628014114, + "created": 1739923617, "currency": "usd", - "customer": null, - "description": "Created by stripe.com/docs demo", + "customer": "cus_RnWzeE8gaYbSve", + "description": "Hooli - Invoice HOO-CAAB-010-001", "invoice": null, "last_payment_error": null, + "latest_charge": "ch_3Qu0oXQ8iJWBZFaM2mejOQKg", "livemode": false, "metadata": { "lago_payment_request_id": "a587e552-36bc-4334-81f2-abcbf034ad3f", @@ -40,35 +165,39 @@ }, "next_action": null, "on_behalf_of": null, - "payment_method": null, + "payment_method": "pm_1Qu0lNQ8iJWBZFaMkKPH3KFv", + "payment_method_configuration_details": null, "payment_method_options": { "card": { "installments": null, "mandate_options": null, "network": null, "request_three_d_secure": "automatic" - } + }, + "crypto": {} }, "payment_method_types": [ - "card" + "card", + "crypto" ], "processing": null, "receipt_email": null, "review": null, "setup_future_usage": null, "shipping": null, + "source": null, "statement_descriptor": null, "statement_descriptor_suffix": null, - "status": "success", + "status": "succeeded", "transfer_data": null, "transfer_group": null } }, "livemode": false, - "pending_webhooks": 0, + "pending_webhooks": 1, "request": { - "id": null, - "idempotency_key": null + "id": "req_STTo2yGuTiiuNq", + "idempotency_key": "payment-31e11fc4-60a4-467f-8661-1f24f410fbe0" }, "type": "payment_intent.succeeded" } diff --git a/spec/fixtures/stripe/payment_intent_event_payment_request_failed.json b/spec/fixtures/stripe/payment_intent_event_payment_request_failed.json new file mode 100644 index 00000000000..cecd06f9ee0 --- /dev/null +++ b/spec/fixtures/stripe/payment_intent_event_payment_request_failed.json @@ -0,0 +1,203 @@ +{ + "id": "evt_3Qu0oXQ8iJWBZFaM2POll434", + "object": "event", + "api_version": "2020-08-27", + "created": 1739923617, + "data": { + "object": { + "id": "pi_3Qu0oXQ8iJWBZFaM2cc2RG6D", + "object": "payment_intent", + "amount": 9995, + "amount_capturable": 0, + "amount_details": { + "tip": {} + }, + "amount_received": 9995, + "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_3Qu0oXQ8iJWBZFaM2mejOQKg", + "object": "charge", + "amount": 9995, + "amount_captured": 9995, + "amount_refunded": 0, + "application": null, + "application_fee": null, + "application_fee_amount": null, + "balance_transaction": "txn_3Qu0oXQ8iJWBZFaM2dA7i3aQ", + "billing_details": { + "address": { + "city": null, + "country": "US", + "line1": null, + "line2": null, + "postal_code": "45678", + "state": null + }, + "email": "julienbourdeau@hey.com", + "name": "Testing Stripe", + "phone": null + }, + "calculated_statement_descriptor": "JULIEN", + "captured": true, + "created": 1739923617, + "currency": "usd", + "customer": "cus_RnWzeE8gaYbSve", + "description": "Hooli - Invoice HOO-CAAB-010-001", + "destination": null, + "dispute": null, + "disputed": false, + "failure_balance_transaction": null, + "failure_code": null, + "failure_message": null, + "fraud_details": {}, + "invoice": null, + "livemode": false, + "metadata": { + "lago_payment_request_id": "a587e552-36bc-4334-81f2-abcbf034ad3f", + "lago_payable_type": "PaymentRequest" + }, + "on_behalf_of": null, + "order": null, + "outcome": { + "advice_code": null, + "network_advice_code": null, + "network_decline_code": null, + "network_status": "approved_by_network", + "reason": null, + "risk_level": "normal", + "risk_score": 14, + "seller_message": "Payment complete.", + "type": "authorized" + }, + "paid": true, + "payment_intent": "pi_3Qu0oXQ8iJWBZFaM2cc2RG6D", + "payment_method": "pm_1Qu0lNQ8iJWBZFaMkKPH3KFv", + "payment_method_details": { + "card": { + "amount_authorized": 9995, + "authorization_code": null, + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": "pass", + "cvc_check": "pass" + }, + "country": "US", + "exp_month": 12, + "exp_year": 2028, + "extended_authorization": { + "status": "disabled" + }, + "fingerprint": "8TOiB4cGytYxCweY", + "funding": "credit", + "incremental_authorization": { + "status": "unavailable" + }, + "installments": null, + "last4": "4242", + "mandate": null, + "multicapture": { + "status": "unavailable" + }, + "network": "visa", + "network_token": { + "used": false + }, + "network_transaction_id": "568479105665299", + "overcapture": { + "maximum_amount_capturable": 9995, + "status": "unavailable" + }, + "regulated_status": "unregulated", + "three_d_secure": null, + "wallet": null + }, + "type": "card" + }, + "radar_options": {}, + "receipt_email": null, + "receipt_number": null, + "receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xUXNPODZROGlKV0JaRmFNKKLB1L0GMgZyY5dzQos6LBYmPDJ2CJS8Ofst88nIYdxk7dQPCLdbAkTKJVa3ly3jF3irJxsp03NFOUjj", + "refunded": false, + "refunds": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/charges/ch_3Qu0oXQ8iJWBZFaM2mejOQKg/refunds" + }, + "review": null, + "shipping": null, + "source": null, + "source_transfer": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "failed", + "transfer_data": null, + "transfer_group": null + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/charges?payment_intent=pi_3Qu0oXQ8iJWBZFaM2cc2RG6D" + }, + "client_secret": "pi_3Qu0oXQ8iJWBZFaM2cc2RG6D_secret_wV73MVI9F0SNoZNeODtkLsOKc", + "confirmation_method": "automatic", + "created": 1739923617, + "currency": "usd", + "customer": "cus_RnWzeE8gaYbSve", + "description": "Hooli - Invoice HOO-CAAB-010-001", + "invoice": null, + "last_payment_error": null, + "latest_charge": "ch_3Qu0oXQ8iJWBZFaM2mejOQKg", + "livemode": false, + "metadata": { + "lago_payment_request_id": "a587e552-36bc-4334-81f2-abcbf034ad3f", + "lago_payable_type": "PaymentRequest" + }, + "next_action": null, + "on_behalf_of": null, + "payment_method": "pm_1Qu0lNQ8iJWBZFaMkKPH3KFv", + "payment_method_configuration_details": null, + "payment_method_options": { + "card": { + "installments": null, + "mandate_options": null, + "network": null, + "request_three_d_secure": "automatic" + }, + "crypto": {} + }, + "payment_method_types": [ + "card", + "crypto" + ], + "processing": null, + "receipt_email": null, + "review": null, + "setup_future_usage": null, + "shipping": null, + "source": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "failed", + "transfer_data": null, + "transfer_group": null + } + }, + "livemode": false, + "pending_webhooks": 1, + "request": { + "id": "req_STTo2yGuTiiuNq", + "idempotency_key": "payment-31e11fc4-60a4-467f-8661-1f24f410fbe0" + }, + "type": "payment_intent.payment_failed" +} diff --git a/spec/services/payment_providers/stripe/handle_event_service_spec.rb b/spec/services/payment_providers/stripe/handle_event_service_spec.rb index a15919966ea..9d119c063bc 100644 --- a/spec/services/payment_providers/stripe/handle_event_service_spec.rb +++ b/spec/services/payment_providers/stripe/handle_event_service_spec.rb @@ -18,81 +18,6 @@ .and_return(service_result) end - context "when payment intent event" do - let(:event_json) do - path = Rails.root.join("spec/fixtures/stripe/payment_intent_event.json") - File.read(path) - end - - it "routes the event to an other service" do - result = event_service.call - - expect(result).to be_success - - expect(Invoices::Payments::StripeService).to have_received(:new) - expect(payment_service).to have_received(:update_payment_status) - .with( - organization_id: organization.id, - status: "succeeded", - stripe_payment: PaymentProviders::StripeProvider::StripePayment.new( - id: "pi_1JKS2Y2VYugoKSBzNHPFBNj9", - status: "success", - metadata: { - lago_invoice_id: "a587e552-36bc-4334-81f2-abcbf034ad3f" - } - ) - ) - end - end - - context "when payment intent event for a payment request" do - let(:payment_service) { instance_double(PaymentRequests::Payments::StripeService) } - - let(:event_json) do - path = Rails.root.join("spec/fixtures/stripe/payment_intent_event_payment_request.json") - File.read(path) - end - - before do - allow(PaymentRequests::Payments::StripeService).to receive(:new) - .and_return(payment_service) - allow(payment_service).to receive(:update_payment_status) - .and_return(service_result) - end - - it "routes the event to an other service" do - result = event_service.call - - expect(result).to be_success - - expect(PaymentRequests::Payments::StripeService).to have_received(:new) - expect(payment_service).to have_received(:update_payment_status) - .with( - organization_id: organization.id, - status: "succeeded", - stripe_payment: PaymentProviders::StripeProvider::StripePayment.new( - id: "pi_1JKS2Y2VYugoKSBzNHPFBNj9", - status: "success", - metadata: { - lago_payment_request_id: "a587e552-36bc-4334-81f2-abcbf034ad3f", - lago_payable_type: "PaymentRequest" - } - ) - ) - end - end - - context "when payment intent event with an invalid payable type" do - let(:event_json) do - path = Rails.root.join("spec/fixtures/stripe/payment_intent_event_invalid_payable_type.json") - File.read(path) - end - - it "routes the event to an other service" do - expect { event_service.call }.to raise_error(NameError, "Invalid lago_payable_type: InvalidPayableTypeName") - end - end - context "when setup intent event" do let(:event_json) do path = Rails.root.join("spec/fixtures/stripe/setup_intent_event.json") diff --git a/spec/services/payment_providers/stripe/webhooks/payment_intent_payment_failed_service_spec.rb b/spec/services/payment_providers/stripe/webhooks/payment_intent_payment_failed_service_spec.rb new file mode 100644 index 00000000000..61e071d4dba --- /dev/null +++ b/spec/services/payment_providers/stripe/webhooks/payment_intent_payment_failed_service_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe PaymentProviders::Stripe::Webhooks::PaymentIntentPaymentFailedService do + subject(:event_service) { described_class.new(organization_id: organization.id, event:) } + + let(:event) { ::Stripe::Event.construct_from(JSON.parse(event_json)) } + let(:organization) { create(:organization) } + + let(:event_json) do + path = Rails.root.join("spec/fixtures/stripe/#{fixtures_filename}") + File.read(path) + end + + context "when payment intent event" do + let(:fixtures_filename) { "payment_intent_event_failed.json" } + + it "updates the payment status and save the payment method" do + expect_any_instance_of(Invoices::Payments::StripeService).to receive(:update_payment_status) # rubocop:disable RSpec/AnyInstance + .with( + organization_id: organization.id, + status: "failed", + stripe_payment: PaymentProviders::StripeProvider::StripePayment.new( + id: "pi_3Qu0oXQ8iJWBZFaM2cc2RG6D", + status: "failed", + metadata: { + invoice_type: "one_off", + lago_customer_id: "475c46b6-b907-4e5e-8dc7-934377b1cb3c", + invoice_issuing_date: "2025-02-19", + lago_invoice_id: "a587e552-36bc-4334-81f2-abcbf034ad3f" + } + ) + ).and_call_original + + create(:payment, provider_payment_id: event.data.object.id) + + result = event_service.call + + expect(result).to be_success + end + end + + context "when payment intent event for a payment request" do + let(:fixtures_filename) { "payment_intent_event_payment_request_failed.json" } + + it "routes the event to an other service" do + expect_any_instance_of(PaymentRequests::Payments::StripeService).to receive(:update_payment_status) # rubocop:disable RSpec/AnyInstance + .with( + organization_id: organization.id, + status: "failed", + stripe_payment: PaymentProviders::StripeProvider::StripePayment.new( + id: "pi_3Qu0oXQ8iJWBZFaM2cc2RG6D", + status: "failed", + metadata: { + lago_payment_request_id: "a587e552-36bc-4334-81f2-abcbf034ad3f", + lago_payable_type: "PaymentRequest" + } + ) + ).and_call_original + + payment = create(:payment, provider_payment_id: event.data.object.id) + create(:payment_request, customer: create(:customer, organization:), payments: [payment]) + + result = event_service.call + + expect(result).to be_success + end + end + + context "when payment intent event with an invalid payable type" do + let(:fixtures_filename) { "payment_intent_event_invalid_payable_type.json" } + + it do + expect { event_service.call }.to raise_error(NameError, "Invalid lago_payable_type: InvalidPayableTypeName") + end + end +end diff --git a/spec/services/payment_providers/stripe/webhooks/payment_intent_succeeded_service_spec.rb b/spec/services/payment_providers/stripe/webhooks/payment_intent_succeeded_service_spec.rb new file mode 100644 index 00000000000..0a94f54318e --- /dev/null +++ b/spec/services/payment_providers/stripe/webhooks/payment_intent_succeeded_service_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe PaymentProviders::Stripe::Webhooks::PaymentIntentSucceededService, type: :service do + subject(:event_service) { described_class.new(organization_id: organization.id, event:) } + + let(:event) { ::Stripe::Event.construct_from(JSON.parse(event_json)) } + let(:organization) { create(:organization) } + + let(:event_json) do + path = Rails.root.join("spec/fixtures/stripe/#{fixtures_filename}") + File.read(path) + end + + context "when payment intent event" do + let(:fixtures_filename) { "payment_intent_event.json" } + + it "updates the payment status and save the payment method" do + expect_any_instance_of(Invoices::Payments::StripeService).to receive(:update_payment_status) # rubocop:disable RSpec/AnyInstance + .with( + organization_id: organization.id, + status: "succeeded", + stripe_payment: PaymentProviders::StripeProvider::StripePayment.new( + id: "pi_3Qu0oXQ8iJWBZFaM2cc2RG6D", + status: "succeeded", + metadata: { + invoice_type: "one_off", + lago_customer_id: "475c46b6-b907-4e5e-8dc7-934377b1cb3c", + invoice_issuing_date: "2025-02-19", + lago_invoice_id: "a587e552-36bc-4334-81f2-abcbf034ad3f" + } + ) + ).and_call_original + + payment = create(:payment, provider_payment_id: event.data.object.id) + + result = event_service.call + + expect(result).to be_success + + expect(payment.reload.provider_payment_method_data).to eq({ + "id" => "pm_1Qu0lNQ8iJWBZFaMkKPH3KFv", + "type" => "card", + "brand" => "visa", + "last4" => "4242" + }) + end + end + + context "when payment intent event for a payment request" do + let(:fixtures_filename) { "payment_intent_event_payment_request.json" } + + it "routes the event to an other service" do + expect_any_instance_of(PaymentRequests::Payments::StripeService).to receive(:update_payment_status) # rubocop:disable RSpec/AnyInstance + .with( + organization_id: organization.id, + status: "succeeded", + stripe_payment: PaymentProviders::StripeProvider::StripePayment.new( + id: "pi_3Qu0oXQ8iJWBZFaM2cc2RG6D", + status: "succeeded", + metadata: { + lago_payment_request_id: "a587e552-36bc-4334-81f2-abcbf034ad3f", + lago_payable_type: "PaymentRequest" + } + ) + ).and_call_original + + payment = create(:payment, provider_payment_id: event.data.object.id) + create(:payment_request, customer: create(:customer, organization:), payments: [payment]) + + result = event_service.call + + expect(result).to be_success + expect(payment.reload.provider_payment_method_data).to eq({ + "id" => "pm_1Qu0lNQ8iJWBZFaMkKPH3KFv", + "type" => "card", + "brand" => "visa", + "last4" => "4242" + }) + end + end + + context "when payment intent event with an invalid payable type" do + let(:fixtures_filename) { "payment_intent_event_invalid_payable_type.json" } + + it do + expect { event_service.call }.to raise_error(NameError, "Invalid lago_payable_type: InvalidPayableTypeName") + end + end +end