mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
feat: clean up the stripe payment integration
This commit is contained in:
@@ -47,8 +47,11 @@ export class CreateInvoiceCheckoutSession {
|
||||
const stripeAccountId = stripePaymentMethod?.paymentIntegration?.accountId;
|
||||
const paymentIntegrationId = stripePaymentMethod?.paymentIntegration?.id;
|
||||
|
||||
const session = await this.createCheckoutSession(invoice, stripeAccountId);
|
||||
|
||||
// Creates checkout session for the given invoice.
|
||||
const session = await this.createCheckoutSession(invoice, stripeAccountId, {
|
||||
tenantId,
|
||||
paymentLinkId: paymentLink.id,
|
||||
});
|
||||
return {
|
||||
sessionId: session.id,
|
||||
publishableKey: config.stripePayment.publishableKey,
|
||||
@@ -64,7 +67,8 @@ export class CreateInvoiceCheckoutSession {
|
||||
*/
|
||||
private createCheckoutSession(
|
||||
invoice: ISaleInvoice,
|
||||
stripeAccountId: string
|
||||
stripeAccountId: string,
|
||||
metadata?: Record<string, any>
|
||||
) {
|
||||
return this.stripePaymentService.stripe.checkout.sessions.create(
|
||||
{
|
||||
@@ -84,11 +88,13 @@ export class CreateInvoiceCheckoutSession {
|
||||
mode: 'payment',
|
||||
success_url: `${origin}/success`,
|
||||
cancel_url: `${origin}/cancel`,
|
||||
metadata: {
|
||||
saleInvoiceId: invoice.id,
|
||||
resource: 'SaleInvoice',
|
||||
...metadata,
|
||||
},
|
||||
},
|
||||
{
|
||||
stripeAccount: stripeAccountId,
|
||||
// stripeAccount: 'acct_1Q0nE7ESY7RfeebE',
|
||||
}
|
||||
{ stripeAccount: stripeAccountId }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { StripePaymentService } from '@/services/StripePayment/StripePaymentService';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { snakeCase } from 'lodash';
|
||||
import { StripePaymentService } from './StripePaymentService';
|
||||
import HasTenancyService from '../Tenancy/TenancyService';
|
||||
|
||||
interface CreateStripeAccountDTO {
|
||||
name: string;
|
||||
}
|
||||
import { CreateStripeAccountDTO } from './types';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class CreateStripeAccountService {
|
||||
@@ -15,29 +13,43 @@ export class CreateStripeAccountService {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Creates a new Stripe account for Bigcapital.
|
||||
* @param {number} tenantId
|
||||
* @param {number} createStripeAccountDTO
|
||||
* Creates a new Stripe account.
|
||||
* @param {number} tenantId
|
||||
* @param {CreateStripeAccountDTO} stripeAccountDTO
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async createAccount(
|
||||
async createStripeAccount(
|
||||
tenantId: number,
|
||||
createStripeAccountDTO: CreateStripeAccountDTO
|
||||
) {
|
||||
stripeAccountDTO?: CreateStripeAccountDTO
|
||||
): Promise<string> {
|
||||
const { PaymentIntegration } = this.tenancy.models(tenantId);
|
||||
const stripeAccount = await this.stripePaymentService.createAccount();
|
||||
const stripeAccountId = stripeAccount.id;
|
||||
|
||||
// Creates a new Stripe account.
|
||||
const account = await this.stripePaymentService.createAccount();
|
||||
|
||||
const slug = snakeCase(createStripeAccountDTO.name);
|
||||
|
||||
// Store the Stripe account on tenant store.
|
||||
const parsedStripeAccountDTO = {
|
||||
...stripeAccountDTO,
|
||||
name: 'Stripe',
|
||||
};
|
||||
// Stores the details of the Stripe account.
|
||||
await PaymentIntegration.query().insert({
|
||||
service: 'stripe',
|
||||
name: createStripeAccountDTO.name,
|
||||
slug,
|
||||
enable: true,
|
||||
accountId: account.id,
|
||||
name: parsedStripeAccountDTO.name,
|
||||
accountId: stripeAccountId,
|
||||
enable: false,
|
||||
service: 'Stripe',
|
||||
});
|
||||
// Triggers `onStripeIntegrationAccountCreated` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.stripeIntegration.onAccountCreated,
|
||||
{
|
||||
tenantId,
|
||||
stripeAccountDTO,
|
||||
stripeAccountId,
|
||||
}
|
||||
);
|
||||
return stripeAccountId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import HasTenancyService from '../Tenancy/TenancyService';
|
||||
import { StripePaymentService } from './StripePaymentService';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class DeleteStripePaymentLinkInvoice {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@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 {Knex.Transaction} knex -
|
||||
*/
|
||||
async deleteInvoicePaymentLink(
|
||||
tenantId: number,
|
||||
invoiceId: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
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;
|
||||
|
||||
await this.stripePayment.stripe.paymentLinks.update(
|
||||
stripePlinkId,
|
||||
{ active: false },
|
||||
stripeAcocunt
|
||||
);
|
||||
// Triggers `onStripePaymentLinkInactivated` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.stripeIntegration.onPaymentLinkInactivated,
|
||||
{
|
||||
tenantId,
|
||||
saleInvoiceId: invoiceId,
|
||||
paymentIntegrationId,
|
||||
stripePlinkId,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ISaleInvoice } from '@/interfaces';
|
||||
import { StripePaymentService } from './StripePaymentService';
|
||||
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 {
|
||||
@Inject()
|
||||
private stripePayment: StripePaymentService;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* 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>}
|
||||
*/
|
||||
async createPaymentLink(
|
||||
tenantId: number,
|
||||
stripeIntegrationId: number,
|
||||
invoiceId: number
|
||||
) {
|
||||
const { SaleInvoice, PaymentIntegration } = this.tenancy.models(tenantId);
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
{ 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: 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,22 +1,13 @@
|
||||
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';
|
||||
import { CreateInvoiceCheckoutSession } from './CreateInvoiceCheckoutSession';
|
||||
import { StripeInvoiceCheckoutSessionPOJO } from '@/interfaces/StripePayment';
|
||||
import { CreateStripeAccountService } from './CreateStripeAccountService';
|
||||
import { CreateStripeAccountDTO } from './types';
|
||||
|
||||
export class StripePaymentApplication {
|
||||
@Inject()
|
||||
private createStripeAccountService: CreateStripeAccountService;
|
||||
|
||||
@Inject()
|
||||
private saleInvoiceStripePaymentLinkService: SaleInvoiceStripePaymentLink;
|
||||
|
||||
@Inject()
|
||||
private deleteStripePaymentLinkInvoice: DeleteStripePaymentLinkInvoice;
|
||||
|
||||
@Inject()
|
||||
private createSaleInvoiceCheckoutSessionService: CreateInvoiceCheckoutSession;
|
||||
|
||||
@@ -35,25 +26,6 @@ export class StripePaymentApplication {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the Stripe checkout session from the given sale invoice.
|
||||
* @param {number} tenantId
|
||||
@@ -69,22 +41,4 @@ export class StripePaymentApplication {
|
||||
paymentLinkId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ export class StripePaymentService {
|
||||
|
||||
constructor() {
|
||||
this.stripe = new stripe(config.stripePayment.secretKey, {
|
||||
apiVersion: '2023-10-16',
|
||||
apiVersion: '2024-06-20',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { EventSubscriber } from '@/lib/EventPublisher/EventPublisher';
|
||||
import {
|
||||
PaymentIntegrationTransactionLinkDeleteEventPayload,
|
||||
PaymentIntegrationTransactionLinkEventPayload,
|
||||
} from '@/interfaces';
|
||||
import { SaleInvoiceStripePaymentLink } from '../SaleInvoiceStripePaymentLink';
|
||||
import { runAfterTransaction } from '@/services/UnitOfWork/TransactionsHooks';
|
||||
import events from '@/subscribers/events';
|
||||
import { DeleteStripePaymentLinkInvoice } from '../DeleteStripePaymentLinkInvoice';
|
||||
|
||||
@Service()
|
||||
export class CreatePaymentLinkOnInvoiceCreated extends EventSubscriber {
|
||||
@Inject()
|
||||
private invoiceStripePaymentLink: SaleInvoiceStripePaymentLink;
|
||||
|
||||
@Inject()
|
||||
private deleteStripePaymentLinkInvoice: DeleteStripePaymentLinkInvoice;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
*/
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.paymentIntegrationLink.onPaymentIntegrationLink,
|
||||
this.handleCreatePaymentLinkOnIntegrationLink
|
||||
);
|
||||
bus.subscribe(
|
||||
events.paymentIntegrationLink.onPaymentIntegrationDeleteLink,
|
||||
this.handleDeletePaymentLinkOnIntegrationLinkDelete
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Plaid item transactions
|
||||
* @param {IPlaidItemCreatedEventPayload} payload - Event payload.
|
||||
*/
|
||||
private handleCreatePaymentLinkOnIntegrationLink = async ({
|
||||
tenantId,
|
||||
paymentIntegrationId,
|
||||
referenceId,
|
||||
referenceType,
|
||||
trx,
|
||||
}: 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,
|
||||
paymentIntegrationId,
|
||||
referenceId
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes the Stripe payment link once the associated invoice deleted.
|
||||
* @param {ISaleInvoiceDeletedPayload}
|
||||
*/
|
||||
private handleDeletePaymentLinkOnIntegrationLinkDelete = async ({
|
||||
oldSaleInvoiceId,
|
||||
tenantId,
|
||||
trx,
|
||||
}: PaymentIntegrationTransactionLinkDeleteEventPayload) => {
|
||||
await this.deleteStripePaymentLinkInvoice.deleteInvoicePaymentLink(
|
||||
tenantId,
|
||||
oldSaleInvoiceId,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
6
packages/server/src/services/StripePayment/types.ts
Normal file
6
packages/server/src/services/StripePayment/types.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
|
||||
|
||||
export interface CreateStripeAccountDTO {
|
||||
name: string;
|
||||
}
|
||||
Reference in New Issue
Block a user