hcengineering/platform · error · Error

Failed to uncancel subscription ${providerSubscriptionId}

Error message

Failed to uncancel subscription ${providerSubscriptionId}

What it means

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.

Source

Thrown at services/payment/pod-payment/src/providers/stripe/provider.ts:287

    }
  }

  async cancelSubscription (ctx: MeasureContext, providerSubscriptionId: string): Promise<SubscriptionData> {
    const stripeSubscription = await this.stripe.cancelSubscription(ctx, providerSubscriptionId)
    const subscriptionData = transformStripeSubscriptionToData(ctx, stripeSubscription)

    if (subscriptionData == null) {
      throw new Error(`Failed to cancel subscription ${providerSubscriptionId}`)
    }

    return subscriptionData
  }

  async uncancelSubscription (ctx: MeasureContext, providerSubscriptionId: string): Promise<SubscriptionData> {
    const stripeSubscription = await this.stripe.uncancelSubscription(ctx, providerSubscriptionId)
    const subscriptionData = transformStripeSubscriptionToData(ctx, stripeSubscription)
    if (subscriptionData == null) {
      throw new Error(`Failed to uncancel subscription ${providerSubscriptionId}`)
    }

    return subscriptionData
  }

  async updateSubscriptionPlan (
    ctx: MeasureContext,
    subscriptionId: string,
    newPlan: string,
    workspaceUrl: string,
    accountUuid: string
  ): Promise<SubscriptionData | CheckoutResponse | null> {
    // Get the current subscription to check if it's free
    const currentSub = await this.stripe.getSubscription(ctx, subscriptionId)

    // Check if subscription is free by checking if the price amount is 0
    const price = currentSub.items.data[0]?.price
    const isFreeSubscription = price?.unit_amount === 0 || price === undefined

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Confirm the subscription is still in status 'active' with cancel_at_period_end=true before calling uncancelSubscription
  2. Extend transformStripeSubscriptionToData to map the failing status instead of returning null
  3. Inspect the raw Stripe subscription (dashboard/logs) to see its actual status at the time of the call
  4. Retry after fixing the subscription state at Stripe if it was already fully canceled (reactivation is then not possible; a new subscription is needed)

Example fix

// before
const stripeSubscription = await this.stripe.uncancelSubscription(ctx, providerSubscriptionId)
const subscriptionData = transformStripeSubscriptionToData(ctx, stripeSubscription)
if (subscriptionData == null) {
  throw new Error(`Failed to uncancel subscription ${providerSubscriptionId}`)
}
// after
const stripeSubscription = await this.stripe.uncancelSubscription(ctx, providerSubscriptionId)
const subscriptionData = transformStripeSubscriptionToData(ctx, stripeSubscription)
if (subscriptionData == null) {
  throw new Error(
    `Failed to uncancel subscription ${providerSubscriptionId}: status=${stripeSubscription.status} not mappable`
  ) // include Stripe status for diagnosis
}
Defensive patterns

Strategy: try-catch

Validate before calling

const sub = await paymentProvider.getSubscription(ctx, providerSubscriptionId)
if (sub?.status !== 'active' || !sub.cancelAtPeriodEnd) {
  throw new Error('Subscription is not scheduled for cancellation; nothing to uncancel')
}

Type guard

function isSubscriptionData(x: SubscriptionData | null | undefined): x is SubscriptionData {
  return x != null && typeof x.providerSubscriptionId === 'string'
}

Try / catch

try {
  return await paymentProvider.uncancelSubscription(ctx, providerSubscriptionId)
} catch (err) {
  if ((err as Error).message.startsWith('Failed to uncancel subscription')) {
    // subscription may already be fully canceled — reactivation requires a new subscription
    const current = await paymentProvider.getSubscription(ctx, providerSubscriptionId)
    if (current?.status === 'canceled') {
      throw new ApiError(409, 'Subscription already fully canceled; create a new subscription instead')
    }
  }
  throw err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/c97fef3e0dc35421. Report an issue: GitHub.