PageSourceSearch

https://nutrebem.prod.nutrebem.com.br/assets/controllers/credit_card_form_controller-89d49c6e.js

js nutrebem.com.br collected 2026-10-01 16:39:01 UTC 4,723 bytes, 140 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2import { validarDocumento } from "lib/documento"
3import { bandeiraAceita } from "lib/cartao"
4
5// Deposito por cartao de credito. O botao so libera com numero, validade, titular, documento
6// e CVV validos; no envio, os dados vao cifrados pelo PagSeguro e o formulario carrega so o
7// resultado — o numero do cartao nunca e enviado ao nosso servidor.
8//
9// A validade e digitada como mm/aa e sai em dois campos ocultos que a cifragem consome.
10export default class extends Controller {
11  static targets = ["numero", "titular", "validade", "mes", "ano", "cvv", "documento", "cifrado", "enviar"]
12  static values = { chavePublica: String }
13
14  // Sem validar ao conectar, de proposito: o formulario abre limpo e o botao ja nasce
15  // desabilitado pelo ERB. Validar aqui marcaria os campos vazios de vermelho na chegada.
16  connect() {
17    this.anoAtual = new Date().getFullYear() % 100
18    this.#aplicarMascaras()
19  }
20
21  // Duas coisas acontecem aqui, e a ordem importa para o que o usuario ve.
22  //
23  // O campo que acabou de ser digitado e sempre conferido. Ja a checagem que libera o botao
24  // para na primeira falha — entao os campos seguintes nao chegam a ser marcados. E como a
25  // tela sempre se comportou: quem ainda nao foi preenchido nao aparece em vermelho so
26  // porque outro campo antes dele esta invalido.
27  validar(evento) {
28    this.#conferirCampo(evento?.target)
29
30    this.enviarTarget.disabled = !(
31      this.#validarNumero() &&
32      this.#validarValidade() &&
33      this.#validarTitular() &&
34      this.#validarDocumento() &&
35      this.#validarCvv()
36    )
37  }
38
39  #conferirCampo(campo) {
40    const porCampo = new Map([
41      [this.numeroTarget, () => this.#validarNumero()],
42      [this.validadeTarget, () => this.#validarValidade()],
43      [this.titularTarget, () => this.#validarTitular()],
44      [this.documentoTarget, () => this.#validarDocumento()],
45      [this.cvvTarget, () => this.#validarCvv()],
46    ])
47    porCampo.get(campo)?.()
48  }
49
50  #validarDocumento() {
51    return this.#marcar(this.documentoTarget, validarDocumento(this.documentoTarget.value))
52  }
53
54  enviar(evento) {
55    evento.preventDefault()
56    this.enviarTarget.disabled = true
57
58    const cartao = PagSeguro.encryptCard({
59      publicKey: this.chavePublicaValue,
60      holder: this.titularTarget.value,
61      number: this.#digitos(this.numeroTarget),
62      expMonth: this.#digitos(this.mesTarget),
63      expYear: this.#digitos(this.anoTarget),
64      securityCode: this.#digitos(this.cvvTarget),
65    })
66
67    if (cartao.hasErrors) {
68      console.log(cartao.errors[0].code)
69      this.cifradoTarget.value = cartao.errors[0].code
70      this.enviarTarget.disabled = false
71      return
72    }
73
74    this.cifradoTarget.value = cartao.encryptedCard
75    this.element.submit()
76  }
77
78  #aplicarMascaras() {
79    $(this.numeroTarget).mask("0000 0000 0000 0000")
80    $(this.cvvTarget).mask("0000")
81
82    $(this.validadeTarget).mask("00/00", {
83      translation: { 0: { pattern: /[0-9]/, optional: false } },
84      onKeyPress: (valor, evento, campo, opcoes) => {
85        const atual = campo.val()
86
87        // Mes fora de 01-12 e recusado assim que fecha as duas casas.
88        if (atual.length === 2) {
89          const mes = parseInt(atual.slice(0, 2), 10)
90          if (mes < 1 || mes > 12) campo.val("")
91        }
92
93        // Ano no passado ou depois de 2039 apaga so o ano.
94        if (atual.length === 5) {
95          const ano = parseInt(atual.slice(3, 5), 10)
96          if (ano < this.anoAtual || ano > 39) campo.val(atual.slice(0, 3))
97        }
98
99        campo.mask("00/00", opcoes)
100      },
101    })
102  }
103
104  #validarNumero() {
105    return this.#marcar(this.numeroTarget, bandeiraAceita(this.numeroTarget.value))
106  }
107
108  // O titular vai em caixa alta e sem acento, como o provedor exige.
109  #validarTitular() {
110    const campo = this.titularTarget
111    campo.value = campo.value.toUpperCase().normalize("NFD").replace(/[̀-ͯ]/g, "")
112
113    return this.#marcar(campo, campo.value.length > 0)
114  }
115
116  #validarValidade() {
117    const texto = this.validadeTarget.value
118    const mes = parseInt(texto.slice(0, 2), 10)
119    const ano = parseInt(texto.slice(3, 5), 10)
120    const valido = mes >= 1 && mes <= 12 && ano >= this.anoAtual && ano <= 39
121
122    this.mesTarget.value = valido ? texto.slice(0, 2) : ""
123    this.anoTarget.value = valido ? `20${texto.slice(3, 5)}` : ""
124
125    return this.#marcar(this.validadeTarget, valido)
126  }
127
128  #validarCvv() {
129    return this.#marcar(this.cvvTarget, this.cvvTarget.value.length >= 3)
130  }
131
132  #digitos(campo) {
133    return campo.value.replace(/[^0-9]/g, "")
134  }
135
136  #marcar(campo, valido) {
137    campo.classList.toggle("nutrebem-error-input", !valido)
138    return valido
139  }
140}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.