Skip to content

fix(migrate): revoking an already ended subscription is success - #408

Merged
ABB65 merged 1 commit into
mainfrom
fix/migrate-revoke-ended-subscription
Oct 4, 2026
Merged

ABB65 merged 1 commit into
mainfrom
fix/migrate-revoke-ended-subscription

Conversation

@ABB65

@ABB65 ABB65 commented Oct 3, 2026

Copy link
Copy Markdown
Member

What

t6 note 2 on #406: subscriptions.revoke throws for a subscription Polar already ended, so the revoke endpoint looped on 502 with the grant still live.

  • PaymentProvider.cancelSubscription returns 'canceled' | 'already_ended'; Polar maps AlreadyCanceledSubscription / ResourceNotFound to already_ended, anything else still throws.
  • Revoke marks the grant revoked and answers subscription_canceled:false (no 502, jti kept used).

Tests

Route test for the already-ended case; plugin tests for canceled / already_ended (both errors) / other failure rethrown. Unit, delete-routes and billing-webhook integration, eslint, typecheck green.

Polar's subscriptions.revoke throws AlreadyCanceledSubscription (or
ResourceNotFound) when an operator already refunded the subscription or an
earlier call revoked it but marking the grant fell through. That made the
revoke endpoint answer 502 forever while the grant stayed live. The plugin
now returns 'already_ended' for those two errors; the endpoint marks the
grant revoked and answers subscription_canceled:false. Other failures
still throw.
@ABB65

ABB65 commented Oct 3, 2026

Copy link
Copy Markdown
Member Author

t6 review @ a89c436 — ONAY (merge when the ci job is green; it was pending when I posted, postgres-lineage pass)

Local evidence at this head: --project unit 182 files / 2015 tests, integration 52 / 487, nuxt 43 / 274 pass; eslint on changed files and vue-tsc --noEmit clean.

What I checked:

  • The error shape is real, not just a mock: in @polar-sh/sdk@0.47.0 AlreadyCanceledSubscription and ResourceNotFound both set this.error to their literal name, and subscriptionsRevoke lists both in its error union. kind === 'AlreadyCanceledSubscription' || 'ResourceNotFound' matches the SDK's own classes.
  • A real outage is not treated as success: every other error (SDK ConnectionError, timeout, SubscriptionLocked, validation, plain Error) has no matching .error literal and is rethrown, so the route still answers 502, leaves the grant live and gives the jti back. Pinned by "still throws any other provider failure" and the existing 502 route test.
  • Already-ended case: grant is marked revoked, subscription_canceled:false, jti not released. Stripe provider keeps 'canceled'; the other caller (workspaces/[workspaceId]/index.delete.ts) ignores the return value.

Note (non-blocking): ResourceNotFound also covers a subscription id Polar cannot find because the token points at the wrong Polar organisation or environment (sandbox vs production). That would mark the grant revoked while the real subscription lives on. Not reachable with correct config, but worth keeping in mind when the production token is set.

@ABB65
ABB65 merged commit 6d3cef7 into main Oct 4, 2026
2 checks passed
@ABB65
ABB65 deleted the fix/migrate-revoke-ended-subscription branch October 4, 2026 08:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant