diff --git a/src/controller/checkout.controller.ts b/src/controller/checkout.controller.ts index 7502a480..ab46382f 100644 --- a/src/controller/checkout.controller.ts +++ b/src/controller/checkout.controller.ts @@ -70,24 +70,17 @@ export function checkoutController(usersService: UsersService, paymentsService: const userExists = await usersService.findUserByUuid(userUuid).catch(() => null); if (userExists) { - await paymentsService.updateCustomer( - userExists.customerId, - { - customer: { - name: customerName, - }, - }, - { - email, - address: { - line1: lineAddress1, - line2: lineAddress2, - city, - postal_code: postalCode, - country, - }, + await stripePaymentsAdapter.updateCustomer(userExists.customerId, { + name: customerName, + email, + address: { + line1: lineAddress1, + line2: lineAddress2, + city, + postalCode, + country, }, - ); + }); customerId = userExists.customerId; } else { const { id } = await stripePaymentsAdapter.createCustomer({ diff --git a/src/controller/payments.controller.ts b/src/controller/payments.controller.ts index 0217ecf7..07319926 100644 --- a/src/controller/payments.controller.ts +++ b/src/controller/payments.controller.ts @@ -23,6 +23,7 @@ import { VERIFICATION_CHARGE } from '../constants'; import { setupAuth } from '../plugins/auth'; import { PaymentService } from '../services/payment.service'; import { InvalidLicenseCodeError } from '../errors/LicenseCodeErrors'; +import { stripePaymentsAdapter } from '../infrastructure/adapters/stripe.adapter'; const allowedCurrency = ['eur', 'usd']; @@ -334,7 +335,7 @@ export function paymentsController( async (req, rep) => { const user = await assertUser(req, rep, usersService); const { address, phoneNumber } = req.body; - await paymentService.updateCustomerBillingInfo(user.customerId, { + await stripePaymentsAdapter.updateCustomer(user.customerId, { address: { line1: address, }, diff --git a/src/infrastructure/adapters/stripe.adapter.ts b/src/infrastructure/adapters/stripe.adapter.ts index 1de77bbf..e86a6a00 100644 --- a/src/infrastructure/adapters/stripe.adapter.ts +++ b/src/infrastructure/adapters/stripe.adapter.ts @@ -2,8 +2,9 @@ import Stripe from 'stripe'; import { UserNotFoundError } from '../../errors/PaymentErrors'; import { PaymentsAdapter } from '../domain/ports/payments.adapter'; -import { Customer, CreateCustomerParams } from '../domain/entities/customer'; +import { Customer, CreateCustomerParams, UpdateCustomerParams } from '../domain/entities/customer'; import envVariablesConfig from '../../config'; +import { PaymentMethod } from '../domain/entities/paymentMethod'; export class StripePaymentsAdapter implements PaymentsAdapter { private readonly provider: Stripe = new Stripe(envVariablesConfig.STRIPE_SECRET_KEY, { @@ -20,7 +21,7 @@ export class StripePaymentsAdapter implements PaymentsAdapter { return Customer.toDomain(stripeCustomer); } - async updateCustomer(customerId: Customer['id'], params: Partial): Promise { + async updateCustomer(customerId: Customer['id'], params: Partial): Promise { const updatedCustomer = await this.provider.customers.update(customerId, this.toStripeCustomerParams(params)); return Customer.toDomain(updatedCustomer); @@ -49,18 +50,24 @@ export class StripePaymentsAdapter implements PaymentsAdapter { return customers.data.map((customer) => Customer.toDomain(customer)); } - private toStripeCustomerParams(params: Partial): Stripe.CustomerCreateParams { + async retrievePaymentMethod(paymentMethodId: PaymentMethod['id']): Promise { + const paymentMethods = await this.provider.paymentMethods.retrieve(paymentMethodId, {}); + return PaymentMethod.toDomain(paymentMethods); + } + + private toStripeCustomerParams(params: Partial): Stripe.CustomerCreateParams { return { ...(params.name && { name: params.name }), ...(params.email && { email: params.email }), + ...(params.phone && { phone: params.phone }), ...(params.address && { address: { - line1: params.address.line1, - line2: params.address.line2, - city: params.address.city, - state: params.address.state, - country: params.address.country, - postal_code: params.address.postalCode, + line1: params.address.line1 ?? undefined, + line2: params.address.line2 ?? undefined, + city: params.address.city ?? undefined, + state: params.address.state ?? undefined, + country: params.address.country ?? undefined, + postal_code: params.address.postalCode ?? undefined, }, }), }; diff --git a/src/infrastructure/domain/entities/customer.ts b/src/infrastructure/domain/entities/customer.ts index bf994a8b..7446fcfe 100644 --- a/src/infrastructure/domain/entities/customer.ts +++ b/src/infrastructure/domain/entities/customer.ts @@ -1,19 +1,19 @@ import Stripe from 'stripe'; import { BadRequestError } from '../../../errors/Errors'; - -export interface CustomerAddress { - line1: string; - line2: string; - city: string; - state: string; - country: string; - postalCode: string; -} +import { Address } from '../types'; export interface CreateCustomerParams { name: string; email: string; - address: Partial; + address: Partial
; +} + +export interface UpdateCustomerParams extends Partial { + phone?: string; + tax?: { + id: string; + type: Stripe.TaxIdCreateParams.Type; + }; } export class Customer { @@ -21,7 +21,7 @@ export class Customer { public readonly id: string, public readonly name: string, public readonly email: string, - public readonly address: CustomerAddress, + public readonly address?: Address, public readonly phone?: string, ) {} @@ -34,21 +34,17 @@ export class Customer { throw new BadRequestError('Customer email is required'); } - if (!stripeCustomer.address) { - throw new BadRequestError('Customer address is required'); - } - return new Customer( stripeCustomer.id, stripeCustomer.name, stripeCustomer.email, { - line1: stripeCustomer.address.line1 ?? '', - line2: stripeCustomer.address.line2 ?? '', - city: stripeCustomer.address.city ?? '', - state: stripeCustomer.address.state ?? '', - country: stripeCustomer.address.country ?? '', - postalCode: stripeCustomer.address.postal_code ?? '', + line1: stripeCustomer.address?.line1, + line2: stripeCustomer.address?.line2, + city: stripeCustomer.address?.city, + state: stripeCustomer.address?.state, + country: stripeCustomer.address?.country, + postalCode: stripeCustomer.address?.postal_code, }, stripeCustomer.phone ?? undefined, ); @@ -62,7 +58,7 @@ export class Customer { return this.email; } - getAddress(): CustomerAddress { + getAddress(): Address | undefined { return this.address; } } diff --git a/src/infrastructure/domain/entities/paymentMethod.ts b/src/infrastructure/domain/entities/paymentMethod.ts new file mode 100644 index 00000000..e6f7236d --- /dev/null +++ b/src/infrastructure/domain/entities/paymentMethod.ts @@ -0,0 +1,28 @@ +import Stripe from 'stripe'; +import { Address } from '../types'; + +export class PaymentMethod { + constructor( + public readonly id: string, + public readonly address?: Address, + ) {} + + static toDomain(stripePaymentMethod: Stripe.PaymentMethod): PaymentMethod { + return new PaymentMethod(stripePaymentMethod.id, { + line1: stripePaymentMethod.billing_details.address?.line1, + line2: stripePaymentMethod.billing_details.address?.line2, + city: stripePaymentMethod.billing_details.address?.city, + state: stripePaymentMethod.billing_details.address?.state, + country: stripePaymentMethod.billing_details.address?.country, + postalCode: stripePaymentMethod.billing_details.address?.postal_code, + }); + } + + getId(): string { + return this.id; + } + + getAddress(): Address | undefined { + return this.address; + } +} diff --git a/src/infrastructure/domain/ports/payments.adapter.ts b/src/infrastructure/domain/ports/payments.adapter.ts index 4a2283ca..592e66f4 100644 --- a/src/infrastructure/domain/ports/payments.adapter.ts +++ b/src/infrastructure/domain/ports/payments.adapter.ts @@ -1,8 +1,10 @@ -import { Customer, CreateCustomerParams } from '../entities/customer'; +import { Customer, CreateCustomerParams, UpdateCustomerParams } from '../entities/customer'; +import { PaymentMethod } from '../entities/paymentMethod'; export interface PaymentsAdapter { createCustomer: (params: CreateCustomerParams) => Promise; - updateCustomer: (customerId: Customer['id'], params: Partial) => Promise; + updateCustomer: (customerId: Customer['id'], params: Partial) => Promise; getCustomer: (customerId: Customer['id']) => Promise; searchCustomer: (email: Customer['email']) => Promise; + retrievePaymentMethod: (paymentMethodId: PaymentMethod['id']) => Promise; } diff --git a/src/infrastructure/domain/types.ts b/src/infrastructure/domain/types.ts new file mode 100644 index 00000000..34686f0e --- /dev/null +++ b/src/infrastructure/domain/types.ts @@ -0,0 +1,8 @@ +export interface Address { + line1?: string | null; + line2?: string | null; + city?: string | null; + state?: string | null; + country?: string | null; + postalCode?: string | null; +} diff --git a/src/services/payment.service.ts b/src/services/payment.service.ts index 3b579577..7a99f2d4 100644 --- a/src/services/payment.service.ts +++ b/src/services/payment.service.ts @@ -35,7 +35,7 @@ import { SetupIntent, PaymentMethod, CustomerSource, - Customer, + Customer as StripeCustomer, } from '../types/stripe'; import { PaymentIntent, PromotionCode, PriceByIdResponse, Reason } from '../types/payment'; import { @@ -427,15 +427,6 @@ export class PaymentService { }; } - async updateCustomerBillingInfo( - customerId: CustomerId, - payload: Pick, - ): Promise { - const customer = await this.provider.customers.update(customerId, payload); - - return customer; - } - async subscribe( customerId: CustomerId, priceId: PriceId, @@ -725,7 +716,7 @@ export class PaymentService { }); } - async getCustomersByEmail(customerEmail: CustomerEmail): Promise { + async getCustomersByEmail(customerEmail: CustomerEmail): Promise { const res = await this.provider.customers.list({ email: customerEmail as string }); return res.data; @@ -1553,36 +1544,6 @@ export class PaymentService { }); } - async updateCustomer( - customerId: Stripe.Customer['id'], - updatableAttributes: { - customer?: Partial>; - tax?: { - id: string; - type: Stripe.TaxIdCreateParams.Type; - }; - }, - additionalOptions?: Partial, - ): Promise { - if (updatableAttributes.customer && Object.keys(updatableAttributes.customer).length > 0) { - await this.provider.customers.update(customerId, { - name: updatableAttributes.customer.name, - ...additionalOptions, - }); - } - if (updatableAttributes.tax) { - await this.provider.taxIds.create({ - owner: { - customer: customerId, - type: 'customer', - }, - - type: updatableAttributes.tax.type, - value: updatableAttributes.tax.id, - }); - } - } - async getCryptoCurrencies() { const currencies = await this.bit2MeService.getCurrencies(); diff --git a/src/webhooks/index.ts b/src/webhooks/index.ts index 7ac2e5aa..3452c3fb 100644 --- a/src/webhooks/index.ts +++ b/src/webhooks/index.ts @@ -96,18 +96,19 @@ export default function ( case 'payment_intent.succeeded': { const eventData = event.data.object; - const paymentMethod = await stripe.paymentMethods.retrieve(eventData.payment_method as string); - const userAddressBillingDetails = paymentMethod.billing_details.address; + + const paymentMethod = await stripePaymentsAdapter.retrievePaymentMethod(eventData.payment_method as string); + const userAddressBillingDetails = paymentMethod.getAddress(); if (userAddressBillingDetails) { - await stripe.customers.update(eventData.customer as string, { + await stripePaymentsAdapter.updateCustomer(eventData.customer as string, { address: { - city: userAddressBillingDetails.city as string, - line1: userAddressBillingDetails.line1 as string, - line2: userAddressBillingDetails.line2 as string, - country: userAddressBillingDetails.country as string, - postal_code: userAddressBillingDetails.postal_code as string, - state: userAddressBillingDetails.state as string, + city: userAddressBillingDetails.city, + line1: userAddressBillingDetails.line1, + line2: userAddressBillingDetails.line2, + country: userAddressBillingDetails.country, + postalCode: userAddressBillingDetails.postalCode, + state: userAddressBillingDetails.state, }, }); } diff --git a/tests/src/controller/checkout.controller.test.ts b/tests/src/controller/checkout.controller.test.ts index 7aa8ef76..c293cf16 100644 --- a/tests/src/controller/checkout.controller.test.ts +++ b/tests/src/controller/checkout.controller.test.ts @@ -76,7 +76,9 @@ describe('Checkout controller', () => { jest.spyOn(verifyRecaptcha, 'verifyRecaptcha').mockResolvedValue(true); jest.spyOn(UsersService.prototype, 'findUserByUuid').mockResolvedValue(mockedUser); - const updateCustomerSpy = jest.spyOn(PaymentService.prototype, 'updateCustomer').mockResolvedValue(); + const updateCustomerSpy = jest + .spyOn(StripePaymentsAdapter.prototype, 'updateCustomer') + .mockResolvedValue({} as any); const response = await app.inject({ path: '/checkout/customer', @@ -93,24 +95,17 @@ describe('Checkout controller', () => { customerId: mockedUser.customerId, token: userToken, }); - expect(updateCustomerSpy).toHaveBeenCalledWith( - mockedUser.customerId, - { - customer: { - name: customerData.customerName, - }, - }, - { - email: userEmail, - address: { - line1: customerData.lineAddress1, - line2: customerData.lineAddress2, - city: customerData.city, - postal_code: customerData.postalCode, - country: customerData.country, - }, + expect(updateCustomerSpy).toHaveBeenCalledWith(mockedUser.customerId, { + name: customerData.customerName, + email: userEmail, + address: { + line1: customerData.lineAddress1, + line2: customerData.lineAddress2, + city: customerData.city, + postalCode: customerData.postalCode, + country: customerData.country, }, - ); + }); }); test('when the user does not exist, then a new customer is created and saved in the database and the customer id and its token are returned', async () => { @@ -192,7 +187,7 @@ describe('Checkout controller', () => { jest.spyOn(verifyRecaptcha, 'verifyRecaptcha').mockResolvedValue(true); jest.spyOn(UsersService.prototype, 'findUserByUuid').mockResolvedValue(mockedUser); - jest.spyOn(PaymentService.prototype, 'updateCustomer').mockResolvedValue(); + jest.spyOn(StripePaymentsAdapter.prototype, 'updateCustomer').mockResolvedValue({} as any); const attachVatIdSpy = jest .spyOn(PaymentService.prototype, 'getVatIdAndAttachTaxIdToCustomer') .mockResolvedValue(); @@ -235,7 +230,7 @@ describe('Checkout controller', () => { jest.spyOn(verifyRecaptcha, 'verifyRecaptcha').mockResolvedValue(true); jest.spyOn(UsersService.prototype, 'findUserByUuid').mockResolvedValue(mockedUser); - jest.spyOn(PaymentService.prototype, 'updateCustomer').mockResolvedValue(); + jest.spyOn(StripePaymentsAdapter.prototype, 'updateCustomer').mockResolvedValue({} as any); const attachVatIdSpy = jest.spyOn(PaymentService.prototype, 'getVatIdAndAttachTaxIdToCustomer'); const response = await app.inject({ diff --git a/tests/src/infrastructure/adapters/stripe.adapter.test.ts b/tests/src/infrastructure/adapters/stripe.adapter.test.ts index 58738569..97e9bbaa 100644 --- a/tests/src/infrastructure/adapters/stripe.adapter.test.ts +++ b/tests/src/infrastructure/adapters/stripe.adapter.test.ts @@ -1,8 +1,9 @@ -import { getCustomer } from '../../fixtures'; +import { getCustomer, getPaymentMethod } from '../../fixtures'; import { stripePaymentsAdapter } from '../../../../src/infrastructure/adapters/stripe.adapter'; import Stripe from 'stripe'; import { Customer } from '../../../../src/infrastructure/domain/entities/customer'; import { UserNotFoundError } from '../../../../src/errors/PaymentErrors'; +import { PaymentMethod } from '../../../../src/infrastructure/domain/entities/paymentMethod'; describe('Stripe Adapter', () => { describe('Create customer', () => { @@ -41,10 +42,68 @@ describe('Stripe Adapter', () => { const updatedCustomer = await stripePaymentsAdapter.updateCustomer(mockedCustomer.id, { email: mockedCustomer.email as string, name: mockedCustomer.name as string, + address: { + line1: mockedCustomer.address?.line1 ?? '', + line2: mockedCustomer.address?.line2 ?? '', + city: mockedCustomer.address?.city ?? '', + state: mockedCustomer.address?.state ?? '', + country: mockedCustomer.address?.country ?? '', + postalCode: mockedCustomer.address?.postal_code ?? '', + }, }); expect(updatedCustomer).toStrictEqual(Customer.toDomain(mockedCustomer)); }); + + test('When address is not provided, then the existing address should be preserved', async () => { + const originalAddress = { + postal_code: '08001', + country: 'ES', + city: 'Barcelona', + line1: 'Carrer Major 1', + line2: 'Piso 2', + state: 'Catalunya', + }; + + const initialCustomer = getCustomer({ + name: 'Original Name', + email: 'original@internxt.com', + address: originalAddress, + }); + + const updatedCustomer = getCustomer({ + id: initialCustomer.id, + name: 'Updated Name', + email: 'original@internxt.com', + address: originalAddress, + }); + + const updateSpy = jest + .spyOn(stripePaymentsAdapter.getInstance().customers, 'update') + .mockResolvedValue(updatedCustomer as Stripe.Response); + + const result = await stripePaymentsAdapter.updateCustomer(initialCustomer.id, { + name: 'Updated Name', + }); + + expect(updateSpy).toHaveBeenCalledWith(initialCustomer.id, { + name: 'Updated Name', + }); + expect(updateSpy).toHaveBeenCalledWith( + expect.any(String), + expect.not.objectContaining({ address: expect.anything() }), + ); + + expect(result.name).toBe('Updated Name'); + expect(result.address).toStrictEqual({ + line1: originalAddress.line1, + line2: originalAddress.line2, + city: originalAddress.city, + state: originalAddress.state, + country: originalAddress.country, + postalCode: originalAddress.postal_code, + }); + }); }); describe('Get customer', () => { @@ -97,4 +156,18 @@ describe('Stripe Adapter', () => { await expect(stripePaymentsAdapter.searchCustomer(mockedCustomer.email as string)).rejects.toThrow(mockedError); }); }); + + describe('Get Payment methods', () => { + test('When retrieving a payment method, then the payment method is returned', async () => { + const mockedPaymentMethod = getPaymentMethod(); + + jest + .spyOn(stripePaymentsAdapter.getInstance().paymentMethods, 'retrieve') + .mockResolvedValue(mockedPaymentMethod as Stripe.Response); + + const paymentMethod = await stripePaymentsAdapter.retrievePaymentMethod(mockedPaymentMethod.id); + + expect(paymentMethod).toStrictEqual(PaymentMethod.toDomain(mockedPaymentMethod)); + }); + }); }); diff --git a/tests/src/infrastructure/domain/entities/customer.test.ts b/tests/src/infrastructure/domain/entities/customer.test.ts index c465c27b..d7300afa 100644 --- a/tests/src/infrastructure/domain/entities/customer.test.ts +++ b/tests/src/infrastructure/domain/entities/customer.test.ts @@ -4,7 +4,6 @@ import { BadRequestError } from '../../../../../src/errors/Errors'; describe('Customer entity', () => { const mockedCustomer = getCustomer(); - const badRequestNotFoundError = new BadRequestError(); test('When converting the customer to domain, then the customer is created successfully', () => { const customer = Customer.toDomain(mockedCustomer); @@ -63,11 +62,4 @@ describe('Customer entity', () => { expect(() => Customer.toDomain(customerWithoutEmail)).toThrow(badRequestNotFoundError); }); - - test('When converting a customer without address, then an error is thrown', () => { - const badRequestNotFoundError = new BadRequestError('Customer address is required'); - const customerWithoutAddress = { ...mockedCustomer, address: null }; - - expect(() => Customer.toDomain(customerWithoutAddress)).toThrow(badRequestNotFoundError); - }); }); diff --git a/tests/src/infrastructure/domain/entities/paymentMethod.test.ts b/tests/src/infrastructure/domain/entities/paymentMethod.test.ts new file mode 100644 index 00000000..59a75f94 --- /dev/null +++ b/tests/src/infrastructure/domain/entities/paymentMethod.test.ts @@ -0,0 +1,43 @@ +import { PaymentMethod } from '../../../../../src/infrastructure/domain/entities/paymentMethod'; +import { getPaymentMethod } from '../../../fixtures'; + +describe('Payment method entity', () => { + test('When creating a payment method, then it is created successfully', () => { + const mockedPaymentMethod = getPaymentMethod(); + + const paymentMethod = PaymentMethod.toDomain(mockedPaymentMethod); + + expect(paymentMethod.getId()).toStrictEqual(mockedPaymentMethod.id); + expect(paymentMethod.getAddress()).toStrictEqual({ + line1: mockedPaymentMethod.billing_details.address?.line1, + line2: mockedPaymentMethod.billing_details.address?.line2, + city: mockedPaymentMethod.billing_details.address?.city, + state: mockedPaymentMethod.billing_details.address?.state, + country: mockedPaymentMethod.billing_details.address?.country, + postalCode: mockedPaymentMethod.billing_details.address?.postal_code, + }); + }); + + test('When requesting the payment method id, then the payment method id is returned', () => { + const mockedPaymentMethod = getPaymentMethod(); + + const paymentMethod = PaymentMethod.toDomain(mockedPaymentMethod); + + expect(paymentMethod.getId()).toStrictEqual(mockedPaymentMethod.id); + }); + + test('When requesting the payment method address, then the payment method address is returned', () => { + const mockedPaymentMethod = getPaymentMethod(); + + const paymentMethod = PaymentMethod.toDomain(mockedPaymentMethod); + + expect(paymentMethod.getAddress()).toStrictEqual({ + line1: mockedPaymentMethod.billing_details.address?.line1, + line2: mockedPaymentMethod.billing_details.address?.line2, + city: mockedPaymentMethod.billing_details.address?.city, + state: mockedPaymentMethod.billing_details.address?.state, + country: mockedPaymentMethod.billing_details.address?.country, + postalCode: mockedPaymentMethod.billing_details.address?.postal_code, + }); + }); +}); diff --git a/tests/src/webhooks/webhook.test.ts b/tests/src/webhooks/webhook.test.ts index d4a5b22f..0165a5e6 100644 --- a/tests/src/webhooks/webhook.test.ts +++ b/tests/src/webhooks/webhook.test.ts @@ -1,7 +1,7 @@ import { FastifyInstance } from 'fastify'; import { closeServerAndDatabase, initializeServerAndDatabase } from '../utils/initializeServer'; import Stripe from 'stripe'; -import { getCustomer, getInvoice, getPaymentIntent } from '../fixtures'; +import { getCustomer, getInvoice, getPaymentIntent, getPaymentMethod } from '../fixtures'; import handleFundsCaptured from '../../../src/webhooks/handleFundsCaptured'; import { PaymentService } from '../../../src/services/payment.service'; import { ObjectStorageService } from '../../../src/services/objectStorage.service'; @@ -10,6 +10,7 @@ import handleInvoicePaymentFailed from '../../../src/webhooks/handleInvoicePayme import { InvoiceCompletedHandler } from '../../../src/webhooks/events/invoices/InvoiceCompletedHandler'; import { StripePaymentsAdapter } from '../../../src/infrastructure/adapters/stripe.adapter'; import { Customer } from '../../../src/infrastructure/domain/entities/customer'; +import { PaymentMethod } from '../../../src/infrastructure/domain/entities/paymentMethod'; let app: FastifyInstance; @@ -99,6 +100,49 @@ describe('Webhook events', () => { ); }); + test('When the event payment_intent.succeeded is triggered, then the correct function is called', async () => { + const mockedPaymentIntent = getPaymentIntent(); + const mockedPaymentMethods = getPaymentMethod({ + id: mockedPaymentIntent.payment_method as string, + }); + const mockedPaymentMethodToDomain = PaymentMethod.toDomain(mockedPaymentMethods); + + const event = { + id: 'evt_3', + type: 'payment_intent.succeeded', + data: { object: mockedPaymentIntent }, + }; + const payloadToString = JSON.stringify(event); + + const getPaymentMethodSpy = jest + .spyOn(StripePaymentsAdapter.prototype, 'retrievePaymentMethod') + .mockResolvedValue(mockedPaymentMethodToDomain); + const updateCustomerSpy = jest + .spyOn(StripePaymentsAdapter.prototype, 'updateCustomer') + .mockResolvedValue({} as any); + + const header = Stripe.webhooks.generateTestHeaderString({ + payload: payloadToString, + secret, + }); + + const response = await app.inject({ + method: 'POST', + path: 'webhook', + body: Buffer.from(payloadToString), + headers: { + 'stripe-signature': header, + 'content-type': 'application/json', + }, + }); + + expect(response.statusCode).toBe(204); + expect(getPaymentMethodSpy).toHaveBeenCalledWith(mockedPaymentIntent.payment_method as string); + expect(updateCustomerSpy).toHaveBeenCalledWith(mockedPaymentIntent.customer as string, { + address: mockedPaymentMethodToDomain.address, + }); + }); + describe('Invoice Payment completed', () => { test('When the event invoice.paid is triggered, then the correct function is called', async () => { const mockedInvoice = getInvoice({