UNPKG

quantictech-subscription-components

Version:

Biblioteca de componentes reutilizáveis para sistema de assinatura com Stripe - Arquitetura Service-to-Service

374 lines (373 loc) 24.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.StripePaymentElementWrapper = void 0; var jsx_runtime_1 = require("react/jsx-runtime"); var Email_1 = __importDefault(require("@mui/icons-material/Email")); var Lock_1 = __importDefault(require("@mui/icons-material/Lock")); var material_1 = require("@mui/material"); var react_stripe_js_1 = require("@stripe/react-stripe-js"); var stripe_js_1 = require("@stripe/stripe-js"); var react_1 = require("react"); var stripeConfig_1 = require("../utils/stripeConfig"); // Inicializar Stripe var stripePromise = null; var getStripePromise = function () { if (!stripePromise) { try { var publicKey = (0, stripeConfig_1.getStripePublicKey)(); stripePromise = (0, stripe_js_1.loadStripe)(publicKey); } catch (error) { console.error('Erro ao inicializar Stripe:', error); } } return stripePromise; }; var StripePaymentElementWrapper = function (_a) { var clientSecret = _a.clientSecret, selectedPlan = _a.selectedPlan, customerEmail = _a.customerEmail, customerName = _a.customerName, returnUrl = _a.returnUrl, onFormReady = _a.onFormReady, onSuccess = _a.onSuccess, onError = _a.onError, _b = _a.cpfValid, cpfValid = _b === void 0 ? false : _b, cpfValue = _a.cpfValue, userId = _a.userId; if (!clientSecret) { return ((0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { textAlign: 'center', py: 4 }, children: [(0, jsx_runtime_1.jsx)(material_1.CircularProgress, { size: 30 }), (0, jsx_runtime_1.jsx)(material_1.Typography, { sx: { mt: 2 }, children: "Carregando informa\u00E7\u00F5es de pagamento..." })] })); } var stripe = getStripePromise(); if (!stripe) { return ((0, jsx_runtime_1.jsx)(material_1.Alert, { severity: "error", children: "Erro ao carregar sistema de pagamento. Verifique a configura\u00E7\u00E3o do Stripe." })); } return ((0, jsx_runtime_1.jsx)(react_stripe_js_1.Elements, { stripe: stripe, options: { clientSecret: clientSecret, appearance: { theme: 'stripe', variables: { colorPrimary: '#1976d2', colorBackground: '#ffffff', colorText: '#30313d', colorDanger: '#df1b41', fontFamily: 'Roboto, -apple-system, sans-serif', spacingUnit: '4px', borderRadius: '4px', }, rules: { '.Input': { border: '1px solid #E0E0E0', boxShadow: '0px 1px 1px rgba(0, 0, 0, 0.03)', borderRadius: '6px', }, '.Input:focus': { border: '1px solid #1976d2', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.08)', }, '.Label': { fontWeight: '500', }, '.Tab': { padding: '10px 16px', borderRadius: '6px', }, '.Tab:hover': { border: 'none', boxShadow: '0px 1px 2px rgba(0, 0, 0, 0.08)', }, '.TabIcon': { display: 'block', }, '.TabLabel': { display: 'block', }, } }, locale: 'pt-BR', loader: 'always' }, children: (0, jsx_runtime_1.jsx)(StripePaymentForm, { clientSecret: clientSecret, selectedPlan: selectedPlan, customerEmail: customerEmail, customerName: customerName, returnUrl: returnUrl, onFormReady: onFormReady, onSuccess: onSuccess, onError: onError, cpfValid: cpfValid, cpfValue: cpfValue, userId: userId }) })); }; exports.StripePaymentElementWrapper = StripePaymentElementWrapper; var StripePaymentForm = function (_a) { var clientSecret = _a.clientSecret, selectedPlan = _a.selectedPlan, customerEmail = _a.customerEmail, customerName = _a.customerName, returnUrl = _a.returnUrl, onFormReady = _a.onFormReady, onSuccess = _a.onSuccess, onError = _a.onError, _b = _a.cpfValid, cpfValid = _b === void 0 ? false : _b, cpfValue = _a.cpfValue, userId = _a.userId; var stripe = (0, react_stripe_js_1.useStripe)(); var elements = (0, react_stripe_js_1.useElements)(); var theme = (0, material_1.useTheme)(); var _c = (0, react_1.useState)(false), isLoading = _c[0], setIsLoading = _c[1]; var _d = (0, react_1.useState)(null), errorMessage = _d[0], setErrorMessage = _d[1]; var _e = (0, react_1.useState)(false), isFormComplete = _e[0], setIsFormComplete = _e[1]; // Log para verificar se o email foi recebido corretamente (0, react_1.useEffect)(function () { console.log('StripePaymentForm dados recebidos:', { hasCustomerEmail: !!customerEmail, hasCustomerName: !!customerName, emailDomain: customerEmail ? customerEmail.split('@')[1] : undefined, nameLength: (customerName === null || customerName === void 0 ? void 0 : customerName.length) || 0 }); }, [customerEmail, customerName]); // Função para traduzir mensagens de erro do Stripe para português var traduzirErroStripe = function (erro) { // Se o erro for uma string direta, tenta traduzir if (typeof erro === 'string') { var mensagensErro = { 'Your card number is incomplete.': 'O número do cartão está incompleto.', 'Your card\'s expiration date is incomplete.': 'A data de validade do cartão está incompleta.', 'Your card\'s security code is incomplete.': 'O código de segurança (CVC) está incompleto.', 'Your card number is invalid.': 'O número do cartão é inválido.', 'Your card\'s expiration year is in the past.': 'O ano de validade do cartão está no passado.', 'Your card\'s expiration month is invalid.': 'O mês de validade do cartão é inválido.', 'Your card\'s expiration year is invalid.': 'O ano de validade do cartão é inválido.', 'Your card\'s security code is invalid.': 'O código de segurança (CVC) é inválido.', 'Your card has expired.': 'Seu cartão expirou.', 'Your card was declined.': 'Seu cartão foi recusado.', 'Your card has insufficient funds.': 'Seu cartão não possui fundos suficientes.', 'Your card was declined. Use a different payment method.': 'Seu cartão foi recusado. Use um método de pagamento diferente.', 'Your card does not support this type of purchase.': 'Seu cartão não suporta este tipo de compra.', 'Your card cannot be used for this payment.': 'Seu cartão não pode ser usado para este pagamento.', 'Processing error': 'Erro de processamento. Tente novamente mais tarde.', 'Invalid postal code': 'Código postal inválido.', 'Your payment method was declined.': 'Seu método de pagamento foi recusado.', 'We are unable to authenticate your payment method.': 'Não foi possível autenticar seu método de pagamento.', 'Too many attempts, please try again later': 'Muitas tentativas. Tente novamente mais tarde.', 'Something went wrong on our end': 'Ocorreu um erro no nosso servidor. Por favor, tente novamente.', }; return mensagensErro[erro] || erro; } // Se for um objeto de erro do Stripe var stripError = erro; if (stripError.type === 'card_error') { switch (stripError.code) { case 'card_declined': return 'Cartão recusado. Verifique os dados ou tente outro cartão.'; case 'expired_card': return 'Este cartão expirou. Por favor, use outro cartão.'; case 'incorrect_cvc': return 'O código de segurança (CVC) está incorreto.'; case 'incorrect_number': return 'O número do cartão está incorreto.'; case 'incomplete_cvc': return 'O código de segurança (CVC) está incompleto.'; case 'incomplete_number': return 'O número do cartão está incompleto.'; case 'incomplete_expiry': return 'A data de validade está incompleta.'; case 'insufficient_funds': return 'Cartão sem fundos suficientes.'; case 'lost_card': return 'Este cartão foi reportado como perdido.'; case 'stolen_card': return 'Este cartão foi reportado como roubado.'; case 'processing_error': return 'Erro de processamento. Tente novamente em alguns minutos.'; case 'rate_limit': return 'Muitas tentativas. Aguarde alguns minutos e tente novamente.'; default: return stripError.message || 'Erro no cartão. Verifique os dados e tente novamente.'; } } return stripError.message || 'Ocorreu um erro ao processar o pagamento. Por favor, tente novamente.'; }; // Função para formatar moeda var formatCurrency = function (amount, currency) { if (currency === void 0) { currency = 'BRL'; } try { return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: currency, minimumFractionDigits: 2, maximumFractionDigits: 2, }).format(amount / 100); } catch (_a) { // Fallback se a formatação falhar return "R$ ".concat((amount / 100).toFixed(2).replace('.', ',')); } }; // Função para traduzir intervalo var traduzirIntervalo = function (interval) { var intervalos = { 'month': 'mês', 'year': 'ano', 'week': 'semana', 'day': 'dia' }; return intervalos[interval] || interval; }; var handleSubmit = function (e) { return __awaiter(void 0, void 0, void 0, function () { var error, error, pi, result, mensagemTraduzida, error_1, mensagemErro; var _a; return __generator(this, function (_b) { switch (_b.label) { case 0: e.preventDefault(); if (!stripe || !elements || !clientSecret) { error = 'O sistema de pagamento não está pronto. Recarregue a página e tente novamente.'; setErrorMessage(error); if (onError) onError(error); return [2 /*return*/]; } if (!isFormComplete) { error = 'Por favor, preencha todos os campos do cartão.'; setErrorMessage(error); if (onError) onError(error); return [2 /*return*/]; } setIsLoading(true); setErrorMessage(null); _b.label = 1; case 1: _b.trys.push([1, 4, 5, 6]); return [4 /*yield*/, stripe.retrievePaymentIntent(clientSecret)]; case 2: pi = _b.sent(); if (['succeeded', 'processing', 'requires_capture'].includes(((_a = pi.paymentIntent) === null || _a === void 0 ? void 0 : _a.status) || '')) { console.log('Pagamento já confirmado e processado com sucesso'); if (onSuccess) onSuccess(); return [2 /*return*/]; } return [4 /*yield*/, stripe.confirmPayment({ elements: elements, confirmParams: { return_url: returnUrl, payment_method_data: { billing_details: { name: customerName, email: customerEmail, } } } })]; case 3: result = _b.sent(); if (result.error) { console.error('Erro do Stripe:', result.error); mensagemTraduzida = traduzirErroStripe(result.error); setErrorMessage(mensagemTraduzida); if (onError) onError(mensagemTraduzida); } else { console.log('Pagamento processado com sucesso!'); if (onSuccess) onSuccess(); } return [3 /*break*/, 6]; case 4: error_1 = _b.sent(); console.error('Erro inesperado ao processar pagamento:', error_1); mensagemErro = 'Erro inesperado ao processar pagamento. Tente novamente.'; setErrorMessage(mensagemErro); if (onError) onError(mensagemErro); return [3 /*break*/, 6]; case 5: setIsLoading(false); return [7 /*endfinally*/]; case 6: return [2 /*return*/]; } }); }); }; var handlePaymentElementChange = function (event) { setIsFormComplete(event.complete); if (event.error) { var mensagemTraduzida = traduzirErroStripe(event.error.message); setErrorMessage(mensagemTraduzida); } else { setErrorMessage(null); } // Notificar o componente pai sobre o estado do formulário if (onFormReady) { onFormReady(event.complete); } }; return ((0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { maxWidth: 600, mx: 'auto' }, children: [selectedPlan && ((0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { mb: 4, p: 3, borderRadius: 2, bgcolor: (0, material_1.alpha)(theme.palette.primary.main, 0.04), border: "1px solid ".concat((0, material_1.alpha)(theme.palette.primary.main, 0.1)) }, children: [(0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "h6", sx: { mb: 2, fontWeight: 600 }, children: "Resumo do Pedido" }), (0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { display: 'flex', justifyContent: 'space-between', mb: 1 }, children: [(0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "body2", color: "text.secondary", children: "Plano:" }), (0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "body2", fontWeight: 500, children: selectedPlan.name })] }), (0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { display: 'flex', justifyContent: 'space-between', mb: 1 }, children: [(0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "body2", color: "text.secondary", children: "Valor:" }), (0, jsx_runtime_1.jsxs)(material_1.Typography, { variant: "body2", fontWeight: 500, children: [formatCurrency(selectedPlan.price, selectedPlan.currency), "/", traduzirIntervalo(selectedPlan.interval || 'month')] })] }), (0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { display: 'flex', justifyContent: 'space-between', mb: 1 }, children: [(0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "body2", color: "text.secondary", children: "Cliente:" }), (0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "body2", fontWeight: 500, children: customerName })] }), (0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { display: 'flex', justifyContent: 'space-between' }, children: [(0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "body2", color: "text.secondary", children: "Email:" }), (0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "body2", fontWeight: 500, children: customerEmail })] })] })), (0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { mb: 3, p: 2, borderRadius: 2, bgcolor: (0, material_1.alpha)(theme.palette.success.main, 0.04), border: "1px solid ".concat((0, material_1.alpha)(theme.palette.success.main, 0.2)) }, children: [(0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { display: 'flex', alignItems: 'center', mb: 1 }, children: [(0, jsx_runtime_1.jsx)(Lock_1.default, { sx: { fontSize: 16, color: 'success.main', mr: 1 } }), (0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "body2", color: "success.dark", fontWeight: 500, children: "Pagamento 100% Seguro" })] }), (0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "caption", color: "text.secondary", children: "Seus dados s\u00E3o protegidos com criptografia SSL e processados pelo Stripe, uma das plataformas de pagamento mais seguras do mundo." })] }), (0, jsx_runtime_1.jsxs)("form", { onSubmit: handleSubmit, children: [(0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { mb: 3 }, children: [(0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "h6", sx: { mb: 2, fontWeight: 600 }, children: "Informa\u00E7\u00F5es do Cart\u00E3o" }), (0, jsx_runtime_1.jsx)(react_stripe_js_1.PaymentElement, { onChange: handlePaymentElementChange, options: { layout: 'tabs', fields: { billingDetails: { name: 'auto', email: 'auto', phone: 'auto', address: { country: 'auto', line1: 'auto', line2: 'auto', city: 'auto', state: 'auto', postalCode: 'auto' } } } } })] }), (0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { mb: 3 }, children: [(0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "h6", sx: { mb: 2, fontWeight: 600 }, children: "Endere\u00E7o de Cobran\u00E7a" }), (0, jsx_runtime_1.jsx)(react_stripe_js_1.AddressElement, { options: { mode: 'billing', allowedCountries: ['BR'], fields: { phone: 'always' }, validation: { phone: { required: 'never' } } } })] }), errorMessage && ((0, jsx_runtime_1.jsx)(material_1.Alert, { severity: "error", sx: { mb: 3 }, children: errorMessage })), (0, jsx_runtime_1.jsx)(material_1.Button, { type: "submit", variant: "contained", size: "large", fullWidth: true, disabled: !stripe || !elements || isLoading || !isFormComplete, sx: { py: 2, fontSize: '1.1rem', fontWeight: 600, borderRadius: 2, textTransform: 'none', bgcolor: 'success.main', '&:hover': { bgcolor: 'success.dark', }, '&:disabled': { bgcolor: 'grey.300', color: 'grey.600' } }, children: isLoading ? ((0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: [(0, jsx_runtime_1.jsx)(material_1.CircularProgress, { size: 20, color: "inherit" }), "Processando Pagamento..."] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Lock_1.default, { sx: { fontSize: 20, mr: 1 } }), "Confirmar Pagamento", selectedPlan && ((0, jsx_runtime_1.jsx)(material_1.Typography, { component: "span", sx: { ml: 1, opacity: 0.8 }, children: formatCurrency(selectedPlan.price, selectedPlan.currency) }))] })) }), (0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { mt: 3, textAlign: 'center' }, children: [(0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { display: 'flex', alignItems: 'center', justifyContent: 'center', mb: 1 }, children: [(0, jsx_runtime_1.jsx)(Email_1.default, { sx: { fontSize: 16, color: 'text.secondary', mr: 1 } }), (0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "caption", color: "text.secondary", children: "Voc\u00EA receber\u00E1 um email de confirma\u00E7\u00E3o ap\u00F3s o pagamento" })] }), (0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "caption", color: "text.secondary", children: "Ao confirmar o pagamento, voc\u00EA concorda com nossos termos de servi\u00E7o" })] })] })] })); }; exports.default = exports.StripePaymentElementWrapper;