{"record":{"id":"c97fef3e0dc35421","repo":"hcengineering/platform","slug":"failed-to-uncancel-subscription-providersubscrip-c97fef","errorCode":null,"errorMessage":"Failed to uncancel subscription ${providerSubscriptionId}","messagePattern":"Failed to uncancel subscription (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"services/payment/pod-payment/src/providers/stripe/provider.ts","lineNumber":287,"sourceCode":"    }\n  }\n\n  async cancelSubscription (ctx: MeasureContext, providerSubscriptionId: string): Promise<SubscriptionData> {\n    const stripeSubscription = await this.stripe.cancelSubscription(ctx, providerSubscriptionId)\n    const subscriptionData = transformStripeSubscriptionToData(ctx, stripeSubscription)\n\n    if (subscriptionData == null) {\n      throw new Error(`Failed to cancel subscription ${providerSubscriptionId}`)\n    }\n\n    return subscriptionData\n  }\n\n  async uncancelSubscription (ctx: MeasureContext, providerSubscriptionId: string): Promise<SubscriptionData> {\n    const stripeSubscription = await this.stripe.uncancelSubscription(ctx, providerSubscriptionId)\n    const subscriptionData = transformStripeSubscriptionToData(ctx, stripeSubscription)\n    if (subscriptionData == null) {\n      throw new Error(`Failed to uncancel subscription ${providerSubscriptionId}`)\n    }\n\n    return subscriptionData\n  }\n\n  async updateSubscriptionPlan (\n    ctx: MeasureContext,\n    subscriptionId: string,\n    newPlan: string,\n    workspaceUrl: string,\n    accountUuid: string\n  ): Promise<SubscriptionData | CheckoutResponse | null> {\n    // Get the current subscription to check if it's free\n    const currentSub = await this.stripe.getSubscription(ctx, subscriptionId)\n\n    // Check if subscription is free by checking if the price amount is 0\n    const price = currentSub.items.data[0]?.price\n    const isFreeSubscription = price?.unit_amount === 0 || price === undefined","sourceCodeStart":269,"sourceCodeEnd":305,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/payment/pod-payment/src/providers/stripe/provider.ts#L269-L305","documentation":"uncancelSubscription reactivates a subscription scheduled for cancellation at period end via the Stripe client, then maps the returned Stripe.Subscription with transformStripeSubscriptionToData. If the mapping returns null the provider throws this error, meaning the un-cancel likely succeeded at Stripe but the result could not be converted into SubscriptionData.","triggerScenarios":"Calling uncancelSubscription on a providerSubscriptionId whose Stripe subscription is in a status the transform does not support (e.g. already fully canceled, or paused), causing transformStripeSubscriptionToData to return null; calling it on a subscription that was never scheduled for cancellation.","commonSituations":"User clicks 'resume subscription' after the subscription already reached canceled status (Stripe refuses/reactivates oddly); un-cancel requested while an incomplete payment leaves the subscription in an unmappable status; API version drift changing Stripe subscription fields the mapper expects.","solutions":["Confirm the subscription is still in status 'active' with cancel_at_period_end=true before calling uncancelSubscription","Extend transformStripeSubscriptionToData to map the failing status instead of returning null","Inspect the raw Stripe subscription (dashboard/logs) to see its actual status at the time of the call","Retry after fixing the subscription state at Stripe if it was already fully canceled (reactivation is then not possible; a new subscription is needed)"],"exampleFix":"// before\nconst stripeSubscription = await this.stripe.uncancelSubscription(ctx, providerSubscriptionId)\nconst subscriptionData = transformStripeSubscriptionToData(ctx, stripeSubscription)\nif (subscriptionData == null) {\n  throw new Error(`Failed to uncancel subscription ${providerSubscriptionId}`)\n}\n// after\nconst stripeSubscription = await this.stripe.uncancelSubscription(ctx, providerSubscriptionId)\nconst subscriptionData = transformStripeSubscriptionToData(ctx, stripeSubscription)\nif (subscriptionData == null) {\n  throw new Error(\n    `Failed to uncancel subscription ${providerSubscriptionId}: status=${stripeSubscription.status} not mappable`\n  ) // include Stripe status for diagnosis\n}","handlingStrategy":"try-catch","validationCode":"const sub = await paymentProvider.getSubscription(ctx, providerSubscriptionId)\nif (sub?.status !== 'active' || !sub.cancelAtPeriodEnd) {\n  throw new Error('Subscription is not scheduled for cancellation; nothing to uncancel')\n}","typeGuard":"function isSubscriptionData(x: SubscriptionData | null | undefined): x is SubscriptionData {\n  return x != null && typeof x.providerSubscriptionId === 'string'\n}","tryCatchPattern":"try {\n  return await paymentProvider.uncancelSubscription(ctx, providerSubscriptionId)\n} catch (err) {\n  if ((err as Error).message.startsWith('Failed to uncancel subscription')) {\n    // subscription may already be fully canceled — reactivation requires a new subscription\n    const current = await paymentProvider.getSubscription(ctx, providerSubscriptionId)\n    if (current?.status === 'canceled') {\n      throw new ApiError(409, 'Subscription already fully canceled; create a new subscription instead')\n    }\n  }\n  throw err\n}","preventionTips":["Only offer 'resume subscription' in the UI while cancel_at_period_end is true","Handle every Stripe status in transformStripeSubscriptionToData, including canceled/incomplete/paused","Log the raw Stripe status when mapping fails to speed up diagnosis","Keep the Stripe SDK API version aligned with the account's API version"],"tags":["stripe","billing","data-transform","uncancel"],"backgroundTag":"stripe-subscription-transform-null","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}