feat: Link transations with payment methods

This commit is contained in:
Ahmed Bouhuolia
2024-09-15 19:42:43 +02:00
parent 542e61dbfc
commit 430cf19533
21 changed files with 581 additions and 8 deletions

View File

@@ -0,0 +1,28 @@
import { Transformer } from '@/lib/Transformer/Transformer';
export class GeneratePaymentLinkTransformer extends Transformer {
/**
* Exclude these attributes from payment link object.
* @returns {Array}
*/
public excludeAttributes = (): string[] => {
return ['linkId'];
};
/**
* Included attributes.
* @returns {string[]}
*/
public includeAttributes = (): string[] => {
return ['link'];
};
/**
*
* @param link
* @returns
*/
public link(link) {
return `http://localhost:3000/payment/${link.linkId}`;
}
}

View File

@@ -0,0 +1,85 @@
import { Knex } from 'knex';
import { Inject, Service } from 'typedi';
import { v4 as uuidv4 } from 'uuid';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import UnitOfWork from '@/services/UnitOfWork';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import events from '@/subscribers/events';
import { PaymentLink } from '@/system/models';
import { GeneratePaymentLinkTransformer } from './GeneratePaymentLinkTransformer';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
@Service()
export class GenerateShareLink {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private uow: UnitOfWork;
@Inject()
private eventPublisher: EventPublisher;
@Inject()
private transformer: TransformerInjectable;
/**
* Generates private or public payment link for the given sale invoice.
* @param {number} tenantId - Tenant id.
* @param {number} invoiceId - Sale invoice id.
* @param {string} publicOrPrivate - Public or private.
* @param {string} expiryTime - Expiry time.
*/
async generatePaymentLink(
tenantId: number,
transactionId: number,
transactionType: string,
publicity: string = 'private',
expiryTime: string = ''
) {
const { SaleInvoice } = this.tenancy.models(tenantId);
const foundInvoice = await SaleInvoice.query()
.findById(transactionId)
.throwIfNotFound();
// Generate unique uuid for sharable link.
const linkId = uuidv4() as string;
const commonEventPayload = {
tenantId,
transactionId,
transactionType,
publicity,
expiryTime,
};
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onPublicSharableLinkGenerating` event.
await this.eventPublisher.emitAsync(
events.saleInvoice.onPublicLinkGenerating,
{ ...commonEventPayload, trx }
);
const paymentLink = await PaymentLink.query().insert({
linkId,
tenantId,
publicity,
resourceId: foundInvoice.id,
resourceType: 'SaleInvoice',
});
// Triggers `onPublicSharableLinkGenerated` event.
await this.eventPublisher.emitAsync(
events.saleInvoice.onPublicLinkGenerated,
{
...commonEventPayload,
paymentLink,
trx,
}
);
return this.transformer.transform(
tenantId,
paymentLink,
new GeneratePaymentLinkTransformer()
);
});
}
}

View File

@@ -0,0 +1,59 @@
import moment from 'moment';
import { ServiceError } from '@/exceptions';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { PaymentLink } from '@/system/models';
import { Inject, Service } from 'typedi';
import { GeneratePaymentLinkTransformer } from './GeneratePaymentLinkTransformer';
import { GetInvoicePaymentLinkMetaTransformer } from './GetInvoicePaymentLinkTransformer';
import { initalizeTenantServices } from '@/api/middleware/TenantDependencyInjection';
@Service()
export class GetInvoicePaymentLinkMetadata {
@Inject()
tenancy: HasTenancyService;
@Inject()
private transformer: TransformerInjectable;
/**
* Retrieves the invoice sharable link meta of the link id.
* @param {number}
* @param {string} linkId
*/
async getInvoicePaymentLinkMeta(linkId: string) {
const paymentLink = await PaymentLink.query()
.findOne('linkId', linkId)
.throwIfNotFound();
//
if (paymentLink.resourceType !== 'SaleInvoice') {
throw new ServiceError('');
}
// Validate the expiry at date.
if (paymentLink.expiryAt) {
const currentDate = moment();
const expiryDate = moment(paymentLink.expiryAt);
if (expiryDate.isBefore(currentDate)) {
throw new ServiceError('PAYMENT_LINK_EXPIRED');
}
}
const tenantId = paymentLink.tenantId;
await initalizeTenantServices(tenantId);
const { SaleInvoice } = this.tenancy.models(tenantId);
const invoice = await SaleInvoice.query()
.findById(paymentLink.resourceId)
.withGraphFetched('entries')
.withGraphFetched('customer')
.throwIfNotFound();
return this.transformer.transform(
tenantId,
invoice,
new GetInvoicePaymentLinkMetaTransformer()
);
}
}

View File

@@ -0,0 +1,46 @@
import { SaleInvoiceTransformer } from './SaleInvoiceTransformer';
export class GetInvoicePaymentLinkMetaTransformer extends SaleInvoiceTransformer {
/**
* Exclude these attributes from payment link object.
* @returns {Array}
*/
public excludeAttributes = (): string[] => {
return ['*'];
};
/**
* Included attributes.
* @returns {string[]}
*/
public includeAttributes = (): string[] => {
return [
'companyName',
'customerName',
'dueAmount',
'dueDateFormatted',
'invoiceDateFormatted',
'total',
'totalFormatted',
'totalLocalFormatted',
'subtotal',
'subtotalFormatted',
'subtotalLocalFormatted',
'dueAmount',
'dueAmountFormatted',
'paymentAmount',
'paymentAmountFormatted',
'dueDate',
'dueDateFormatted',
'invoiceNo',
];
};
public customerName(invoice) {
return invoice.customer.displayName;
}
public companyName() {
return 'Bigcapital Technology, Inc.';
}
}

View File

@@ -0,0 +1,43 @@
import { Inject, Service } from 'typedi';
import { StripePaymentService } from './StripePaymentService';
import HasTenancyService from '../Tenancy/TenancyService';
import { snakeCase } from 'lodash';
interface CreateStripeAccountDTO {
name: string;
}
@Service()
export class CreateStripeAccountService {
@Inject()
private stripePaymentService: StripePaymentService;
@Inject()
private tenancy: HasTenancyService;
/**
* Creates a new Stripe account for Bigcapital.
* @param {number} tenantId
* @param {number} createStripeAccountDTO
*/
async createAccount(
tenantId: number,
createStripeAccountDTO: CreateStripeAccountDTO
) {
const { PaymentIntegration } = this.tenancy.models(tenantId);
// Creates a new Stripe account.
const account = await this.stripePaymentService.createAccount();
const slug = snakeCase(createStripeAccountDTO.name);
// Store the Stripe account on tenant store.
await PaymentIntegration.query().insert({
service: 'stripe',
name: createStripeAccountDTO.name,
slug,
enable: true,
accountId: account.id,
});
}
}

View File

@@ -33,11 +33,15 @@ export class StripePaymentService {
}
}
/**
*
* @returns {Promise<string>}
*/
public async createAccount(): Promise<string> {
try {
const account = await this.stripe.accounts.create({});
return account.id;
return account;
} catch (error) {
throw new Error(
'An error occurred when calling the Stripe API to create an account'