Repository navigation
feat(subscription_manager): Stripe customers, Customer Portal, idempotency - #117
Merged
Merged
Conversation
Reuse one Stripe customer per telegram user (stored on billing_accounts), pass customer= on Checkout, open the Customer Portal via /manage, send Idempotency-Key on customer and session creates, and keep integration_identifier stable (botspot-credits / botspot-plan). invoice.payment_failed marks the local sub past_due and notifies with a portal link; replayed webhook event ids return False except invoice.paid. Co-Authored-By: Claude Fable 5.1 <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.
Connect Stripe Checkout to a reused customer so the Customer Portal has something to open, and close the other gaps the Stripe planner flagged on the existing adapter.
What
stripe_customer_idlives on the per-userbilling_accountsrow. First CheckoutPOST /v1/customerswithmetadata[telegram_user_id],name/description= telegram username if known, no email. Later sessions passcustomer=<id>for bothmode=paymentandmode=subscription.client_reference_idand signed metadata are unchanged.create_stripe_portal_url(user_id) -> str→POST /v1/billing_portal/sessionswithreturn_url = public_base_url + "/billing/success". RaisesSubscriptionPaymentErrorwith a user message when the user has no Stripe customer yet./manage(behindregister_commands) replies with the portal link. Portal configuration is not created from code.Idempotency-Key: <invoice_id>on Checkout Session create;Idempotency-Key: customer:<telegram_user_id>on customer create.stripe_request_headerstakes an optional key.integration_identifier.botspot-credits/botspot-plan(settable viaBOTSPOT_SUBSCRIPTION_MANAGER_STRIPE_INTEGRATION_IDENTIFIER_PREFIX, defaultbotspot). No random suffix.invoice.payment_failed. InSTRIPE_FAIL_EVENT_TYPES. Resolves the subscription viainvoice.subscriptionor Basil+parent.subscription_details.subscription, marks the local rowpast_due(does not cancel), and sends a one-line Telegram notice with the portal link ifstripe_customer_idexists. Smart Retries +customer.subscription.deletedstill handle the end.Falseimmediately exceptinvoice.paid(current renew behaviour kept).No
stripeSDK; raw REST +Stripe-Versionas today. Placeholders only.Tests
uv run pytest tests/components/test_subscription_manager.py— existing tests plus:test_stripe_request_headers_optional_idempotency_keytest_stripe_integration_identifier_is_stabletest_stripe_integration_identifier_prefix_envtest_stripe_customer_reused_on_checkouttest_stripe_plan_checkout_passes_customer_and_stable_idtest_create_stripe_portal_urltest_stripe_invoice_payment_failed_marks_past_duetest_stripe_invoice_payment_failed_parent_subscription_no_notice_without_customertest_stripe_replayed_event_returns_false_before_fulfilltest_stripe_invoice_paid_replay_still_processesruff clean. pyright 0 errors on
botspot/components/new/subscription_manager. Full suite 267 passed, coverage 71% (gate 60%). No Telegram token, no network.Env names added (values never)
Default
botspot→ identifiersbotspot-credits/botspot-plan. Existing Stripe env names unchanged (STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET,STRIPE_ALLOW_LIVE,STRIPE_PRICE_IDS_JSON,PUBLIC_BASE_URL).Do not merge.
🤖 Generated with Claude Code