feat: inactive associated Stripe payment link on invoice deleting

This commit is contained in:
Ahmed Bouhuolia
2024-09-18 23:41:59 +02:00
parent 4665f529e6
commit d2cd32a735
11 changed files with 214 additions and 38 deletions

View File

@@ -68,6 +68,7 @@ import { StripeIntegrationController } from './controllers/StripeIntegration/Str
import { ShareLinkController } from './controllers/ShareLink/ShareLinkController'; import { ShareLinkController } from './controllers/ShareLink/ShareLinkController';
import { PublicSharableLinkController } from './controllers/ShareLink/PublicSharableLinkController'; import { PublicSharableLinkController } from './controllers/ShareLink/PublicSharableLinkController';
import { PdfTemplatesController } from './controllers/PdfTemplates/PdfTemplatesController'; import { PdfTemplatesController } from './controllers/PdfTemplates/PdfTemplatesController';
import { PaymentServicesController } from './controllers/PaymentServices/PaymentServicesController';
export default () => { export default () => {
const app = Router(); const app = Router();
@@ -160,7 +161,10 @@ export default () => {
'/pdf-templates', '/pdf-templates',
Container.get(PdfTemplatesController).router() Container.get(PdfTemplatesController).router()
); );
dashboard.use(
'/payment-services',
Container.get(PaymentServicesController).router()
);
dashboard.use('/', Container.get(ProjectTasksController).router()); dashboard.use('/', Container.get(ProjectTasksController).router());
dashboard.use('/', Container.get(ProjectTimesController).router()); dashboard.use('/', Container.get(ProjectTimesController).router());
dashboard.use('/', Container.get(WarehousesItemController).router()); dashboard.use('/', Container.get(WarehousesItemController).router());

View File

@@ -23,6 +23,16 @@ export interface PaymentIntegrationTransactionLinkEventPayload {
trx?: Knex.Transaction trx?: Knex.Transaction
} }
export interface PaymentIntegrationTransactionLinkDeleteEventPayload {
tenantId: number;
enable: true;
paymentIntegrationId: number;
referenceType: string;
referenceId: number;
oldSaleInvoiceId: number;
trx?: Knex.Transaction
}
export interface ISaleInvoice { export interface ISaleInvoice {
id: number; id: number;
amount: number; amount: number;
@@ -156,9 +166,15 @@ export interface ISaleInvoiceEditingPayload {
export interface ISaleInvoiceDeletePayload { export interface ISaleInvoiceDeletePayload {
tenantId: number; tenantId: number;
saleInvoice: ISaleInvoice; oldSaleInvoice: ISaleInvoice;
saleInvoiceId: number; saleInvoiceId: number;
trx: Knex.Transaction; }
export interface ISaleInvoiceDeletingPayload {
tenantId: number;
oldSaleInvoice: ISaleInvoice;
saleInvoiceId: number;
trx: Knex.Transaction;
} }
export interface ISaleInvoiceDeletedPayload { export interface ISaleInvoiceDeletedPayload {

View File

@@ -30,6 +30,17 @@ export class TransactionPaymentServiceEntry extends TenantModel {
* Relationship mapping. * Relationship mapping.
*/ */
static get relationMappings() { 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',
},
},
};
} }
} }

View File

@@ -3,7 +3,7 @@ import { isEmpty } from 'lodash';
import { import {
ISaleInvoiceCreatedPayload, ISaleInvoiceCreatedPayload,
ISaleInvoiceCreatingPaylaod, ISaleInvoiceCreatingPaylaod,
ISaleInvoiceDeletePayload, ISaleInvoiceDeletingPayload,
ISaleInvoiceEditedPayload, ISaleInvoiceEditedPayload,
} from '@/interfaces'; } from '@/interfaces';
import events from '@/subscribers/events'; import events from '@/subscribers/events';
@@ -146,13 +146,13 @@ export class AttachmentsOnSaleInvoiceCreated {
*/ */
private async handleUnlinkAttachmentsOnInvoiceDeleted({ private async handleUnlinkAttachmentsOnInvoiceDeleted({
tenantId, tenantId,
saleInvoice, oldSaleInvoice,
trx, trx,
}: ISaleInvoiceDeletePayload) { }: ISaleInvoiceDeletingPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys( await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId, tenantId,
'SaleInvoice', 'SaleInvoice',
saleInvoice.id, oldSaleInvoice.id,
trx trx
); );
} }

View File

@@ -4,6 +4,7 @@ import {
ISystemUser, ISystemUser,
ISaleInvoiceDeletePayload, ISaleInvoiceDeletePayload,
ISaleInvoiceDeletedPayload, ISaleInvoiceDeletedPayload,
ISaleInvoiceDeletingPayload,
} from '@/interfaces'; } from '@/interfaces';
import events from '@/subscribers/events'; import events from '@/subscribers/events';
import UnitOfWork from '@/services/UnitOfWork'; import UnitOfWork from '@/services/UnitOfWork';
@@ -82,10 +83,10 @@ export class DeleteSaleInvoice {
) { ) {
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId); const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
const saleInvoice = await saleInvoiceRepository.findOneById( const saleInvoice = await saleInvoiceRepository.findOneById(saleInvoiceId, [
saleInvoiceId, 'entries',
'entries' 'paymentMethods',
); ]);
if (!saleInvoice) { if (!saleInvoice) {
throw new ServiceError(ERRORS.SALE_INVOICE_NOT_FOUND); 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. // Validate the sale invoice has applied to credit note transaction.
await this.validateInvoiceHasNoAppliedToCredit(tenantId, saleInvoiceId); 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. // Deletes sale invoice transaction and associate transactions with UOW env.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onSaleInvoiceDelete` event. // Triggers `onSaleInvoiceDeleting` event.
await this.eventPublisher.emitAsync(events.saleInvoice.onDeleting, { await this.eventPublisher.emitAsync(events.saleInvoice.onDeleting, {
tenantId, tenantId,
saleInvoice: oldSaleInvoice, oldSaleInvoice,
saleInvoiceId, saleInvoiceId,
trx, trx,
} as ISaleInvoiceDeletePayload); } as ISaleInvoiceDeletingPayload);
// Unlink the converted sale estimates from the given sale invoice. // Unlink the converted sale estimates from the given sale invoice.
await this.unlockEstimateFromInvoice.unlinkConvertedEstimateFromInvoice( await this.unlockEstimateFromInvoice.unlinkConvertedEstimateFromInvoice(

View File

@@ -3,7 +3,9 @@ import { omit } from 'lodash';
import events from '@/subscribers/events'; import events from '@/subscribers/events';
import { import {
ISaleInvoiceCreatedPayload, ISaleInvoiceCreatedPayload,
ISaleInvoiceDeletingPayload,
PaymentIntegrationTransactionLink, PaymentIntegrationTransactionLink,
PaymentIntegrationTransactionLinkDeleteEventPayload,
PaymentIntegrationTransactionLinkEventPayload, PaymentIntegrationTransactionLinkEventPayload,
} from '@/interfaces'; } from '@/interfaces';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
@@ -21,6 +23,10 @@ export class InvoicePaymentIntegrationSubscriber {
events.saleInvoice.onCreated, events.saleInvoice.onCreated,
this.handleCreatePaymentIntegrationEvents this.handleCreatePaymentIntegrationEvents
); );
bus.subscribe(
events.saleInvoice.onDeleting,
this.handleCreatePaymentIntegrationEventsOnDeleteInvoice
);
return bus; 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
);
}
);
};
} }

View File

@@ -1,7 +1,9 @@
import { Knex } from 'knex';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import HasTenancyService from '../Tenancy/TenancyService'; import HasTenancyService from '../Tenancy/TenancyService';
import { StripePaymentService } from './StripePaymentService'; import { StripePaymentService } from './StripePaymentService';
import { Knex } from 'knex'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import events from '@/subscribers/events';
@Service() @Service()
export class DeleteStripePaymentLinkInvoice { export class DeleteStripePaymentLinkInvoice {
@@ -11,27 +13,53 @@ export class DeleteStripePaymentLinkInvoice {
@Inject() @Inject()
private stripePayment: StripePaymentService; private stripePayment: StripePaymentService;
@Inject()
private eventPublisher: EventPublisher;
/** /**
* Deletes the Stripe payment link associates to the given sale invoice. * Deletes the Stripe payment link associates to the given sale invoice.
* @param {number} tenantId * @param {number} tenantId -
* @param {number} invoiceId * @param {number} invoiceId -
* @param {Knex.Transaction} knex -
*/ */
async deletePaymentLink( async deleteInvoicePaymentLink(
tenantId: number, tenantId: number,
invoiceId: number, invoiceId: number,
trx?: Knex.Transaction trx?: Knex.Transaction
): Promise<void> { ): Promise<void> {
const { SaleInvoice } = this.tenancy.models(tenantId); 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( await this.stripePayment.stripe.paymentLinks.update(
invoice.stripePlinkId, stripePlinkId,
{ active: false }, { active: false },
stripeAcocunt stripeAcocunt
); );
// Triggers `onStripePaymentLinkInactivated` event.
await this.eventPublisher.emitAsync(
events.stripeIntegration.onPaymentLinkInactivated,
{
tenantId,
saleInvoiceId: invoiceId,
paymentIntegrationId,
stripePlinkId,
}
);
} }
} }
} }

View File

@@ -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<string>}
*/
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<void>}
*/
public deleteInvoicePaymentLink(
tenantId: number,
invoiceId: number,
trx?: Knex.Transaction
): Promise<void> {
return this.deleteStripePaymentLinkInvoice.deleteInvoicePaymentLink(
tenantId,
invoiceId,
trx
);
}
}

View File

@@ -1,7 +1,7 @@
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { EventSubscriber } from '@/lib/EventPublisher/EventPublisher'; import { EventSubscriber } from '@/lib/EventPublisher/EventPublisher';
import { import {
ISaleInvoiceDeletedPayload, PaymentIntegrationTransactionLinkDeleteEventPayload,
PaymentIntegrationTransactionLinkEventPayload, PaymentIntegrationTransactionLinkEventPayload,
} from '@/interfaces'; } from '@/interfaces';
import { SaleInvoiceStripePaymentLink } from '../SaleInvoiceStripePaymentLink'; import { SaleInvoiceStripePaymentLink } from '../SaleInvoiceStripePaymentLink';
@@ -25,10 +25,10 @@ export class CreatePaymentLinkOnInvoiceCreated extends EventSubscriber {
events.paymentIntegrationLink.onPaymentIntegrationLink, events.paymentIntegrationLink.onPaymentIntegrationLink,
this.handleCreatePaymentLinkOnIntegrationLink this.handleCreatePaymentLinkOnIntegrationLink
); );
// bus.subscribe( bus.subscribe(
// events.saleInvoice.onDeleted, events.paymentIntegrationLink.onPaymentIntegrationDeleteLink,
// this.handleDeletePaymentLinkOnInvoiceDeleted this.handleDeletePaymentLinkOnIntegrationLinkDelete
// ); );
} }
/** /**
@@ -59,13 +59,15 @@ export class CreatePaymentLinkOnInvoiceCreated extends EventSubscriber {
* Deletes the Stripe payment link once the associated invoice deleted. * Deletes the Stripe payment link once the associated invoice deleted.
* @param {ISaleInvoiceDeletedPayload} * @param {ISaleInvoiceDeletedPayload}
*/ */
private handleDeletePaymentLinkOnInvoiceDeleted = async ({ private handleDeletePaymentLinkOnIntegrationLinkDelete = async ({
saleInvoiceId, oldSaleInvoiceId,
tenantId, tenantId,
}: ISaleInvoiceDeletedPayload) => { trx,
await this.deleteStripePaymentLinkInvoice.deletePaymentLink( }: PaymentIntegrationTransactionLinkDeleteEventPayload) => {
await this.deleteStripePaymentLinkInvoice.deleteInvoicePaymentLink(
tenantId, tenantId,
saleInvoiceId oldSaleInvoiceId,
trx
); );
}; };
} }

View File

@@ -51,7 +51,7 @@ export default class SalesTransactionLockingGuardSubscriber {
this.transactionLockinGuardOnInvoiceWritingoffCanceling this.transactionLockinGuardOnInvoiceWritingoffCanceling
); );
bus.subscribe( bus.subscribe(
events.saleInvoice.onDeleting, events.saleInvoice.onDelete,
this.transactionLockingGuardOnInvoiceDeleting this.transactionLockingGuardOnInvoiceDeleting
); );
@@ -176,15 +176,15 @@ export default class SalesTransactionLockingGuardSubscriber {
* @param {ISaleInvoiceDeletePayload} payload * @param {ISaleInvoiceDeletePayload} payload
*/ */
private transactionLockingGuardOnInvoiceDeleting = async ({ private transactionLockingGuardOnInvoiceDeleting = async ({
saleInvoice, oldSaleInvoice,
tenantId, tenantId,
}: ISaleInvoiceDeletePayload) => { }: ISaleInvoiceDeletePayload) => {
// Can't continue if the old invoice not published. // Can't continue if the old invoice not published.
if (!saleInvoice.isDelivered) return; if (!oldSaleInvoice.isDelivered) return;
await this.salesLockingGuard.transactionLockingGuard( await this.salesLockingGuard.transactionLockingGuard(
tenantId, tenantId,
saleInvoice.invoiceDate oldSaleInvoice.invoiceDate
); );
}; };

View File

@@ -705,7 +705,8 @@ export default {
// Payment methods integrations // Payment methods integrations
paymentIntegrationLink: { paymentIntegrationLink: {
onPaymentIntegrationLink: 'onPaymentIntegrationLink' onPaymentIntegrationLink: 'onPaymentIntegrationLink',
onPaymentIntegrationDeleteLink: 'onPaymentIntegrationDeleteLink'
}, },
// Stripe Payment Integration // Stripe Payment Integration
@@ -714,5 +715,6 @@ export default {
onAccountDeleted: 'onStripeIntegrationAccountDeleted', onAccountDeleted: 'onStripeIntegrationAccountDeleted',
onPaymentLinkCreated: 'onStripePaymentLinkCreated', onPaymentLinkCreated: 'onStripePaymentLinkCreated',
onPaymentLinkInactivated: 'onStripePaymentLinkInactivated'
} }
}; };