rebours/scripts/stripe-sync.mjs

223 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* stripe-sync.mjs
* ────────────────
* Synchronise les produits de la BDD vers Stripe :
* 1. Pour chaque produit avec un prix (price != null) :
* - Crée le Product Stripe (ou le retrouve via metadata.slug)
* - Crée le Price Stripe
* - Met à jour le stripePriceId dans la BDD
* 2. Les produits sans prix sont ignorés
*
* Usage :
* node scripts/stripe-sync.mjs # mode dry-run
* node scripts/stripe-sync.mjs --confirm # exécution réelle
*/
import 'dotenv/config'
import Stripe from 'stripe'
import { PrismaClient } from '@prisma/client'
const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY
if (!STRIPE_SECRET_KEY) {
console.error('❌ STRIPE_SECRET_KEY manquant dans .env')
process.exit(1)
}
const stripe = new Stripe(STRIPE_SECRET_KEY)
const prisma = new PrismaClient()
const dryRun = !process.argv.includes('--confirm')
if (dryRun) {
console.log('🔍 MODE DRY-RUN — rien ne sera créé ni modifié')
console.log(' Ajouter --confirm pour exécuter réellement\n')
}
/**
* Cherche un produit Stripe existant par metadata.slug
*/
async function findExistingStripeProduct(slug) {
let hasMore = true
let startingAfter = undefined
while (hasMore) {
const params = { limit: 100, active: true }
if (startingAfter) params.starting_after = startingAfter
const products = await stripe.products.list(params)
for (const product of products.data) {
if (product.metadata?.slug === slug) {
return product
}
}
hasMore = products.has_more
if (products.data.length > 0) {
startingAfter = products.data[products.data.length - 1].id
}
}
return null
}
/**
* Cherche un prix actif existant pour un produit Stripe
* avec le bon montant et la bonne devise
*/
async function findExistingPrice(stripeProductId, amount, currency) {
const prices = await stripe.prices.list({
product: stripeProductId,
active: true,
limit: 100,
})
return prices.data.find(
p => p.unit_amount === amount && p.currency === currency.toLowerCase()
)
}
async function main() {
// Récupérer tous les produits publiés avec un prix
const products = await prisma.product.findMany({
where: {
isPublished: true,
price: { not: null },
},
orderBy: { sortOrder: 'asc' },
})
if (products.length === 0) {
console.log(' Aucun produit avec un prix trouvé dans la BDD')
return
}
console.log(`📦 ${products.length} produit(s) à synchroniser vers Stripe\n`)
let created = 0
let updated = 0
let skipped = 0
for (const product of products) {
const priceCents = product.price
const currency = product.currency.toLowerCase()
console.log(`── ${product.productDisplayName} (${product.slug})`)
console.log(` Prix: ${priceCents / 100} ${currency.toUpperCase()}`)
// 1. Chercher ou créer le Product Stripe
let stripeProduct = await findExistingStripeProduct(product.slug)
if (stripeProduct) {
console.log(` ✓ Produit Stripe existant: ${stripeProduct.id}`)
if (!dryRun) {
// Mettre à jour les infos du produit
stripeProduct = await stripe.products.update(stripeProduct.id, {
name: product.productDisplayName,
description: product.description,
images: product.ogImage ? [product.ogImage] : undefined,
metadata: {
slug: product.slug,
stripeKey: product.stripeKey || product.slug,
dbId: product.id,
},
})
console.log(` ✓ Produit Stripe mis à jour`)
}
} else {
if (dryRun) {
console.log(` 📌 Produit Stripe SERAIT créé`)
} else {
stripeProduct = await stripe.products.create({
name: product.productDisplayName,
description: product.description,
images: product.ogImage ? [product.ogImage] : [],
metadata: {
slug: product.slug,
stripeKey: product.stripeKey || product.slug,
dbId: product.id,
},
})
console.log(` ✓ Produit Stripe créé: ${stripeProduct.id}`)
}
}
// 2. Chercher ou créer le Price Stripe
let stripePrice = null
if (stripeProduct) {
stripePrice = await findExistingPrice(stripeProduct.id, priceCents, currency)
}
if (stripePrice) {
console.log(` ✓ Prix Stripe existant: ${stripePrice.id} (${priceCents / 100} ${currency.toUpperCase()})`)
// Vérifier si le stripePriceId dans la BDD est à jour
if (product.stripePriceId === stripePrice.id) {
console.log(` ✓ BDD déjà à jour`)
skipped++
} else {
if (dryRun) {
console.log(` 📌 stripePriceId SERAIT mis à jour: ${product.stripePriceId || '(vide)'}${stripePrice.id}`)
updated++
} else {
await prisma.product.update({
where: { id: product.id },
data: { stripePriceId: stripePrice.id },
})
console.log(` ✓ BDD mise à jour: stripePriceId = ${stripePrice.id}`)
updated++
}
}
} else {
// Créer un nouveau prix
if (dryRun) {
console.log(` 📌 Prix Stripe SERAIT créé: ${priceCents / 100} ${currency.toUpperCase()}`)
console.log(` 📌 stripePriceId SERAIT mis à jour dans la BDD`)
created++
} else {
stripePrice = await stripe.prices.create({
product: stripeProduct.id,
unit_amount: priceCents,
currency: currency,
metadata: {
slug: product.slug,
dbId: product.id,
},
})
console.log(` ✓ Prix Stripe créé: ${stripePrice.id}`)
// 3. Mettre à jour le stripePriceId dans la BDD
await prisma.product.update({
where: { id: product.id },
data: { stripePriceId: stripePrice.id },
})
console.log(` ✓ BDD mise à jour: stripePriceId = ${stripePrice.id}`)
created++
}
}
console.log()
}
console.log('─'.repeat(50))
if (dryRun) {
console.log(`📊 Résumé (dry-run) :`)
console.log(` ${created} produit(s)/prix SERAIENT créés`)
console.log(` ${updated} stripePriceId SERAIENT mis à jour`)
console.log(` ${skipped} déjà synchronisé(s)`)
console.log(`\n👉 Relancer avec --confirm pour exécuter`)
} else {
console.log(`✅ Synchronisation terminée :`)
console.log(` ${created} créé(s), ${updated} mis à jour, ${skipped} inchangé(s)`)
}
}
main()
.catch(err => {
console.error('❌ Erreur:', err.message)
process.exit(1)
})
.finally(() => prisma.$disconnect())