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 === undefinedView on GitHub (pinned to 63e28dc964)
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)
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
- 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
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
- Failed to cancel subscription ${providerSubscriptionId}
- Missing priceId for plan: ${planKey}
- No price configured for plan: ${planKey}
- BillingError: server response text
- Failed to fetch deepgram requests ${res.status}: ${text}
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/c97fef3e0dc35421.
Report an issue: GitHub.