UNPKG

qivopay-sdk

Version:

SDK de pagamentos com MercadoPago e PagarMe

401 lines (330 loc) 14.1 kB
import pagarme from 'pagarme' import 'dotenv/config' type Gateway = 'mercadopago' | 'pagarme' | 'asaas' interface SdkConfig { gateway: Gateway chooseBestGateway?: boolean preference?: 'fee' | 'daysToReceive' paymentMethod: string installmentCount?: number publicKey: string containerId: string amount: number buttonText?: string onTokenGenerated: (token: string) => void customer: { name: string email: string phone: string document: string address: { number: string postalCode: string } } } export class PaymentSdk { private gatewayKey = '' private baseApiQivoPay = process.env.BASE_API_QIVOPAY constructor(private config: SdkConfig) {} async init() { if (this.config.chooseBestGateway) { const { gateway, apiKey } = await this.chooseBestGateway() this.config.gateway = gateway this.gatewayKey = apiKey await this.loadGateways(this.config.gateway) return } if (!this.config.chooseBestGateway) { if (!this.config.gateway) { const { gateway, apiKey } = await this.chooseBestGateway() this.config.gateway = gateway this.gatewayKey = apiKey await this.loadGateways(this.config.gateway) return } const { gateway, apiKey } = await this.getGatewayInfo(this.config.gateway) this.config.gateway = gateway this.gatewayKey = apiKey await this.loadGateways(this.config.gateway) } } private async loadGateways(gateway: Gateway) { switch(gateway) { case 'mercadopago': await this.loadMercadoPago() this.mountMercadoPagoComponent() break case 'pagarme': await this.loadPagarme() this.mountPagarmeForm() break case 'asaas': this.mountAsaasForm() break default: throw new Error('Gateway não suportado') } } private async chooseBestGateway() { const { amount, publicKey } = this.config const response = await fetch(`${this.baseApiQivoPay}/gateway/best-gateway?amount=${amount}&paymentMethod=${this?.config?.paymentMethod || 'credit_card'}&preference=${this?.config?.preference || 'fee'}&installmentCount=${this?.config?.installmentCount || 1}`, { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${publicKey}` } }) if (!response.ok) { throw new Error('Erro ao buscar gateway') } const { data: { gateway, apiKey } } = await response.json() return { gateway, apiKey } } private async getGatewayInfo(gatewayToFind: Gateway) { const { publicKey } = this.config const response = await fetch(`${this.baseApiQivoPay}/choosed-gateway?gateway=${gatewayToFind}`, { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${publicKey}` } }) if (!response.ok) { throw new Error('Erro ao buscar gateway') } const { gateway, apiKey } = await response.json() return { gateway, apiKey } } async loadMercadoPago() { return new Promise((resolve, reject) => { const scriptId = 'mercado-pago-sdk' if (document.getElementById(scriptId)) return resolve(null) const script = document.createElement('script') script.id = scriptId script.src = 'https://sdk.mercadopago.com/js/v2' script.onload = () => resolve(null) script.onerror = reject document.body.appendChild(script) }) } mountMercadoPagoComponent() { const mp = new window.MercadoPago(this.gatewayKey) const form = document.getElementById(this.config.containerId) if (!form) { throw new Error('Formulário não encontrado') } form.innerHTML = ` <form id="mercadopago-form" style="max-width:400px;margin:auto;padding:16px;background:#fff;border-radius:8px;display:flex;flex-direction:column;gap:12px;"> <input type="text" id="form-name" placeholder="Nome no cartão" style="padding:10px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%;" /> <input type="text" id="form-number" placeholder="Número do cartão" style="padding:10px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%;" /> <input type="text" id="form-expiration" placeholder="MM/YY" style="padding:10px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%;" /> <input type="text" id="form-cvv" placeholder="CVV" style="padding:10px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%;" /> <select id="form-installments" style="padding:10px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%;"> ${[...Array(12)].map((_, i) => `<option value="${i + 1}">${i + 1}x</option>`).join('')} </select> <select id="form-issuer" style="padding:10px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%; display: none;"> ${[...Array(12)].map((_, i) => `<option value="${i + 1}">Bandeira ${i + 1}</option>`).join('')} </select> <button type="submit" style="padding:12px;background:#2563eb;color:white;border:none;border-radius:6px;font-weight:bold;font-size:16px;cursor:pointer;transition:background 0.2s;"> ${this.config.buttonText || 'Comprar'} </button> </form> ` const formFinded = document.getElementById('mercadopago-form') if (!formFinded) { throw new Error('Formulário não encontrado') } if (formFinded) { const cardForm = mp.cardForm({ amount: this.config.amount.toString() || '0', autoMount: true, form: { id: `mercadopago-form`, cardholderName: { id: 'form-name', placeholder: 'Nome no cartão' }, cardNumber: { id: 'form-number', placeholder: 'Número do cartão' }, expirationDate: { id: 'form-expiration', placeholder: 'MM/YY' }, securityCode: { id: 'form-cvv', placeholder: 'CVV' }, installments: { id: 'form-installments', placeholder: 'Parcelas' }, issuer: { id: 'form-issuer', placeholder: 'Bandeira' } }, callbacks: { onFormMounted: (error: any) => { if (error) return console.warn('Erro ao montar o form:', error) }, onSubmit: async (event: any) => { event.preventDefault() const { token } = await cardForm.getCardFormData() this.config.onTokenGenerated(token) } } }) requestAnimationFrame(() => { cardForm.mount() }) } } async loadPagarme() { if (!pagarme) { throw new Error('SDK do Pagar.me não carregado') } } mountPagarmeForm() { const form = document.getElementById(this.config.containerId) if (!form) { throw new Error('Formulário não encontrado') } form.innerHTML = ` <form id="pagarme-form" style="max-width:400px;margin:auto;padding:16px;background:#fff;border-radius:8px;display:flex;flex-direction:column;gap:12px;"> <input type="text" id="card-number" name="number" placeholder="Número do cartão" style="padding:10px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%;" /> <input type="text" id="card-holder" name="holderName" placeholder="Nome do titular" style="padding:10px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%;" /> <div style="display:flex;gap:8px;"> <input type="text" id="card-exp-month" name="expMonth" placeholder="MM" style="flex:1;padding:10px;border:1px solid #ccc;border-radius:6px;font-size:14px;" /> <input type="text" id="card-exp-year" name="expYear" placeholder="YYYY" style="flex:1;padding:10px;border:1px solid #ccc;border-radius:6px;font-size:14px;" /> </div> <input type="text" id="card-cvv" name="cvv" placeholder="CVV" style="padding:10px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%;" /> <select id="form-installments" style="padding:10px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%;"> ${[...Array(12)].map((_, i) => `<option value="${i + 1}">${i + 1}x</option>`).join('')} </select> <select id="form-issuer" style="padding:10px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%; display: none;"> ${[...Array(12)].map((_, i) => `<option value="${i + 1}">Bandeira ${i + 1}</option>`).join('')} </select> <button type="submit" style="padding:12px;background:#2563eb;color:white;border:none;border-radius:6px;font-weight:bold;font-size:16px;cursor:pointer;transition:background 0.2s;"> ${this.config.buttonText || 'Comprar'} </button> </form> ` const getValue = (name: string) => (form.querySelector(`input[name="${name}"]`) as HTMLInputElement)?.value?.trim() || '' form.addEventListener('submit', async (e) => { e.preventDefault() const cardData = { number: getValue('number'), holderName: getValue('holderName'), expMonth: getValue('expMonth'), expYear: getValue('expYear'), cvv: getValue('cvv'), } const client = await pagarme.client.connect({ api_key: this.gatewayKey }) const cardHash = await client.security.createCardHash({ card_number: cardData.number, card_holder_name: cardData.holderName, card_expiration_date: `${cardData.expMonth}${cardData.expYear.slice(2)}`, // MMYY card_cvv: cardData.cvv }) this.config.onTokenGenerated(cardHash) }) } mountAsaasForm() { const form = document.getElementById(this.config.containerId) if (!form) { throw new Error('Formulário não encontrado') } form.innerHTML = ` <form id="asaas-form" style="max-width:400px;margin:auto;padding:16px;background:#fff;border-radius:8px;display:flex;flex-direction:column;gap:12px;"> <input type="text" id="form-name" placeholder="Nome no cartão" style="padding:10px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%;" /> <input type="text" id="form-number" placeholder="Número do cartão" style="padding:10px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%;" /> <input type="text" id="form-expiration" placeholder="MM/YY" style="padding:10px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%;" /> <input type="text" id="form-cvv" placeholder="CVV" style="padding:10px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%;" /> <select id="form-installments" style="padding:10px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%;"> ${[...Array(12)].map((_, i) => `<option value="${i + 1}">${i + 1}x</option>`).join('')} </select> <select id="form-issuer" style="padding:10px;border:1px solid #ccc;border-radius:6px;font-size:14px;width:100%; display: none;"> ${[...Array(12)].map((_, i) => `<option value="${i + 1}">Bandeira ${i + 1}</option>`).join('')} </select> <button type="submit" style="padding:12px;background:#2563eb;color:white;border:none;border-radius:6px;font-weight:bold;font-size:16px;cursor:pointer;transition:background 0.2s;"> ${this.config.buttonText || 'Comprar'} </button> </form> ` const getValue = (name: string) => (form.querySelector(`input[name="${name}"]`) as HTMLInputElement)?.value?.trim() || '' form.addEventListener('submit', async (e) => { e.preventDefault() const apiKey = this.gatewayKey const asaasBaseUrl = 'https://api.asaas.com/v3' const getCustomer: any = await fetch(`${asaasBaseUrl}/customers?email=${this.config.customer.email}`, { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` } }) let createdCustomer: any = null if (!getCustomer.data) { createdCustomer = await fetch(`${asaasBaseUrl}/customers`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify({ email: this.config.customer.email, name: this.config.customer.name, phone: this.config.customer.phone, cpfCnpj: this.config.customer.document }) }) } const cardData = { customer: createdCustomer.data.id, number: getValue('number'), holderName: getValue('holderName'), expMonth: getValue('expMonth'), expYear: getValue('expYear'), cvv: getValue('cvv'), creditCard: { holderName: getValue('holderName'), number: getValue('number'), expiryMonth: getValue('expMonth'), expiryYear: getValue('expYear'), cvv: getValue('cvv') }, creditCardHolderInfo: { name: getValue('holderName'), email: getValue('email'), cpfCnpj: getValue('document'), phone: getValue('phone'), postalCode: this.config.customer.address.postalCode, addressNumber: this.config.customer.address.number, } } fetch(`${asaasBaseUrl}/creditCard/tokenizeCreditCard`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.gatewayKey}` }, body: JSON.stringify(cardData) }) .then(res => res.json()) .then(data => { this.config.onTokenGenerated(data.token) }) }) } destroy() { const container = document.getElementById(this.config.containerId) if (container) container.innerHTML = '' } }