1. Usuario cancela
Se marca cancelAtPeriodEnd = true y cancelAt = "2026-05-15". El usuario
sigue con acceso hasta esa fecha.
Este endpoint permite a un usuario autenticado cancelar su suscripción activa de forma diferida: no la elimina de inmediato, sino que la marca para que se cancele automáticamente al finalizar el período de facturación actual.
POST/api/user-endpoints/cancel-subscription
Auth JWT Requiere token en
header Authorization
Autenticación — Verifica que el usuario tenga un JWT válido.
Validación del body — Comprueba que origin, vertical y subscriptionID estén presentes y sean válidos.
Selección de Stripe — Usa selectStripeCrm(origin) para obtener la instancia de Stripe correcta según el CRM (Inspiria o Alebat).
Búsqueda del customer — Localiza al cliente en Stripe por su email.
Verificación de suscripción — Confirma que el usuario tiene una suscripción activa en Strapi y en Stripe para esa vertical.
Cancelación diferida — Llama a stripe.subscriptions.update() con cancel_at_period_end: true para programar la cancelación al final del ciclo.
Actualización en Strapi — Guarda cancelAtPeriodEnd: true y cancelAt (fecha de expiración) en la base de datos.
Respuesta — Retorna success: true junto con activeUntil indicando hasta cuándo tiene acceso el usuario.
Archivo: src/api/user-endpoints/controllers/check-subscription.ts
async cancelSubscription(ctx) { const user = ctx.state.user; if (!user) return ctx.unauthorized(ERROR_UNAUTHORIZED);
validateKeys(ctx.request.body, CANCEL_SUBSCRIPTION_KEYS, FIELD_ROOT); validateRequiredFields(ctx.request.body, CANCEL_SUBSCRIPTION_KEYS); const { origin, vertical, subscriptionID } = ctx.request.body;
const stripe = await selectStripeCrm(origin); const customer = await searchCustomer(stripe, user.email);
const isUserSubscribed = await strapi .service(USER_SERVICE) .isUserSubscribed(stripe, customer.data[0].id, user.email, origin, vertical);
if (!isUserSubscribed) { ... }
const cancelledSubscription = await strapi .service(SUBSCRIPTION_STRIPE_SERVICES) .cancelSubscription(stripe, user.email, origin, vertical, subscriptionID);
return ctx.send({ success: true, message: SUBSCRIPTION_CANCELLED_MESSAGE, activeUntil: cancelledSubscription.activeUntil, });}Archivo: src/api/subscription/services/stripe.ts
cancelSubscription: async function (stripe, userEmail, origin, vertical, subscriptionID) { // Busca suscripción activa en Strapi const subscription = await findOne(SUBSCRIPTION, CANCEL_SUBSCRIPTION_FETCH_FIELDS, { user: { email: userEmail }, stripeCrm: origin, stateSubscription: { $in: ['active'] }, vertical, id: subscriptionID, }, null);
if (!subscription || !subscription.subscriptionID) throw new Error(ERROR_SUBSCRIPTION_NOT_FOUND_FOR_CANCELLATION);
if (subscription.cancelAtPeriodEnd || subscription.cancelAt) throw new Error(ERROR_SUBSCRIPTION_ALREADY_CANCELLED);
// Marca cancelación diferida en Stripe const cancelledSubscription = await stripe.subscriptions.update( subscription.subscriptionID, { cancel_at_period_end: true } );
// Calcula fecha de expiración const activeUntil = cancelledSubscription.cancel_at ? new Date(cancelledSubscription.cancel_at * 1000) : null;
// Sincroniza en Strapi await update(SUBSCRIPTION, { documentId: subscription.documentId }, { cancelAtPeriodEnd: cancelledSubscription.cancel_at_period_end, cancelAt: activeUntil, });
return { ...cancelledSubscription, activeUntil };}| Campo | Valor |
|---|---|
cancelAtPeriodEnd | true |
cancelAt | Fecha de fin de período (convertida desde timestamp Stripe) |
| Error | Cuándo ocurre |
|---|---|
ERROR_SUBSCRIPTION_NOT_FOUND_FOR_CANCELLATION | No existe suscripción activa con ese ID y vertical |
ERROR_SUBSCRIPTION_ALREADY_CANCELLED | La suscripción ya estaba marcada para cancelarse |
POST /api/user-endpoints/cancel-subscriptionAuthorization: Bearer <JWT_TOKEN>Content-Type: application/json{ "origin": "Inspiria", "vertical": "cirugia", "subscriptionID": 42}| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
origin | string | ✅ | CRM de Stripe. Valores: "Inspiria" o "Alebat" |
vertical | string | ✅ | Vertical del producto (ej: "cirugia", "farma") |
subscriptionID | number | ✅ | ID interno (Strapi) de la suscripción a cancelar |
{ "success": true, "message": "Subscription cancelled successfully. It will remain active until the end of the current billing period.", "activeUntil": "2026-05-15T00:00:00.000Z"}activeUntil indica la fecha hasta la cual el usuario mantiene acceso.
{ "success": false, "message": "User does not have an active subscription to cancel"}{ "success": false, "message": "Error cancelling user subscription: Subscription is already set to cancel at the end of the period"}{ "error": { "status": 401, "message": "Unauthorized" }}fetchconst response = await fetch("/api/user-endpoints/cancel-subscription", { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ origin: "Inspiria", vertical: "cirugia", subscriptionID: 42, }),});
const data = await response.json();
if (data.success) { console.log(`Suscripción activa hasta: ${data.activeUntil}`);}POST /cancel-subscription │ ▼¿Autenticado? ──No──▶ 401 Unauthorized │ Sí ▼Validar body (origin, vertical, subscriptionID) │ ▼selectStripeCrm(origin) → Stripe instance │ ▼searchCustomer(stripe, email) │ ▼isUserSubscribed() ──No──▶ 400 Sin suscripción │ Sí ▼cancelSubscription() ├─ findOne() → suscripción activa ├─ ¿Ya cancelada? ─Sí─▶ Error ├─ stripe.subscriptions.update({ cancel_at_period_end: true }) └─ update Strapi (cancelAtPeriodEnd, cancelAt) │ ▼200 { success, activeUntil }1. Usuario cancela
Se marca cancelAtPeriodEnd = true y cancelAt = "2026-05-15". El usuario
sigue con acceso hasta esa fecha.
2. Llega la fecha de corte
Stripe cancela la suscripción automáticamente. El acceso premium se revoca.
| Archivo | Rol |
|---|---|
src/api/user-endpoints/controllers/check-subscription.ts | Controlador — valida auth, body y orquesta el flujo |
src/api/user-endpoints/routes/user-endpoints.ts | Definición de la ruta |
src/api/subscription/services/stripe.ts | Servicio — lógica de Stripe y actualización en Strapi |
src/extensions/users-permissions/services/userServices.ts | Verifica si el usuario tiene suscripción activa |
src/utils/stripe-crm.ts | Selecciona la instancia de Stripe según origin |