{"record":{"id":"012fc3ef99b6aec5","repo":"hcengineering/platform","slug":"failed-to-cancel-subscription-providersubscripti","errorCode":null,"errorMessage":"Failed to cancel subscription ${providerSubscriptionId}","messagePattern":"Failed to cancel subscription (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"services/payment/pod-payment/src/providers/polar/provider.ts","lineNumber":262,"sourceCode":"\n      ctx.info('Polar subscription reconciliation completed', {\n        polarActiveCount: polarActiveSubscriptions.length,\n        ourActiveCount: ourActiveSubscriptions.length,\n        upsertedCount: upsertCount,\n        staleUpdatedCount: staleCount\n      })\n    } catch (err) {\n      ctx.error('Polar subscription reconciliation failed', { err })\n      throw err\n    }\n  }\n\n  async cancelSubscription (ctx: MeasureContext, providerSubscriptionId: string): Promise<SubscriptionData> {\n    const polarSubscription = await this.polar.cancelSubscription(ctx, providerSubscriptionId)\n    const subscriptionData = transformPolarSubscriptionToData(polarSubscription)\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 polarSubscription = await this.polar.uncancelSubscription(ctx, providerSubscriptionId)\n    const subscriptionData = transformPolarSubscriptionToData(polarSubscription)\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,","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/payment/pod-payment/src/providers/polar/provider.ts#L244-L280","documentation":"cancelSubscription calls the Polar API to cancel, then converts the returned subscription to internal SubscriptionData via transformPolarSubscriptionToData. If the transform returns null (the returned subscription is in a shape/state the transformer does not recognize), the provider cannot honor the cancellation result and throws 'Failed to cancel subscription <id>'. The API call itself may have succeeded; the failure is in interpreting the response.","triggerScenarios":"polar.cancelSubscription returns a subscription object whose fields (prices, product, status) make transformPolarSubscriptionToData return null — e.g. unexpected status value, missing prices array, or a Polar API schema change — for subscription id <id>.","commonSituations":"Polar API/SDK version bump changing response shape; subscription already fully canceled/expired so the response omits fields; sandbox vs production product data differences; transformer not updated for new Polar subscription states.","solutions":["Inspect the raw Polar response for that subscriptionId and update transformPolarSubscriptionToData to handle its shape/status","Check whether the subscription is already canceled server-side (idempotent retry path) before re-failing","Pin/align the Polar SDK version with the transformer's expectations","Add logging in the transformer to record why it returned null"],"exampleFix":"// before\nconst subscriptionData = transformPolarSubscriptionToData(polarSubscription)\nif (subscriptionData == null) {\n  throw new Error(`Failed to cancel subscription ${providerSubscriptionId}`)\n}\n// after (caller idempotency guard)\nconst sub = await getSubscription(ctx, providerSubscriptionId)\nif (sub?.status === 'canceled') return sub\nconst subscriptionData = await provider.cancelSubscription(ctx, providerSubscriptionId)","handlingStrategy":"try-catch","validationCode":"const current = await provider.getSubscription(ctx, providerSubscriptionId)\nif (current == null) throw new Error(`Subscription ${providerSubscriptionId} unknown to provider`)","typeGuard":"function isPolarSubscription(x: unknown): x is PolarSubscription {\n  return typeof x === 'object' && x !== null && 'id' in x && 'status' in x\n}","tryCatchPattern":"try {\n  await provider.cancelSubscription(ctx, providerSubscriptionId)\n} catch (e) {\n  if (e.message.startsWith('Failed to cancel subscription')) {\n    // verify actual state in Polar before retry; may already be canceled\n  } else throw e\n}","preventionTips":["Make cancellation idempotent: check current status first","Update transformPolarSubscriptionToData whenever the Polar SDK is upgraded","Log the raw Polar response inside the transformer on null results","Pin Polar SDK versions and review changelogs"],"tags":["payment","polar","transformation","api-response"],"backgroundTag":"subscription-transform-failed","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}