mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 13:50:31 +00:00
feat: integrate Stripe payment to invoices
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import HasTenancyService from '../Tenancy/TenancyService';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import { GetPaymentServicesSpecificInvoiceTransformer } from './GetPaymentServicesSpecificInvoiceTransformer';
|
||||
|
||||
@Service()
|
||||
export class GetPaymentServicesSpecificInvoice {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transform: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieves the payment services of the given invoice.
|
||||
* @param {number} tenantId
|
||||
* @param {number} invoiceId
|
||||
* @returns
|
||||
*/
|
||||
async getPaymentServicesInvoice(tenantId: number) {
|
||||
const { PaymentIntegration } = this.tenancy.models(tenantId);
|
||||
|
||||
const paymentGateways = await PaymentIntegration.query()
|
||||
.where('enable', true)
|
||||
.orderBy('name', 'ASC');
|
||||
|
||||
return this.transform.transform(
|
||||
tenantId,
|
||||
paymentGateways,
|
||||
new GetPaymentServicesSpecificInvoiceTransformer()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
|
||||
export class GetPaymentServicesSpecificInvoiceTransformer extends Transformer {
|
||||
/**
|
||||
* Exclude attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['accountId'];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { GetPaymentServicesSpecificInvoice } from './GetPaymentServicesSpecificInvoice';
|
||||
|
||||
@Service()
|
||||
export class PaymentServicesApplication {
|
||||
@Inject()
|
||||
private getPaymentServicesSpecificInvoice: GetPaymentServicesSpecificInvoice;
|
||||
|
||||
/**
|
||||
* Retrieves the payment services for a specific invoice.
|
||||
* @param {number} tenantId - The ID of the tenant.
|
||||
* @param {number} invoiceId - The ID of the invoice.
|
||||
* @returns {Promise<any>} The payment services for the specified invoice.
|
||||
*/
|
||||
async getPaymentServicesForInvoice(tenantId: number): Promise<any> {
|
||||
return this.getPaymentServicesSpecificInvoice.getPaymentServicesInvoice(
|
||||
tenantId
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { omit } from 'lodash';
|
||||
import events from '@/subscribers/events';
|
||||
import {
|
||||
ISaleInvoiceCreatedPayload,
|
||||
PaymentIntegrationTransactionLink,
|
||||
PaymentIntegrationTransactionLinkEventPayload,
|
||||
} from '@/interfaces';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
|
||||
@Service()
|
||||
export class InvoicePaymentIntegrationSubscriber {
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
*/
|
||||
public attach = (bus) => {
|
||||
bus.subscribe(
|
||||
events.saleInvoice.onCreated,
|
||||
this.handleCreatePaymentIntegrationEvents
|
||||
);
|
||||
return bus;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the creation of payment integration events when a sale invoice is created.
|
||||
* This method filters enabled payment methods from the invoice and emits a payment
|
||||
* integration link event for each method.
|
||||
* @param {ISaleInvoiceCreatedPayload} payload - The payload containing sale invoice creation details.
|
||||
*/
|
||||
private handleCreatePaymentIntegrationEvents = ({
|
||||
tenantId,
|
||||
saleInvoiceDTO,
|
||||
saleInvoice,
|
||||
trx,
|
||||
}: ISaleInvoiceCreatedPayload) => {
|
||||
const paymentMethods =
|
||||
saleInvoice.paymentMethods?.filter((method) => method.enable) || [];
|
||||
|
||||
paymentMethods.map(
|
||||
async (paymentMethod: PaymentIntegrationTransactionLink) => {
|
||||
const payload = {
|
||||
...omit(paymentMethod, ['id']),
|
||||
tenantId,
|
||||
saleInvoiceId: saleInvoice.id,
|
||||
trx,
|
||||
};
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.paymentIntegrationLink.onPaymentIntegrationLink,
|
||||
payload as PaymentIntegrationTransactionLinkEventPayload
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ISaleInvoice } from '@/interfaces';
|
||||
import { StripePaymentService } from './StripePaymentService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import HasTenancyService from '../Tenancy/TenancyService';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import { StripePaymentLinkCreatedEventPayload } from '@/interfaces/StripePayment';
|
||||
import { STRIPE_PAYMENT_LINK_REDIRECT } from './constants';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class SaleInvoiceStripePaymentLink {
|
||||
@@ -12,48 +15,131 @@ export class SaleInvoiceStripePaymentLink {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Creates a Stripe payment link for the given sale invoice.
|
||||
* @param {number} tenantId
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} stripeIntegrationId - Stripe integration id.
|
||||
* @param {ISaleInvoice} saleInvoice - Sale invoice id.
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async createPaymentLink(tenantId: number, saleInvoice: ISaleInvoice) {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
const saleInvoiceId = saleInvoice.id;
|
||||
async createPaymentLink(
|
||||
tenantId: number,
|
||||
stripeIntegrationId: number,
|
||||
invoiceId: number
|
||||
) {
|
||||
const { SaleInvoice, PaymentIntegration } = this.tenancy.models(tenantId);
|
||||
|
||||
try {
|
||||
const stripeAcocunt = { stripeAccount: 'acct_1Px3dSPjeOqFxnPw' };
|
||||
const price = await this.stripePayment.stripe.prices.create(
|
||||
{
|
||||
unit_amount: saleInvoice.total * 100,
|
||||
currency: 'usd',
|
||||
product_data: {
|
||||
name: saleInvoice.invoiceNo,
|
||||
},
|
||||
const stripeIntegration = await PaymentIntegration.query()
|
||||
.findById(stripeIntegrationId)
|
||||
.throwIfNotFound();
|
||||
const stripeAccountId = stripeIntegration.accountId;
|
||||
|
||||
const invoice = await SaleInvoice.query()
|
||||
.findById(invoiceId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Creates Stripe price.
|
||||
const price = await this.createStripePrice(invoice, stripeAccountId);
|
||||
|
||||
// Creates Stripe payment link.
|
||||
const paymentLink = await this.createStripePaymentLink(
|
||||
price.id,
|
||||
invoice,
|
||||
stripeAccountId,
|
||||
{ tenantId }
|
||||
);
|
||||
// Associate the payment link id to the invoice.
|
||||
await this.updateInvoiceWithPaymentLink(
|
||||
tenantId,
|
||||
invoiceId,
|
||||
paymentLink.id
|
||||
);
|
||||
// Triggers `onStripePaymentLinkCreated` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.stripeIntegration.onPaymentLinkCreated,
|
||||
{
|
||||
tenantId,
|
||||
stripeIntegrationId,
|
||||
saleInvoiceId: invoiceId,
|
||||
paymentLinkId: paymentLink.id,
|
||||
} as StripePaymentLinkCreatedEventPayload
|
||||
);
|
||||
return paymentLink.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Stripe price for the invoice.
|
||||
* @param {ISaleInvoice} invoice - Sale invoice.
|
||||
* @param {string} stripeAccountId - Stripe account id.
|
||||
* @returns {Promise<Stripe.Price>}
|
||||
*/
|
||||
private async createStripePrice(
|
||||
invoice: ISaleInvoice,
|
||||
stripeAccountId: string
|
||||
) {
|
||||
return this.stripePayment.stripe.prices.create(
|
||||
{
|
||||
unit_amount: invoice.total * 100,
|
||||
currency: 'usd',
|
||||
product_data: {
|
||||
name: invoice.invoiceNo,
|
||||
},
|
||||
stripeAcocunt
|
||||
);
|
||||
const paymentLinkInfo = {
|
||||
line_items: [{ price: price.id, quantity: 1 }],
|
||||
after_completion: {
|
||||
type: 'redirect',
|
||||
redirect: {
|
||||
url: STRIPE_PAYMENT_LINK_REDIRECT,
|
||||
},
|
||||
},
|
||||
{ stripeAccount: stripeAccountId }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Stripe payment link.
|
||||
* @param {string} priceId - Stripe price id.
|
||||
* @param {ISaleInvoice} invoice - Sale invoice.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {string} stripeAccountId - Stripe account id.
|
||||
* @returns {Promise<Stripe.PaymentLink>}
|
||||
*/
|
||||
private async createStripePaymentLink(
|
||||
priceId: string,
|
||||
invoice: ISaleInvoice,
|
||||
stripeAccountId: string,
|
||||
metadata: Record<string, any> = {}
|
||||
) {
|
||||
const paymentLinkInfo = {
|
||||
line_items: [{ price: priceId, quantity: 1 }],
|
||||
after_completion: {
|
||||
type: 'redirect',
|
||||
redirect: {
|
||||
url: STRIPE_PAYMENT_LINK_REDIRECT,
|
||||
},
|
||||
metadata: { saleInvoiceId, tenantId, resource: 'SaleInvoice' },
|
||||
};
|
||||
const paymentLink = await this.stripePayment.stripe.paymentLinks.create(
|
||||
paymentLinkInfo,
|
||||
stripeAcocunt
|
||||
);
|
||||
await SaleInvoice.query().findById(saleInvoiceId).patch({
|
||||
stripePlinkId: paymentLink.id,
|
||||
});
|
||||
return paymentLink.id;
|
||||
} catch (error) {
|
||||
console.error('Error creating payment link:', error);
|
||||
}
|
||||
},
|
||||
metadata: {
|
||||
saleInvoiceId: invoice.id,
|
||||
resource: 'SaleInvoice',
|
||||
...metadata,
|
||||
},
|
||||
};
|
||||
return this.stripePayment.stripe.paymentLinks.create(paymentLinkInfo, {
|
||||
stripeAccount: stripeAccountId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the sale invoice with the Stripe payment link id.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} invoiceId - Sale invoice id.
|
||||
* @param {string} paymentLinkId - Stripe payment link id.
|
||||
*/
|
||||
private async updateInvoiceWithPaymentLink(
|
||||
tenantId: number,
|
||||
invoiceId: number,
|
||||
paymentLinkId: string
|
||||
) {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
await SaleInvoice.query().findById(invoiceId).patch({
|
||||
stripePlinkId: paymentLinkId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { EventSubscriber } from '@/lib/EventPublisher/EventPublisher';
|
||||
import {
|
||||
ISaleInvoiceCreatedPayload,
|
||||
ISaleInvoiceDeletedPayload,
|
||||
PaymentIntegrationTransactionLinkEventPayload,
|
||||
} from '@/interfaces';
|
||||
import { SaleInvoiceStripePaymentLink } from '../SaleInvoiceStripePaymentLink';
|
||||
import { runAfterTransaction } from '@/services/UnitOfWork/TransactionsHooks';
|
||||
@@ -22,29 +22,35 @@ export class CreatePaymentLinkOnInvoiceCreated extends EventSubscriber {
|
||||
*/
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.saleInvoice.onCreated,
|
||||
this.handleUpdateTransactionsOnItemCreated
|
||||
);
|
||||
bus.subscribe(
|
||||
events.saleInvoice.onDeleted,
|
||||
this.handleDeletePaymentLinkOnInvoiceDeleted
|
||||
events.paymentIntegrationLink.onPaymentIntegrationLink,
|
||||
this.handleCreatePaymentLinkOnIntegrationLink
|
||||
);
|
||||
// bus.subscribe(
|
||||
// events.saleInvoice.onDeleted,
|
||||
// this.handleDeletePaymentLinkOnInvoiceDeleted
|
||||
// );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Plaid item transactions
|
||||
* @param {IPlaidItemCreatedEventPayload} payload - Event payload.
|
||||
*/
|
||||
private handleUpdateTransactionsOnItemCreated = async ({
|
||||
saleInvoice,
|
||||
saleInvoiceId,
|
||||
private handleCreatePaymentLinkOnIntegrationLink = async ({
|
||||
tenantId,
|
||||
paymentIntegrationId,
|
||||
referenceId,
|
||||
referenceType,
|
||||
trx,
|
||||
}: ISaleInvoiceCreatedPayload) => {
|
||||
}: PaymentIntegrationTransactionLinkEventPayload) => {
|
||||
// Can't continue if the link request is not coming from the invoice transaction.
|
||||
if ('SaleInvoice' !== referenceType) {
|
||||
return;
|
||||
}
|
||||
runAfterTransaction(trx, async () => {
|
||||
await this.invoiceStripePaymentLink.createPaymentLink(
|
||||
tenantId,
|
||||
saleInvoice
|
||||
paymentIntegrationId,
|
||||
referenceId
|
||||
);
|
||||
});
|
||||
};
|
||||
@@ -61,6 +67,5 @@ export class CreatePaymentLinkOnInvoiceCreated extends EventSubscriber {
|
||||
tenantId,
|
||||
saleInvoiceId
|
||||
);
|
||||
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user