Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(payment-stripe): update services structure #176

Merged
merged 4 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { configMock } from '@__mocks__/config';
import * as remoteConfig from '@config/remote';
import TaxBehaviour from '@constants/tax-behaviour';
import TransactionRole from '@constants/transaction-role';
import Calculation from '@services/calculation';
import Calculation from '@services/common/calculation';

describe('services/calculation', () => {
describe('services/common/calculation', () => {
const sandbox = sinon.createSandbox();
const mathRoundSpy = sinon.spy(Math, 'round');
const remoteConfigStub = sinon.stub().resolves(configMock);
Expand Down
5 changes: 1 addition & 4 deletions microservices/payment-stripe/src/entities/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,9 @@ class Transaction {
@IsNullable()
cardId: string | null;

/**
* Setup intent don't have card or bank account id
*/
@JSONSchema({
description: `Payment method that was used to charge, for instance: card, bank account, etc.. Payment method is
stripe entity that attached to customer`,
stripe entity that attached to customer. Setup intent don't have card or bank account id`,
example: 'pm_1N0vl32eZvKYlo2CiORpHAvo',
})
@Column({ type: 'varchar', length: 27, default: null })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Endpoint, IsUndefinable } from '@lomray/microservice-helpers';
import { IsBoolean, IsEnum, IsNumber } from 'class-validator';
import { JSONSchema } from 'class-validator-jsonschema';
import TransactionRole from '@constants/transaction-role';
import Calculation from '@services/calculation';
import Calculation from '@services/common/calculation';
import Stripe from '@services/payment-gateway/stripe';

class PaymentIntentFeesInput {
Expand Down
193 changes: 96 additions & 97 deletions microservices/payment-stripe/src/services/payment-gateway/stripe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import TCurrency from '@interfaces/currency';
import IFees from '@interfaces/fees';
import type ITax from '@interfaces/tax';
import CardRepository from '@repositories/card';
import Calculation from '@services/calculation';
import Calculation from '@services/common/calculation';
import Parser from '@services/parser';
import WebhookHandlers from '@services/webhook-handlers';
import type {
Expand Down Expand Up @@ -1323,18 +1323,16 @@ class Stripe extends Abstract {
* Returns positive int amount
* @description Should return the positive integer representing how much
* to charge in the smallest currency unit
* @TODO: remove. use client api parser
*/
public toSmallestCurrencyUnit(amount: number | string): number {
/**
* Convert the amount to a number if it's a string
*/
const parsedAmount = typeof amount === 'string' ? Number(amount) : amount;

return parsedAmount * 100;
// Convert the amount to a number if it's a string
return Number(amount) * 100;
}

/**
* Returns float value from unit
* @TODO: remove. use client api parser
*/
public fromSmallestCurrencyUnit(amount: number): number {
return amount / 100;
Expand Down Expand Up @@ -1490,6 +1488,93 @@ class Stripe extends Abstract {
};
}

/**
* Returns payout method data
*/
protected async getPayoutMethodAllowances(
userId: string,
payoutMethod?: IPayoutMethod,
): Promise<{ externalAccountId: string; isInstantPayoutAllowed: boolean } | undefined> {
const externalAccountNotFoundError = messages.getNotFoundMessage(
OlegDO marked this conversation as resolved.
Show resolved Hide resolved
'External account for instant payout',
);

if (!payoutMethod) {
const queries = [
this.bankAccountRepository.createQueryBuilder('pm'),
this.cardRepository.createQueryBuilder('pm'),
];

const selectQueries = queries.map((query) =>
query
.where('pm.userId = :userId AND pm."isInstantPayoutAllowed" = true', { userId })
.getOne(),
);

const results = await Promise.all(selectQueries);

if (!results.length) {
throw new BaseException({
status: 500,
message: 'User does not have any available instant payout method.',
});
}

const [bankAccount, card] = results as [BankAccount | undefined, Card | undefined];

const externalAccountId = bankAccount?.params?.bankAccountId || card?.params?.cardId;

if (!externalAccountId) {
throw new BaseException({
status: 500,
message: externalAccountNotFoundError,
});
}

return {
externalAccountId,
isInstantPayoutAllowed:
Boolean(bankAccount?.isInstantPayoutAllowed) || Boolean(card?.isInstantPayoutAllowed),
};
}

const { method, id } = payoutMethod;

if (method === PayoutMethodType.CARD) {
const card = await this.cardRepository.findOne(id, {
select: ['id', 'params'],
});

if (!card?.params?.cardId) {
throw new BaseException({
status: 500,
message: externalAccountNotFoundError,
});
}

return {
externalAccountId: card.params.cardId,
isInstantPayoutAllowed: Boolean(card?.isInstantPayoutAllowed),
};
}

const bankAccount = await this.bankAccountRepository.findOne(id, {
select: ['id', 'params'],
});

if (!bankAccount?.params?.bankAccountId) {
throw new BaseException({
status: 500,
message: externalAccountNotFoundError,
});
}

return {
externalAccountId: bankAccount?.params?.bankAccountId,
isInstantPayoutAllowed: Boolean(bankAccount?.isInstantPayoutAllowed),
};
}

/**
* Returns card for charging payment
*/
Expand Down Expand Up @@ -1635,8 +1720,9 @@ class Stripe extends Abstract {

/**
* Process webhook event
* @TODO: make this extendable
*/
private async processWebhookEvent(event: StripeSdk.Event, webhookType: string): Promise<void> {
protected async processWebhookEvent(event: StripeSdk.Event, webhookType: string): Promise<void> {
const webhookHandlers = WebhookHandlers.init(this.manager);

switch (event.type) {
Expand Down Expand Up @@ -1856,7 +1942,7 @@ class Stripe extends Abstract {
/**
* Validate and transform coupon duration input
*/
private static validateAndTransformCouponDurationInput({
protected static validateAndTransformCouponDurationInput({
duration,
durationInMonths,
}: Pick<IStripeCouponParams, 'duration' | 'durationInMonths'>): Pick<
Expand All @@ -1880,7 +1966,7 @@ class Stripe extends Abstract {
/**
* Validate and transform coupon discount input
*/
private static validateAndTransformCouponDiscountInput({
protected static validateAndTransformCouponDiscountInput({
percentOff,
amountOff,
}: Pick<IStripeCouponParams, 'percentOff' | 'amountOff'>): Pick<
Expand Down Expand Up @@ -1908,93 +1994,6 @@ class Stripe extends Abstract {
percent_off: percentOff,
};
}

/**
* Returns payout method data
*/
private async getPayoutMethodAllowances(
userId: string,
payoutMethod?: IPayoutMethod,
): Promise<{ externalAccountId: string; isInstantPayoutAllowed: boolean } | undefined> {
const externalAccountNotFoundError = messages.getNotFoundMessage(
'External account for instant payout',
);

if (!payoutMethod) {
const queries = [
this.bankAccountRepository.createQueryBuilder('pm'),
this.cardRepository.createQueryBuilder('pm'),
];

const selectQueries = queries.map((query) =>
query
.where('pm.userId = :userId AND pm."isInstantPayoutAllowed" = true', { userId })
.getOne(),
);

const results = await Promise.all(selectQueries);

if (!results.length) {
throw new BaseException({
status: 500,
message: 'User does not have any available instant payout method.',
});
}

const [bankAccount, card] = results as [BankAccount | undefined, Card | undefined];

const externalAccountId = bankAccount?.params?.bankAccountId || card?.params?.cardId;

if (!externalAccountId) {
throw new BaseException({
status: 500,
message: externalAccountNotFoundError,
});
}

return {
externalAccountId,
isInstantPayoutAllowed:
Boolean(bankAccount?.isInstantPayoutAllowed) || Boolean(card?.isInstantPayoutAllowed),
};
}

const { method, id } = payoutMethod;

if (method === PayoutMethodType.CARD) {
const card = await this.cardRepository.findOne(id, {
select: ['id', 'params'],
});

if (!card?.params?.cardId) {
throw new BaseException({
status: 500,
message: externalAccountNotFoundError,
});
}

return {
externalAccountId: card.params.cardId,
isInstantPayoutAllowed: Boolean(card?.isInstantPayoutAllowed),
};
}

const bankAccount = await this.bankAccountRepository.findOne(id, {
select: ['id', 'params'],
});

if (!bankAccount?.params?.bankAccountId) {
throw new BaseException({
status: 500,
message: externalAccountNotFoundError,
});
}

return {
externalAccountId: bankAccount?.params?.bankAccountId,
isInstantPayoutAllowed: Boolean(bankAccount?.isInstantPayoutAllowed),
};
}
}

export default Stripe;
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ class WebhookHandlers {

/**
* @constructor
* @private
*/
private constructor(manager: EntityManager) {
this.customer = new Customer(manager);
Expand All @@ -87,7 +86,7 @@ class WebhookHandlers {
* Init service
*/
public static init(manager: EntityManager): WebhookHandlers {
return new WebhookHandlers(manager);
return new this(manager);
}
}

Expand Down
Loading