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.