@cactus-agents/user
Serviço de gerenciamento de usuário: perfil, segurança, login history, responsible gaming e income report. Framework-agnostic.
Instalação
pnpm add @cactus-agents/user
Uso básico
A forma recomendada é usar createUserFromClient, que aceita um ApiClient diretamente:
import { createApiClient } from '@cactus-agents/api-client';
import { createUserFromClient } from '@cactus-agents/user';
const client = createApiClient({ baseUrl, tenant, language });
const user = createUserFromClient(client);
const history = await user.getLoginHistory(1);
Uso de baixo nível (fetcher manual)
Se precisar de controle total sobre o adapter HTTP, use createUserService. O fetcher precisa implementar get, post, patch e delete:
import { createUserService } from '@cactus-agents/user';
const userService = createUserService({
get: (path) => myHttpClient.get(path),
post: (path, body) => myHttpClient.post(path, body),
patch: (path, body) => myHttpClient.patch(path, body),
delete: (path) => myHttpClient.delete(path),
});
API — UserService
Perfil
updateProfile(id, data)
POST /users/update/{id}
interface UpdateUserPayload {
captcha_token?: string;
kyc_id?: number;
password_check?: string;
state?: string;
zipcode?: string;
city?: string;
address?: string;
phone?: string;
ddi?: string;
affiliation_code?: string;
email?: string;
birth_date?: string;
terms?: boolean;
language?: string;
timezone?: string;
country?: string;
gender?: number;
first_name?: string;
last_name?: string;
mother_name?: string;
}
getAddress()
GET /bff/users/address-by-user — retorna UserAddress.
updateEmail(data)
PATCH /bff/users/self-email
interface UpdateEmailPayload {
email: string;
password_check?: string;
kyc_id?: number;
}
addPhone(data)
PATCH /bff/users/add-phone
interface AddPhonePayload {
phone: string;
ddi: string;
password_check?: string;
kyc_id?: number;
}
updateAddress(data)
PATCH /bff/users/update-address
interface UpdateAddressPayload {
address: string; // Rua, numero e complemento (a API espera "address", nao "street")
city: string;
state: string;
zipcode: string;
country?: string;
ddi?: string;
captcha_token?: string;
password_check?: string;
kyc_id?: number;
}
storeDocument(data)
POST /documents/{endpoint} — Cadastra documento do usuario. O endpoint varia por pais (definido em DocumentConfig.storeEndpoint).
interface StoreDocumentPayload {
number: string; // Numero do documento (raw, sem formatacao)
captcha_token: string; // Token anti-robot
endpoint: "store-user-document" | "store-mex-document"; // Endpoint da API
}
"store-user-document"→ usado para CPF (BRA)"store-mex-document"→ usado para RUT (CHL), CURP (MEX), DNI (PER), Generic (FIN, XYZ)
Exemplo de uso no template (via proxy server-side):
// No front-web-base, a chamada passa por /api/user/store-document
// que le o JWT do cookie HttpOnly e faz a request autenticada
const res = await fetch("/api/user/store-document", {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
number: docConfig.strip(documentValue),
captcha_token: "",
endpoint: docConfig.storeEndpoint,
}),
});
updateContracts(type)
PATCH /bff/users/self-contracts — envia { type }.
type ContractType = 'tc' | 'privacy' | 'lgpd' | 'law' | 'migrate' | 'mkt';
updateMarketing(data)
PATCH /bff/users/self-mkt
interface UpdateMarketingPayload {
mkt_accepted: boolean;
}
Segurança
changePassword(id, data)
POST /users/change-password/{id}
interface ChangePasswordPayload {
old_password: string;
password: string;
password_confirmation: string;
captcha_token?: string;
kyc_id?: number;
}
checkPassword(data)
POST /bff/users/check-password — retorna { success: boolean }.
toggleTwoFactor(data)
PATCH /bff/users/self-two-factor
interface TwoFactorPayload {
two_factor_type: 'email' | 'sms';
two_factor_enabled: boolean;
password_check?: string;
kyc_id?: number;
}
getSocialAccounts()
GET /bff/users/account-social — retorna SocialAccount[].
connectSocial(type)
POST /bff/users/account-social/connect/{type}
disconnectSocial(id)
DELETE /bff/users/account-social/{id}
getLoginHistory(page?)
GET /bff/users/login-history?page={page} — default page 1.
Retorna dados já normalizados via transformLoginHistoryResponse. O template não precisa lidar com o shape raw da API.
// Tipo público (o que o template consome)
interface LoginHistoryItem {
userId: number;
ip: string | null;
location: string | null; // "São Paulo, SP" — city + state compostos automaticamente
createdAt: string; // ISO 8601: "2026-03-17T18:14:42"
latitude: string | null;
longitude: string | null;
}
interface LoginHistoryResponse {
data: LoginHistoryItem[];
currentPage: number;
lastPage: number;
perPage: number;
total: number;
}
:::note Shape raw da API
A API retorna date (não created_at), campos city/state/latitude/longitude separados, sem id e sem user_agent. O SDK normaliza tudo automaticamente — o template nunca vê o shape raw.
:::
Responsible Gaming
updateLimits(data)
PATCH /bff/users/update-limits — atualiza limites de depósito, aposta, perda e/ou tempo.
interface UpdateLimitsPayload {
user_limit_deposit?: number;
user_limit_deposit_active?: number;
user_limit_deposit_period?: string; // 'daily' | 'weekly' | 'monthly'
user_limit_bet?: number;
user_limit_bet_active?: number;
user_limit_bet_period?: string;
user_limit_loss?: number;
user_limit_loss_active?: number;
user_limit_loss_period?: string;
user_limit_time?: number;
user_limit_time_active?: number;
user_limit_time_period?: string;
kyc_id?: number;
password_check?: string;
}
setTimeoutLimits(data)
PATCH /bff/users/timeout-limits — pausa temporária (1 a 45 dias).
interface TimeoutLimitsPayload {
user_pause: number; // dias (1-45)
reason: number;
kyc_id?: number;
password_check?: string;
}
setSelfExclusion(data)
PATCH /bff/users/self-exclusion — autoexclusão por meses ou permanente.
interface SelfExclusionPayload {
user_exclusion: number; // meses (3, 6, 12, 24, 36, 60) ou -1 permanente
reason: number;
kyc_id?: number;
password_check?: string;
}
Income Report (IRPF)
generateIncomeReport(data)
POST /income-report/generate — retorna IncomeReportData.
interface IncomeReportGeneratePayload {
reference_year: number;
type: 'email' | 'download';
}
getIncomeReportStatus(id)
GET /income-report/{id} — retorna IncomeReportData. Usado para polling até o status mudar.
interface IncomeReportData {
id: number;
reference_year: number;
status: 1 | 2 | 3 | 4; // 1=pendente, 2=processando, 3=concluído, 4=falhou
delivery_type: 'email' | 'download';
requested_at: string;
generated_at: string | null;
path: string | null;
download_url: string | null;
}
getAvailableYears()
GET /income-report/available-years — retorna { years: number[] }.
Wallet
:::info Compatibilidade legada
O módulo de wallet foi extraído para @cactus-agents/wallet. No @cactus-agents/user permanecem apenas re-exports e compatibilidade temporária.
:::
Para novos fluxos, use:
createWalletFromClientfetchWalletDataaggregateWalletData
Documentacao completa em @cactus-agents/wallet.
Responsible Gaming — Helpers e Constantes
Exportados de @cactus-agents/user (fonte de verdade para regras de responsible gaming). O template nunca redefine essas constantes.
Constantes
// Opções de pausa temporária (dias)
const TIMEOUT_DAYS_OPTIONS = [1, 3, 7, 14, 30, 45] as const;
// Opções de autoexclusão (meses). -1 = permanente.
const SELF_EXCLUSION_MONTHS_OPTIONS = [3, 6, 12, 24, 36, 60, -1] as const;
// Códigos de período da API
const LIMIT_PERIOD = { DAILY: 1, WEEKLY: 2, MONTHLY: 3, YEARLY: 4 };
Helpers
parseLimitPeriod(value)
Converte qualquer representação de período (string da API, número, undefined) para o código numérico correto. Fallback: DAILY (1).
parseLimitPeriod("daily") // → 1
parseLimitPeriod("weekly") // → 2
parseLimitPeriod(3) // → 3
parseLimitPeriod(undefined) // → 1 (daily)
isoDurationToHours(raw)
Parseia ISO 8601 duration ou valor numérico para horas. Retorna null quando inválido.
isoDurationToHours("PT4H") // → 4
isoDurationToHours(4) // → 4
isoDurationToHours(null) // → null
isoDurationToHours("bad") // → null
hoursToIsoDuration(hours)
Converte horas para ISO 8601 duration string.
hoursToIsoDuration(4) // → "PT4H"
hoursToIsoDuration(0.5) // → "PT0.5H"
isPermanentExclusion(months)
Retorna true quando o valor representa exclusão permanente (-1 ou >= 99).
isPermanentExclusion(-1) // → true
isPermanentExclusion(99) // → true
isPermanentExclusion(12) // → false
isLimitChangeBlocked(changeAt)
Retorna true quando o limite foi alterado nas ultimas 24 horas (cooldown ativo). Usado para impedir alteracoes consecutivas antes de 24h.
isLimitChangeBlocked("2024-01-15T10:00:00Z") // → true (se < 24h atras)
isLimitChangeBlocked(null) // → false (sem alteracao anterior)
getLimitChangeUnblocksAt(changeAt)
Retorna a data ISO 8601 de quando o cooldown de 24h vai expirar. Retorna null quando nao ha cooldown ativo.
getLimitChangeUnblocksAt("2024-01-15T10:00:00Z") // → "2024-01-16T10:00:00.000Z"
getLimitChangeUnblocksAt(null) // → null
Restriction Extension Helpers
Quando o usuario ja tem uma autoexclusao ou pausa ativa, ele pode estender (aumentar o periodo) — mas nunca reduzir. Essas funcoes filtram as opcoes disponiveis com base no estado atual do usuario.
RestrictionMinInput
Interface de entrada (subset de AuthUserInfo):
interface RestrictionMinInput {
user_exclusion?: number;
user_exclusion_min?: number | null;
user_pause?: number;
user_pause_min?: number | null;
}
user_exclusion_min/user_pause_min— vem da API, define o minimo absoluto.- Quando
*_minnao existe, o fallback e o valor atual (user_exclusion/user_pause).
getMinSelfExclusionMonths(input)
Retorna o minimo de meses que o usuario pode selecionar. Retorna 0 se nenhuma exclusao esta ativa.
getMinSelfExclusionMonths({ user_exclusion: 3, user_exclusion_min: 6 }) // → 6
getMinSelfExclusionMonths({ user_exclusion: 12 }) // → 12 (min nao definido, usa atual)
getMinSelfExclusionMonths(null) // → 0
filterSelfExclusionOptions(input)
Filtra SELF_EXCLUSION_MONTHS_OPTIONS para apenas periodos >= minimo. Permanente (-1) sempre incluso, exceto quando ja permanente.
filterSelfExclusionOptions({ user_exclusion: 3, user_exclusion_min: 6 })
// → [6, 12, 24, 36, 60, -1] (3 excluido porque min e 6)
filterSelfExclusionOptions({ user_exclusion: 99 })
// → [] (ja permanente — sem opcoes)
filterSelfExclusionOptions(null)
// → [3, 6, 12, 24, 36, 60, -1] (todas as opcoes)
getMinTimeoutDays(input)
Retorna o minimo de dias que o usuario pode selecionar. Retorna 0 se nenhuma pausa esta ativa.
getMinTimeoutDays({ user_pause: 7, user_pause_min: 14 }) // → 14
getMinTimeoutDays({ user_pause: 7 }) // → 7 (min nao definido, usa atual)
getMinTimeoutDays(null) // → 0
filterTimeoutOptions(input)
Filtra TIMEOUT_DAYS_OPTIONS para apenas periodos >= minimo.
filterTimeoutOptions({ user_pause: 7, user_pause_min: 7 })
// → [7, 14, 30, 45] (1 e 3 excluidos)
filterTimeoutOptions({ user_pause: 45, user_pause_min: 60 })
// → [] (nenhuma opcao disponivel)
Uso no template:
import {
TIMEOUT_DAYS_OPTIONS,
SELF_EXCLUSION_MONTHS_OPTIONS,
isoDurationToHours,
parseLimitPeriod,
isPermanentExclusion,
filterSelfExclusionOptions,
filterTimeoutOptions,
} from "@cactus-agents/user";
// No componente — zero lógica local
const currentHours = isoDurationToHours(userInfo.user_limit_time);
const periodCode = parseLimitPeriod(userInfo.user_limit_deposit_period);
const isPermanent = isPermanentExclusion(userInfo.user_exclusion);
// Extensao — opcoes filtradas com base no estado do usuario
const exclusionOptions = filterSelfExclusionOptions(userInfo);
const timeoutOptions = filterTimeoutOptions(userInfo);
Account Restriction
Modulo de deteccao de restricoes de conta. Centraliza toda a logica para verificar se a conta esta em modo restrito (so saques) e qual tipo de restricao esta ativa.
isAccountRestricted(input)
Retorna true quando a conta tem qualquer restricao ativa.
isAccountRestricted(userInfo) // → true quando qualquer restricao esta ativa
isAccountRestricted(null) // → false
getAccountRestriction(input)
Retorna a restricao ativa de maior prioridade com todos os metadados, ou null.
Prioridade (mesma do legado): SPA > Operator > Self-exclusion > Timeout
const r = getAccountRestriction(userInfo);
if (r) {
r.type // "spa_exclusion" | "operator_exclusion" | "self_exclusion" | "timeout"
r.isPermanent // true quando permanente
r.endsAt // ISO date de expiracao (ou null)
r.activatedAt // ISO date de ativacao
r.note // Nota do operador/SPA
r.spaCode // Codigo SPA (99, 102, 103)
r.months // Meses (self-exclusion)
r.days // Dias (timeout)
}
getRestrictionType(input)
Shorthand — retorna apenas o tipo como string ou null.
getRestrictionType(userInfo) // → "self_exclusion" | "timeout" | ... | null
SPA_EXCLUSION_CODES
Codigos de exclusao impostos pela autoridade reguladora (SPA):
const SPA_EXCLUSION_CODES = {
PERMANENT: 99, // Exclusao permanente pelo regulador
SOCIAL_BENEFIT: 102, // Bloqueio por ser beneficiario social (ex: IN 22/24 no Brasil)
CENTRALIZED: 103, // Autoexclusao centralizada via plataforma governamental
};
AccountRestriction
interface AccountRestriction {
type: "self_exclusion" | "timeout" | "operator_exclusion" | "spa_exclusion";
isPermanent: boolean;
endsAt: string | null;
activatedAt: string | null;
note: string | null;
spaCode: number | null;
months: number | null;
days: number | null;
}
AccountRestrictionInput
Subset de AuthUserInfo necessario para as funcoes de deteccao:
interface AccountRestrictionInput {
user_exclusion?: number;
user_exclusion_at?: string | null;
user_exclusion_in?: string | null;
user_pause?: number;
user_pause_at?: string | null;
user_pause_in?: string | null;
operator_exclusion?: number;
operator_exclusion_at?: string | null;
operator_exclusion_note?: string | null;
spa_exclusion?: number;
spa_exclusion_at?: string | null;
spa_exclusion_note?: string | null;
}
Transform
transformLoginHistoryResponse(raw)
Normaliza a resposta raw da API (snake_case, formato de data sem T) para o tipo público camelCase com ISO 8601.
transformLoginHistoryItem(raw)
Normaliza um único item: compõe location a partir de city + state, converte date para createdAt ISO 8601.
transformWalletResponse(raw)
transformWalletResponse permanece exportado por compatibilidade, mas o uso recomendado é importar de @cactus-agents/wallet.
Tipos principais
UserLimits
Retornado dentro do userInfo pelo getUserProfile do auth. Contém todos os limites e exclusões do usuário:
interface UserLimits {
user_pause: number;
user_pause_at: string | null;
user_pause_in: string | null;
user_pause_min: number | null;
user_exclusion: number;
user_exclusion_at: string | null;
user_exclusion_in: string | null;
user_exclusion_min: number | null;
user_limit_deposit: number;
user_limit_deposit_active: number;
user_limit_deposit_period: string;
user_limit_deposit_change: string | null;
user_limit_bet: number;
user_limit_bet_active: number;
user_limit_bet_period: string;
user_limit_bet_change: string | null;
user_limit_loss: number;
user_limit_loss_active: number;
user_limit_loss_period: string;
user_limit_loss_change: string | null;
user_limit_time: number;
user_limit_time_active: number;
user_limit_time_period: string;
user_limit_time_change: string | null;
operator_exclusion: number;
operator_exclusion_at: string | null;
operator_exclusion_note: string;
spa_exclusion: number;
spa_exclusion_at: string | null;
spa_exclusion_note: string;
}
Wallet
interface Wallet {
real: RealWallet;
bonus: BonusWallet;
cashback: CashbackWallet;
}
interface RealWallet {
credit: number;
walletId: number;
withdrawEnabled: boolean;
minWithdrawAmount: number;
maxWithdrawAmount: number;
hasWalletDeposit: boolean;
withdrawEnableNow: boolean;
withdrawNextDate: string;
}
interface BonusWallet {
credit: number;
creditHold: number;
sportsAmountHold: number;
casinoAmountHold: number;
expiryDatetime: string;
}
interface CashbackWallet {
credit: number;
expiresAt: string;
}
UserFetcher
Interface que o createUserService espera receber:
interface UserFetcher {
get<T>(path: string): Promise<T>;
post<T>(path: string, body?: unknown): Promise<T>;
patch<T>(path: string, body?: unknown): Promise<T>;
delete<T>(path: string): Promise<T>;
}
Diferente do
ClientLike(get/post), oUserFetcherexige tambémpatchedelete. O wrappercreateUserFromClientfaz a conversão automaticamente.