feat: new secrets

This commit is contained in:
2026-07-20 15:23:16 +02:00
parent 5a6833c1a2
commit 3fff09ba81
18 changed files with 121 additions and 109 deletions
+2
View File
@@ -8,6 +8,8 @@ npm-debug.*
*.key *.key
*.mobileprovision *.mobileprovision
*.orig.* *.orig.*
functions/.secret.local
functions/serviceAccountKey.json
web-build/ web-build/
ios/ ios/
android/ android/
-10
View File
@@ -1,10 +0,0 @@
exports.GEMINI_API_KEY = 'AIzaSyBoPQC5ZaMKP73TlKGpZQp1mAw8ArHiH9Y'
exports.SUNO_API_KEY = 'c1636e04f606811511e19ec6e1545aa6' // api key
exports.RESEND_API_KEY = 're_NLcHmYiz_4LoFrzvPBbShNQBgNGTQmBbu'
exports.STRIPE_SECRET_KEY =
'sk_test_51SPfcjCzf2o5bDRdUFGNrQYIE271EDfS2Ucn31f98Ublttcl1EBNRoOoJX1RfXXzHp7mKRGrIlCG24biiqUZ2YMh00s9WODluu'
exports.STRIPE_WEBHOOK_SECRET = 'whsec_pDrvXVjjMuZjtsnRaFVJrDmKO5QEtNkW'
exports.STRIPE_RETURN_URL = ''
+15
View File
@@ -0,0 +1,15 @@
const { defineSecret } = require('firebase-functions/params')
const GEMINI_API_KEY = defineSecret('GEMINI_API_KEY')
const SUNO_API_KEY = defineSecret('SUNO_API_KEY')
const RESEND_API_KEY = defineSecret('RESEND_API_KEY')
const STRIPE_SECRET_KEY = defineSecret('STRIPE_SECRET_KEY')
const STRIPE_WEBHOOK_SECRET = defineSecret('STRIPE_WEBHOOK_SECRET')
module.exports = {
GEMINI_API_KEY,
SUNO_API_KEY,
RESEND_API_KEY,
STRIPE_SECRET_KEY,
STRIPE_WEBHOOK_SECRET,
}
+4 -4
View File
@@ -1,14 +1,14 @@
const { googleAI } = require('@genkit-ai/googleai') const { googleAI } = require('@genkit-ai/googleai')
const { genkit, z } = require('genkit') const { genkit, z } = require('genkit')
const { GEMINI_API_KEY } = require('../config/keys') const { GEMINI_API_KEY } = require('../config/secrets')
const admin = require('firebase-admin') const admin = require('firebase-admin')
const { Buffer } = require('buffer') const { Buffer } = require('buffer')
const { setTimeout } = require('timers/promises') const { setTimeout } = require('timers/promises')
// --- CONFIGURATION --- // --- CONFIGURATION ---
// Plus intelligente que le Flash original, ultra rapide, et stable sur l'API. // Plus intelligente que le Flash original, ultra rapide, et stable sur l'API.
const TEXT_MODEL_NAME = 'gemini-3-pro-preview' const TEXT_MODEL_NAME = 'gemini-3.5-flash'
const IMAGE_MODEL_NAME = 'gemini-3-pro-image-preview' const IMAGE_MODEL_NAME = 'gemini-3-pro-image'
// --- SINGLETON PATTERN (WARM START) --- // --- SINGLETON PATTERN (WARM START) ---
// On stocke l'instance en dehors de la fonction pour la réutiliser // On stocke l'instance en dehors de la fonction pour la réutiliser
@@ -19,7 +19,7 @@ const getAiInstance = () => {
if (!aiInstance) { if (!aiInstance) {
console.log('⚡ [Gemini] Initialisation froide (Cold Start)') console.log('⚡ [Gemini] Initialisation froide (Cold Start)')
aiInstance = genkit({ aiInstance = genkit({
plugins: [googleAI({ apiKey: GEMINI_API_KEY })], plugins: [googleAI({ apiKey: GEMINI_API_KEY.value() })],
}) })
} }
return aiInstance return aiInstance
+7 -19
View File
@@ -3,17 +3,11 @@ const { HttpsError } = require('firebase-functions/https')
const Stripe = require('stripe') const Stripe = require('stripe')
const { URL } = require('url') const { URL } = require('url')
const { const { STRIPE_SECRET_KEY } = require('../config/secrets')
STRIPE_SECRET_KEY = '',
STRIPE_RETURN_URL = '',
STRIPE_PORTAL_CONFIGURATION = '',
STRIPE_MODE: CONFIG_STRIPE_MODE,
} = require('../config/keys')
const STRIPE_MODE = const STRIPE_RETURN_URL = process.env.STRIPE_RETURN_URL?.trim() || ''
typeof CONFIG_STRIPE_MODE === 'string' && CONFIG_STRIPE_MODE.trim() const STRIPE_PORTAL_CONFIGURATION = process.env.STRIPE_PORTAL_CONFIGURATION?.trim() || ''
? CONFIG_STRIPE_MODE.trim() const STRIPE_MODE = process.env.STRIPE_MODE?.trim() || 'test'
: 'test'
const requireEnv = (key) => { const requireEnv = (key) => {
const value = process.env?.[key] const value = process.env?.[key]
@@ -31,15 +25,9 @@ let cachedStripeClient = null
let cachedPortalConfigurationId = null let cachedPortalConfigurationId = null
const resolveStripeSecretKey = () => { const resolveStripeSecretKey = () => {
const inlineKey = typeof STRIPE_SECRET_KEY === 'string' ? STRIPE_SECRET_KEY.trim() : '' const secretKey = STRIPE_SECRET_KEY.value().trim()
if (secretKey) {
if (inlineKey) { return secretKey
return inlineKey
}
const required = requireEnv('STRIPE_SECRET_KEY')
if (typeof required === 'string' && required.trim()) {
return required.trim()
} }
throw new Error('STRIPE_SECRET_KEY not configured') throw new Error('STRIPE_SECRET_KEY not configured')
-13
View File
@@ -1,13 +0,0 @@
{
"type": "service_account",
"project_id": "musicland-d33f9",
"private_key_id": "c1110a2aa31eb3ac7cf42d931249a2572bf68e37",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCZKlW0nTIg4Il2\nPtr/BYi247mWp8pxpCwS2ggwGBU7iO44jLQAgLWtZdHrQ7kpRfjVuceJDrkLrS82\ntkt8wQpytCFKfZjil9fdyS1hrVIQdfaPppovbRtzDpe0varA8XxgWXG8L+CQtCQd\nNhcixKONFhgOWTKB/tvEtGEExtpRRTb7y21o/EFbfpZqSbGEPcota2DPPvNh1r1y\nnhF7AcboGFTouDaTVL/oFIYYm3eWQP6nJRiM15xPqEhar+2z3lG9Y6HoRkkynFFj\nwvhQ6AUidA4A1ZW90HC33jsY5u7MAbzXY/QZ2MC82gaf26rda/7hThmk0RUSEaOv\n/G2f2lQBAgMBAAECggEAKnssdEDZQ3cjiTFbyjm9toQo2FlHdAeF1eC1PqmfUfjj\nobmcvzrzqNlBxM/uzjzDDHxPlHsJ5ewt2S3sd/iOrku5pBB70/UrgS8oMBxDU590\nOFmMEDVh1KKqfw3QC12vEoYcXoak8V2VWqjC9/i0KBCFsY0ZziZizAb/lbSVns52\nuJQhjvZURHQQOQmrZDm5mhklKCjE6qDxAMjL0yIhDQLAATzqeVPPeP8Rvq6u7yiB\noiB40OGiGHLdt28Q7IAXSQ+/Vqig7aw/qwrfZrFqgbgCYJKo1GiYFaDgRbH6GiaE\nhBJg7LtM0EaDR5G0raBWv5szqpfOb2DC6K802dcQRQKBgQDQm7HoJjmvEu9a6hxF\nVwS3rdJO67dTtxFMZM0HespQu659NT8f21QsRf/2JIcwktTnGzSeVTca0GZ4z8de\nkP9Wid0VPcA6Y3U7yplBf7Xh/5KW7/O6iSCEN03Y3/lH6vK6tBe5xCT3kfYKkrAd\n/xePLLBx++dAL5YFixMzBFfI5wKBgQC79i2S6Bcu6WQc0yykVHkAMStPz4gsAYA5\nqqCuWEwFcgEEY5AiP/oH056Hu2Z2HJHPp4OKYWSbqdxkNM214vysO0yhrF59BjQX\nyo+hwQfBqS0JfH7bJgUjNuDjP947qGtRXVo2i3FRJuZ4NmS2cGC9gT00p6rZRrbL\nJkaKSuVW1wKBgH3xsgxVn7fqd6Xs99qJOejnQbSyLUGWohP8O6jLYoAxf5xKXG0Q\nTxZXwLxLu28BBxbsXu1Ex3sh3OwNjfGzGuWVYGvllzvpNis+Gx7nStlCIq+L+Rti\nlMYeWZqZRng8CgXcmwJlwGfTsLcbbsb9bqzbLz/x9ODUmf+IwrOnaJR7AoGBALA7\nPjm6YFo8vAAGw5mEFjtm3owjMjLIaXgoEiXFZenJHUkHLNHgGEcUVFRBEe12FgST\ntFKfxFvrJFfT5ng+TdgSdWQ0oBcrJNaVtpvbx7D9NNvrHWnOOQzuXGbVsEFRROXx\n2C7CCUokZfyaS01/CkVEEqX2+spjceoBhiwN0v/bAoGBAK4sYw+3o3yjH26/M/Ni\nn5hwxofTktvAkYi7GlSFaRf2QwwxMBeA6qM/AJn7rer6pRzWwApbae/kOyfZfivq\nLnfpwloJMtds/Mm2egz0zoX7WIZ15uEx4vha2IUmi4Qz92kUzgaD1jTN8ZLUCypn\npR/zlJVkLv5BFqOtfX8shz1T\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-fbsvc@musicland-d33f9.iam.gserviceaccount.com",
"client_id": "114560176426659117925",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40musicland-d33f9.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}
+2
View File
@@ -7,6 +7,7 @@ const sharp = require('sharp')
const crypto = require('crypto') const crypto = require('crypto')
// Imports internes // Imports internes
const { GEMINI_API_KEY } = require('../config/secrets')
const { generateImageV2 } = require('../helpers/gemini') const { generateImageV2 } = require('../helpers/gemini')
const { generatePicturePrompt } = require('../helpers/prompts') const { generatePicturePrompt } = require('../helpers/prompts')
const { ALERT_TYPE, refList } = require('../index') const { ALERT_TYPE, refList } = require('../index')
@@ -284,6 +285,7 @@ exports.onTaskCreateGenerateCover = onDocumentCreated(
timeoutSeconds: 540, // 9 minutes max (Imagen peut être lent) timeoutSeconds: 540, // 9 minutes max (Imagen peut être lent)
memory: '1GiB', memory: '1GiB',
document: 'tasks/{taskId}', document: 'tasks/{taskId}',
secrets: [GEMINI_API_KEY],
}, },
async (event) => { async (event) => {
const data = event.data?.data() || {} const data = event.data?.data() || {}
+4 -4
View File
@@ -2,7 +2,7 @@ const { onCall, HttpsError } = require('firebase-functions/v2/https')
const { z } = require('genkit') const { z } = require('genkit')
const { generateAI, analyseLyrics } = require('../helpers/gemini') const { generateAI, analyseLyrics } = require('../helpers/gemini')
const { SUNO_API_BASE, SUNO_TIMESTAMPED_LYRICS_PATH } = require('../config/suno') const { SUNO_API_BASE, SUNO_TIMESTAMPED_LYRICS_PATH } = require('../config/suno')
const { SUNO_API_KEY } = require('../config/keys') const { GEMINI_API_KEY, SUNO_API_KEY } = require('../config/secrets')
const axios = require('axios') const axios = require('axios')
const { FieldValue } = require('firebase-admin/firestore') const { FieldValue } = require('firebase-admin/firestore')
const { refList } = require('../index') const { refList } = require('../index')
@@ -137,7 +137,7 @@ const buildModerationBrief = ({
// --- GENERATION DE PAROLES (MAIN) --- // --- GENERATION DE PAROLES (MAIN) ---
exports.generateLyrics = onCall({}, async ({ auth = {}, data = {} }) => { exports.generateLyrics = onCall({ secrets: [GEMINI_API_KEY] }, async ({ auth = {}, data = {} }) => {
try { try {
const { const {
objective = '', objective = '',
@@ -344,7 +344,7 @@ const softenModerationDecision = (rawResult = {}) => {
} }
} }
exports.analyseLyricsToxicity = onCall({}, async ({ data = {} }) => { exports.analyseLyricsToxicity = onCall({ secrets: [GEMINI_API_KEY] }, async ({ data = {} }) => {
try { try {
const requestSchema = z.object({ const requestSchema = z.object({
title: z.string().optional(), title: z.string().optional(),
@@ -420,7 +420,7 @@ async function getSunoTimestamps(projectId) {
{ {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authorization: `Bearer ${SUNO_API_KEY}`, Authorization: `Bearer ${SUNO_API_KEY.value()}`,
}, },
timeout: 30000, timeout: 30000,
} }
+5 -5
View File
@@ -8,7 +8,7 @@ const { randomUUID } = require('crypto')
const { ALERT_TYPE, refList } = require('../index') const { ALERT_TYPE, refList } = require('../index')
const { sendNotification } = require('./notifications') const { sendNotification } = require('./notifications')
const { createOrderDocument, ORDER_TYPES } = require('./helpers/orders') const { createOrderDocument, ORDER_TYPES } = require('./helpers/orders')
const { SUNO_API_KEY } = require('../config/keys') const { SUNO_API_KEY } = require('../config/secrets')
const { const {
SUNO_MODEL, SUNO_MODEL,
SUNO_CALLBACK_URL, SUNO_CALLBACK_URL,
@@ -468,7 +468,7 @@ const mergeMusicUrls = async (projectRef, newUrls) => {
// 5. FONCTIONS PRINCIPALES (CLOUD FUNCTIONS) // 5. FONCTIONS PRINCIPALES (CLOUD FUNCTIONS)
// ========================================== // ==========================================
exports.generateMusic = onCall(async ({ data = {} }) => { exports.generateMusic = onCall({ secrets: [SUNO_API_KEY] }, async ({ data = {} }) => {
try { try {
// Extraction des données du Frontend // Extraction des données du Frontend
const { const {
@@ -542,7 +542,7 @@ exports.generateMusic = onCall(async ({ data = {} }) => {
const response = await axios.post(`${SUNO_API_BASE}${SUNO_API_PATH}`, payload, { const response = await axios.post(`${SUNO_API_BASE}${SUNO_API_PATH}`, payload, {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authorization: `Bearer ${SUNO_API_KEY}`, Authorization: `Bearer ${SUNO_API_KEY.value()}`,
}, },
}) })
@@ -571,12 +571,12 @@ exports.generateMusic = onCall(async ({ data = {} }) => {
}) })
// GET STATUS (inchangé mais inclus pour complétude) // GET STATUS (inchangé mais inclus pour complétude)
exports.getSunoStatus = onCall(async ({ data = {} }) => { exports.getSunoStatus = onCall({ secrets: [SUNO_API_KEY] }, async ({ data = {} }) => {
const { taskId } = data const { taskId } = data
if (!taskId) throw new Error('TaskId manquant') if (!taskId) throw new Error('TaskId manquant')
try { try {
const response = await axios.get(`${SUNO_API_BASE}${SUNO_STATUS_PATH}?taskId=${taskId}`, { const response = await axios.get(`${SUNO_API_BASE}${SUNO_STATUS_PATH}?taskId=${taskId}`, {
headers: { Authorization: `Bearer ${SUNO_API_KEY}` }, headers: { Authorization: `Bearer ${SUNO_API_KEY.value()}` },
timeout: 30000, timeout: 30000,
}) })
return { return {
+28 -21
View File
@@ -4,9 +4,16 @@ const { refList, ALERT_TYPE } = require('../index')
const { Expo } = require('expo-server-sdk') const { Expo } = require('expo-server-sdk')
const { Resend } = require('resend') const { Resend } = require('resend')
const { basicTemplate } = require('../helpers/email') const { basicTemplate } = require('../helpers/email')
const { RESEND_API_KEY } = require('../config/keys') const { RESEND_API_KEY } = require('../config/secrets')
const resendInstance = RESEND_API_KEY ? new Resend(RESEND_API_KEY) : null let resendInstance = null
const getResendInstance = () => {
if (!resendInstance) {
resendInstance = new Resend(RESEND_API_KEY.value())
}
return resendInstance
}
// Initialisation de Expo SDK // Initialisation de Expo SDK
let expo = new Expo() let expo = new Expo()
@@ -58,7 +65,11 @@ function buildNotificationEmailPayload({ title = '', message = '', template = {}
} }
exports.sendNotificationWhenDocIsCreated = onDocumentCreated( exports.sendNotificationWhenDocIsCreated = onDocumentCreated(
{ region: 'europe-west1', document: 'notifications/{notificationId}' }, {
region: 'europe-west1',
document: 'notifications/{notificationId}',
secrets: [RESEND_API_KEY],
},
async (event) => { async (event) => {
try { try {
const { const {
@@ -112,24 +123,20 @@ exports.sendNotificationWhenDocIsCreated = onDocumentCreated(
} }
if ((emailNotifications || mailOnly) && !!receiverEmail) { if ((emailNotifications || mailOnly) && !!receiverEmail) {
if (!resendInstance) { try {
console.warn('Resend client not configured; unable to send notification email.') const { subject, html } = buildNotificationEmailPayload({
} else { title,
try { message,
const { subject, html } = buildNotificationEmailPayload({ template: notifData?.email,
title, })
message, await getResendInstance().emails.send({
template: notifData?.email, from: EMAIL_FROM,
}) to: [receiverEmail],
await resendInstance.emails.send({ subject,
from: EMAIL_FROM, html,
to: [receiverEmail], })
subject, } catch (e) {
html, console.log('Error sending email:', e)
})
} catch (e) {
console.log('Error sending email:', e)
}
} }
} else { } else {
console.log( console.log(
+7 -1
View File
@@ -3,11 +3,17 @@ const { FieldValue } = require('firebase-admin/firestore')
const { onDocumentWritten } = require('firebase-functions/firestore') const { onDocumentWritten } = require('firebase-functions/firestore')
const _ = require('lodash') const _ = require('lodash')
const { refList, db } = require('../index') const { refList, db } = require('../index')
const { SUNO_API_KEY } = require('../config/secrets')
const { getSunoTimestamps } = require('./lyrics') const { getSunoTimestamps } = require('./lyrics')
const { deleteFolder } = require('../helpers/firebase') const { deleteFolder } = require('../helpers/firebase')
const { buildMonthKey } = require('../helpers/stats') const { buildMonthKey } = require('../helpers/stats')
exports.onProjectWritten = onDocumentWritten('projects/{projectId}', async (projectSnap) => { const PROJECT_FUNCTION_OPTIONS = {
document: 'projects/{projectId}',
secrets: [SUNO_API_KEY],
}
exports.onProjectWritten = onDocumentWritten(PROJECT_FUNCTION_OPTIONS, async (projectSnap) => {
try { try {
const { projectId } = projectSnap.params const { projectId } = projectSnap.params
const beforeData = projectSnap?.data?.before?.data() || null const beforeData = projectSnap?.data?.before?.data() || null
+7 -5
View File
@@ -9,6 +9,7 @@ try {
} }
const { REGION, refsList } = require('../index') const { REGION, refsList } = require('../index')
const { STRIPE_SECRET_KEY } = require('../config/secrets')
const STRIPE_MODE = 'test' const STRIPE_MODE = 'test'
const { const {
getStripeClient, getStripeClient,
@@ -22,6 +23,7 @@ const {
} = require('../helpers/stripe') } = require('../helpers/stripe')
const paymentsCollection = admin.firestore().collection('payments') const paymentsCollection = admin.firestore().collection('payments')
const STRIPE_FUNCTION_OPTIONS = { region: REGION, secrets: [STRIPE_SECRET_KEY] }
const getServerTimestamp = () => { const getServerTimestamp = () => {
if (FieldValue?.serverTimestamp) { if (FieldValue?.serverTimestamp) {
@@ -47,7 +49,7 @@ const normalizePaymentIntent = (paymentIntent) => ({
metadata: paymentIntent.metadata, metadata: paymentIntent.metadata,
}) })
const createCheckoutSession = onCall({ region: REGION }, async (request) => { const createCheckoutSession = onCall(STRIPE_FUNCTION_OPTIONS, async (request) => {
try { try {
const uid = request?.auth?.uid const uid = request?.auth?.uid
if (!uid) { if (!uid) {
@@ -123,7 +125,7 @@ const createCheckoutSession = onCall({ region: REGION }, async (request) => {
} }
}) })
const getPremiumStatus = onCall({ region: REGION }, async (request) => { const getPremiumStatus = onCall(STRIPE_FUNCTION_OPTIONS, async (request) => {
try { try {
const uid = request?.auth?.uid const uid = request?.auth?.uid
if (!uid) { if (!uid) {
@@ -203,7 +205,7 @@ const getPremiumStatus = onCall({ region: REGION }, async (request) => {
} }
}) })
const createStripeCustomerPortalSession = onCall({ region: REGION }, async (request) => { const createStripeCustomerPortalSession = onCall(STRIPE_FUNCTION_OPTIONS, async (request) => {
try { try {
const uid = request?.auth?.uid const uid = request?.auth?.uid
if (!uid) { if (!uid) {
@@ -365,7 +367,7 @@ const ensureStripeConnectAccount = async ({ uid, stripe, userRef, userData }) =>
} }
} }
const createStripeConnectLoginLink = onCall({ region: REGION }, async (request) => { const createStripeConnectLoginLink = onCall(STRIPE_FUNCTION_OPTIONS, async (request) => {
try { try {
const uid = request?.auth?.uid const uid = request?.auth?.uid
if (!uid) { if (!uid) {
@@ -425,7 +427,7 @@ const createStripeConnectLoginLink = onCall({ region: REGION }, async (request)
} }
}) })
const verifyStripePayment = onCall({ region: REGION }, async (request) => { const verifyStripePayment = onCall(STRIPE_FUNCTION_OPTIONS, async (request) => {
try { try {
const uid = request?.auth?.uid const uid = request?.auth?.uid
if (!uid) { if (!uid) {
+4 -2
View File
@@ -1,6 +1,7 @@
const { HttpsError, onCall } = require('firebase-functions/https') const { HttpsError, onCall } = require('firebase-functions/https')
const { getStripeClient, mapStripeErrorToHttps } = require('../../helpers/stripe') const { getStripeClient, mapStripeErrorToHttps } = require('../../helpers/stripe')
const { STRIPE_SECRET_KEY } = require('../../config/secrets')
const { REGION } = require('./config') const { REGION } = require('./config')
const { const {
SUBSCRIPTION_PRICE_IDS, SUBSCRIPTION_PRICE_IDS,
@@ -8,6 +9,7 @@ const {
COIN_PACK_PRODUCTS, COIN_PACK_PRODUCTS,
} = require('./constants') } = require('./constants')
const { parseCoinsPerMonth, formatCoinPack } = require('./shared') const { parseCoinsPerMonth, formatCoinPack } = require('./shared')
const STRIPE_FUNCTION_OPTIONS = { region: REGION, secrets: [STRIPE_SECRET_KEY] }
const formatPlan = (price, priceId) => { const formatPlan = (price, priceId) => {
if (!price || typeof price !== 'object') { if (!price || typeof price !== 'object') {
@@ -40,7 +42,7 @@ const formatPlan = (price, priceId) => {
} }
} }
const listSubscriptionPlans = onCall({ region: REGION }, async () => { const listSubscriptionPlans = onCall(STRIPE_FUNCTION_OPTIONS, async () => {
try { try {
const stripe = getStripeClient() const stripe = getStripeClient()
@@ -76,7 +78,7 @@ const listSubscriptionPlans = onCall({ region: REGION }, async () => {
} }
}) })
const listCoinPacks = onCall({ region: REGION }, async () => { const listCoinPacks = onCall(STRIPE_FUNCTION_OPTIONS, async () => {
try { try {
const stripe = getStripeClient() const stripe = getStripeClient()
+6 -4
View File
@@ -8,6 +8,7 @@ const {
formatCheckoutSessionResponse, formatCheckoutSessionResponse,
mapStripeErrorToHttps, mapStripeErrorToHttps,
} = require('../../helpers/stripe') } = require('../../helpers/stripe')
const { STRIPE_SECRET_KEY } = require('../../config/secrets')
const { REGION } = require('./config') const { REGION } = require('./config')
const { const {
ALL_SUBSCRIPTION_PRICE_IDS, ALL_SUBSCRIPTION_PRICE_IDS,
@@ -17,6 +18,7 @@ const {
const { refsList, formatCoinPack, getSubscriptionMetaFromPrice } = require('./shared') const { refsList, formatCoinPack, getSubscriptionMetaFromPrice } = require('./shared')
const CHECKOUT_UI_MODES = new Set(['hosted', 'embedded']) const CHECKOUT_UI_MODES = new Set(['hosted', 'embedded'])
const STRIPE_FUNCTION_OPTIONS = { region: REGION, secrets: [STRIPE_SECRET_KEY] }
const sanitizePriceId = (value) => { const sanitizePriceId = (value) => {
if (typeof value !== 'string') { if (typeof value !== 'string') {
@@ -65,7 +67,7 @@ const withCheckoutNavigationParams = (baseParams, { uiMode, successUrl, cancelUr
} }
} }
const createSubscriptionCheckoutSession = onCall({ region: REGION }, async (request) => { const createSubscriptionCheckoutSession = onCall(STRIPE_FUNCTION_OPTIONS, async (request) => {
try { try {
const uid = request?.auth?.uid const uid = request?.auth?.uid
if (!uid) { if (!uid) {
@@ -152,7 +154,7 @@ const createSubscriptionCheckoutSession = onCall({ region: REGION }, async (requ
} }
}) })
const createSongDownloadCheckoutSession = onCall({ region: REGION }, async (request) => { const createSongDownloadCheckoutSession = onCall(STRIPE_FUNCTION_OPTIONS, async (request) => {
try { try {
const uid = request?.auth?.uid const uid = request?.auth?.uid
if (!uid) { if (!uid) {
@@ -231,7 +233,7 @@ const createSongDownloadCheckoutSession = onCall({ region: REGION }, async (requ
} }
}) })
const createPlaybackDownloadCheckoutSession = onCall({ region: REGION }, async (request) => { const createPlaybackDownloadCheckoutSession = onCall(STRIPE_FUNCTION_OPTIONS, async (request) => {
try { try {
const uid = request?.auth?.uid const uid = request?.auth?.uid
if (!uid) { if (!uid) {
@@ -310,7 +312,7 @@ const createPlaybackDownloadCheckoutSession = onCall({ region: REGION }, async (
} }
}) })
const createCoinPackCheckoutSession = onCall({ region: REGION }, async (request) => { const createCoinPackCheckoutSession = onCall(STRIPE_FUNCTION_OPTIONS, async (request) => {
try { try {
const uid = request?.auth?.uid const uid = request?.auth?.uid
if (!uid) { if (!uid) {
+4 -2
View File
@@ -1,11 +1,13 @@
const { HttpsError, onCall } = require('firebase-functions/https') const { HttpsError, onCall } = require('firebase-functions/https')
const { getStripeClient, mapStripeErrorToHttps } = require('../../helpers/stripe') const { getStripeClient, mapStripeErrorToHttps } = require('../../helpers/stripe')
const { STRIPE_SECRET_KEY } = require('../../config/secrets')
const { REGION } = require('./config') const { REGION } = require('./config')
const { CANCELABLE_SUBSCRIPTION_STATUSES, ACTIVE_SUBSCRIPTION_STATUSES } = require('./constants') const { CANCELABLE_SUBSCRIPTION_STATUSES, ACTIVE_SUBSCRIPTION_STATUSES } = require('./constants')
const { refsList, formatSubscriptionForClient, resolveUserContext } = require('./shared') const { refsList, formatSubscriptionForClient, resolveUserContext } = require('./shared')
const STRIPE_FUNCTION_OPTIONS = { region: REGION, secrets: [STRIPE_SECRET_KEY] }
const cancelActiveSubscription = onCall({ region: REGION }, async (request) => { const cancelActiveSubscription = onCall(STRIPE_FUNCTION_OPTIONS, async (request) => {
try { try {
const uid = request?.auth?.uid const uid = request?.auth?.uid
if (!uid) { if (!uid) {
@@ -101,7 +103,7 @@ const cancelActiveSubscription = onCall({ region: REGION }, async (request) => {
} }
}) })
const getActiveSubscription = onCall({ region: REGION }, async (request) => { const getActiveSubscription = onCall(STRIPE_FUNCTION_OPTIONS, async (request) => {
try { try {
const uid = request?.auth?.uid const uid = request?.auth?.uid
if (!uid) { if (!uid) {
+2 -8
View File
@@ -2,7 +2,7 @@ const admin = require('firebase-admin')
const { FieldValue } = require('firebase-admin/firestore') const { FieldValue } = require('firebase-admin/firestore')
const { refList } = require('../../index') const { refList } = require('../../index')
const { STRIPE_WEBHOOK_SECRET } = require('../../config/keys') const { STRIPE_WEBHOOK_SECRET } = require('../../config/secrets')
const { const {
SUBSCRIPTION_LEVEL_ALLOWANCES, SUBSCRIPTION_LEVEL_ALLOWANCES,
SUBSCRIPTION_PRICE_METADATA, SUBSCRIPTION_PRICE_METADATA,
@@ -120,13 +120,7 @@ const resolveStripeWebhookSecret = () => {
return cachedStripeWebhookSecret return cachedStripeWebhookSecret
} }
const envSecret = const secret = STRIPE_WEBHOOK_SECRET.value().trim()
typeof process?.env?.STRIPE_WEBHOOK_SECRET === 'string'
? process.env.STRIPE_WEBHOOK_SECRET.trim()
: ''
const inlineSecret = typeof STRIPE_WEBHOOK_SECRET === 'string' ? STRIPE_WEBHOOK_SECRET.trim() : ''
const secret = envSecret || inlineSecret
if (!secret) { if (!secret) {
throw new Error('STRIPE_WEBHOOK_SECRET not configured') throw new Error('STRIPE_WEBHOOK_SECRET not configured')
} }
+6 -1
View File
@@ -3,6 +3,7 @@ const { HttpsError } = require('firebase-functions/https')
const { ORDER_TYPES, createOrderDocument } = require('../helpers/orders') const { ORDER_TYPES, createOrderDocument } = require('../helpers/orders')
const { getStripeClient } = require('../../helpers/stripe') const { getStripeClient } = require('../../helpers/stripe')
const { STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET } = require('../../config/secrets')
const { REGION } = require('./config') const { REGION } = require('./config')
const { const {
paymentsCollection, paymentsCollection,
@@ -20,6 +21,10 @@ const {
upsertPaymentDocument, upsertPaymentDocument,
} = require('./shared') } = require('./shared')
const { PREMIUM_SUBSCRIPTION_STATUSES } = require('./constants') const { PREMIUM_SUBSCRIPTION_STATUSES } = require('./constants')
const STRIPE_WEBHOOK_OPTIONS = {
region: REGION,
secrets: [STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET],
}
const isPaidCheckoutSession = (session) => { const isPaidCheckoutSession = (session) => {
if (!session || typeof session !== 'object') { if (!session || typeof session !== 'object') {
@@ -749,7 +754,7 @@ const handleStripeWebhookEvent = async ({ event, stripe }) => {
} }
} }
const handleStripeWebhook = onRequest({ region: REGION }, async (req, res) => { const handleStripeWebhook = onRequest(STRIPE_WEBHOOK_OPTIONS, async (req, res) => {
if (req.method !== 'POST') { if (req.method !== 'POST') {
res.status(405).send('Method Not Allowed') res.status(405).send('Method Not Allowed')
return return
+18 -10
View File
@@ -6,14 +6,26 @@ const { ORDER_TYPES, createOrderDocument } = require('./helpers/orders')
const { deleteFolder } = require('../helpers/firebase') const { deleteFolder } = require('../helpers/firebase')
const { Resend } = require('resend') const { Resend } = require('resend')
const { welcomeTemplate } = require('../helpers/email') const { welcomeTemplate } = require('../helpers/email')
const { RESEND_API_KEY } = require('../config/keys') const { RESEND_API_KEY } = require('../config/secrets')
const { onRequest } = require('firebase-functions/https') const { onRequest } = require('firebase-functions/https')
const resendClient = new Resend(RESEND_API_KEY) let resendClient = null
const getResendClient = () => {
if (!resendClient) {
resendClient = new Resend(RESEND_API_KEY.value())
}
return resendClient
}
const WELCOME_EMAIL_FROM = 'MusicLand <musicland@musicland.ai>' const WELCOME_EMAIL_FROM = 'MusicLand <musicland@musicland.ai>'
const WELCOME_EMAIL_SUBJECT = 'Bienvenue sur MusicLand' const WELCOME_EMAIL_SUBJECT = 'Bienvenue sur MusicLand'
const USER_CREATED_OPTIONS = {
document: 'users/{userID}',
secrets: [RESEND_API_KEY],
}
exports.testWelcomMail = onRequest(async (req, res) => { exports.testWelcomMail = onRequest({ secrets: [RESEND_API_KEY] }, async (req, res) => {
if (req.method !== 'GET') { if (req.method !== 'GET') {
res.set('Allow', 'GET') res.set('Allow', 'GET')
return res.status(405).json({ success: false, error: 'Method not allowed' }) return res.status(405).json({ success: false, error: 'Method not allowed' })
@@ -22,7 +34,7 @@ exports.testWelcomMail = onRequest(async (req, res) => {
const targetEmail = req.query.email || 'tdtomthomas@gmail.com' const targetEmail = req.query.email || 'tdtomthomas@gmail.com'
const firstName = req.query.firstName || 'Toto' const firstName = req.query.firstName || 'Toto'
const lastName = req.query.lastName || 'Test' const lastName = req.query.lastName || 'Test'
const { data, error } = await resendClient.emails.send({ const { data, error } = await getResendClient().emails.send({
from: WELCOME_EMAIL_FROM, from: WELCOME_EMAIL_FROM,
to: [targetEmail], to: [targetEmail],
subject: WELCOME_EMAIL_SUBJECT, subject: WELCOME_EMAIL_SUBJECT,
@@ -40,7 +52,7 @@ exports.testWelcomMail = onRequest(async (req, res) => {
} }
}) })
exports.onUserCreated = onDocumentCreated('users/{userID}', async (event) => { exports.onUserCreated = onDocumentCreated(USER_CREATED_OPTIONS, async (event) => {
try { try {
const { email = '', firstName = '', lastName = '' } = event?.data?.data() || {} const { email = '', firstName = '', lastName = '' } = event?.data?.data() || {}
@@ -62,13 +74,9 @@ exports.onUserCreated = onDocumentCreated('users/{userID}', async (event) => {
) )
} }
if (email) { if (email) {
if (!resendClient) {
console.warn('Resend API key not configured; skipping welcome email.')
return
}
try { try {
console.log(`Sending welcome email to ${email}`) console.log(`Sending welcome email to ${email}`)
const { data, error } = await resendClient.emails.send({ const { data, error } = await getResendClient().emails.send({
from: WELCOME_EMAIL_FROM, from: WELCOME_EMAIL_FROM,
to: [email], to: [email],
subject: WELCOME_EMAIL_SUBJECT, subject: WELCOME_EMAIL_SUBJECT,