@infrab4a/connect 5.7.10-alpha.0 → 5.7.10-alpha.2

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/index.cjs.js CHANGED
@@ -1933,62 +1933,153 @@ class AntifraudBankSlipService {
1933
1933
  }
1934
1934
  }
1935
1935
 
1936
+ const ANTIFRAUD_CONFIG_CACHE_TTL_SECONDS = 60;
1937
+ /** Defaults personalizáveis via shopConfigs.antifraude; usados só como fallback. */
1938
+ const DEFAULT_ANTIFRAUD_VOLUME_LIMITS = {
1939
+ enabled: true,
1940
+ source: 'fallback',
1941
+ day: {
1942
+ subscriber: { cpf: 4, email: 4, phone: 4, card: 4, zip: 4 },
1943
+ nonSubscriber: { cpf: 2, email: 2, phone: 2, card: 2, zip: 2 },
1944
+ },
1945
+ week: {
1946
+ subscriber: { cpf: 12, email: 12, phone: 12, card: 12, zip: Infinity },
1947
+ nonSubscriber: { cpf: 7, email: 7, phone: 7, card: 7, zip: Infinity },
1948
+ },
1949
+ blockedAttemptsDay: {
1950
+ subscriber: 7,
1951
+ nonSubscriber: 5,
1952
+ },
1953
+ };
1954
+ function isPositiveInteger(value) {
1955
+ return typeof value === 'number' && Number.isFinite(value) && value >= 1 && Number.isInteger(value);
1956
+ }
1957
+ function normalizeLimitOrders(raw, allowInfiniteZip) {
1958
+ if (!raw)
1959
+ return null;
1960
+ const zip = allowInfiniteZip && (raw.zip === null || raw.zip === undefined || raw.zip === Infinity) ? Infinity : raw.zip;
1961
+ const normalized = {
1962
+ cpf: raw.cpf,
1963
+ email: raw.email,
1964
+ phone: raw.phone,
1965
+ zip: zip,
1966
+ card: raw.card,
1967
+ };
1968
+ for (const key of ['cpf', 'email', 'phone', 'zip', 'card']) {
1969
+ if (key === 'zip' && normalized.zip === Infinity)
1970
+ continue;
1971
+ if (!isPositiveInteger(normalized[key]))
1972
+ return null;
1973
+ }
1974
+ return normalized;
1975
+ }
1976
+ /** Expõe resolução para testes; config inválida → fallback (comportamento atual). */
1977
+ function resolveAntifraudVolumeLimits(antifraude) {
1978
+ if (!antifraude || typeof antifraude.enabled !== 'boolean') {
1979
+ return {
1980
+ ...DEFAULT_ANTIFRAUD_VOLUME_LIMITS,
1981
+ day: {
1982
+ subscriber: { ...DEFAULT_ANTIFRAUD_VOLUME_LIMITS.day.subscriber },
1983
+ nonSubscriber: { ...DEFAULT_ANTIFRAUD_VOLUME_LIMITS.day.nonSubscriber },
1984
+ },
1985
+ week: {
1986
+ subscriber: { ...DEFAULT_ANTIFRAUD_VOLUME_LIMITS.week.subscriber },
1987
+ nonSubscriber: { ...DEFAULT_ANTIFRAUD_VOLUME_LIMITS.week.nonSubscriber },
1988
+ },
1989
+ blockedAttemptsDay: { ...DEFAULT_ANTIFRAUD_VOLUME_LIMITS.blockedAttemptsDay },
1990
+ source: 'fallback',
1991
+ };
1992
+ }
1993
+ const daySubscriber = normalizeLimitOrders(antifraude.day?.subscriber, false);
1994
+ const dayNonSubscriber = normalizeLimitOrders(antifraude.day?.nonSubscriber, false);
1995
+ const weekSubscriber = normalizeLimitOrders(antifraude.week?.subscriber, true);
1996
+ const weekNonSubscriber = normalizeLimitOrders(antifraude.week?.nonSubscriber, true);
1997
+ const blocked = antifraude.blockedAttemptsDay;
1998
+ if (!daySubscriber ||
1999
+ !dayNonSubscriber ||
2000
+ !weekSubscriber ||
2001
+ !weekNonSubscriber ||
2002
+ !blocked ||
2003
+ !isPositiveInteger(blocked.subscriber) ||
2004
+ !isPositiveInteger(blocked.nonSubscriber)) {
2005
+ // Kill-switch explícito prevalece mesmo com payload inválido (senão o fallback religaria o volume).
2006
+ if (antifraude.enabled === false) {
2007
+ const fallback = resolveAntifraudVolumeLimits(null);
2008
+ return { ...fallback, enabled: false, source: 'fallback' };
2009
+ }
2010
+ return resolveAntifraudVolumeLimits(null);
2011
+ }
2012
+ return {
2013
+ enabled: antifraude.enabled,
2014
+ source: 'config',
2015
+ day: { subscriber: daySubscriber, nonSubscriber: dayNonSubscriber },
2016
+ week: { subscriber: weekSubscriber, nonSubscriber: weekNonSubscriber },
2017
+ blockedAttemptsDay: {
2018
+ subscriber: blocked.subscriber,
2019
+ nonSubscriber: blocked.nonSubscriber,
2020
+ },
2021
+ };
2022
+ }
1936
2023
  class AntifraudCardService {
1937
- constructor(orderRepository, orderBlockedRepository) {
2024
+ constructor(orderRepository, orderBlockedRepository, shopConfigsRepository) {
1938
2025
  this.orderRepository = orderRepository;
1939
2026
  this.orderBlockedRepository = orderBlockedRepository;
1940
- this.LIMIT_ORDERS_DAY = null;
1941
- this.LIMIT_ORDERS_WEEK = null;
2027
+ this.shopConfigsRepository = shopConfigsRepository;
1942
2028
  }
1943
2029
  async validate(checkout, card) {
1944
- this.setLimitsByUserType(checkout.user.isSubscriber);
1945
- await this.validateBlockedOrderAttempts(checkout, card);
1946
- await this.validateDayAndWeekOrderLimits(checkout, card);
2030
+ // Limites locais por request — evita race em provider Nest singleton entre awaits.
2031
+ const resolved = await this.loadResolvedLimits();
2032
+ const isSubscriber = !!checkout.user?.isSubscriber;
2033
+ const dayLimits = isSubscriber ? resolved.day.subscriber : resolved.day.nonSubscriber;
2034
+ const weekLimits = isSubscriber ? resolved.week.subscriber : resolved.week.nonSubscriber;
2035
+ const blockedAttemptsDay = isSubscriber
2036
+ ? resolved.blockedAttemptsDay.subscriber
2037
+ : resolved.blockedAttemptsDay.nonSubscriber;
2038
+ if (!resolved.enabled) {
2039
+ console.info(JSON.stringify({
2040
+ msg: 'antifraud_card_volume_skipped',
2041
+ source: resolved.source,
2042
+ enabled: false,
2043
+ checkoutId: checkout.id,
2044
+ isSubscriber,
2045
+ }));
2046
+ return true;
2047
+ }
2048
+ console.info(JSON.stringify({
2049
+ msg: 'antifraud_card_volume_limits',
2050
+ source: resolved.source,
2051
+ enabled: true,
2052
+ checkoutId: checkout.id,
2053
+ isSubscriber,
2054
+ blockedAttemptsDay,
2055
+ day: dayLimits,
2056
+ week: weekLimits,
2057
+ }));
2058
+ await this.validateBlockedOrderAttempts(checkout, card, blockedAttemptsDay);
2059
+ await this.validateDayAndWeekOrderLimits(checkout, card, dayLimits, weekLimits);
1947
2060
  return true;
1948
2061
  }
1949
- setLimitsByUserType(isSubscriber) {
1950
- this.LIMIT_ORDERS_DAY = {
1951
- subscriber: {
1952
- cpf: 4,
1953
- email: 4,
1954
- phone: 4,
1955
- card: 4,
1956
- zip: 4,
1957
- },
1958
- nonSubscriber: {
1959
- cpf: 2,
1960
- email: 2,
1961
- phone: 2,
1962
- card: 2,
1963
- zip: 2,
1964
- },
1965
- };
1966
- this.LIMIT_ORDERS_WEEK = {
1967
- subscriber: {
1968
- cpf: 12,
1969
- email: 12,
1970
- phone: 12,
1971
- card: 12,
1972
- zip: Infinity,
1973
- },
1974
- nonSubscriber: {
1975
- cpf: 7,
1976
- email: 7,
1977
- phone: 7,
1978
- card: 7,
1979
- zip: Infinity,
1980
- },
1981
- };
1982
- this.LIMIT_BLOCKED_ORDERS_DAY = isSubscriber ? 7 : 5;
1983
- }
1984
- getLimitsByUserType(type, isSubscriber) {
1985
- const limits = type === 'day' ? this.LIMIT_ORDERS_DAY : this.LIMIT_ORDERS_WEEK;
1986
- return isSubscriber ? limits['subscriber'] : limits['nonSubscriber'];
2062
+ async loadResolvedLimits() {
2063
+ if (!this.shopConfigsRepository) {
2064
+ return resolveAntifraudVolumeLimits(null);
2065
+ }
2066
+ try {
2067
+ const result = await this.shopConfigsRepository.find({}, { cache: { enabled: true, ttl: ANTIFRAUD_CONFIG_CACHE_TTL_SECONDS } });
2068
+ return resolveAntifraudVolumeLimits(result?.data?.at(0)?.antifraude);
2069
+ }
2070
+ catch (error) {
2071
+ console.warn(JSON.stringify({
2072
+ msg: 'antifraud_card_volume_config_load_failed',
2073
+ source: 'fallback',
2074
+ error: error instanceof Error ? error.message : String(error),
2075
+ }));
2076
+ return resolveAntifraudVolumeLimits(null);
2077
+ }
1987
2078
  }
1988
- async validateBlockedOrderAttempts(checkout, card) {
1989
- const isValid = await this.verifyBlockedOrderAttempts(checkout, card);
2079
+ async validateBlockedOrderAttempts(checkout, card, blockedAttemptsDay) {
2080
+ const isValid = await this.verifyBlockedOrderAttempts(checkout, card, blockedAttemptsDay);
1990
2081
  if (!isValid) {
1991
- throw new FraudValidationError(`Cliente com mais de ${this.LIMIT_BLOCKED_ORDERS_DAY} compras negadas/bloqueadas no dia`, exports.ErrorsCode.fraudPreventionInternal, {
2082
+ throw new FraudValidationError(`Cliente com mais de ${blockedAttemptsDay} compras negadas/bloqueadas no dia`, exports.ErrorsCode.fraudPreventionInternal, {
1992
2083
  checkoutId: checkout.id,
1993
2084
  userEmail: checkout.user.email,
1994
2085
  info: {
@@ -1999,8 +2090,8 @@ class AntifraudCardService {
1999
2090
  });
2000
2091
  }
2001
2092
  }
2002
- async validateDayAndWeekOrderLimits(checkout, card) {
2003
- const isValid = await this.verifyDayAndWeekOrders(checkout, card);
2093
+ async validateDayAndWeekOrderLimits(checkout, card, dayLimits, weekLimits) {
2094
+ const isValid = await this.verifyDayAndWeekOrders(checkout, card, dayLimits, weekLimits);
2004
2095
  if (!isValid) {
2005
2096
  throw new FraudValidationError('Cliente tentando comprar mais do que o permitido no dia/semana', exports.ErrorsCode.fraudPreventionInternal, {
2006
2097
  checkoutId: checkout.id,
@@ -2013,15 +2104,15 @@ class AntifraudCardService {
2013
2104
  });
2014
2105
  }
2015
2106
  }
2016
- async verifyBlockedOrderAttempts(checkout, card) {
2107
+ async verifyBlockedOrderAttempts(checkout, card, blockedAttemptsDay) {
2017
2108
  const dateRange = this.getTodayDateRange();
2018
2109
  const blockedOrders = await this.getBlockedOrdersByMultipleCriteria(checkout, dateRange);
2019
2110
  const totalBlockedAttempts = this.calculateUniqueBlockedAttempts(blockedOrders, checkout);
2020
- if (totalBlockedAttempts >= this.LIMIT_BLOCKED_ORDERS_DAY) {
2111
+ if (totalBlockedAttempts >= blockedAttemptsDay) {
2021
2112
  await this.createBlockedOrderRecord({
2022
2113
  checkout,
2023
2114
  card,
2024
- reason: `More than ${this.LIMIT_BLOCKED_ORDERS_DAY} attempts have failed`,
2115
+ reason: `More than ${blockedAttemptsDay} attempts have failed`,
2025
2116
  key: 'Failed attempts',
2026
2117
  period: 'day',
2027
2118
  });
@@ -2123,12 +2214,12 @@ class AntifraudCardService {
2123
2214
  card,
2124
2215
  });
2125
2216
  }
2126
- async verifyDayAndWeekOrders(checkout, card) {
2217
+ async verifyDayAndWeekOrders(checkout, card, dayLimits, weekLimits) {
2127
2218
  const validationParams = this.buildValidationParams(checkout, card);
2128
- const isDayLimitValid = await this.validateDayOrderLimits(checkout, validationParams);
2219
+ const isDayLimitValid = await this.validateDayOrderLimits(checkout, validationParams, dayLimits);
2129
2220
  if (!isDayLimitValid)
2130
2221
  return false;
2131
- const isWeekLimitValid = await this.validateWeekOrderLimits(checkout, validationParams);
2222
+ const isWeekLimitValid = await this.validateWeekOrderLimits(checkout, validationParams, weekLimits);
2132
2223
  return isWeekLimitValid;
2133
2224
  }
2134
2225
  buildValidationParams(checkout, card) {
@@ -2140,9 +2231,8 @@ class AntifraudCardService {
2140
2231
  card,
2141
2232
  };
2142
2233
  }
2143
- async validateDayOrderLimits(checkout, params) {
2234
+ async validateDayOrderLimits(checkout, params, limits) {
2144
2235
  const ordersPerDay = await this.validateOrdersByRange(params, this.getDateRange('day'));
2145
- const limits = this.getLimitsByUserType('day', checkout.user.isSubscriber);
2146
2236
  return this.checkOrderLimitsAndBlock({
2147
2237
  checkout,
2148
2238
  orderCounts: ordersPerDay,
@@ -2150,9 +2240,8 @@ class AntifraudCardService {
2150
2240
  period: 'day',
2151
2241
  });
2152
2242
  }
2153
- async validateWeekOrderLimits(checkout, params) {
2243
+ async validateWeekOrderLimits(checkout, params, limits) {
2154
2244
  const ordersPerWeek = await this.validateOrdersByRange(params, this.getDateRange('week'));
2155
- const limits = this.getLimitsByUserType('week', checkout.user.isSubscriber);
2156
2245
  return this.checkOrderLimitsAndBlock({
2157
2246
  checkout,
2158
2247
  orderCounts: ordersPerWeek,
@@ -11762,6 +11851,7 @@ exports.ConnectDocumentService = ConnectDocumentService;
11762
11851
  exports.ConnectFirestoreService = ConnectFirestoreService;
11763
11852
  exports.Coupon = Coupon;
11764
11853
  exports.CouponFirestoreRepository = CouponFirestoreRepository;
11854
+ exports.DEFAULT_ANTIFRAUD_VOLUME_LIMITS = DEFAULT_ANTIFRAUD_VOLUME_LIMITS;
11765
11855
  exports.Debug = Debug;
11766
11856
  exports.DebugDecoratorHelper = DebugDecoratorHelper;
11767
11857
  exports.DebugHelper = DebugHelper;
@@ -11902,6 +11992,7 @@ exports.isDebuggable = isDebuggable;
11902
11992
  exports.isUUID = isUUID;
11903
11993
  exports.parseDateTime = parseDateTime;
11904
11994
  exports.registerClass = registerClass;
11995
+ exports.resolveAntifraudVolumeLimits = resolveAntifraudVolumeLimits;
11905
11996
  exports.resolveCacheConfig = resolveCacheConfig;
11906
11997
  exports.resolveClass = resolveClass;
11907
11998
  exports.serialize = serialize;
package/index.esm.js CHANGED
@@ -1927,62 +1927,153 @@ class AntifraudBankSlipService {
1927
1927
  }
1928
1928
  }
1929
1929
 
1930
+ const ANTIFRAUD_CONFIG_CACHE_TTL_SECONDS = 60;
1931
+ /** Defaults personalizáveis via shopConfigs.antifraude; usados só como fallback. */
1932
+ const DEFAULT_ANTIFRAUD_VOLUME_LIMITS = {
1933
+ enabled: true,
1934
+ source: 'fallback',
1935
+ day: {
1936
+ subscriber: { cpf: 4, email: 4, phone: 4, card: 4, zip: 4 },
1937
+ nonSubscriber: { cpf: 2, email: 2, phone: 2, card: 2, zip: 2 },
1938
+ },
1939
+ week: {
1940
+ subscriber: { cpf: 12, email: 12, phone: 12, card: 12, zip: Infinity },
1941
+ nonSubscriber: { cpf: 7, email: 7, phone: 7, card: 7, zip: Infinity },
1942
+ },
1943
+ blockedAttemptsDay: {
1944
+ subscriber: 7,
1945
+ nonSubscriber: 5,
1946
+ },
1947
+ };
1948
+ function isPositiveInteger(value) {
1949
+ return typeof value === 'number' && Number.isFinite(value) && value >= 1 && Number.isInteger(value);
1950
+ }
1951
+ function normalizeLimitOrders(raw, allowInfiniteZip) {
1952
+ if (!raw)
1953
+ return null;
1954
+ const zip = allowInfiniteZip && (raw.zip === null || raw.zip === undefined || raw.zip === Infinity) ? Infinity : raw.zip;
1955
+ const normalized = {
1956
+ cpf: raw.cpf,
1957
+ email: raw.email,
1958
+ phone: raw.phone,
1959
+ zip: zip,
1960
+ card: raw.card,
1961
+ };
1962
+ for (const key of ['cpf', 'email', 'phone', 'zip', 'card']) {
1963
+ if (key === 'zip' && normalized.zip === Infinity)
1964
+ continue;
1965
+ if (!isPositiveInteger(normalized[key]))
1966
+ return null;
1967
+ }
1968
+ return normalized;
1969
+ }
1970
+ /** Expõe resolução para testes; config inválida → fallback (comportamento atual). */
1971
+ function resolveAntifraudVolumeLimits(antifraude) {
1972
+ if (!antifraude || typeof antifraude.enabled !== 'boolean') {
1973
+ return {
1974
+ ...DEFAULT_ANTIFRAUD_VOLUME_LIMITS,
1975
+ day: {
1976
+ subscriber: { ...DEFAULT_ANTIFRAUD_VOLUME_LIMITS.day.subscriber },
1977
+ nonSubscriber: { ...DEFAULT_ANTIFRAUD_VOLUME_LIMITS.day.nonSubscriber },
1978
+ },
1979
+ week: {
1980
+ subscriber: { ...DEFAULT_ANTIFRAUD_VOLUME_LIMITS.week.subscriber },
1981
+ nonSubscriber: { ...DEFAULT_ANTIFRAUD_VOLUME_LIMITS.week.nonSubscriber },
1982
+ },
1983
+ blockedAttemptsDay: { ...DEFAULT_ANTIFRAUD_VOLUME_LIMITS.blockedAttemptsDay },
1984
+ source: 'fallback',
1985
+ };
1986
+ }
1987
+ const daySubscriber = normalizeLimitOrders(antifraude.day?.subscriber, false);
1988
+ const dayNonSubscriber = normalizeLimitOrders(antifraude.day?.nonSubscriber, false);
1989
+ const weekSubscriber = normalizeLimitOrders(antifraude.week?.subscriber, true);
1990
+ const weekNonSubscriber = normalizeLimitOrders(antifraude.week?.nonSubscriber, true);
1991
+ const blocked = antifraude.blockedAttemptsDay;
1992
+ if (!daySubscriber ||
1993
+ !dayNonSubscriber ||
1994
+ !weekSubscriber ||
1995
+ !weekNonSubscriber ||
1996
+ !blocked ||
1997
+ !isPositiveInteger(blocked.subscriber) ||
1998
+ !isPositiveInteger(blocked.nonSubscriber)) {
1999
+ // Kill-switch explícito prevalece mesmo com payload inválido (senão o fallback religaria o volume).
2000
+ if (antifraude.enabled === false) {
2001
+ const fallback = resolveAntifraudVolumeLimits(null);
2002
+ return { ...fallback, enabled: false, source: 'fallback' };
2003
+ }
2004
+ return resolveAntifraudVolumeLimits(null);
2005
+ }
2006
+ return {
2007
+ enabled: antifraude.enabled,
2008
+ source: 'config',
2009
+ day: { subscriber: daySubscriber, nonSubscriber: dayNonSubscriber },
2010
+ week: { subscriber: weekSubscriber, nonSubscriber: weekNonSubscriber },
2011
+ blockedAttemptsDay: {
2012
+ subscriber: blocked.subscriber,
2013
+ nonSubscriber: blocked.nonSubscriber,
2014
+ },
2015
+ };
2016
+ }
1930
2017
  class AntifraudCardService {
1931
- constructor(orderRepository, orderBlockedRepository) {
2018
+ constructor(orderRepository, orderBlockedRepository, shopConfigsRepository) {
1932
2019
  this.orderRepository = orderRepository;
1933
2020
  this.orderBlockedRepository = orderBlockedRepository;
1934
- this.LIMIT_ORDERS_DAY = null;
1935
- this.LIMIT_ORDERS_WEEK = null;
2021
+ this.shopConfigsRepository = shopConfigsRepository;
1936
2022
  }
1937
2023
  async validate(checkout, card) {
1938
- this.setLimitsByUserType(checkout.user.isSubscriber);
1939
- await this.validateBlockedOrderAttempts(checkout, card);
1940
- await this.validateDayAndWeekOrderLimits(checkout, card);
2024
+ // Limites locais por request — evita race em provider Nest singleton entre awaits.
2025
+ const resolved = await this.loadResolvedLimits();
2026
+ const isSubscriber = !!checkout.user?.isSubscriber;
2027
+ const dayLimits = isSubscriber ? resolved.day.subscriber : resolved.day.nonSubscriber;
2028
+ const weekLimits = isSubscriber ? resolved.week.subscriber : resolved.week.nonSubscriber;
2029
+ const blockedAttemptsDay = isSubscriber
2030
+ ? resolved.blockedAttemptsDay.subscriber
2031
+ : resolved.blockedAttemptsDay.nonSubscriber;
2032
+ if (!resolved.enabled) {
2033
+ console.info(JSON.stringify({
2034
+ msg: 'antifraud_card_volume_skipped',
2035
+ source: resolved.source,
2036
+ enabled: false,
2037
+ checkoutId: checkout.id,
2038
+ isSubscriber,
2039
+ }));
2040
+ return true;
2041
+ }
2042
+ console.info(JSON.stringify({
2043
+ msg: 'antifraud_card_volume_limits',
2044
+ source: resolved.source,
2045
+ enabled: true,
2046
+ checkoutId: checkout.id,
2047
+ isSubscriber,
2048
+ blockedAttemptsDay,
2049
+ day: dayLimits,
2050
+ week: weekLimits,
2051
+ }));
2052
+ await this.validateBlockedOrderAttempts(checkout, card, blockedAttemptsDay);
2053
+ await this.validateDayAndWeekOrderLimits(checkout, card, dayLimits, weekLimits);
1941
2054
  return true;
1942
2055
  }
1943
- setLimitsByUserType(isSubscriber) {
1944
- this.LIMIT_ORDERS_DAY = {
1945
- subscriber: {
1946
- cpf: 4,
1947
- email: 4,
1948
- phone: 4,
1949
- card: 4,
1950
- zip: 4,
1951
- },
1952
- nonSubscriber: {
1953
- cpf: 2,
1954
- email: 2,
1955
- phone: 2,
1956
- card: 2,
1957
- zip: 2,
1958
- },
1959
- };
1960
- this.LIMIT_ORDERS_WEEK = {
1961
- subscriber: {
1962
- cpf: 12,
1963
- email: 12,
1964
- phone: 12,
1965
- card: 12,
1966
- zip: Infinity,
1967
- },
1968
- nonSubscriber: {
1969
- cpf: 7,
1970
- email: 7,
1971
- phone: 7,
1972
- card: 7,
1973
- zip: Infinity,
1974
- },
1975
- };
1976
- this.LIMIT_BLOCKED_ORDERS_DAY = isSubscriber ? 7 : 5;
1977
- }
1978
- getLimitsByUserType(type, isSubscriber) {
1979
- const limits = type === 'day' ? this.LIMIT_ORDERS_DAY : this.LIMIT_ORDERS_WEEK;
1980
- return isSubscriber ? limits['subscriber'] : limits['nonSubscriber'];
2056
+ async loadResolvedLimits() {
2057
+ if (!this.shopConfigsRepository) {
2058
+ return resolveAntifraudVolumeLimits(null);
2059
+ }
2060
+ try {
2061
+ const result = await this.shopConfigsRepository.find({}, { cache: { enabled: true, ttl: ANTIFRAUD_CONFIG_CACHE_TTL_SECONDS } });
2062
+ return resolveAntifraudVolumeLimits(result?.data?.at(0)?.antifraude);
2063
+ }
2064
+ catch (error) {
2065
+ console.warn(JSON.stringify({
2066
+ msg: 'antifraud_card_volume_config_load_failed',
2067
+ source: 'fallback',
2068
+ error: error instanceof Error ? error.message : String(error),
2069
+ }));
2070
+ return resolveAntifraudVolumeLimits(null);
2071
+ }
1981
2072
  }
1982
- async validateBlockedOrderAttempts(checkout, card) {
1983
- const isValid = await this.verifyBlockedOrderAttempts(checkout, card);
2073
+ async validateBlockedOrderAttempts(checkout, card, blockedAttemptsDay) {
2074
+ const isValid = await this.verifyBlockedOrderAttempts(checkout, card, blockedAttemptsDay);
1984
2075
  if (!isValid) {
1985
- throw new FraudValidationError(`Cliente com mais de ${this.LIMIT_BLOCKED_ORDERS_DAY} compras negadas/bloqueadas no dia`, ErrorsCode.fraudPreventionInternal, {
2076
+ throw new FraudValidationError(`Cliente com mais de ${blockedAttemptsDay} compras negadas/bloqueadas no dia`, ErrorsCode.fraudPreventionInternal, {
1986
2077
  checkoutId: checkout.id,
1987
2078
  userEmail: checkout.user.email,
1988
2079
  info: {
@@ -1993,8 +2084,8 @@ class AntifraudCardService {
1993
2084
  });
1994
2085
  }
1995
2086
  }
1996
- async validateDayAndWeekOrderLimits(checkout, card) {
1997
- const isValid = await this.verifyDayAndWeekOrders(checkout, card);
2087
+ async validateDayAndWeekOrderLimits(checkout, card, dayLimits, weekLimits) {
2088
+ const isValid = await this.verifyDayAndWeekOrders(checkout, card, dayLimits, weekLimits);
1998
2089
  if (!isValid) {
1999
2090
  throw new FraudValidationError('Cliente tentando comprar mais do que o permitido no dia/semana', ErrorsCode.fraudPreventionInternal, {
2000
2091
  checkoutId: checkout.id,
@@ -2007,15 +2098,15 @@ class AntifraudCardService {
2007
2098
  });
2008
2099
  }
2009
2100
  }
2010
- async verifyBlockedOrderAttempts(checkout, card) {
2101
+ async verifyBlockedOrderAttempts(checkout, card, blockedAttemptsDay) {
2011
2102
  const dateRange = this.getTodayDateRange();
2012
2103
  const blockedOrders = await this.getBlockedOrdersByMultipleCriteria(checkout, dateRange);
2013
2104
  const totalBlockedAttempts = this.calculateUniqueBlockedAttempts(blockedOrders, checkout);
2014
- if (totalBlockedAttempts >= this.LIMIT_BLOCKED_ORDERS_DAY) {
2105
+ if (totalBlockedAttempts >= blockedAttemptsDay) {
2015
2106
  await this.createBlockedOrderRecord({
2016
2107
  checkout,
2017
2108
  card,
2018
- reason: `More than ${this.LIMIT_BLOCKED_ORDERS_DAY} attempts have failed`,
2109
+ reason: `More than ${blockedAttemptsDay} attempts have failed`,
2019
2110
  key: 'Failed attempts',
2020
2111
  period: 'day',
2021
2112
  });
@@ -2117,12 +2208,12 @@ class AntifraudCardService {
2117
2208
  card,
2118
2209
  });
2119
2210
  }
2120
- async verifyDayAndWeekOrders(checkout, card) {
2211
+ async verifyDayAndWeekOrders(checkout, card, dayLimits, weekLimits) {
2121
2212
  const validationParams = this.buildValidationParams(checkout, card);
2122
- const isDayLimitValid = await this.validateDayOrderLimits(checkout, validationParams);
2213
+ const isDayLimitValid = await this.validateDayOrderLimits(checkout, validationParams, dayLimits);
2123
2214
  if (!isDayLimitValid)
2124
2215
  return false;
2125
- const isWeekLimitValid = await this.validateWeekOrderLimits(checkout, validationParams);
2216
+ const isWeekLimitValid = await this.validateWeekOrderLimits(checkout, validationParams, weekLimits);
2126
2217
  return isWeekLimitValid;
2127
2218
  }
2128
2219
  buildValidationParams(checkout, card) {
@@ -2134,9 +2225,8 @@ class AntifraudCardService {
2134
2225
  card,
2135
2226
  };
2136
2227
  }
2137
- async validateDayOrderLimits(checkout, params) {
2228
+ async validateDayOrderLimits(checkout, params, limits) {
2138
2229
  const ordersPerDay = await this.validateOrdersByRange(params, this.getDateRange('day'));
2139
- const limits = this.getLimitsByUserType('day', checkout.user.isSubscriber);
2140
2230
  return this.checkOrderLimitsAndBlock({
2141
2231
  checkout,
2142
2232
  orderCounts: ordersPerDay,
@@ -2144,9 +2234,8 @@ class AntifraudCardService {
2144
2234
  period: 'day',
2145
2235
  });
2146
2236
  }
2147
- async validateWeekOrderLimits(checkout, params) {
2237
+ async validateWeekOrderLimits(checkout, params, limits) {
2148
2238
  const ordersPerWeek = await this.validateOrdersByRange(params, this.getDateRange('week'));
2149
- const limits = this.getLimitsByUserType('week', checkout.user.isSubscriber);
2150
2239
  return this.checkOrderLimitsAndBlock({
2151
2240
  checkout,
2152
2241
  orderCounts: ordersPerWeek,
@@ -11581,4 +11670,4 @@ class ProductsVertexSearch {
11581
11670
  }
11582
11671
  }
11583
11672
 
11584
- export { AccessoryImportances, Address, AdyenCardAxiosAdapter, AdyenPaymentMethodFactory, AntifraudBankSlipService, AntifraudCardService, AntifraudGlampointsService, AntifraudPixService, AntifraudProviderFactory, AntifraudProviders, Area, Authentication, AuthenticationFirebaseAuthService, AxiosAdapter, Base, BaseModel, BeardProblems, BeardSizes, BeautyProductImportances, BeautyProfile, BeautyQuestionsHelper, BillingStatus, BodyProblems, BodyShapes, BodyTattoos, BrandCategory, BrandCategoryFirestoreRepository, BrandEquityOptions, BusinessError, BusinessUnitEnum, Buy2Win, Buy2WinFirestoreRepository, Campaign, CampaignBanner, CampaignDashboard, CampaignDashboardFirestoreRepository, CampaignHashtag, CampaignHashtagFirestoreRepository, Category, CategoryCollectionChildren, CategoryCollectionChildrenHasuraGraphQLRepository, CategoryFilter, CategoryFilterHasuraGraphQLRepository, CategoryFirestoreRepository, CategoryHasuraGraphQL, CategoryHasuraGraphQLRepository, CategoryProduct, CategoryProductHasuraGraphQLRepository, Checkout, CheckoutFirestoreRepository, CheckoutSubscription, CheckoutSubscriptionFirestoreRepository, CheckoutTypes, ClassNameHelper, ConnectBaseDocumentSnapshot, ConnectCollectionService, ConnectDocumentService, ConnectFirestoreService, Coupon, CouponCategories, CouponCategory, CouponChannels, CouponFirestoreRepository, CouponOldCategories, CouponSubtypes, CouponTypes, Debug, DebugDecoratorHelper, DebugHelper, DebugNamespaces, DuplicatedResultsError, Edition, EditionStatus, ErrorsCode, Exclusivities, FaceSkinOilinesses, FaceSkinProblems, FaceSkinTones, FamilyIncomes, Filter, FilterHasuraGraphQLRepository, FilterOption, FilterOptionHasuraGraphQLRepository, FilterType, FirebaseFileUploaderService, FragranceImportances, FraudValidationError, GenderDestination, GlampointsPaymentMethodFactory, GlampointsPaymentService, Group, GroupFirestoreRepository, HairColors, HairProblems, HairStrands, HairTypes, Home, HomeFirestoreRepository, InvalidArgumentError, InvalidCheckoutError, KitProduct, KitProductHasuraGraphQL, Lead, LeadFirestoreRepository, LegacyOrderFirestoreRepository, LineItem, Log, LogDocument, LogFirestoreRepository, Logger, MercadoPagoBankSlipAxiosAdapter, MercadoPagoCardAxiosAdapter, MercadoPagoErrorHelper, MercadoPagoPaymentMethodFactory, MercadoPagoPixAxiosAdapter, MercadoPagoRequestHelper, MercadoPagoResponseHelper, MercadoPagoStatusDetailEnum, MercadoPagoStatusEnum, NotFoundError, ObsEmitter, OfficePosition, Order, OrderBlocked, OrderBlockedFirestoreRepository, OrderBlockedType, OrderFirestoreRepository, OrderPaymentStatus, OrderStatus, PagarMeV5OrderStatus, PagarMeV5PaymentStatus, PagarmeBankSlipAxiosAdapter, PagarmeCardAxiosAdapter, PagarmePaymentMethodFactory, PagarmePaymentStatus, PagarmePixAxiosAdapter, PagarmeV5BankSlipAxiosAdapter, PagarmeV5BaseAxiosAdapter, PagarmeV5CardAxiosAdapter, PagarmeV5PixAxiosAdapter, Payment, PaymentError, PaymentFirestoreRepository, PaymentMethods, PaymentProviderFactory, PaymentProviders, PaymentTransaction, PaymentType, PersonTypes, Plans, Product, ProductCatalogHasuraGraphQL, ProductCatalogHasuraGraphQLRepository, ProductErrors, ProductErrorsHasuraGraphQL, ProductErrorsHasuraGraphQLRepository, ProductFirestoreRepository, ProductGroup, ProductGroupHasuraGraphQLRepository, ProductHasuraGraphQL, ProductHasuraGraphQLRepository, ProductLabelEnum, ProductPriceLog, ProductPriceLogHasuraGraphQLRepository, ProductReview, ProductReviewHasuraGraphQLRepository, ProductSpents, ProductStockEntry, ProductStockEntryHasuraGraphQL, ProductStockEntryHasuraGraphQLRepository, ProductStockNotification, ProductStockNotificationHasuraGraphQLRepository, ProductVariantFirestoreRepository, ProductsIndex, ProductsVertexSearch, QuestionsFilters, RecoveryPassword, ReflectHelper, Register, RegisterFirebaseAuthService, RequiredArgumentError, RestCacheAdapter, RoundProductPricesHelper, Sequence, SequenceFirestoreRepository, ShippingMethod, ShopConfigs, ShopConfigsFirestoreRepository, ShopMenu, ShopMenuFirestoreRepository, ShopPageName, ShopSettings, ShopSettingsFirestoreRepository, ShoppingRecurrence, ShoppingRecurrenceCycle, ShoppingRecurrenceEdition, ShoppingRecurrenceEditionFirestoreRepository, ShoppingRecurrenceEditionStatus, ShoppingRecurrenceErrorLog, ShoppingRecurrenceErrorLogFirestoreRepository, ShoppingRecurrenceFirestoreRepository, ShoppingRecurrenceStatus, Shops, SignInMethods, SignOut, Status, StockLimitError, StockOutError, Subscription, SubscriptionEditionFirestoreRepository, SubscriptionFirestoreRepository, SubscriptionMaterialization, SubscriptionMaterializationFirestoreRepository, SubscriptionPayment, SubscriptionPaymentFirestoreRepository, SubscriptionPlan, SubscriptionPlanFirestoreRepository, SubscriptionProductFirestoreRepository, SubscriptionSummary, SubscriptionSummaryFirestoreRepository, Trace, TransactionPaymentMethods, UnauthorizedError, UpdateOptionActions, UpdateUserImage, User, UserAddress, UserAddressFirestoreRepository, UserAlreadyRegisteredError, UserBeautyProfileFirestoreRepository, UserFirestoreRepository, UserPaymentMethod, UserPaymentMethodFirestoreRepository, UserType, Variant, VariantHasuraGraphQL, VariantHasuraGraphQLRepository, VertexAxiosAdapter, WeakPasswordError, Where, Wishlist, WishlistHasuraGraphQLRepository, WishlistLogType, deserialize, getClass, is, isDebuggable, isUUID, parseDateTime, registerClass, resolveCacheConfig, resolveClass, serialize, toCents, withCreateFirestore, withCreateHasuraGraphQL, withCrudFirestore, withCrudHasuraGraphQL, withDeleteFirestore, withDeleteHasuraGraphQL, withFindFirestore, withFindHasuraGraphQL, withFirestore, withGetFirestore, withGetHasuraGraphQL, withHasuraGraphQL, withHelpers, withSubCollection, withUpdateFirestore, withUpdateHasuraGraphQL };
11673
+ export { AccessoryImportances, Address, AdyenCardAxiosAdapter, AdyenPaymentMethodFactory, AntifraudBankSlipService, AntifraudCardService, AntifraudGlampointsService, AntifraudPixService, AntifraudProviderFactory, AntifraudProviders, Area, Authentication, AuthenticationFirebaseAuthService, AxiosAdapter, Base, BaseModel, BeardProblems, BeardSizes, BeautyProductImportances, BeautyProfile, BeautyQuestionsHelper, BillingStatus, BodyProblems, BodyShapes, BodyTattoos, BrandCategory, BrandCategoryFirestoreRepository, BrandEquityOptions, BusinessError, BusinessUnitEnum, Buy2Win, Buy2WinFirestoreRepository, Campaign, CampaignBanner, CampaignDashboard, CampaignDashboardFirestoreRepository, CampaignHashtag, CampaignHashtagFirestoreRepository, Category, CategoryCollectionChildren, CategoryCollectionChildrenHasuraGraphQLRepository, CategoryFilter, CategoryFilterHasuraGraphQLRepository, CategoryFirestoreRepository, CategoryHasuraGraphQL, CategoryHasuraGraphQLRepository, CategoryProduct, CategoryProductHasuraGraphQLRepository, Checkout, CheckoutFirestoreRepository, CheckoutSubscription, CheckoutSubscriptionFirestoreRepository, CheckoutTypes, ClassNameHelper, ConnectBaseDocumentSnapshot, ConnectCollectionService, ConnectDocumentService, ConnectFirestoreService, Coupon, CouponCategories, CouponCategory, CouponChannels, CouponFirestoreRepository, CouponOldCategories, CouponSubtypes, CouponTypes, DEFAULT_ANTIFRAUD_VOLUME_LIMITS, Debug, DebugDecoratorHelper, DebugHelper, DebugNamespaces, DuplicatedResultsError, Edition, EditionStatus, ErrorsCode, Exclusivities, FaceSkinOilinesses, FaceSkinProblems, FaceSkinTones, FamilyIncomes, Filter, FilterHasuraGraphQLRepository, FilterOption, FilterOptionHasuraGraphQLRepository, FilterType, FirebaseFileUploaderService, FragranceImportances, FraudValidationError, GenderDestination, GlampointsPaymentMethodFactory, GlampointsPaymentService, Group, GroupFirestoreRepository, HairColors, HairProblems, HairStrands, HairTypes, Home, HomeFirestoreRepository, InvalidArgumentError, InvalidCheckoutError, KitProduct, KitProductHasuraGraphQL, Lead, LeadFirestoreRepository, LegacyOrderFirestoreRepository, LineItem, Log, LogDocument, LogFirestoreRepository, Logger, MercadoPagoBankSlipAxiosAdapter, MercadoPagoCardAxiosAdapter, MercadoPagoErrorHelper, MercadoPagoPaymentMethodFactory, MercadoPagoPixAxiosAdapter, MercadoPagoRequestHelper, MercadoPagoResponseHelper, MercadoPagoStatusDetailEnum, MercadoPagoStatusEnum, NotFoundError, ObsEmitter, OfficePosition, Order, OrderBlocked, OrderBlockedFirestoreRepository, OrderBlockedType, OrderFirestoreRepository, OrderPaymentStatus, OrderStatus, PagarMeV5OrderStatus, PagarMeV5PaymentStatus, PagarmeBankSlipAxiosAdapter, PagarmeCardAxiosAdapter, PagarmePaymentMethodFactory, PagarmePaymentStatus, PagarmePixAxiosAdapter, PagarmeV5BankSlipAxiosAdapter, PagarmeV5BaseAxiosAdapter, PagarmeV5CardAxiosAdapter, PagarmeV5PixAxiosAdapter, Payment, PaymentError, PaymentFirestoreRepository, PaymentMethods, PaymentProviderFactory, PaymentProviders, PaymentTransaction, PaymentType, PersonTypes, Plans, Product, ProductCatalogHasuraGraphQL, ProductCatalogHasuraGraphQLRepository, ProductErrors, ProductErrorsHasuraGraphQL, ProductErrorsHasuraGraphQLRepository, ProductFirestoreRepository, ProductGroup, ProductGroupHasuraGraphQLRepository, ProductHasuraGraphQL, ProductHasuraGraphQLRepository, ProductLabelEnum, ProductPriceLog, ProductPriceLogHasuraGraphQLRepository, ProductReview, ProductReviewHasuraGraphQLRepository, ProductSpents, ProductStockEntry, ProductStockEntryHasuraGraphQL, ProductStockEntryHasuraGraphQLRepository, ProductStockNotification, ProductStockNotificationHasuraGraphQLRepository, ProductVariantFirestoreRepository, ProductsIndex, ProductsVertexSearch, QuestionsFilters, RecoveryPassword, ReflectHelper, Register, RegisterFirebaseAuthService, RequiredArgumentError, RestCacheAdapter, RoundProductPricesHelper, Sequence, SequenceFirestoreRepository, ShippingMethod, ShopConfigs, ShopConfigsFirestoreRepository, ShopMenu, ShopMenuFirestoreRepository, ShopPageName, ShopSettings, ShopSettingsFirestoreRepository, ShoppingRecurrence, ShoppingRecurrenceCycle, ShoppingRecurrenceEdition, ShoppingRecurrenceEditionFirestoreRepository, ShoppingRecurrenceEditionStatus, ShoppingRecurrenceErrorLog, ShoppingRecurrenceErrorLogFirestoreRepository, ShoppingRecurrenceFirestoreRepository, ShoppingRecurrenceStatus, Shops, SignInMethods, SignOut, Status, StockLimitError, StockOutError, Subscription, SubscriptionEditionFirestoreRepository, SubscriptionFirestoreRepository, SubscriptionMaterialization, SubscriptionMaterializationFirestoreRepository, SubscriptionPayment, SubscriptionPaymentFirestoreRepository, SubscriptionPlan, SubscriptionPlanFirestoreRepository, SubscriptionProductFirestoreRepository, SubscriptionSummary, SubscriptionSummaryFirestoreRepository, Trace, TransactionPaymentMethods, UnauthorizedError, UpdateOptionActions, UpdateUserImage, User, UserAddress, UserAddressFirestoreRepository, UserAlreadyRegisteredError, UserBeautyProfileFirestoreRepository, UserFirestoreRepository, UserPaymentMethod, UserPaymentMethodFirestoreRepository, UserType, Variant, VariantHasuraGraphQL, VariantHasuraGraphQLRepository, VertexAxiosAdapter, WeakPasswordError, Where, Wishlist, WishlistHasuraGraphQLRepository, WishlistLogType, deserialize, getClass, is, isDebuggable, isUUID, parseDateTime, registerClass, resolveAntifraudVolumeLimits, resolveCacheConfig, resolveClass, serialize, toCents, withCreateFirestore, withCreateHasuraGraphQL, withCrudFirestore, withCrudHasuraGraphQL, withDeleteFirestore, withDeleteHasuraGraphQL, withFindFirestore, withFindHasuraGraphQL, withFirestore, withGetFirestore, withGetHasuraGraphQL, withHasuraGraphQL, withHelpers, withSubCollection, withUpdateFirestore, withUpdateHasuraGraphQL };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@infrab4a/connect",
3
- "version": "5.7.10-alpha.0",
3
+ "version": "5.7.10-alpha.2",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org"
6
6
  },
@@ -12,6 +12,11 @@ export declare class ShopConfigs extends BaseModel<ShopConfigs> {
12
12
  subscriber: LimitOrders;
13
13
  nonSubscriber: LimitOrders;
14
14
  };
15
+ /** Falhas/bloqueios internos por dia (além de compras dia/semana). */
16
+ blockedAttemptsDay: {
17
+ subscriber: number;
18
+ nonSubscriber: number;
19
+ };
15
20
  };
16
21
  sameDayNotAvaliable?: ShopSameDayNotAvailable;
17
22
  static get identifiersFields(): GenericIdentifier[];
@@ -2,6 +2,7 @@ export type LimitOrders = {
2
2
  cpf: number;
3
3
  email: number;
4
4
  phone: number;
5
- zip: number;
6
- card?: number;
5
+ /** Na semana, `null` no Firestore vira Infinity no runtime do antifraude. */
6
+ zip: number | null;
7
+ card: number;
7
8
  };
@@ -1,17 +1,42 @@
1
+ import { ShopConfigs, ShopConfigsRepository } from '../../shop-settings';
1
2
  import { AntifraudProvider } from '../interfaces';
2
3
  import { Checkout } from '../models';
3
4
  import { OrderBlockedRepository, OrderRepository } from '../repositories';
4
5
  import { PaymentCardInfo } from '../types';
6
+ type LimitOrders = {
7
+ cpf: number;
8
+ email: number;
9
+ phone: number;
10
+ zip: number;
11
+ card?: number;
12
+ };
13
+ type ResolvedAntifraudVolumeLimits = {
14
+ enabled: boolean;
15
+ source: 'config' | 'fallback';
16
+ day: {
17
+ subscriber: LimitOrders;
18
+ nonSubscriber: LimitOrders;
19
+ };
20
+ week: {
21
+ subscriber: LimitOrders;
22
+ nonSubscriber: LimitOrders;
23
+ };
24
+ blockedAttemptsDay: {
25
+ subscriber: number;
26
+ nonSubscriber: number;
27
+ };
28
+ };
29
+ /** Defaults personalizáveis via shopConfigs.antifraude; usados só como fallback. */
30
+ export declare const DEFAULT_ANTIFRAUD_VOLUME_LIMITS: ResolvedAntifraudVolumeLimits;
31
+ /** Expõe resolução para testes; config inválida → fallback (comportamento atual). */
32
+ export declare function resolveAntifraudVolumeLimits(antifraude: ShopConfigs['antifraude'] | undefined | null): ResolvedAntifraudVolumeLimits;
5
33
  export declare class AntifraudCardService implements AntifraudProvider {
6
34
  private readonly orderRepository;
7
35
  private readonly orderBlockedRepository;
8
- private LIMIT_ORDERS_DAY;
9
- private LIMIT_ORDERS_WEEK;
10
- private LIMIT_BLOCKED_ORDERS_DAY;
11
- constructor(orderRepository: OrderRepository, orderBlockedRepository: OrderBlockedRepository);
36
+ private readonly shopConfigsRepository?;
37
+ constructor(orderRepository: OrderRepository, orderBlockedRepository: OrderBlockedRepository, shopConfigsRepository?: ShopConfigsRepository);
12
38
  validate(checkout: Checkout, card: PaymentCardInfo): Promise<Boolean>;
13
- private setLimitsByUserType;
14
- private getLimitsByUserType;
39
+ private loadResolvedLimits;
15
40
  private validateBlockedOrderAttempts;
16
41
  private validateDayAndWeekOrderLimits;
17
42
  private verifyBlockedOrderAttempts;
@@ -35,3 +60,4 @@ export declare class AntifraudCardService implements AntifraudProvider {
35
60
  private countOrdersByField;
36
61
  private getDateRange;
37
62
  }
63
+ export {};