Skip to content

Instantly share code, notes, and snippets.

@zyriab
Last active March 12, 2025 14:20
Show Gist options
  • Save zyriab/461b737cd2d01624303b31bcdf702288 to your computer and use it in GitHub Desktop.
Save zyriab/461b737cd2d01624303b31bcdf702288 to your computer and use it in GitHub Desktop.
Copy Stripe products, plan and prices from live to test
// This program copies the production products, plans and prices of Stripe
// over to the testing environment
//
//nolint:forbidigo,forcetypeassert,exhaustruct,mnd,gocognit,err113,maintidx
package main
import (
"fmt"
"log"
"os"
"github.com/joho/godotenv"
"github.com/stripe/stripe-go/v81"
"github.com/stripe/stripe-go/v81/client"
)
func main() {
_ = godotenv.Load()
stripeLiveSecretKey := os.Getenv("STRIPE_LIVE_SECRET")
stripeTestSecretKey := os.Getenv("STRIPE_TEST_SECRET")
if stripeLiveSecretKey == "" {
log.Fatalln("STRIPE_LIVE_SECRET is not set")
}
if stripeTestSecretKey == "" {
log.Fatalln("STRIPE_TEST_SECRET is not set")
}
stripeLiveClient := client.New(stripeLiveSecretKey, nil)
stripeTestClient := client.New(stripeTestSecretKey, nil)
if err := stripeCopyLiveToTest(stripeLiveClient, stripeTestClient); err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Println("Products, plans, and prices copied successfully.")
}
// stripeCopyLiveToTest copies products, plans, and prices from live to test environment
func stripeCopyLiveToTest(stripeProd, stripeTest *client.API) error {
productMapping := make(map[string]string)
products := stripeProd.Products.List(
&stripe.ProductListParams{
ListParams: stripe.ListParams{Limit: stripe.Int64(100)},
},
)
for products.Next() {
product := products.Current().(*stripe.Product)
existingTestProducts := stripeTest.Products.List(
&stripe.ProductListParams{
ListParams: stripe.ListParams{Limit: stripe.Int64(100)},
},
)
var isDuplicate bool
for existingTestProducts.Next() {
testProduct := existingTestProducts.Current().(*stripe.Product)
if testProduct.Metadata["original_live_id"] == product.ID {
productMapping[product.ID] = testProduct.ID
fmt.Printf(
"Product already exists: %s (original live ID: %s)\n",
testProduct.ID,
product.ID,
)
isDuplicate = true
break
}
}
if isDuplicate {
continue
}
productData := &stripe.ProductParams{
Name: stripe.String(product.Name),
Type: stripe.String(string(product.Type)),
Description: nullableString(product.Description),
Shippable: nullableBool(product.Shippable),
URL: nullableString(product.URL),
Metadata: map[string]string{
"original_live_id": product.ID,
},
}
testProduct, err := stripeTest.Products.New(productData)
if err != nil {
return fmt.Errorf("error creating product %s: %w", product.ID, err)
}
productMapping[product.ID] = testProduct.ID
fmt.Printf(
"Created Product: %s (original live ID: %s)\n",
testProduct.ID,
product.ID,
)
}
plans := stripeProd.Plans.List(&stripe.PlanListParams{
ListParams: stripe.ListParams{Limit: stripe.Int64(100)},
Expand: []*string{stripe.String("data.tiers")},
})
for plans.Next() {
plan := plans.Current().(*stripe.Plan)
existingTestPlans := stripeTest.Plans.List(&stripe.PlanListParams{
ListParams: stripe.ListParams{Limit: stripe.Int64(1)},
})
var isDuplicate bool
for existingTestPlans.Next() {
testPlan := existingTestPlans.Current().(*stripe.Plan)
if testPlan.Metadata["original_live_plan_id"] == plan.ID {
isDuplicate = true
fmt.Printf(
"Plan already exists: %s (original live ID: %s)\n",
testPlan.ID,
plan.ID,
)
break
}
}
if isDuplicate {
continue
}
testProductID, exists := productMapping[plan.Product.ID]
if !exists {
return fmt.Errorf(
"no test product mapping found for live product %s in plan %s",
plan.Product.ID,
plan.ID,
)
}
planData := &stripe.PlanParams{
Currency: stripe.String(string(plan.Currency)),
Interval: stripe.String(string(plan.Interval)),
ProductID: stripe.String(testProductID),
Nickname: nullableString(plan.Nickname),
TrialPeriodDays: nullableInt64(plan.TrialPeriodDays),
Metadata: map[string]string{
"original_live_plan_id": plan.ID,
},
}
if plan.Amount > 0 {
planData.Amount = stripe.Int64(plan.Amount)
}
if len(plan.Tiers) > 0 {
planData.TiersMode = stripe.String(string(plan.TiersMode))
for _, tier := range plan.Tiers {
tierParams := &stripe.PlanTierParams{
UpTo: stripe.Int64(tier.UpTo),
}
if tier.UnitAmount > 0 {
tierParams.UnitAmount = stripe.Int64(tier.UnitAmount)
}
if tier.FlatAmount > 0 {
tierParams.FlatAmount = stripe.Int64(tier.FlatAmount)
}
if tier.UpTo == 0 {
tierParams.UpToInf = stripe.Bool(true)
}
planData.Tiers = append(planData.Tiers, tierParams)
}
}
testPlan, err := stripeTest.Plans.New(planData)
if err != nil {
return fmt.Errorf("error creating plan %s: %w", plan.ID, err)
}
fmt.Printf(
"Created Plan: %s (original live ID: %s)\n",
testPlan.ID,
plan.ID,
)
}
prices := stripeProd.Prices.List(
&stripe.PriceListParams{
ListParams: stripe.ListParams{Limit: stripe.Int64(100)},
},
)
for prices.Next() {
price := prices.Current().(*stripe.Price)
existingTestPrices := stripeTest.Prices.List(&stripe.PriceListParams{
ListParams: stripe.ListParams{Limit: stripe.Int64(1)},
})
var isDuplicate bool
for existingTestPrices.Next() {
testPrice := existingTestPrices.Current().(*stripe.Price)
if testPrice.Metadata["original_live_price_id"] == price.ID {
isDuplicate = true
fmt.Printf(
"Price already exists: %s (original live ID: %s)\n",
testPrice.ID,
price.ID,
)
break
}
}
if isDuplicate {
continue
}
if testProductID, exists := productMapping[price.Product.ID]; exists {
priceData := &stripe.PriceParams{
UnitAmount: stripe.Int64(price.UnitAmount),
Currency: stripe.String(string(price.Currency)),
Product: stripe.String(testProductID),
Recurring: nil,
Metadata: map[string]string{
"original_live_price_id": price.ID,
},
}
if price.Recurring != nil {
priceData.Recurring = &stripe.PriceRecurringParams{
Interval: stripe.String(
string(price.Recurring.Interval),
),
IntervalCount: stripe.Int64(price.Recurring.IntervalCount),
UsageType: stripe.String(
string(price.Recurring.UsageType),
),
AggregateUsage: stripe.String(
string(price.Recurring.AggregateUsage),
),
}
}
testPrice, err := stripeTest.Prices.New(priceData)
if err != nil {
return fmt.Errorf(
"error creating price for product %s: %w",
testProductID,
err,
)
}
fmt.Printf(
"Created Price: %s for Product: %s (original live ID: %s)\n",
testPrice.ID,
testProductID,
price.ID,
)
}
}
return nil
}
// Helper functions to handle nullable fields
func nullableString(s string) *string {
if s == "" {
return nil
}
return stripe.String(s)
}
func nullableBool(b bool) *bool {
if !b {
return nil
}
return stripe.Bool(b)
}
func nullableInt64(i int64) *int64 {
if i == 0 {
return nil
}
return stripe.Int64(i)
}
@zyriab
Copy link
Author

zyriab commented Mar 12, 2025

To use this, copy the file to your own system, cd where it is and run

go mod init foo
go mod tidy
go run yourfile.go

Remember to set the environment variables, you can specify them first in te go run ... command like STRIPE_TEST_SECRET=foo go run ... or you can use a .env file next to the go file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment