This collection of scripts is designed to gather subscription and customer data from Stripe, map prices and discounts to their Paddle equivalents, and export the data to Paddle-compatible CSV files for migration purposes.
paddle_migration_output.csv.prices-discounts-mapping.py.stripe-mig-data-gather.py (paddle_migration_output.csv) and prices-discounts-mapping-ref.csv to map Stripe price and discount IDs to Paddle IDs, and exports the mapped data to paddle_migration_output_mapped.csv..env file with the Stripe API key:
STRIPE_API_KEY=your_stripe_api_key
Don't reuse an existing application/secret key for this. Create a restricted API key (starts with rk_live_) in the Stripe Dashboard under Developers > API keys > Create restricted key, named something obvious like paddle-migration-export, and give it only the read permissions listed below. A dedicated key means:
Start from "no permissions" rather than a preset, set everything to None, then switch on only the resources below. Write access is not needed anywhere — both scripts are read-only against Stripe.
Optionally, attach an access policy to the key restricting it to the IP address you run the export from.
| Permission (Dashboard resource) | Level | Why it's needed |
|---|---|---|
| Subscriptions | Read | GET /v1/subscriptions — the main listing that drives the whole export. |
| Customers | Read | Customer is expanded on each subscription (email, name, address), and GET /v1/customers/{id}/tax_ids provides business_tax_identifier. |
| Prices | Read | Subscription items expand price, used for price_id_N and the billing interval. |
| Payment Methods | Read | GET /v1/payment_methods?customer=...&type=card provides the card_token column. |
| Coupons | Read | GET /v1/coupons/{id} resolves coupon duration for discount_remaining_cycles. |
Notes:
403 whose error message names the exact permission to add — and you can check the key's request logs to see which endpoint was rejected.prices-discounts-mapping.py does not talk to Stripe at all — it only reads the CSV files — so it needs no key.The data you migrate is live data, so the key must be a live mode key. If you want to dry-run the scripts first, create the equivalent restricted key in a sandbox and run against that before switching.
.env file, and make sure .env was never committed — add it to .gitignore before your first run.pip3 install stripe python-dotenv
This script fetches subscription and customer data from the Stripe API and exports it to a CSV file.
fetch_stripe_subscriptions(limit): Fetches subscription and customer data from Stripe.fetch_card_token(customer_id): Fetches the card token for a given customer.fetch_tax_id(customer_id): Fetches the tax ID for a given customer.calculate_remaining_discount_cycles(subscription): Calculates the remaining discount cycles for a subscription.export_to_csv(data, file_path): Exports the fetched data to a CSV file.main(): Orchestrates the script execution.Ensure you have a .env file with your Stripe API key in the project folder. The .env file should contain a declaration as follows: STRIPE_API_KEY='Your_Stripe_API_KEY'
Run the script:
python3 stripe-mig-data-gather.py
The script will generate a CSV file named paddle_migration_output.csv with the fetched data.
The script will return a number of columns where the data requested by Paddle is not readily available in the Stripe API. In these cases, explanatory values are provided in each column in the outputted CSV. Follow the instructions in the columns to obtain the necessary values from other sources and/or delete these columns as appropriate.
For example, Stripe does not provide a value for business_company_number, so the script will output in the respective column:
business_company_number: "Not found in Stripe. Add your own internal value if desired, otherwise delete this column"
You should either replace this with your internal value or delete the column if it is not needed. Review and action all similar columns before submitting the migration data to Paddle.
This 4-column CSV file should be filled out by the seller to contain the reference mapping between Stripe and Paddle price and discount IDs. Below are the required columns. You can edit the example provided with your own Stripe and Paddle ID's.
stripe_price_id: Stripe price ID.paddle_price_id: Equivalent Paddle price ID.stripe_discount_id: Stripe discount ID.paddle_discount_id: Equivalent Paddle discount ID.This script maps Stripe price and discount IDs to Paddle price and discount IDs using a reference CSV file.
load_mapping(file_path, key_column, value_column): Loads the mapping from a CSV file.map_prices(rows, price_mapping): Maps Stripe price IDs to Paddle price IDs.map_discounts(rows, discount_mapping): Maps Stripe discount IDs to Paddle discount IDs.main(): Orchestrates the script execution.paddle_migration_output.csv generated by stripe-mig-data-gather.py.prices-discounts-mapping-ref.csv containing the reference mapping between Stripe and Paddle price and discount IDs. You need to create this for yourself. You can edit the example provided with your own ID's.python3 prices-discounts-mapping.py
paddle_migration_output_mapped.csv with the mapped data.paddle_migration_output_mapped.csv with your Solutions Engineer who will advise on next steps.paused_at values in its API. You may need to pull this from your database, if available.enable_checkout column in the Paddle migration CSV - if the subscription is manual, this defaults to TRUE, meaning a checkout will be available on the subscriber’s invoices. If you do not want this to be the case, amend the script to populate ‘FALSE’ instead.purchase_order_number column in the Paddle migration CSV - there is no specific field for this in Stripe. You may be storing this as custom data. You should update the script to pull the desired value here.additional_information column in the Paddle migration CSV - the corresponding value may be found the “description” value in the Stripe Invoice API. Check and include if necessary.payment_terms_interval column in the Paddle migration - the Stripe API appears to assume “day” in every case. “day” is automatically used here. Update this if you have terms not measured in days.discount_remaining_cycles match your expectation.discount_id per subscription. In Stripe, you may have multiple discounts applied to a subscription. The price and discount mapping script only attempts to map a single Paddle discount_id that you provide against a single Stripe discount_id. You will need to ensure you are pulling the correct discount, or else applying a larger discount in Paddle that accounts for the total Stripe discount amount.Python
100.0%
This collection of scripts is designed to gather subscription and customer data from Stripe, map prices and discounts to their Paddle equivalents, and export the data to Paddle-compatible CSV files for migration purposes.
paddle_migration_output.csv.prices-discounts-mapping.py.stripe-mig-data-gather.py (paddle_migration_output.csv) and prices-discounts-mapping-ref.csv to map Stripe price and discount IDs to Paddle IDs, and exports the mapped data to paddle_migration_output_mapped.csv..env file with the Stripe API key:
STRIPE_API_KEY=your_stripe_api_key
Don't reuse an existing application/secret key for this. Create a restricted API key (starts with rk_live_) in the Stripe Dashboard under Developers > API keys > Create restricted key, named something obvious like paddle-migration-export, and give it only the read permissions listed below. A dedicated key means:
Start from "no permissions" rather than a preset, set everything to None, then switch on only the resources below. Write access is not needed anywhere — both scripts are read-only against Stripe.
Optionally, attach an access policy to the key restricting it to the IP address you run the export from.
| Permission (Dashboard resource) | Level | Why it's needed |
|---|---|---|
| Subscriptions | Read | GET /v1/subscriptions — the main listing that drives the whole export. |
| Customers | Read | Customer is expanded on each subscription (email, name, address), and GET /v1/customers/{id}/tax_ids provides business_tax_identifier. |
| Prices | Read | Subscription items expand price, used for price_id_N and the billing interval. |
| Payment Methods | Read | GET /v1/payment_methods?customer=...&type=card provides the card_token column. |
| Coupons | Read | GET /v1/coupons/{id} resolves coupon duration for discount_remaining_cycles. |
Notes:
403 whose error message names the exact permission to add — and you can check the key's request logs to see which endpoint was rejected.prices-discounts-mapping.py does not talk to Stripe at all — it only reads the CSV files — so it needs no key.The data you migrate is live data, so the key must be a live mode key. If you want to dry-run the scripts first, create the equivalent restricted key in a sandbox and run against that before switching.
.env file, and make sure .env was never committed — add it to .gitignore before your first run.pip3 install stripe python-dotenv
This script fetches subscription and customer data from the Stripe API and exports it to a CSV file.
fetch_stripe_subscriptions(limit): Fetches subscription and customer data from Stripe.fetch_card_token(customer_id): Fetches the card token for a given customer.fetch_tax_id(customer_id): Fetches the tax ID for a given customer.calculate_remaining_discount_cycles(subscription): Calculates the remaining discount cycles for a subscription.export_to_csv(data, file_path): Exports the fetched data to a CSV file.main(): Orchestrates the script execution.Ensure you have a .env file with your Stripe API key in the project folder. The .env file should contain a declaration as follows: STRIPE_API_KEY='Your_Stripe_API_KEY'
Run the script:
python3 stripe-mig-data-gather.py
The script will generate a CSV file named paddle_migration_output.csv with the fetched data.
The script will return a number of columns where the data requested by Paddle is not readily available in the Stripe API. In these cases, explanatory values are provided in each column in the outputted CSV. Follow the instructions in the columns to obtain the necessary values from other sources and/or delete these columns as appropriate.
For example, Stripe does not provide a value for business_company_number, so the script will output in the respective column:
business_company_number: "Not found in Stripe. Add your own internal value if desired, otherwise delete this column"
You should either replace this with your internal value or delete the column if it is not needed. Review and action all similar columns before submitting the migration data to Paddle.
This 4-column CSV file should be filled out by the seller to contain the reference mapping between Stripe and Paddle price and discount IDs. Below are the required columns. You can edit the example provided with your own Stripe and Paddle ID's.
stripe_price_id: Stripe price ID.paddle_price_id: Equivalent Paddle price ID.stripe_discount_id: Stripe discount ID.paddle_discount_id: Equivalent Paddle discount ID.This script maps Stripe price and discount IDs to Paddle price and discount IDs using a reference CSV file.
load_mapping(file_path, key_column, value_column): Loads the mapping from a CSV file.map_prices(rows, price_mapping): Maps Stripe price IDs to Paddle price IDs.map_discounts(rows, discount_mapping): Maps Stripe discount IDs to Paddle discount IDs.main(): Orchestrates the script execution.paddle_migration_output.csv generated by stripe-mig-data-gather.py.prices-discounts-mapping-ref.csv containing the reference mapping between Stripe and Paddle price and discount IDs. You need to create this for yourself. You can edit the example provided with your own ID's.python3 prices-discounts-mapping.py
paddle_migration_output_mapped.csv with the mapped data.paddle_migration_output_mapped.csv with your Solutions Engineer who will advise on next steps.paused_at values in its API. You may need to pull this from your database, if available.enable_checkout column in the Paddle migration CSV - if the subscription is manual, this defaults to TRUE, meaning a checkout will be available on the subscriber’s invoices. If you do not want this to be the case, amend the script to populate ‘FALSE’ instead.purchase_order_number column in the Paddle migration CSV - there is no specific field for this in Stripe. You may be storing this as custom data. You should update the script to pull the desired value here.additional_information column in the Paddle migration CSV - the corresponding value may be found the “description” value in the Stripe Invoice API. Check and include if necessary.payment_terms_interval column in the Paddle migration - the Stripe API appears to assume “day” in every case. “day” is automatically used here. Update this if you have terms not measured in days.discount_remaining_cycles match your expectation.discount_id per subscription. In Stripe, you may have multiple discounts applied to a subscription. The price and discount mapping script only attempts to map a single Paddle discount_id that you provide against a single Stripe discount_id. You will need to ensure you are pulling the correct discount, or else applying a larger discount in Paddle that accounts for the total Stripe discount amount.Python
100.0%