Repository navigation
Move new purchases from Stripe to Polar - #278
Merged
Merged
Conversation
New checkouts, prices, and the billing portal now go through Polar. Stripe's webhook and billing portal stay for legacy subscribers whose subscriptions still renew on Stripe. - Add a minimal Polar REST client with Standard Webhooks verification (standard and legacy Polar secret derivations) - Add /polar routes: price, create-checkout-session (now auth-required), fulfill-checkout, webhook, billing-portal - Fulfillment is idempotent so redirect + webhook don't double-notify - Revoke premium on subscription.past_due/revoked, restore on subscription.active and order.paid - Add polarCustomerId to users and a shared HasPremium helper - Regenerate Swagger docs Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment out the lifetime card and stop fetching lifetime products, so /polar/price only requires the monthly and yearly product IDs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The paywall used hardcoded mock price IDs in development, which Polar rejects at checkout. Fetch prices from the server in every environment and only fall back to the mocks in development if that request fails. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
New purchases now go through Polar instead of Stripe. Existing Stripe subscribers keep renewing on Stripe, so its webhook and billing portal stay; only Stripe checkout and price fetching are removed. Moving those subscribers over is a follow-up, done with Polar's migration tool.
Backend
server/services/polar), pinned to API version2026-10. Polar has no Go SDK./api/polar/*routes:price,create-checkout-session,fulfill-checkout,webhook,billing-portal.create-checkout-sessionnow requires sign-in and uses the signed-in user, not auserIdfrom the request body.order.paidwebhook can both fire, but only one Slack "paid" message is sent.order.paidsubscription.past_duesubscription.activesubscription.revokedpolarCustomerIdto users and a sharedUser.HasPremium()helper. A user with either a Stripe or a Polar customer ID counts as premium.Frontend
Other
server/docs/*has a lot of unrelated churn.swag initalso needs--parseDependencynow.DEPLOYMENT.mdandserver/.env.template.Deploy checklist
Production Polar products and the webhook endpoint (
https://timeful.app/api/polar/webhook, format Raw, API version2026-10) already exist. Before or with the deploy, set:POLAR_ACCESS_TOKEN: production token withcheckouts:write,products:read,customer_sessions:writePOLAR_WEBHOOK_SECRET: from the production webhook endpointPOLAR_SERVER=productionPOLAR_MONTHLY_PRODUCT_ID,POLAR_YEARLY_PRODUCT_ID,POLAR_MONTHLY_STUDENT_PRODUCT_ID,POLAR_YEARLY_STUDENT_PRODUCT_IDTest plan
Tested in the Polar sandbox:
order.paidwithsubscription_cycle, user stays premium4000 0000 0000 0341):past_due, premium removed, then card switched back:order.paid+subscription.active, premium restoredgo test ./services/polar/, frontendnpm run buildandnpm run test:unit🤖 Generated with Claude Code