@nimee/calculations 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/prices.d.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { IAccountModel, IEventModel, IUserEventModel } from "@nimee/shared-types";
2
- export declare const getTotalTicketsAmount: (allUsersEvent: IUserEventModel[]) => number;
1
+ import { IAccountModel, IEventModel } from "@nimee/shared-types";
2
+ export declare const paymeFee: number;
3
3
  export declare const calculateFinalPriceWithNimiFee: (totalPriceWithoutCouponDiscount: number, event: IEventModel, account: IAccountModel, totalPriceWithCouponDiscount: number) => {
4
4
  nimiFeePercentage: number;
5
5
  nimiFee: number;
6
6
  finalPriceWithNimiFee: number;
7
+ marketplaceFee: number;
7
8
  };
8
9
  export declare const calculateFinalPriceWithCouponDiscount: (totalPrice: number, couponDiscount: number) => number;
9
- export declare const validateFreeOrder: (ticketIds: string[], couponDiscount: number, accountId: string, eventId: string) => Promise<boolean>;
10
+ export declare const calculateFinalPriceWithEndUserSeasonTicket: (totalPrice: number, endUserSeasonTicketDiscountPrice: any) => number;
package/dist/prices.js CHANGED
@@ -1,83 +1,88 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
4
  };
11
5
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.validateFreeOrder = exports.calculateFinalPriceWithCouponDiscount = exports.calculateFinalPriceWithNimiFee = exports.getTotalTicketsAmount = void 0;
6
+ exports.calculateFinalPriceWithEndUserSeasonTicket = exports.calculateFinalPriceWithCouponDiscount = exports.calculateFinalPriceWithNimiFee = exports.paymeFee = void 0;
13
7
  /* Central the prices logic so we won't repeat it */
14
8
  const shared_types_1 = require("@nimee/shared-types");
15
- const api_clients_1 = require("@nimee/api-clients");
9
+ const logger_1 = __importDefault(require("@nimee/logger"));
16
10
  const error_handler_1 = require("@nimee/error-handler");
17
- // Support both legacy and new userEvent schema (with ticketsInfo and without.)
18
- const getTotalTicketsAmount = (allUsersEvent) => {
19
- return allUsersEvent.reduce((acc, userEvent) => {
20
- var _a;
21
- // For user-event to one ticket.
22
- if (!((_a = userEvent === null || userEvent === void 0 ? void 0 : userEvent.ticketsInfo) === null || _a === void 0 ? void 0 : _a.length)) {
23
- return acc + 1;
24
- }
25
- // For user-event to many ticket.
26
- return acc + userEvent.ticketsInfo.length;
27
- }, 0);
28
- };
29
- exports.getTotalTicketsAmount = getTotalTicketsAmount;
11
+ exports.paymeFee = 1.1 * 1.17;
30
12
  // we need to calculate the final price with nimi fee and return the nimi fee percentage and nimi fee
31
13
  // we need the original price becuase the fee is calculated on top of the original price
32
14
  // then we return the coupon discount and the final price with nimi fee
33
15
  // then we calculate the nimi fee percentage from the final price after discount with nimi fee
16
+ const roundToTwoDecimals = (num) => Math.round(num * 100) / 100;
34
17
  const calculateFinalPriceWithNimiFee = (totalPriceWithoutCouponDiscount, event, account, totalPriceWithCouponDiscount) => {
35
18
  switch (event.feeCollectionType) {
36
19
  case shared_types_1.IFeeCollectionType.PERCENTAGE_FROM_TICKET:
37
- return { finalPriceWithNimiFee: totalPriceWithCouponDiscount, nimiFee: 0, nimiFeePercentage: account.marketFee }; // we use here marketFee (and not originalMarketFee) because we need to send the fee to payme without their fee
20
+ const marketplaceFeeOfPERCENTAGE_FROM_TICKET = (totalPriceWithCouponDiscount * (account.originalMarketFee - exports.paymeFee - account.marketFee)) / 100;
21
+ return {
22
+ finalPriceWithNimiFee: totalPriceWithCouponDiscount,
23
+ nimiFee: 0,
24
+ nimiFeePercentage: account.marketFee,
25
+ marketplaceFee: roundToTwoDecimals(marketplaceFeeOfPERCENTAGE_FROM_TICKET),
26
+ }; // we use here marketFee (and not originalMarketFee) because we need to send the fee to payme without their fee
38
27
  case shared_types_1.IFeeCollectionType.ON_TOP_OF_TICKET:
39
28
  // check if the totalPrice with coupon discount is 0 then we return 0 for the fee and the percentage
40
29
  if (totalPriceWithCouponDiscount === 0) {
41
- return { finalPriceWithNimiFee: 0, nimiFee: 0, nimiFeePercentage: 0 };
30
+ return { finalPriceWithNimiFee: 0, nimiFee: 0, nimiFeePercentage: 0, marketplaceFee: 0 };
42
31
  }
43
32
  // minimum 3 so we use max we totalPriceWithoutCouponDiscount because the fee is calculated on top of the original price and not the price after discount
44
33
  const unroundedFee = Math.max(3, (totalPriceWithoutCouponDiscount * account.originalMarketFee) / 100); // here we use originalMarketFee because we need to calculate the fee on top of the original price (later we will subtract payme fee from the fee we send to payme)
45
- const roundedFee = Math.round(unroundedFee);
34
+ let roundedFee = Math.ceil(unroundedFee * 10) / 10;
46
35
  // nimiFeePercentage has unknown range that need to be aware of - we get error when we send to payme with range above 60 so we use max
47
- const nimiFeePercentage = Math.min(59, (roundedFee / (totalPriceWithCouponDiscount + roundedFee)) * 100);
36
+ // const nimiFeePercentage = Math.min(59, (roundedFee / totalPriceWithCouponDiscount) * 100); // we use here totalPriceWithCouponDiscount because we send payme the price after the discount plus the nimiFee (roundedFee) so we need to know the percentage of the fee from the final price
48
37
  // we need to make sure we send marketFee that is greater or equal to 0 so we use max and we must subtract payme fee from nimiFeePercentage because payme ask to send them the fee without their fee
49
- const nimiFeePercentageMinusPaymeFee = Math.max(0, nimiFeePercentage - 1.1 * 1.17);
38
+ // const nimiFeePercentageMinusPaymeFee = Math.max(0, nimiFeePercentage - 1.1 * 1.17);
39
+ let marketplaceFeeOfON_TOP_OF_TICKET = roundedFee - ((roundedFee + totalPriceWithCouponDiscount) * (account.marketFee + exports.paymeFee)) / 100; // payme takes the percentage from the price. so we calculate payme and nimi fee from the price and we substruct the result from the roundedFee
40
+ if (marketplaceFeeOfON_TOP_OF_TICKET <= 0) {
41
+ // means the originalMarketFee is equal or close to the marketFee+paymeFee) - happens when the price of the ticket is high and the fee of payme and nimi is high.. so we want to add more money to the fee so the seller wont lose here
42
+ logger_1.default.debug(`marketplaceFee is less than 0 marketplaceFee: ${marketplaceFeeOfON_TOP_OF_TICKET} and nimiFee: ${roundedFee}, account: ${account._id}, event: ${event._id}`);
43
+ const newUnroundedFee = roundedFee + Math.abs(marketplaceFeeOfON_TOP_OF_TICKET) + 1; // so we want to add the minus fee as absolute and another small amount because the solek takes from the new value (price+new rounded fee)
44
+ roundedFee = Math.ceil(newUnroundedFee * 10) / 10;
45
+ marketplaceFeeOfON_TOP_OF_TICKET =
46
+ roundedFee - ((roundedFee + totalPriceWithCouponDiscount) * (account.marketFee + exports.paymeFee)) / 100; // payme takes the percentage from the price. so we calculate payme and nimi fee from the price and we substruct the result from the roundedFee
47
+ logger_1.default.info(`so assign new fee: ${marketplaceFeeOfON_TOP_OF_TICKET}, nimiFee: ${roundedFee}, account: ${account._id}, event: ${event._id}`);
48
+ }
50
49
  return {
51
50
  finalPriceWithNimiFee: roundedFee + totalPriceWithCouponDiscount,
52
51
  nimiFee: roundedFee,
53
- nimiFeePercentage: nimiFeePercentageMinusPaymeFee,
52
+ nimiFeePercentage: account.marketFee,
53
+ marketplaceFee: roundToTwoDecimals(marketplaceFeeOfON_TOP_OF_TICKET),
54
54
  };
55
55
  default:
56
- return { finalPriceWithNimiFee: totalPriceWithCouponDiscount, nimiFee: 0, nimiFeePercentage: account.marketFee };
56
+ throw new error_handler_1.CustomError("fee_collection_type_not_supported", 400, `fee collection type not supported - ${event.feeCollectionType}`);
57
57
  }
58
58
  };
59
59
  exports.calculateFinalPriceWithNimiFee = calculateFinalPriceWithNimiFee;
60
60
  const calculateFinalPriceWithCouponDiscount = (totalPrice, couponDiscount) => totalPrice - couponDiscount < 0 ? 0 : totalPrice - couponDiscount;
61
61
  exports.calculateFinalPriceWithCouponDiscount = calculateFinalPriceWithCouponDiscount;
62
+ const calculateFinalPriceWithEndUserSeasonTicket = (totalPrice, endUserSeasonTicketDiscountPrice) => totalPrice - endUserSeasonTicketDiscountPrice < 0 ? 0 : totalPrice - endUserSeasonTicketDiscountPrice;
63
+ exports.calculateFinalPriceWithEndUserSeasonTicket = calculateFinalPriceWithEndUserSeasonTicket;
62
64
  // Validate the order is really free, rather the tickets free or with the coupon discount.
63
65
  // we do this so the client won't be able to create orders with price 0 and it won't be validated
64
- const validateFreeOrder = (ticketIds, couponDiscount, accountId, eventId) => __awaiter(void 0, void 0, void 0, function* () {
65
- const ticketClient = new api_clients_1.TicketClient();
66
- const tickets = yield ticketClient.getTicketsByIds({ token: process.env.JWT_ADMIN, ticketIds, accountId, eventId });
67
- if (!(tickets === null || tickets === void 0 ? void 0 : tickets.length)) {
68
- throw new error_handler_1.CustomError("tickets_not_found_validate_free_order", 400, `tickets not found when try to validate free order with ticketIds - ${ticketIds} and eventId - ${eventId}`);
69
- }
70
- const rawOrderPriceFromTickets = tickets.reduce((acc, ticket) => acc + ticket.price, 0);
71
- if (rawOrderPriceFromTickets === 0) {
72
- return true;
73
- }
74
- if (couponDiscount) {
75
- const orderPriceAfterCoupon = (0, exports.calculateFinalPriceWithCouponDiscount)(rawOrderPriceFromTickets, couponDiscount);
76
- if (orderPriceAfterCoupon === 0) {
77
- return true;
78
- }
79
- }
80
- return false;
81
- });
82
- exports.validateFreeOrder = validateFreeOrder;
66
+ // export const validateFreeOrder = async (ticketIds: string[], couponDiscount: number, accountId: string, eventId: string) => {
67
+ // const ticketClient = new TicketClient();
68
+ // const tickets = await ticketClient.getTicketsByIds({ token: process.env.JWT_ADMIN, ticketIds, accountId, eventId });
69
+ // if (!tickets?.length) {
70
+ // throw new CustomError(
71
+ // "tickets_not_found_validate_free_order",
72
+ // 400,
73
+ // `tickets not found when try to validate free order with ticketIds - ${ticketIds} and eventId - ${eventId}`
74
+ // );
75
+ // }
76
+ // const rawOrderPriceFromTickets = tickets.reduce((acc: number, ticket: { price: number }) => acc + ticket.price, 0);
77
+ // if (rawOrderPriceFromTickets === 0) {
78
+ // return true;
79
+ // }
80
+ // if (couponDiscount) {
81
+ // const orderPriceAfterCoupon = calculateFinalPriceWithCouponDiscount(rawOrderPriceFromTickets, couponDiscount);
82
+ // if (orderPriceAfterCoupon === 0) {
83
+ // return true;
84
+ // }
85
+ // }
86
+ // return false;
87
+ // };
83
88
  //# sourceMappingURL=prices.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"prices.js","sourceRoot":"","sources":["../src/prices.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,oDAAoD;AACpD,sDAAsG;AACtG,oDAAkD;AAClD,wDAAmD;AAEnD,+EAA+E;AACxE,MAAM,qBAAqB,GAAG,CAAC,aAAgC,EAAU,EAAE;IAChF,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC,GAAW,EAAE,SAA0B,EAAE,EAAE;;QACtE,gCAAgC;QAChC,IAAI,CAAC,CAAA,MAAA,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,WAAW,0CAAE,MAAM,CAAA,EAAE;YACnC,OAAO,GAAG,GAAG,CAAC,CAAC;SAChB;QACD,iCAAiC;QACjC,OAAO,GAAG,GAAG,SAAS,CAAC,WAAW,CAAC,MAAM,CAAC;IAC5C,CAAC,EAAE,CAAC,CAAC,CAAC;AACR,CAAC,CAAC;AATW,QAAA,qBAAqB,yBAShC;AAEF,qGAAqG;AACrG,wFAAwF;AACxF,uEAAuE;AACvE,8FAA8F;AACvF,MAAM,8BAA8B,GAAG,CAC5C,+BAAuC,EACvC,KAAkB,EAClB,OAAsB,EACtB,4BAAoC,EAC2C,EAAE;IACjF,QAAQ,KAAK,CAAC,iBAAiB,EAAE;QAC/B,KAAK,iCAAkB,CAAC,sBAAsB;YAC5C,OAAO,EAAE,qBAAqB,EAAE,4BAA4B,EAAE,OAAO,EAAE,CAAC,EAAE,iBAAiB,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,gHAAgH;QACpO,KAAK,iCAAkB,CAAC,gBAAgB;YACtC,oGAAoG;YACpG,IAAI,4BAA4B,KAAK,CAAC,EAAE;gBACtC,OAAO,EAAE,qBAAqB,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAC;aACvE;YACD,yJAAyJ;YACzJ,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,+BAA+B,GAAG,OAAO,CAAC,iBAAiB,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,oKAAoK;YAC3Q,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAC5C,uIAAuI;YACvI,MAAM,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,UAAU,GAAG,CAAC,4BAA4B,GAAG,UAAU,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;YACzG,yMAAyM;YACzM,MAAM,8BAA8B,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,iBAAiB,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;YACnF,OAAO;gBACL,qBAAqB,EAAE,UAAU,GAAG,4BAA4B;gBAChE,OAAO,EAAE,UAAU;gBACnB,iBAAiB,EAAE,8BAA8B;aAClD,CAAC;QACJ;YACE,OAAO,EAAE,qBAAqB,EAAE,4BAA4B,EAAE,OAAO,EAAE,CAAC,EAAE,iBAAiB,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC;KACpH;AACH,CAAC,CAAC;AA7BW,QAAA,8BAA8B,kCA6BzC;AACK,MAAM,qCAAqC,GAAG,CAAC,UAAkB,EAAE,cAAsB,EAAU,EAAE,CAC1G,UAAU,GAAG,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,GAAG,cAAc,CAAC;AADvD,QAAA,qCAAqC,yCACkB;AAEpE,0FAA0F;AAC1F,iGAAiG;AAC1F,MAAM,iBAAiB,GAAG,CAAO,SAAmB,EAAE,cAAsB,EAAE,SAAiB,EAAE,OAAe,EAAE,EAAE;IACzH,MAAM,YAAY,GAAG,IAAI,0BAAY,EAAE,CAAC;IACxC,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;IACpH,IAAI,CAAC,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,MAAM,CAAA,EAAE;QACpB,MAAM,IAAI,2BAAW,CACnB,uCAAuC,EACvC,GAAG,EACH,sEAAsE,SAAS,kBAAkB,OAAO,EAAE,CAC3G,CAAC;KACH;IAED,MAAM,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAW,EAAE,MAAyB,EAAE,EAAE,CAAC,GAAG,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACnH,IAAI,wBAAwB,KAAK,CAAC,EAAE;QAClC,OAAO,IAAI,CAAC;KACb;IAED,IAAI,cAAc,EAAE;QAClB,MAAM,qBAAqB,GAAG,IAAA,6CAAqC,EAAC,wBAAwB,EAAE,cAAc,CAAC,CAAC;QAC9G,IAAI,qBAAqB,KAAK,CAAC,EAAE;YAC/B,OAAO,IAAI,CAAC;SACb;KACF;IAED,OAAO,KAAK,CAAC;AACf,CAAC,CAAA,CAAC;AAxBW,QAAA,iBAAiB,qBAwB5B"}
1
+ {"version":3,"file":"prices.js","sourceRoot":"","sources":["../src/prices.ts"],"names":[],"mappings":"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"}
package/jest.config.js ADDED
@@ -0,0 +1,18 @@
1
+ const config = require("@nimee/tests");
2
+
3
+ const { clearMocks, coverageDirectory, collectCoverage, coverageReporters, forceExit, transform, testTimeout } =
4
+ config.jestConfig;
5
+
6
+ module.exports = {
7
+ setupFiles: ["dotenv/config"],
8
+ clearMocks: clearMocks,
9
+ coverageDirectory: coverageDirectory,
10
+ testEnvironment: "node",
11
+ collectCoverage: collectCoverage,
12
+ coverageReporters: coverageReporters,
13
+ forceExit: forceExit,
14
+ transform: { "^.+\\.tsx?$": "ts-jest" },
15
+ testTimeout: testTimeout,
16
+ modulePathIgnorePatterns: ["<rootDir>/test-client/", "<rootDir>/dist/"],
17
+ transformIgnorePatterns: ["node_modules/(?!axios)"],
18
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimee/calculations",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "nimi calculations like prices for back and front",
5
5
  "main": "dist/index.js",
6
6
  "author": "dan goldberg",
@@ -11,24 +11,34 @@
11
11
  "build": "npm run build-ts",
12
12
  "lint": "eslint -c ../../.eslintrc.js --ext .ts src/**",
13
13
  "prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write",
14
- "incVersionAndPublish": "npm version patch && npm run publish-it && npm run i-all-workspaces",
14
+ "incVersionAndPublish": "npm run test:deploy && npm version patch && npm run publish-it && npm run i-all-workspaces",
15
15
  "publish-it": "npm run build && npm publish --scope=nimee --access public",
16
- "i-all-workspaces": "version=${npm_package_version} npm i @nimee/calculations@$version --workspace=@nimee/event --workspace=@nimee/user-event --workspace=@nimee/send-notification --workspace=@nimee/user --workspace=@nimee/payment --workspace=@nimee/analytics"
16
+ "i-all-workspaces": "version=${npm_package_version} npm i @nimee/calculations@$version --workspace=@nimee/event --workspace=@nimee/user-event --workspace=@nimee/send-notification --workspace=@nimee/user --workspace=@nimee/payment --workspace=@nimee/analytics --workspace=@nimee/crm",
17
+ "test": "jest --detectOpenHandles --watch",
18
+ "test:deploy": "jest --ci --coverage"
17
19
  },
18
20
  "devDependencies": {
21
+ "@nimee/tests": "^0.0.15",
22
+ "@types/jest": "^23.3.12",
19
23
  "@typescript-eslint/eslint-plugin": "^5.18.0",
20
24
  "@typescript-eslint/eslint-plugin-tslint": "^5.18.0",
21
25
  "@typescript-eslint/parser": "^5.18.0",
22
26
  "eslint": "8.22.0",
23
27
  "eslint-plugin-jsdoc": "^38.1.6",
24
28
  "eslint-plugin-no-null": "^1.0.2",
29
+ "jest": "^29.0.0",
30
+ "jest-junit": "^12.0.0",
31
+ "nock": "^13.0.5",
25
32
  "nodemon": "^1.19.1",
33
+ "sinon": "^9.2.1",
34
+ "ts-jest": "^29.0.0",
26
35
  "typescript": "^5.2.2"
27
36
  },
28
37
  "types": "dist/index.d.ts",
29
38
  "dependencies": {
30
39
  "@nimee/api-clients": "^1.1.15",
31
40
  "@nimee/error-handler": "^0.0.9",
41
+ "@nimee/logger": "^1.0.24",
32
42
  "axios": "1.2.1"
33
43
  }
34
44
  }
package/src/prices.ts CHANGED
@@ -1,81 +1,100 @@
1
1
  /* Central the prices logic so we won't repeat it */
2
2
  import { IAccountModel, IEventModel, IFeeCollectionType, IUserEventModel } from "@nimee/shared-types";
3
- import { TicketClient } from "@nimee/api-clients";
3
+ import logger from "@nimee/logger";
4
4
  import { CustomError } from "@nimee/error-handler";
5
-
6
- // Support both legacy and new userEvent schema (with ticketsInfo and without.)
7
- export const getTotalTicketsAmount = (allUsersEvent: IUserEventModel[]): number => {
8
- return allUsersEvent.reduce((acc: number, userEvent: IUserEventModel) => {
9
- // For user-event to one ticket.
10
- if (!userEvent?.ticketsInfo?.length) {
11
- return acc + 1;
12
- }
13
- // For user-event to many ticket.
14
- return acc + userEvent.ticketsInfo.length;
15
- }, 0);
16
- };
5
+ export const paymeFee = 1.1 * 1.17;
17
6
 
18
7
  // we need to calculate the final price with nimi fee and return the nimi fee percentage and nimi fee
19
8
  // we need the original price becuase the fee is calculated on top of the original price
20
9
  // then we return the coupon discount and the final price with nimi fee
21
10
  // then we calculate the nimi fee percentage from the final price after discount with nimi fee
11
+ const roundToTwoDecimals = (num: number) => Math.round(num * 100) / 100;
12
+
22
13
  export const calculateFinalPriceWithNimiFee = (
23
14
  totalPriceWithoutCouponDiscount: number,
24
15
  event: IEventModel,
25
16
  account: IAccountModel,
26
17
  totalPriceWithCouponDiscount: number
27
- ): { nimiFeePercentage: number; nimiFee: number; finalPriceWithNimiFee: number } => {
18
+ ): { nimiFeePercentage: number; nimiFee: number; finalPriceWithNimiFee: number; marketplaceFee: number } => {
28
19
  switch (event.feeCollectionType) {
29
20
  case IFeeCollectionType.PERCENTAGE_FROM_TICKET:
30
- return { finalPriceWithNimiFee: totalPriceWithCouponDiscount, nimiFee: 0, nimiFeePercentage: account.marketFee }; // we use here marketFee (and not originalMarketFee) because we need to send the fee to payme without their fee
21
+ const marketplaceFeeOfPERCENTAGE_FROM_TICKET =
22
+ (totalPriceWithCouponDiscount * (account.originalMarketFee - paymeFee - account.marketFee)) / 100;
23
+ return {
24
+ finalPriceWithNimiFee: totalPriceWithCouponDiscount,
25
+ nimiFee: 0,
26
+ nimiFeePercentage: account.marketFee,
27
+ marketplaceFee: roundToTwoDecimals(marketplaceFeeOfPERCENTAGE_FROM_TICKET),
28
+ }; // we use here marketFee (and not originalMarketFee) because we need to send the fee to payme without their fee
31
29
  case IFeeCollectionType.ON_TOP_OF_TICKET:
32
30
  // check if the totalPrice with coupon discount is 0 then we return 0 for the fee and the percentage
33
31
  if (totalPriceWithCouponDiscount === 0) {
34
- return { finalPriceWithNimiFee: 0, nimiFee: 0, nimiFeePercentage: 0 };
32
+ return { finalPriceWithNimiFee: 0, nimiFee: 0, nimiFeePercentage: 0, marketplaceFee: 0 };
35
33
  }
36
34
  // minimum 3 so we use max we totalPriceWithoutCouponDiscount because the fee is calculated on top of the original price and not the price after discount
37
35
  const unroundedFee = Math.max(3, (totalPriceWithoutCouponDiscount * account.originalMarketFee) / 100); // here we use originalMarketFee because we need to calculate the fee on top of the original price (later we will subtract payme fee from the fee we send to payme)
38
- const roundedFee = Math.round(unroundedFee);
36
+ let roundedFee = Math.ceil(unroundedFee * 10) / 10;
39
37
  // nimiFeePercentage has unknown range that need to be aware of - we get error when we send to payme with range above 60 so we use max
40
- const nimiFeePercentage = Math.min(59, (roundedFee / (totalPriceWithCouponDiscount + roundedFee)) * 100);
38
+ // const nimiFeePercentage = Math.min(59, (roundedFee / totalPriceWithCouponDiscount) * 100); // we use here totalPriceWithCouponDiscount because we send payme the price after the discount plus the nimiFee (roundedFee) so we need to know the percentage of the fee from the final price
41
39
  // we need to make sure we send marketFee that is greater or equal to 0 so we use max and we must subtract payme fee from nimiFeePercentage because payme ask to send them the fee without their fee
42
- const nimiFeePercentageMinusPaymeFee = Math.max(0, nimiFeePercentage - 1.1 * 1.17);
40
+ // const nimiFeePercentageMinusPaymeFee = Math.max(0, nimiFeePercentage - 1.1 * 1.17);
41
+ let marketplaceFeeOfON_TOP_OF_TICKET =
42
+ roundedFee - ((roundedFee + totalPriceWithCouponDiscount) * (account.marketFee + paymeFee)) / 100; // payme takes the percentage from the price. so we calculate payme and nimi fee from the price and we substruct the result from the roundedFee
43
+ if (marketplaceFeeOfON_TOP_OF_TICKET <= 0) {
44
+ // means the originalMarketFee is equal or close to the marketFee+paymeFee) - happens when the price of the ticket is high and the fee of payme and nimi is high.. so we want to add more money to the fee so the seller wont lose here
45
+ logger.debug(
46
+ `marketplaceFee is less than 0 marketplaceFee: ${marketplaceFeeOfON_TOP_OF_TICKET} and nimiFee: ${roundedFee}, account: ${account._id}, event: ${event._id}`
47
+ );
48
+ const newUnroundedFee = roundedFee + Math.abs(marketplaceFeeOfON_TOP_OF_TICKET) + 1; // so we want to add the minus fee as absolute and another small amount because the solek takes from the new value (price+new rounded fee)
49
+ roundedFee = Math.ceil(newUnroundedFee * 10) / 10;
50
+ marketplaceFeeOfON_TOP_OF_TICKET =
51
+ roundedFee - ((roundedFee + totalPriceWithCouponDiscount) * (account.marketFee + paymeFee)) / 100; // payme takes the percentage from the price. so we calculate payme and nimi fee from the price and we substruct the result from the roundedFee
52
+ logger.info(
53
+ `so assign new fee: ${marketplaceFeeOfON_TOP_OF_TICKET}, nimiFee: ${roundedFee}, account: ${account._id}, event: ${event._id}`
54
+ );
55
+ }
43
56
  return {
44
57
  finalPriceWithNimiFee: roundedFee + totalPriceWithCouponDiscount,
45
58
  nimiFee: roundedFee,
46
- nimiFeePercentage: nimiFeePercentageMinusPaymeFee,
59
+ nimiFeePercentage: account.marketFee,
60
+ marketplaceFee: roundToTwoDecimals(marketplaceFeeOfON_TOP_OF_TICKET),
47
61
  };
48
62
  default:
49
- return { finalPriceWithNimiFee: totalPriceWithCouponDiscount, nimiFee: 0, nimiFeePercentage: account.marketFee };
63
+ throw new CustomError(
64
+ "fee_collection_type_not_supported",
65
+ 400,
66
+ `fee collection type not supported - ${event.feeCollectionType}`
67
+ );
50
68
  }
51
69
  };
52
70
  export const calculateFinalPriceWithCouponDiscount = (totalPrice: number, couponDiscount: number): number =>
53
71
  totalPrice - couponDiscount < 0 ? 0 : totalPrice - couponDiscount;
54
-
72
+ export const calculateFinalPriceWithEndUserSeasonTicket = (totalPrice: number, endUserSeasonTicketDiscountPrice: any): number =>
73
+ totalPrice - endUserSeasonTicketDiscountPrice < 0 ? 0 : totalPrice - endUserSeasonTicketDiscountPrice;
55
74
  // Validate the order is really free, rather the tickets free or with the coupon discount.
56
75
  // we do this so the client won't be able to create orders with price 0 and it won't be validated
57
- export const validateFreeOrder = async (ticketIds: string[], couponDiscount: number, accountId: string, eventId: string) => {
58
- const ticketClient = new TicketClient();
59
- const tickets = await ticketClient.getTicketsByIds({ token: process.env.JWT_ADMIN, ticketIds, accountId, eventId });
60
- if (!tickets?.length) {
61
- throw new CustomError(
62
- "tickets_not_found_validate_free_order",
63
- 400,
64
- `tickets not found when try to validate free order with ticketIds - ${ticketIds} and eventId - ${eventId}`
65
- );
66
- }
76
+ // export const validateFreeOrder = async (ticketIds: string[], couponDiscount: number, accountId: string, eventId: string) => {
77
+ // const ticketClient = new TicketClient();
78
+ // const tickets = await ticketClient.getTicketsByIds({ token: process.env.JWT_ADMIN, ticketIds, accountId, eventId });
79
+ // if (!tickets?.length) {
80
+ // throw new CustomError(
81
+ // "tickets_not_found_validate_free_order",
82
+ // 400,
83
+ // `tickets not found when try to validate free order with ticketIds - ${ticketIds} and eventId - ${eventId}`
84
+ // );
85
+ // }
67
86
 
68
- const rawOrderPriceFromTickets = tickets.reduce((acc: number, ticket: { price: number }) => acc + ticket.price, 0);
69
- if (rawOrderPriceFromTickets === 0) {
70
- return true;
71
- }
87
+ // const rawOrderPriceFromTickets = tickets.reduce((acc: number, ticket: { price: number }) => acc + ticket.price, 0);
88
+ // if (rawOrderPriceFromTickets === 0) {
89
+ // return true;
90
+ // }
72
91
 
73
- if (couponDiscount) {
74
- const orderPriceAfterCoupon = calculateFinalPriceWithCouponDiscount(rawOrderPriceFromTickets, couponDiscount);
75
- if (orderPriceAfterCoupon === 0) {
76
- return true;
77
- }
78
- }
92
+ // if (couponDiscount) {
93
+ // const orderPriceAfterCoupon = calculateFinalPriceWithCouponDiscount(rawOrderPriceFromTickets, couponDiscount);
94
+ // if (orderPriceAfterCoupon === 0) {
95
+ // return true;
96
+ // }
97
+ // }
79
98
 
80
- return false;
81
- };
99
+ // return false;
100
+ // };
@@ -0,0 +1,141 @@
1
+ import {
2
+ calculateFinalPriceWithNimiFee,
3
+ calculateFinalPriceWithCouponDiscount,
4
+ calculateFinalPriceWithEndUserSeasonTicket,
5
+ paymeFee,
6
+ } from "../src/prices";
7
+
8
+ describe("calculateFinalPriceWithNimiFee", () => {
9
+ it("when event fee type is PERCENTAGE_FROM_TICKET, should calculate the final price with nimi fee and return the nimi fee percentage and nimi fee and marketplaceFee", () => {
10
+ const totalPriceWithoutCouponDiscount = 100;
11
+ const accountMarketFee = 2;
12
+ const originalMarketFee = 3.1;
13
+ const event = { id: "1", name: "Event 1", feeCollectionType: "percentage_from_ticket" };
14
+ const account = { id: "1", name: "Account 1", marketFee: accountMarketFee, originalMarketFee: originalMarketFee };
15
+ const totalPriceWithCouponDiscount = 90;
16
+
17
+ const result = calculateFinalPriceWithNimiFee(
18
+ totalPriceWithoutCouponDiscount,
19
+ event as any,
20
+ account as any,
21
+ totalPriceWithCouponDiscount
22
+ );
23
+ const expectedMarketplaceFee = parseFloat(
24
+ ((totalPriceWithCouponDiscount * (account.originalMarketFee - paymeFee - account.marketFee)) / 100).toFixed(2)
25
+ );
26
+
27
+ expect(result.nimiFeePercentage).toBe(accountMarketFee);
28
+ expect(result.nimiFee).toBe(0);
29
+ expect(result.finalPriceWithNimiFee).toBe(totalPriceWithCouponDiscount);
30
+ expect(result.marketplaceFee).toBe(expectedMarketplaceFee);
31
+ });
32
+ it("when event fee type is ON_TOP_OF_TICKET, should calculate the final price with nimi fee and return the nimi fee percentage and nimi fee and marketplaceFee", () => {
33
+ const totalPriceWithoutCouponDiscount = 100;
34
+ const accountMarketFee = 2;
35
+ const originalMarketFee = 3.1;
36
+ const event = { id: "1", name: "Event 1", feeCollectionType: "on_top_of_ticket" };
37
+ const account = { id: "1", name: "Account 1", marketFee: accountMarketFee, originalMarketFee: originalMarketFee };
38
+ const totalPriceWithCouponDiscount = 90;
39
+
40
+ const result = calculateFinalPriceWithNimiFee(
41
+ totalPriceWithoutCouponDiscount,
42
+ event as any,
43
+ account as any,
44
+ totalPriceWithCouponDiscount
45
+ );
46
+ const nimiFee = 3.1;
47
+ const expectedMarketplaceFee = parseFloat(
48
+ (nimiFee - ((totalPriceWithCouponDiscount + nimiFee) * (paymeFee + account.marketFee)) / 100).toFixed(2)
49
+ );
50
+ expect(result.nimiFeePercentage).toBe(accountMarketFee);
51
+ expect(result.nimiFee).toBe(nimiFee);
52
+ expect(result.finalPriceWithNimiFee).toBe(totalPriceWithCouponDiscount + nimiFee);
53
+ expect(result.marketplaceFee).toBe(expectedMarketplaceFee);
54
+ });
55
+ it("when event fee type is ON_TOP_OF_TICKET and the originalMarketFee is float 2, should calculate the final price with nimi fee and return the nimi fee percentage and nimi fee and marketplaceFee", () => {
56
+ const totalPriceWithoutCouponDiscount = 100;
57
+ const accountMarketFee = 2;
58
+ const originalMarketFee = 3.158;
59
+ const event = { id: "1", name: "Event 1", feeCollectionType: "on_top_of_ticket" };
60
+ const account = { id: "1", name: "Account 1", marketFee: accountMarketFee, originalMarketFee: originalMarketFee };
61
+ const totalPriceWithCouponDiscount = 90;
62
+
63
+ const result = calculateFinalPriceWithNimiFee(
64
+ totalPriceWithoutCouponDiscount,
65
+ event as any,
66
+ account as any,
67
+ totalPriceWithCouponDiscount
68
+ );
69
+
70
+ const nimiFee = 3.2;
71
+ const expectedMarketplaceFee = parseFloat(
72
+ (nimiFee - ((totalPriceWithCouponDiscount + nimiFee) * (paymeFee + account.marketFee)) / 100).toFixed(2)
73
+ );
74
+
75
+ expect(result.nimiFeePercentage).toBe(accountMarketFee);
76
+ expect(result.nimiFee).toBe(nimiFee);
77
+ expect(result.finalPriceWithNimiFee).toBe(totalPriceWithCouponDiscount + nimiFee);
78
+ expect(result.marketplaceFee).toBe(expectedMarketplaceFee);
79
+ });
80
+ it("when event fee type is ON_TOP_OF_TICKET and the originalMarketFee is float 2 and result of marketplaceFee is below zero, should calculate the final price with nimi fee and return the nimi fee percentage and nimi fee and marketplaceFee", () => {
81
+ const totalPriceWithoutCouponDiscount = 200;
82
+ const accountMarketFee = 1.287;
83
+ const originalMarketFee = 0.287;
84
+ const event = { id: "1", name: "Event 1", feeCollectionType: "on_top_of_ticket" };
85
+ const account = { id: "1", name: "Account 1", marketFee: accountMarketFee, originalMarketFee: originalMarketFee };
86
+ const totalPriceWithCouponDiscount = 200;
87
+
88
+ const result = calculateFinalPriceWithNimiFee(
89
+ totalPriceWithoutCouponDiscount,
90
+ event as any,
91
+ account as any,
92
+ totalPriceWithCouponDiscount
93
+ );
94
+
95
+ const nimiFee = 6.3;
96
+ expect(result.nimiFeePercentage).toBe(accountMarketFee);
97
+ expect(result.nimiFee).toBe(nimiFee);
98
+ expect(result.finalPriceWithNimiFee).toBe(totalPriceWithCouponDiscount + nimiFee);
99
+ expect(result.marketplaceFee).toBe(0.99);
100
+ });
101
+ });
102
+
103
+ describe("calculateFinalPriceWithCouponDiscount", () => {
104
+ it("should calculate the final price with coupon discount", () => {
105
+ const totalPrice = 100;
106
+ const couponDiscount = 20;
107
+
108
+ const finalPrice = calculateFinalPriceWithCouponDiscount(totalPrice, couponDiscount);
109
+
110
+ expect(finalPrice).toBe(80);
111
+ });
112
+
113
+ it("should return 0 if the final price is negative", () => {
114
+ const totalPrice = 10;
115
+ const couponDiscount = 20;
116
+
117
+ const finalPrice = calculateFinalPriceWithCouponDiscount(totalPrice, couponDiscount);
118
+
119
+ expect(finalPrice).toBe(0);
120
+ });
121
+ });
122
+
123
+ describe("calculateFinalPriceWithEndUserSeasonTicket", () => {
124
+ it("should calculate the final price with end user season ticket discount", () => {
125
+ const totalPrice = 100;
126
+ const endUserSeasonTicketDiscountPrice = 30;
127
+
128
+ const finalPrice = calculateFinalPriceWithEndUserSeasonTicket(totalPrice, endUserSeasonTicketDiscountPrice);
129
+
130
+ expect(finalPrice).toBe(70);
131
+ });
132
+
133
+ it("should return 0 if the final price is negative", () => {
134
+ const totalPrice = 10;
135
+ const endUserSeasonTicketDiscountPrice = 20;
136
+
137
+ const finalPrice = calculateFinalPriceWithEndUserSeasonTicket(totalPrice, endUserSeasonTicketDiscountPrice);
138
+
139
+ expect(finalPrice).toBe(0);
140
+ });
141
+ });