From e04f5d26a370c390ca6a45269359d3dc09a16cc5 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sat, 21 Sep 2024 23:59:54 +0200 Subject: [PATCH] feat: listen to stripe account updated webhook --- .../StripeWebhooksController.ts | 14 ++++--- .../server/src/interfaces/StripePayment.ts | 5 +++ .../Invoices/GetInvoicePaymentLinkMetadata.ts | 9 ++-- .../GetInvoicePaymentLinkTransformer.ts | 2 +- .../events/StripeWebhooksSubscriber.ts | 42 ++++++++++++++++++- packages/server/src/subscribers/events.ts | 3 +- .../CompanyLogoUpload.module.scss | 29 +++++++++++++ 7 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/CompanyLogoUpload.module.scss diff --git a/packages/server/src/api/controllers/StripeIntegration/StripeWebhooksController.ts b/packages/server/src/api/controllers/StripeIntegration/StripeWebhooksController.ts index a9ad582fc..fbe4098de 100644 --- a/packages/server/src/api/controllers/StripeIntegration/StripeWebhooksController.ts +++ b/packages/server/src/api/controllers/StripeIntegration/StripeWebhooksController.ts @@ -2,7 +2,7 @@ import { NextFunction, Request, Response, Router } from 'express'; import { Inject, Service } from 'typedi'; import bodyParser from 'body-parser'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; -import { StripeCheckoutSessionCompletedEventPayload } from '@/interfaces/StripePayment'; +import { StripeWebhookEventPayload } from '@/interfaces/StripePayment'; import { StripePaymentService } from '@/services/StripePayment/StripePaymentService'; import events from '@/subscribers/events'; import config from '@/config'; @@ -59,12 +59,16 @@ export class StripeWebhooksController { events.stripeWebhooks.onCheckoutSessionCompleted, { event, - } as StripeCheckoutSessionCompletedEventPayload + } as StripeWebhookEventPayload ); break; - case 'payment_intent.payment_failed': - // Handle failed payment intent - console.log('PaymentIntent failed.'); + case 'account.updated': + this.eventPublisher.emitAsync( + events.stripeWebhooks.onAccountUpdated, + { + event, + } as StripeWebhookEventPayload + ); break; // Add more cases as needed default: diff --git a/packages/server/src/interfaces/StripePayment.ts b/packages/server/src/interfaces/StripePayment.ts index 9f07c1e5a..a8777cf40 100644 --- a/packages/server/src/interfaces/StripePayment.ts +++ b/packages/server/src/interfaces/StripePayment.ts @@ -14,3 +14,8 @@ export interface StripeInvoiceCheckoutSessionPOJO { publishableKey: string; redirectTo: string; } + + +export interface StripeWebhookEventPayload { + event: any; +} \ No newline at end of file diff --git a/packages/server/src/services/Sales/Invoices/GetInvoicePaymentLinkMetadata.ts b/packages/server/src/services/Sales/Invoices/GetInvoicePaymentLinkMetadata.ts index 6a8f1b388..362cafa7c 100644 --- a/packages/server/src/services/Sales/Invoices/GetInvoicePaymentLinkMetadata.ts +++ b/packages/server/src/services/Sales/Invoices/GetInvoicePaymentLinkMetadata.ts @@ -10,7 +10,7 @@ import { initalizeTenantServices } from '@/api/middleware/TenantDependencyInject @Service() export class GetInvoicePaymentLinkMetadata { @Inject() - tenancy: HasTenancyService; + private tenancy: HasTenancyService; @Inject() private transformer: TransformerInjectable; @@ -23,12 +23,9 @@ export class GetInvoicePaymentLinkMetadata { async getInvoicePaymentLinkMeta(linkId: string) { const paymentLink = await PaymentLink.query() .findOne('linkId', linkId) + .where('resourceType', 'SaleInvoice') .throwIfNotFound(); - - // - if (paymentLink.resourceType !== 'SaleInvoice') { - throw new ServiceError(''); - } + // Validate the expiry at date. if (paymentLink.expiryAt) { const currentDate = moment(); diff --git a/packages/server/src/services/Sales/Invoices/GetInvoicePaymentLinkTransformer.ts b/packages/server/src/services/Sales/Invoices/GetInvoicePaymentLinkTransformer.ts index 0c75a49ae..268ba0586 100644 --- a/packages/server/src/services/Sales/Invoices/GetInvoicePaymentLinkTransformer.ts +++ b/packages/server/src/services/Sales/Invoices/GetInvoicePaymentLinkTransformer.ts @@ -82,7 +82,7 @@ class GetInvoicePaymentLinkEntryMetaTransformer extends ItemEntryTransformer { ]; }; - itemName(entry) { + public itemName(entry) { return entry.item.name; } diff --git a/packages/server/src/services/StripePayment/events/StripeWebhooksSubscriber.ts b/packages/server/src/services/StripePayment/events/StripeWebhooksSubscriber.ts index f9ad3ad5c..87cfc157a 100644 --- a/packages/server/src/services/StripePayment/events/StripeWebhooksSubscriber.ts +++ b/packages/server/src/services/StripePayment/events/StripeWebhooksSubscriber.ts @@ -1,10 +1,18 @@ import { Inject, Service } from 'typedi'; import events from '@/subscribers/events'; import { CreatePaymentReceiveStripePayment } from '../CreatePaymentReceivedStripePayment'; -import { StripeCheckoutSessionCompletedEventPayload } from '@/interfaces/StripePayment'; +import { + StripeCheckoutSessionCompletedEventPayload, + StripeWebhookEventPayload, +} from '@/interfaces/StripePayment'; +import HasTenancyService from '@/services/Tenancy/TenancyService'; +import { Tenant } from '@/system/models'; @Service() export class StripeWebhooksSubscriber { + @Inject() + private tenancy: HasTenancyService; + @Inject() private createPaymentReceiveStripePayment: CreatePaymentReceiveStripePayment; @@ -16,6 +24,10 @@ export class StripeWebhooksSubscriber { events.stripeWebhooks.onCheckoutSessionCompleted, this.handleCheckoutSessionCompleted.bind(this) ); + bus.subscribe( + events.stripeWebhooks.onAccountUpdated, + this.handleAccountUpdated.bind(this) + ); } /** @@ -35,10 +47,38 @@ export class StripeWebhooksSubscriber { // Convert from Stripe amount (cents) to normal amount (dollars) const amountInDollars = amount / 100; + // Creates a new payment received transaction. await this.createPaymentReceiveStripePayment.createPaymentReceived( tenantId, saleInvoiceId, amountInDollars ); } + + /** + * Handles the account updated. + * @param {StripeWebhookEventPayload} + */ + async handleAccountUpdated({ event }: StripeWebhookEventPayload) { + const { metadata } = event.data.object; + const account = event.data.object; + const tenantId = parseInt(metadata.tenantId, 10); + + if (!metadata?.paymentIntegrationId || !metadata.tenantId) return; + + // Find the tenant or throw not found error. + await Tenant.query().findById(tenantId).throwIfNotFound(); + + // Check if the account capabilities are active + if (account.capabilities.card_payments === 'active') { + const { PaymentIntegration } = this.tenancy.models(tenantId); + + // Marks the payment method integration as active. + await PaymentIntegration.query() + .findById(metadata?.paymentIntegrationId) + .patch({ + active: true, + }); + } + } } diff --git a/packages/server/src/subscribers/events.ts b/packages/server/src/subscribers/events.ts index d0444c425..89b01f4e2 100644 --- a/packages/server/src/subscribers/events.ts +++ b/packages/server/src/subscribers/events.ts @@ -728,6 +728,7 @@ export default { // Stripe Payment Webhooks stripeWebhooks: { - onCheckoutSessionCompleted: 'onStripeCheckoutSessionCompleted' + onCheckoutSessionCompleted: 'onStripeCheckoutSessionCompleted', + onAccountUpdated: 'onStripeAccountUpdated' } }; diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/CompanyLogoUpload.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/CompanyLogoUpload.module.scss new file mode 100644 index 000000000..929214b60 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/CompanyLogoUpload.module.scss @@ -0,0 +1,29 @@ +.root { + border: 1px solid #e1e1e1; + border-radius: 8px; + min-height: 120px; + padding: 10px; + justify-content: center; + position: relative; + + &:hover .removeButton{ + visibility: visible; + } +} + +.previewImage { + max-width: 200px; +} + +.title{ + font-size: 13px; + color: #738091; +} + +.removeButton{ + position: absolute; + top: 5px; + right: 5px; + border-radius: 32px; + visibility: hidden; +} \ No newline at end of file