diff --git a/packages/server/src/api/index.ts b/packages/server/src/api/index.ts index b55694d68..7de8d9398 100644 --- a/packages/server/src/api/index.ts +++ b/packages/server/src/api/index.ts @@ -68,6 +68,7 @@ import { StripeIntegrationController } from './controllers/StripeIntegration/Str import { ShareLinkController } from './controllers/ShareLink/ShareLinkController'; import { PublicSharableLinkController } from './controllers/ShareLink/PublicSharableLinkController'; import { PdfTemplatesController } from './controllers/PdfTemplates/PdfTemplatesController'; +import { PaymentServicesController } from './controllers/PaymentServices/PaymentServicesController'; export default () => { const app = Router(); @@ -160,7 +161,10 @@ export default () => { '/pdf-templates', Container.get(PdfTemplatesController).router() ); - + dashboard.use( + '/payment-services', + Container.get(PaymentServicesController).router() + ); dashboard.use('/', Container.get(ProjectTasksController).router()); dashboard.use('/', Container.get(ProjectTimesController).router()); dashboard.use('/', Container.get(WarehousesItemController).router()); diff --git a/packages/server/src/interfaces/SaleInvoice.ts b/packages/server/src/interfaces/SaleInvoice.ts index 29521fefe..5eef382f3 100644 --- a/packages/server/src/interfaces/SaleInvoice.ts +++ b/packages/server/src/interfaces/SaleInvoice.ts @@ -23,6 +23,16 @@ export interface PaymentIntegrationTransactionLinkEventPayload { trx?: Knex.Transaction } +export interface PaymentIntegrationTransactionLinkDeleteEventPayload { + tenantId: number; + enable: true; + paymentIntegrationId: number; + referenceType: string; + referenceId: number; + oldSaleInvoiceId: number; + trx?: Knex.Transaction +} + export interface ISaleInvoice { id: number; amount: number; @@ -156,9 +166,15 @@ export interface ISaleInvoiceEditingPayload { export interface ISaleInvoiceDeletePayload { tenantId: number; - saleInvoice: ISaleInvoice; + oldSaleInvoice: ISaleInvoice; saleInvoiceId: number; - trx: Knex.Transaction; +} + +export interface ISaleInvoiceDeletingPayload { + tenantId: number; + oldSaleInvoice: ISaleInvoice; + saleInvoiceId: number; + trx: Knex.Transaction; } export interface ISaleInvoiceDeletedPayload { diff --git a/packages/server/src/models/TransactionPaymentServiceEntry.ts b/packages/server/src/models/TransactionPaymentServiceEntry.ts index 5d1992634..59313830b 100644 --- a/packages/server/src/models/TransactionPaymentServiceEntry.ts +++ b/packages/server/src/models/TransactionPaymentServiceEntry.ts @@ -30,6 +30,17 @@ export class TransactionPaymentServiceEntry extends TenantModel { * Relationship mapping. */ static get relationMappings() { - return {}; + const { PaymentIntegration } = require('./PaymentIntegration'); + + return { + paymentIntegration: { + relation: TenantModel.BelongsToOneRelation, + modelClass: PaymentIntegration, + join: { + from: 'transactions_payment_methods.paymentIntegrationId', + to: 'payment_integrations.id', + }, + }, + }; } } diff --git a/packages/server/src/services/Attachments/events/AttachmentsOnSaleInvoice.ts b/packages/server/src/services/Attachments/events/AttachmentsOnSaleInvoice.ts index e6a51e4b6..717d956dc 100644 --- a/packages/server/src/services/Attachments/events/AttachmentsOnSaleInvoice.ts +++ b/packages/server/src/services/Attachments/events/AttachmentsOnSaleInvoice.ts @@ -3,7 +3,7 @@ import { isEmpty } from 'lodash'; import { ISaleInvoiceCreatedPayload, ISaleInvoiceCreatingPaylaod, - ISaleInvoiceDeletePayload, + ISaleInvoiceDeletingPayload, ISaleInvoiceEditedPayload, } from '@/interfaces'; import events from '@/subscribers/events'; @@ -146,13 +146,13 @@ export class AttachmentsOnSaleInvoiceCreated { */ private async handleUnlinkAttachmentsOnInvoiceDeleted({ tenantId, - saleInvoice, + oldSaleInvoice, trx, - }: ISaleInvoiceDeletePayload) { + }: ISaleInvoiceDeletingPayload) { await this.unlinkAttachmentService.unlinkAllModelKeys( tenantId, 'SaleInvoice', - saleInvoice.id, + oldSaleInvoice.id, trx ); } diff --git a/packages/server/src/services/Sales/Invoices/DeleteSaleInvoice.ts b/packages/server/src/services/Sales/Invoices/DeleteSaleInvoice.ts index e012e8e26..7d7ec3544 100644 --- a/packages/server/src/services/Sales/Invoices/DeleteSaleInvoice.ts +++ b/packages/server/src/services/Sales/Invoices/DeleteSaleInvoice.ts @@ -4,6 +4,7 @@ import { ISystemUser, ISaleInvoiceDeletePayload, ISaleInvoiceDeletedPayload, + ISaleInvoiceDeletingPayload, } from '@/interfaces'; import events from '@/subscribers/events'; import UnitOfWork from '@/services/UnitOfWork'; @@ -82,10 +83,10 @@ export class DeleteSaleInvoice { ) { const { saleInvoiceRepository } = this.tenancy.repositories(tenantId); - const saleInvoice = await saleInvoiceRepository.findOneById( - saleInvoiceId, - 'entries' - ); + const saleInvoice = await saleInvoiceRepository.findOneById(saleInvoiceId, [ + 'entries', + 'paymentMethods', + ]); if (!saleInvoice) { throw new ServiceError(ERRORS.SALE_INVOICE_NOT_FOUND); } @@ -118,15 +119,22 @@ export class DeleteSaleInvoice { // Validate the sale invoice has applied to credit note transaction. await this.validateInvoiceHasNoAppliedToCredit(tenantId, saleInvoiceId); + // Triggers `onSaleInvoiceDelete` event. + await this.eventPublisher.emitAsync(events.saleInvoice.onDelete, { + tenantId, + oldSaleInvoice, + saleInvoiceId, + } as ISaleInvoiceDeletePayload); + // Deletes sale invoice transaction and associate transactions with UOW env. return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { - // Triggers `onSaleInvoiceDelete` event. + // Triggers `onSaleInvoiceDeleting` event. await this.eventPublisher.emitAsync(events.saleInvoice.onDeleting, { tenantId, - saleInvoice: oldSaleInvoice, + oldSaleInvoice, saleInvoiceId, trx, - } as ISaleInvoiceDeletePayload); + } as ISaleInvoiceDeletingPayload); // Unlink the converted sale estimates from the given sale invoice. await this.unlockEstimateFromInvoice.unlinkConvertedEstimateFromInvoice( diff --git a/packages/server/src/services/Sales/Invoices/subscribers/InvoicePaymentIntegrationSubscriber.ts b/packages/server/src/services/Sales/Invoices/subscribers/InvoicePaymentIntegrationSubscriber.ts index e6e8ede70..8e167729e 100644 --- a/packages/server/src/services/Sales/Invoices/subscribers/InvoicePaymentIntegrationSubscriber.ts +++ b/packages/server/src/services/Sales/Invoices/subscribers/InvoicePaymentIntegrationSubscriber.ts @@ -3,7 +3,9 @@ import { omit } from 'lodash'; import events from '@/subscribers/events'; import { ISaleInvoiceCreatedPayload, + ISaleInvoiceDeletingPayload, PaymentIntegrationTransactionLink, + PaymentIntegrationTransactionLinkDeleteEventPayload, PaymentIntegrationTransactionLinkEventPayload, } from '@/interfaces'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; @@ -21,6 +23,10 @@ export class InvoicePaymentIntegrationSubscriber { events.saleInvoice.onCreated, this.handleCreatePaymentIntegrationEvents ); + bus.subscribe( + events.saleInvoice.onDeleting, + this.handleCreatePaymentIntegrationEventsOnDeleteInvoice + ); return bus; }; @@ -54,4 +60,34 @@ export class InvoicePaymentIntegrationSubscriber { } ); }; + + /** + * + * @param {ISaleInvoiceDeletingPayload} payload + */ + private handleCreatePaymentIntegrationEventsOnDeleteInvoice = ({ + tenantId, + oldSaleInvoice, + trx, + }: ISaleInvoiceDeletingPayload) => { + const paymentMethods = + oldSaleInvoice.paymentMethods?.filter((method) => method.enable) || []; + + paymentMethods.map( + async (paymentMethod: PaymentIntegrationTransactionLink) => { + const payload = { + ...omit(paymentMethod, ['id']), + tenantId, + oldSaleInvoiceId: oldSaleInvoice.id, + trx, + } as PaymentIntegrationTransactionLinkDeleteEventPayload; + + // Triggers `onPaymentIntegrationDeleteLink` event. + await this.eventPublisher.emitAsync( + events.paymentIntegrationLink.onPaymentIntegrationDeleteLink, + payload + ); + } + ); + }; } diff --git a/packages/server/src/services/StripePayment/DeleteStripePaymentLinkInvoice.ts b/packages/server/src/services/StripePayment/DeleteStripePaymentLinkInvoice.ts index 68c45387a..b29919a8f 100644 --- a/packages/server/src/services/StripePayment/DeleteStripePaymentLinkInvoice.ts +++ b/packages/server/src/services/StripePayment/DeleteStripePaymentLinkInvoice.ts @@ -1,7 +1,9 @@ +import { Knex } from 'knex'; import { Inject, Service } from 'typedi'; import HasTenancyService from '../Tenancy/TenancyService'; import { StripePaymentService } from './StripePaymentService'; -import { Knex } from 'knex'; +import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; +import events from '@/subscribers/events'; @Service() export class DeleteStripePaymentLinkInvoice { @@ -11,27 +13,53 @@ export class DeleteStripePaymentLinkInvoice { @Inject() private stripePayment: StripePaymentService; + @Inject() + private eventPublisher: EventPublisher; + /** * Deletes the Stripe payment link associates to the given sale invoice. - * @param {number} tenantId - * @param {number} invoiceId + * @param {number} tenantId - + * @param {number} invoiceId - + * @param {Knex.Transaction} knex - */ - async deletePaymentLink( + async deleteInvoicePaymentLink( tenantId: number, invoiceId: number, trx?: Knex.Transaction ): Promise { const { SaleInvoice } = this.tenancy.models(tenantId); - const invoice = await SaleInvoice.query().findById(invoiceId); - const stripeAcocunt = { stripeAccount: 'acct_1Px3dSPjeOqFxnPw' }; + const invoice = await SaleInvoice.query(trx) + .findById(invoiceId) + .withGraphFetched('paymentMethods.paymentIntegration') + .throwIfNotFound(); + + // It will be only one Stripe payment method associated to the invoice. + const stripePaymentMethod = invoice.paymentMethods?.find( + (method) => method.paymentIntegration?.service === 'Stripe' + ); + const stripeAccountId = stripePaymentMethod?.paymentIntegration?.accountId; + const paymentIntegrationId = stripePaymentMethod?.paymentIntegration?.id; + + if (invoice.stripePlinkId && stripeAccountId) { + const stripeAcocunt = { stripeAccount: stripeAccountId }; + const stripePlinkId = invoice.stripePlinkId; - if (invoice.stripePlinkId) { await this.stripePayment.stripe.paymentLinks.update( - invoice.stripePlinkId, + stripePlinkId, { active: false }, stripeAcocunt ); + // Triggers `onStripePaymentLinkInactivated` event. + await this.eventPublisher.emitAsync( + events.stripeIntegration.onPaymentLinkInactivated, + { + tenantId, + saleInvoiceId: invoiceId, + paymentIntegrationId, + stripePlinkId, + } + ); } } } diff --git a/packages/server/src/services/StripePayment/StripePaymentApplication.ts b/packages/server/src/services/StripePayment/StripePaymentApplication.ts new file mode 100644 index 000000000..c92552bfb --- /dev/null +++ b/packages/server/src/services/StripePayment/StripePaymentApplication.ts @@ -0,0 +1,69 @@ +import { Knex } from 'knex'; +import { Inject } from 'typedi'; +import { CreateStripeAccountService } from '@/api/controllers/StripeIntegration/CreateStripeAccountService'; +import { CreateStripeAccountDTO } from '@/api/controllers/StripeIntegration/types'; +import { SaleInvoiceStripePaymentLink } from './SaleInvoiceStripePaymentLink'; +import { DeleteStripePaymentLinkInvoice } from './DeleteStripePaymentLinkInvoice'; + +export class StripePaymentApplication { + @Inject() + private createStripeAccountService: CreateStripeAccountService; + + @Inject() + private saleInvoiceStripePaymentLinkService: SaleInvoiceStripePaymentLink; + + @Inject() + private deleteStripePaymentLinkInvoice: DeleteStripePaymentLinkInvoice; + + /** + * Creates a new Stripe account for Bigcapital. + * @param {number} tenantId + * @param {number} createStripeAccountDTO + */ + public createStripeAccount( + tenantId: number, + createStripeAccountDTO: CreateStripeAccountDTO + ) { + return this.createStripeAccountService.createStripeAccount( + tenantId, + createStripeAccountDTO + ); + } + + /** + * Creates a Stripe payment link for the given sale invoice. + * @param {number} tenantId - Tenant id. + * @param {number} stripeIntegrationId - Stripe integration id. + * @param {ISaleInvoice} saleInvoice - Sale invoice id. + * @returns {Promise} + */ + public createSaleInvoicePaymentLink( + tenantId: number, + stripeIntegrationId: number, + invoiceId: number + ) { + return this.saleInvoiceStripePaymentLinkService.createPaymentLink( + tenantId, + stripeIntegrationId, + invoiceId + ); + } + + /** + * Deletes the Stripe payment link associated with the given sale invoice. + * @param {number} tenantId - Tenant id. + * @param {number} invoiceId - Sale invoice id. + * @returns {Promise} + */ + public deleteInvoicePaymentLink( + tenantId: number, + invoiceId: number, + trx?: Knex.Transaction + ): Promise { + return this.deleteStripePaymentLinkInvoice.deleteInvoicePaymentLink( + tenantId, + invoiceId, + trx + ); + } +} diff --git a/packages/server/src/services/StripePayment/events/CreatePaymentLinkOnInvoiceCreated.ts b/packages/server/src/services/StripePayment/events/CreatePaymentLinkOnInvoiceCreated.ts index a09be533a..153cd68fd 100644 --- a/packages/server/src/services/StripePayment/events/CreatePaymentLinkOnInvoiceCreated.ts +++ b/packages/server/src/services/StripePayment/events/CreatePaymentLinkOnInvoiceCreated.ts @@ -1,7 +1,7 @@ import { Inject, Service } from 'typedi'; import { EventSubscriber } from '@/lib/EventPublisher/EventPublisher'; import { - ISaleInvoiceDeletedPayload, + PaymentIntegrationTransactionLinkDeleteEventPayload, PaymentIntegrationTransactionLinkEventPayload, } from '@/interfaces'; import { SaleInvoiceStripePaymentLink } from '../SaleInvoiceStripePaymentLink'; @@ -25,10 +25,10 @@ export class CreatePaymentLinkOnInvoiceCreated extends EventSubscriber { events.paymentIntegrationLink.onPaymentIntegrationLink, this.handleCreatePaymentLinkOnIntegrationLink ); - // bus.subscribe( - // events.saleInvoice.onDeleted, - // this.handleDeletePaymentLinkOnInvoiceDeleted - // ); + bus.subscribe( + events.paymentIntegrationLink.onPaymentIntegrationDeleteLink, + this.handleDeletePaymentLinkOnIntegrationLinkDelete + ); } /** @@ -59,13 +59,15 @@ export class CreatePaymentLinkOnInvoiceCreated extends EventSubscriber { * Deletes the Stripe payment link once the associated invoice deleted. * @param {ISaleInvoiceDeletedPayload} */ - private handleDeletePaymentLinkOnInvoiceDeleted = async ({ - saleInvoiceId, + private handleDeletePaymentLinkOnIntegrationLinkDelete = async ({ + oldSaleInvoiceId, tenantId, - }: ISaleInvoiceDeletedPayload) => { - await this.deleteStripePaymentLinkInvoice.deletePaymentLink( + trx, + }: PaymentIntegrationTransactionLinkDeleteEventPayload) => { + await this.deleteStripePaymentLinkInvoice.deleteInvoicePaymentLink( tenantId, - saleInvoiceId + oldSaleInvoiceId, + trx ); }; } diff --git a/packages/server/src/services/TransactionsLocking/SalesTransactionLockingGuardSubscriber.ts b/packages/server/src/services/TransactionsLocking/SalesTransactionLockingGuardSubscriber.ts index 9b80bca26..35f91d3ad 100644 --- a/packages/server/src/services/TransactionsLocking/SalesTransactionLockingGuardSubscriber.ts +++ b/packages/server/src/services/TransactionsLocking/SalesTransactionLockingGuardSubscriber.ts @@ -51,7 +51,7 @@ export default class SalesTransactionLockingGuardSubscriber { this.transactionLockinGuardOnInvoiceWritingoffCanceling ); bus.subscribe( - events.saleInvoice.onDeleting, + events.saleInvoice.onDelete, this.transactionLockingGuardOnInvoiceDeleting ); @@ -176,15 +176,15 @@ export default class SalesTransactionLockingGuardSubscriber { * @param {ISaleInvoiceDeletePayload} payload */ private transactionLockingGuardOnInvoiceDeleting = async ({ - saleInvoice, + oldSaleInvoice, tenantId, }: ISaleInvoiceDeletePayload) => { // Can't continue if the old invoice not published. - if (!saleInvoice.isDelivered) return; + if (!oldSaleInvoice.isDelivered) return; await this.salesLockingGuard.transactionLockingGuard( tenantId, - saleInvoice.invoiceDate + oldSaleInvoice.invoiceDate ); }; diff --git a/packages/server/src/subscribers/events.ts b/packages/server/src/subscribers/events.ts index 1631a92c8..f78c5fbd7 100644 --- a/packages/server/src/subscribers/events.ts +++ b/packages/server/src/subscribers/events.ts @@ -705,7 +705,8 @@ export default { // Payment methods integrations paymentIntegrationLink: { - onPaymentIntegrationLink: 'onPaymentIntegrationLink' + onPaymentIntegrationLink: 'onPaymentIntegrationLink', + onPaymentIntegrationDeleteLink: 'onPaymentIntegrationDeleteLink' }, // Stripe Payment Integration @@ -714,5 +715,6 @@ export default { onAccountDeleted: 'onStripeIntegrationAccountDeleted', onPaymentLinkCreated: 'onStripePaymentLinkCreated', + onPaymentLinkInactivated: 'onStripePaymentLinkInactivated' } };