quantictech-subscription-components
Version:
Biblioteca de componentes reutilizáveis para sistema de assinatura com Stripe - Arquitetura Service-to-Service
260 lines (259 loc) • 16.2 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 };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
var react_1 = require("react");
var paymentApi_1 = require("../utils/paymentApi");
// Função para mapear dados do Stripe para o formato do componente
var mapStripeDataToSubscription = function (stripeData) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5;
if (!(stripeData === null || stripeData === void 0 ? void 0 : stripeData.id) || !(stripeData === null || stripeData === void 0 ? void 0 : stripeData.status))
return null;
try {
console.log('🔄 Mapeando dados do Stripe:', {
id: stripeData.id,
status: stripeData.status,
start_date: stripeData.start_date,
created: stripeData.created,
current_period_end: stripeData.current_period_end,
cancel_at: stripeData.cancel_at,
canceled_at: stripeData.canceled_at
});
// Mapear status do Stripe para o formato do componente
var mapStatus = function (stripeStatus) {
switch (stripeStatus) {
case 'active': return 'active';
case 'canceled': return 'cancelled';
case 'cancelled': return 'cancelled';
case 'incomplete': return 'pending';
case 'incomplete_expired': return 'expired';
case 'past_due': return 'pending';
case 'unpaid': return 'pending';
case 'trialing': return 'active';
default: return 'pending';
}
};
// Calcular datas
var startDate = stripeData.start_date
? new Date(stripeData.start_date * 1000).toISOString()
: stripeData.created
? new Date(stripeData.created * 1000).toISOString()
: new Date().toISOString();
var endDate = stripeData.current_period_end
? new Date(stripeData.current_period_end * 1000).toISOString()
: '';
var cancelAt = stripeData.cancel_at ? new Date(stripeData.cancel_at * 1000) : undefined;
var canceledAt = stripeData.canceled_at ? new Date(stripeData.canceled_at * 1000) : undefined;
// Mapear campos do Stripe para o formato esperado pelo componente
var mappedSubscription = {
_id: stripeData.id,
id: stripeData.id,
userId: ((_a = stripeData.metadata) === null || _a === void 0 ? void 0 : _a.userId) || '',
stripeSubscriptionId: stripeData.id,
status: mapStatus(stripeData.status),
paymentMethod: 'credit_card',
// Datas mapeadas
startDate: startDate,
endDate: endDate,
// Mapear informações do plano
planId: {
_id: ((_b = stripeData.plan) === null || _b === void 0 ? void 0 : _b.id) || ((_f = (_e = (_d = (_c = stripeData.items) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.price) === null || _f === void 0 ? void 0 : _f.id) || '',
name: ((_g = stripeData.plan) === null || _g === void 0 ? void 0 : _g.nickname) || ((_l = (_k = (_j = (_h = stripeData.items) === null || _h === void 0 ? void 0 : _h.data) === null || _j === void 0 ? void 0 : _j[0]) === null || _k === void 0 ? void 0 : _k.price) === null || _l === void 0 ? void 0 : _l.nickname) || 'Plano Premium',
description: "Plano ".concat(((_m = stripeData.plan) === null || _m === void 0 ? void 0 : _m.interval) || ((_s = (_r = (_q = (_p = (_o = stripeData.items) === null || _o === void 0 ? void 0 : _o.data) === null || _p === void 0 ? void 0 : _p[0]) === null || _q === void 0 ? void 0 : _q.price) === null || _r === void 0 ? void 0 : _r.recurring) === null || _s === void 0 ? void 0 : _s.interval) || 'mensal'),
interval: ((_t = stripeData.plan) === null || _t === void 0 ? void 0 : _t.interval) || ((_y = (_x = (_w = (_v = (_u = stripeData.items) === null || _u === void 0 ? void 0 : _u.data) === null || _v === void 0 ? void 0 : _v[0]) === null || _w === void 0 ? void 0 : _w.price) === null || _x === void 0 ? void 0 : _x.recurring) === null || _y === void 0 ? void 0 : _y.interval) || 'month',
price: (((_z = stripeData.plan) === null || _z === void 0 ? void 0 : _z.amount) || ((_3 = (_2 = (_1 = (_0 = stripeData.items) === null || _0 === void 0 ? void 0 : _0.data) === null || _1 === void 0 ? void 0 : _1[0]) === null || _2 === void 0 ? void 0 : _2.price) === null || _3 === void 0 ? void 0 : _3.unit_amount) || 0) / 100,
currency: stripeData.currency || 'brl',
features: []
},
// Mapear informações do cliente dos metadados
customerInfo: stripeData.metadata ? {
email: stripeData.metadata.customerEmail || '',
firstName: ((_4 = stripeData.metadata.customerName) === null || _4 === void 0 ? void 0 : _4.split(' ')[0]) || '',
lastName: ((_5 = stripeData.metadata.customerName) === null || _5 === void 0 ? void 0 : _5.split(' ').slice(1).join(' ')) || '',
document: stripeData.metadata.cpf || '',
documentType: 'cpf'
} : undefined,
// Datas de cancelamento se existirem
cancelAt: cancelAt,
canceledAt: canceledAt,
createdAt: stripeData.created ? new Date(stripeData.created * 1000).toISOString() : undefined,
currentPeriodEnd: stripeData.current_period_end ? new Date(stripeData.current_period_end * 1000).toISOString() : undefined
};
console.log('✅ Subscription mapeada com sucesso:', mappedSubscription);
return mappedSubscription;
}
catch (error) {
console.error('Erro ao mapear dados do Stripe:', error);
return null;
}
};
var useSubscription = function () {
var _a = (0, react_1.useState)(null), subscription = _a[0], setSubscription = _a[1];
var _b = (0, react_1.useState)(true), loading = _b[0], setLoading = _b[1];
var _c = (0, react_1.useState)(null), error = _c[0], setError = _c[1];
// Função utilitária para verificação automática de status
var performAutomaticStatusCheck = function (currentSubscription) { return __awaiter(void 0, void 0, void 0, function () {
var syncKey, lastCheck, now, oneHour, canceledKey, wasCanceledLocally, verificationResult, response, error_1, canceledKey, stripeSubscription, mappedSubscription, error_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!(currentSubscription === null || currentSubscription === void 0 ? void 0 : currentSubscription.stripeSubscriptionId)) {
return [2 /*return*/, currentSubscription];
}
syncKey = "sync_status_".concat(currentSubscription.stripeSubscriptionId);
lastCheck = localStorage.getItem(syncKey);
now = Date.now();
oneHour = 60 * 60 * 1000;
// Se já verificamos recentemente, pular
if (lastCheck && (now - parseInt(lastCheck)) < oneHour) {
console.log('🛡️ Proteção contra loop: Verificação recente encontrada, pulando');
canceledKey = "canceled_locally_".concat(currentSubscription.stripeSubscriptionId);
wasCanceledLocally = localStorage.getItem(canceledKey);
if (wasCanceledLocally) {
console.log('✅ Assinatura já foi cancelada localmente, removendo da interface');
return [2 /*return*/, null];
}
return [2 /*return*/, currentSubscription];
}
_a.label = 1;
case 1:
_a.trys.push([1, 11, , 12]);
console.log('🔄 Verificando status automaticamente no Stripe...');
// Marcar que estamos verificando agora
localStorage.setItem(syncKey, now.toString());
return [4 /*yield*/, (0, paymentApi_1.verifySubscriptionStatusWithStripe)(currentSubscription.stripeSubscriptionId)];
case 2:
verificationResult = _a.sent();
if (!(verificationResult.success && verificationResult.needsSync)) return [3 /*break*/, 9];
console.log('⚠️ Inconsistência detectada! Assinatura cancelada no Stripe mas ativa no sistema');
if (!verificationResult.wasSynced) return [3 /*break*/, 7];
console.log('✅ Sincronização automática bem-sucedida');
_a.label = 3;
case 3:
_a.trys.push([3, 5, , 6]);
return [4 /*yield*/, paymentApi_1.apiEndpoints.getUserSubscription()];
case 4:
response = _a.sent();
if (response.data && Array.isArray(response.data) && response.data.length > 0) {
return [2 /*return*/, response.data[0]];
}
else {
return [2 /*return*/, null];
}
return [3 /*break*/, 6];
case 5:
error_1 = _a.sent();
console.error('Erro ao recarregar dados da assinatura:', error_1);
return [2 /*return*/, null];
case 6: return [3 /*break*/, 8];
case 7:
// Se a sincronização falhou, mas sabemos que foi cancelada no Stripe
console.log('⚠️ Sincronização com backend falhou, mas assinatura foi cancelada no Stripe');
canceledKey = "canceled_locally_".concat(currentSubscription.stripeSubscriptionId);
localStorage.setItem(canceledKey, now.toString());
return [2 /*return*/, null];
case 8: return [3 /*break*/, 10];
case 9:
if (verificationResult.success && verificationResult.subscription) {
// Se obtivemos dados atualizados do Stripe, mapear e usar
console.log('🔄 Dados atualizados obtidos do Stripe, mapeando para formato do componente...');
stripeSubscription = verificationResult.subscription;
if ((stripeSubscription === null || stripeSubscription === void 0 ? void 0 : stripeSubscription.id) && (stripeSubscription === null || stripeSubscription === void 0 ? void 0 : stripeSubscription.status)) {
mappedSubscription = mapStripeDataToSubscription(stripeSubscription);
if (mappedSubscription) {
console.log('✅ Dados do Stripe mapeados com sucesso');
return [2 /*return*/, mappedSubscription];
}
}
}
_a.label = 10;
case 10: return [2 /*return*/, currentSubscription];
case 11:
error_2 = _a.sent();
console.error('Erro na verificação automática de status:', error_2);
localStorage.setItem(syncKey, now.toString());
return [2 /*return*/, currentSubscription];
case 12: return [2 /*return*/];
}
});
}); };
var fetchSubscription = function () { return __awaiter(void 0, void 0, void 0, function () {
var subscriptionResponse, currentSubscription, finalSubscription, error_3;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 3, 4, 5]);
setLoading(true);
setError(null);
return [4 /*yield*/, paymentApi_1.apiEndpoints.getUserSubscription()];
case 1:
subscriptionResponse = _a.sent();
currentSubscription = Array.isArray(subscriptionResponse.data)
? subscriptionResponse.data[0]
: subscriptionResponse.data;
return [4 /*yield*/, performAutomaticStatusCheck(currentSubscription)];
case 2:
finalSubscription = _a.sent();
setSubscription(finalSubscription);
return [3 /*break*/, 5];
case 3:
error_3 = _a.sent();
console.error('❌ Erro ao buscar dados de assinatura:', error_3);
setError('Não foi possível carregar os dados da assinatura.');
return [3 /*break*/, 5];
case 4:
setLoading(false);
return [7 /*endfinally*/];
case 5: return [2 /*return*/];
}
});
}); };
var clearSubscription = function () {
setSubscription(null);
};
(0, react_1.useEffect)(function () {
fetchSubscription();
}, []);
return {
subscription: subscription,
loading: loading,
error: error,
refetch: fetchSubscription,
clearSubscription: clearSubscription
};
};
exports.default = useSubscription;