Migrate live Stripe subscriptions to a new catalogue without changing a single price.
Python
3
4 commits
updated Oct 1, 2026
Move live subscriptions to a new Stripe product catalogue without changing what customers pay.
This repository contains Python scripts and SQL for a catalogue migration where every existing price gets an exact mirror under a new product. The subscription keeps its amount, currency, interval, quantity, renewal date, payment method and discounts. Entitlement moves from product names to price metadata.
The project was extracted from a completed migration with subscriptions, lifetime licenses, perpetual licenses with update windows and rent-to-own plans. Account IDs and customer data are replaced by configuration and placeholders.
Get started · Migration runbook · Catalogue conventions · Lessons
The batch starts with a dry run. It rebuilds the source-to-destination map from the live API, writes out/plan.csv, lists every subscription it would migrate and groups every skipped subscription by reason.
124 subscriptions: 117 to migrate, 7 skipped
SKIPPED
3 no_mirror[studio/web/std]
2 renews_imminently
1 has_schedule
1 multi_item[2]
The numbers above are illustrative. Review the plan from your own account before adding --apply.
Stripe prices keep their product association. Reorganizing a catalogue therefore uses four steps:
tax_behavior.proration_behavior: none.The repository tags and verifies prices, plans and applies the subscription swaps, and provides the entitlement SQL. It does not create products or mirror prices.
Requires Python 3.9 or later and stripe-python 10 or later. The SQL targets Supabase.
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp config.example.py config.py
Fill config.py with your product IDs, tier rules and a unique idempotency prefix. Load STRIPE_API_KEY from a local secret store. Do not paste a live key into a script, commit it or include it in a report.
Run the read-only checks and dry runs first:
cd scripts
python audit_tax_behavior.py
python write_price_metadata.py
python migrate_one.py
python migrate_all.py
Deploy sql/entitlements.sql before the batch. Confirm that legacy and new prices resolve correctly, then follow the migration-day runbook for the rehearsal, first batch, full run and verification.
--apply. Live subscription changes also require typing yes.price_id and product may differ. The batch stops at the first unexpected change.Generated plans, results and exports contain subscription, customer and price IDs. out/, *.csv and config.py are ignored, but they still need restricted storage. export_migration_origins.py includes customer email unless you pass --no-email.
| Area | Files | Purpose |
|---|---|---|
| Configuration | config.example.py | Account-specific IDs, routing and metadata rules |
| Migration | scripts/ | Audit, metadata plan, rehearsal, batch, verification and rollback |
| Entitlement | sql/ | Reference schema, resolver, projections and communication queries |
| Operations | docs/RUNBOOK.md | Ordered checklist for migration day |
| Conventions | docs/CONVENTIONS.md | Price nickname grammar and metadata keys |
| Lessons | docs/LESSONS.md | Failures and edge cases found during the original migration |
This is a reference implementation, not a drop-in migration. It does not create the target catalogue, sync Stripe into a database, complete rent-to-own plans or support plain Postgres without adapting the Supabase roles and user lookup. Test the routing and SQL against your own schema and API version before using a live key.
MIT © 2026 Loris Comba.
Python
79.8%
PLpgSQL
20.2%
Migrate live Stripe subscriptions to a new catalogue without changing a single price.
Python
3
4 commits
updated Oct 1, 2026
Move live subscriptions to a new Stripe product catalogue without changing what customers pay.
This repository contains Python scripts and SQL for a catalogue migration where every existing price gets an exact mirror under a new product. The subscription keeps its amount, currency, interval, quantity, renewal date, payment method and discounts. Entitlement moves from product names to price metadata.
The project was extracted from a completed migration with subscriptions, lifetime licenses, perpetual licenses with update windows and rent-to-own plans. Account IDs and customer data are replaced by configuration and placeholders.
Get started · Migration runbook · Catalogue conventions · Lessons
The batch starts with a dry run. It rebuilds the source-to-destination map from the live API, writes out/plan.csv, lists every subscription it would migrate and groups every skipped subscription by reason.
124 subscriptions: 117 to migrate, 7 skipped
SKIPPED
3 no_mirror[studio/web/std]
2 renews_imminently
1 has_schedule
1 multi_item[2]
The numbers above are illustrative. Review the plan from your own account before adding --apply.
Stripe prices keep their product association. Reorganizing a catalogue therefore uses four steps:
tax_behavior.proration_behavior: none.The repository tags and verifies prices, plans and applies the subscription swaps, and provides the entitlement SQL. It does not create products or mirror prices.
Requires Python 3.9 or later and stripe-python 10 or later. The SQL targets Supabase.
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp config.example.py config.py
Fill config.py with your product IDs, tier rules and a unique idempotency prefix. Load STRIPE_API_KEY from a local secret store. Do not paste a live key into a script, commit it or include it in a report.
Run the read-only checks and dry runs first:
cd scripts
python audit_tax_behavior.py
python write_price_metadata.py
python migrate_one.py
python migrate_all.py
Deploy sql/entitlements.sql before the batch. Confirm that legacy and new prices resolve correctly, then follow the migration-day runbook for the rehearsal, first batch, full run and verification.
--apply. Live subscription changes also require typing yes.price_id and product may differ. The batch stops at the first unexpected change.Generated plans, results and exports contain subscription, customer and price IDs. out/, *.csv and config.py are ignored, but they still need restricted storage. export_migration_origins.py includes customer email unless you pass --no-email.
| Area | Files | Purpose |
|---|---|---|
| Configuration | config.example.py | Account-specific IDs, routing and metadata rules |
| Migration | scripts/ | Audit, metadata plan, rehearsal, batch, verification and rollback |
| Entitlement | sql/ | Reference schema, resolver, projections and communication queries |
| Operations | docs/RUNBOOK.md | Ordered checklist for migration day |
| Conventions | docs/CONVENTIONS.md | Price nickname grammar and metadata keys |
| Lessons | docs/LESSONS.md | Failures and edge cases found during the original migration |
This is a reference implementation, not a drop-in migration. It does not create the target catalogue, sync Stripe into a database, complete rent-to-own plans or support plain Postgres without adapting the Supabase roles and user lookup. Test the routing and SQL against your own schema and API version before using a live key.
MIT © 2026 Loris Comba.
Python
79.8%
PLpgSQL
20.2%