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
JavaScript
;
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;