Last active
March 12, 2025 14:20
-
-
Save zyriab/461b737cd2d01624303b31bcdf702288 to your computer and use it in GitHub Desktop.
Copy Stripe products, plan and prices from live to test
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
// 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) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To use this, copy the file to your own system, cd where it is and run
Remember to set the environment variables, you can specify them first in te
go run ...
command likeSTRIPE_TEST_SECRET=foo go run ...
or you can use a.env
file next to the go file.