feat: new secrets
This commit is contained in:
@@ -8,6 +8,8 @@ npm-debug.*
|
||||
*.key
|
||||
*.mobileprovision
|
||||
*.orig.*
|
||||
functions/.secret.local
|
||||
functions/serviceAccountKey.json
|
||||
web-build/
|
||||
ios/
|
||||
android/
|
||||
|
||||
@@ -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 = ''
|
||||
@@ -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,
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
const { googleAI } = require('@genkit-ai/googleai')
|
||||
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 { Buffer } = require('buffer')
|
||||
const { setTimeout } = require('timers/promises')
|
||||
|
||||
// --- CONFIGURATION ---
|
||||
// Plus intelligente que le Flash original, ultra rapide, et stable sur l'API.
|
||||
const TEXT_MODEL_NAME = 'gemini-3-pro-preview'
|
||||
const IMAGE_MODEL_NAME = 'gemini-3-pro-image-preview'
|
||||
const TEXT_MODEL_NAME = 'gemini-3.5-flash'
|
||||
const IMAGE_MODEL_NAME = 'gemini-3-pro-image'
|
||||
|
||||
// --- SINGLETON PATTERN (WARM START) ---
|
||||
// On stocke l'instance en dehors de la fonction pour la réutiliser
|
||||
@@ -19,7 +19,7 @@ const getAiInstance = () => {
|
||||
if (!aiInstance) {
|
||||
console.log('⚡ [Gemini] Initialisation froide (Cold Start)')
|
||||
aiInstance = genkit({
|
||||
plugins: [googleAI({ apiKey: GEMINI_API_KEY })],
|
||||
plugins: [googleAI({ apiKey: GEMINI_API_KEY.value() })],
|
||||
})
|
||||
}
|
||||
return aiInstance
|
||||
|
||||
@@ -3,17 +3,11 @@ const { HttpsError } = require('firebase-functions/https')
|
||||
const Stripe = require('stripe')
|
||||
const { URL } = require('url')
|
||||
|
||||
const {
|
||||
STRIPE_SECRET_KEY = '',
|
||||
STRIPE_RETURN_URL = '',
|
||||
STRIPE_PORTAL_CONFIGURATION = '',
|
||||
STRIPE_MODE: CONFIG_STRIPE_MODE,
|
||||
} = require('../config/keys')
|
||||
const { STRIPE_SECRET_KEY } = require('../config/secrets')
|
||||
|
||||
const STRIPE_MODE =
|
||||
typeof CONFIG_STRIPE_MODE === 'string' && CONFIG_STRIPE_MODE.trim()
|
||||
? CONFIG_STRIPE_MODE.trim()
|
||||
: 'test'
|
||||
const STRIPE_RETURN_URL = process.env.STRIPE_RETURN_URL?.trim() || ''
|
||||
const STRIPE_PORTAL_CONFIGURATION = process.env.STRIPE_PORTAL_CONFIGURATION?.trim() || ''
|
||||
const STRIPE_MODE = process.env.STRIPE_MODE?.trim() || 'test'
|
||||
|
||||
const requireEnv = (key) => {
|
||||
const value = process.env?.[key]
|
||||
@@ -31,15 +25,9 @@ let cachedStripeClient = null
|
||||
let cachedPortalConfigurationId = null
|
||||
|
||||
const resolveStripeSecretKey = () => {
|
||||
const inlineKey = typeof STRIPE_SECRET_KEY === 'string' ? STRIPE_SECRET_KEY.trim() : ''
|
||||
|
||||
if (inlineKey) {
|
||||
return inlineKey
|
||||
}
|
||||
|
||||
const required = requireEnv('STRIPE_SECRET_KEY')
|
||||
if (typeof required === 'string' && required.trim()) {
|
||||
return required.trim()
|
||||
const secretKey = STRIPE_SECRET_KEY.value().trim()
|
||||
if (secretKey) {
|
||||
return secretKey
|
||||
}
|
||||
|
||||
throw new Error('STRIPE_SECRET_KEY not configured')
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -7,6 +7,7 @@ const sharp = require('sharp')
|
||||
const crypto = require('crypto')
|
||||
|
||||
// Imports internes
|
||||
const { GEMINI_API_KEY } = require('../config/secrets')
|
||||
const { generateImageV2 } = require('../helpers/gemini')
|
||||
const { generatePicturePrompt } = require('../helpers/prompts')
|
||||
const { ALERT_TYPE, refList } = require('../index')
|
||||
@@ -284,6 +285,7 @@ exports.onTaskCreateGenerateCover = onDocumentCreated(
|
||||
timeoutSeconds: 540, // 9 minutes max (Imagen peut être lent)
|
||||
memory: '1GiB',
|
||||
document: 'tasks/{taskId}',
|
||||
secrets: [GEMINI_API_KEY],
|
||||
},
|
||||
async (event) => {
|
||||
const data = event.data?.data() || {}
|
||||
|
||||
@@ -2,7 +2,7 @@ const { onCall, HttpsError } = require('firebase-functions/v2/https')
|
||||
const { z } = require('genkit')
|
||||
const { generateAI, analyseLyrics } = require('../helpers/gemini')
|
||||
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 { FieldValue } = require('firebase-admin/firestore')
|
||||
const { refList } = require('../index')
|
||||
@@ -137,7 +137,7 @@ const buildModerationBrief = ({
|
||||
|
||||
// --- GENERATION DE PAROLES (MAIN) ---
|
||||
|
||||
exports.generateLyrics = onCall({}, async ({ auth = {}, data = {} }) => {
|
||||
exports.generateLyrics = onCall({ secrets: [GEMINI_API_KEY] }, async ({ auth = {}, data = {} }) => {
|
||||
try {
|
||||
const {
|
||||
objective = '',
|
||||
@@ -344,7 +344,7 @@ const softenModerationDecision = (rawResult = {}) => {
|
||||
}
|
||||
}
|
||||
|
||||
exports.analyseLyricsToxicity = onCall({}, async ({ data = {} }) => {
|
||||
exports.analyseLyricsToxicity = onCall({ secrets: [GEMINI_API_KEY] }, async ({ data = {} }) => {
|
||||
try {
|
||||
const requestSchema = z.object({
|
||||
title: z.string().optional(),
|
||||
@@ -420,7 +420,7 @@ async function getSunoTimestamps(projectId) {
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${SUNO_API_KEY}`,
|
||||
Authorization: `Bearer ${SUNO_API_KEY.value()}`,
|
||||
},
|
||||
timeout: 30000,
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ const { randomUUID } = require('crypto')
|
||||
const { ALERT_TYPE, refList } = require('../index')
|
||||
const { sendNotification } = require('./notifications')
|
||||
const { createOrderDocument, ORDER_TYPES } = require('./helpers/orders')
|
||||
const { SUNO_API_KEY } = require('../config/keys')
|
||||
const { SUNO_API_KEY } = require('../config/secrets')
|
||||
const {
|
||||
SUNO_MODEL,
|
||||
SUNO_CALLBACK_URL,
|
||||
@@ -468,7 +468,7 @@ const mergeMusicUrls = async (projectRef, newUrls) => {
|
||||
// 5. FONCTIONS PRINCIPALES (CLOUD FUNCTIONS)
|
||||
// ==========================================
|
||||
|
||||
exports.generateMusic = onCall(async ({ data = {} }) => {
|
||||
exports.generateMusic = onCall({ secrets: [SUNO_API_KEY] }, async ({ data = {} }) => {
|
||||
try {
|
||||
// Extraction des données du Frontend
|
||||
const {
|
||||
@@ -542,7 +542,7 @@ exports.generateMusic = onCall(async ({ data = {} }) => {
|
||||
const response = await axios.post(`${SUNO_API_BASE}${SUNO_API_PATH}`, payload, {
|
||||
headers: {
|
||||
'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)
|
||||
exports.getSunoStatus = onCall(async ({ data = {} }) => {
|
||||
exports.getSunoStatus = onCall({ secrets: [SUNO_API_KEY] }, async ({ data = {} }) => {
|
||||
const { taskId } = data
|
||||
if (!taskId) throw new Error('TaskId manquant')
|
||||
try {
|
||||
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,
|
||||
})
|
||||
return {
|
||||
|
||||
@@ -4,9 +4,16 @@ const { refList, ALERT_TYPE } = require('../index')
|
||||
const { Expo } = require('expo-server-sdk')
|
||||
const { Resend } = require('resend')
|
||||
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
|
||||
let expo = new Expo()
|
||||
@@ -58,7 +65,11 @@ function buildNotificationEmailPayload({ title = '', message = '', template = {}
|
||||
}
|
||||
|
||||
exports.sendNotificationWhenDocIsCreated = onDocumentCreated(
|
||||
{ region: 'europe-west1', document: 'notifications/{notificationId}' },
|
||||
{
|
||||
region: 'europe-west1',
|
||||
document: 'notifications/{notificationId}',
|
||||
secrets: [RESEND_API_KEY],
|
||||
},
|
||||
async (event) => {
|
||||
try {
|
||||
const {
|
||||
@@ -112,24 +123,20 @@ exports.sendNotificationWhenDocIsCreated = onDocumentCreated(
|
||||
}
|
||||
|
||||
if ((emailNotifications || mailOnly) && !!receiverEmail) {
|
||||
if (!resendInstance) {
|
||||
console.warn('Resend client not configured; unable to send notification email.')
|
||||
} else {
|
||||
try {
|
||||
const { subject, html } = buildNotificationEmailPayload({
|
||||
title,
|
||||
message,
|
||||
template: notifData?.email,
|
||||
})
|
||||
await resendInstance.emails.send({
|
||||
from: EMAIL_FROM,
|
||||
to: [receiverEmail],
|
||||
subject,
|
||||
html,
|
||||
})
|
||||
} catch (e) {
|
||||
console.log('Error sending email:', e)
|
||||
}
|
||||
try {
|
||||
const { subject, html } = buildNotificationEmailPayload({
|
||||
title,
|
||||
message,
|
||||
template: notifData?.email,
|
||||
})
|
||||
await getResendInstance().emails.send({
|
||||
from: EMAIL_FROM,
|
||||
to: [receiverEmail],
|
||||
subject,
|
||||
html,
|
||||
})
|
||||
} catch (e) {
|
||||
console.log('Error sending email:', e)
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
|
||||
@@ -3,11 +3,17 @@ const { FieldValue } = require('firebase-admin/firestore')
|
||||
const { onDocumentWritten } = require('firebase-functions/firestore')
|
||||
const _ = require('lodash')
|
||||
const { refList, db } = require('../index')
|
||||
const { SUNO_API_KEY } = require('../config/secrets')
|
||||
const { getSunoTimestamps } = require('./lyrics')
|
||||
const { deleteFolder } = require('../helpers/firebase')
|
||||
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 {
|
||||
const { projectId } = projectSnap.params
|
||||
const beforeData = projectSnap?.data?.before?.data() || null
|
||||
|
||||
@@ -9,6 +9,7 @@ try {
|
||||
}
|
||||
|
||||
const { REGION, refsList } = require('../index')
|
||||
const { STRIPE_SECRET_KEY } = require('../config/secrets')
|
||||
const STRIPE_MODE = 'test'
|
||||
const {
|
||||
getStripeClient,
|
||||
@@ -22,6 +23,7 @@ const {
|
||||
} = require('../helpers/stripe')
|
||||
|
||||
const paymentsCollection = admin.firestore().collection('payments')
|
||||
const STRIPE_FUNCTION_OPTIONS = { region: REGION, secrets: [STRIPE_SECRET_KEY] }
|
||||
|
||||
const getServerTimestamp = () => {
|
||||
if (FieldValue?.serverTimestamp) {
|
||||
@@ -47,7 +49,7 @@ const normalizePaymentIntent = (paymentIntent) => ({
|
||||
metadata: paymentIntent.metadata,
|
||||
})
|
||||
|
||||
const createCheckoutSession = onCall({ region: REGION }, async (request) => {
|
||||
const createCheckoutSession = onCall(STRIPE_FUNCTION_OPTIONS, async (request) => {
|
||||
try {
|
||||
const uid = request?.auth?.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 {
|
||||
const uid = request?.auth?.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 {
|
||||
const uid = request?.auth?.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 {
|
||||
const uid = request?.auth?.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 {
|
||||
const uid = request?.auth?.uid
|
||||
if (!uid) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const { HttpsError, onCall } = require('firebase-functions/https')
|
||||
|
||||
const { getStripeClient, mapStripeErrorToHttps } = require('../../helpers/stripe')
|
||||
const { STRIPE_SECRET_KEY } = require('../../config/secrets')
|
||||
const { REGION } = require('./config')
|
||||
const {
|
||||
SUBSCRIPTION_PRICE_IDS,
|
||||
@@ -8,6 +9,7 @@ const {
|
||||
COIN_PACK_PRODUCTS,
|
||||
} = require('./constants')
|
||||
const { parseCoinsPerMonth, formatCoinPack } = require('./shared')
|
||||
const STRIPE_FUNCTION_OPTIONS = { region: REGION, secrets: [STRIPE_SECRET_KEY] }
|
||||
|
||||
const formatPlan = (price, priceId) => {
|
||||
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 {
|
||||
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 {
|
||||
const stripe = getStripeClient()
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ const {
|
||||
formatCheckoutSessionResponse,
|
||||
mapStripeErrorToHttps,
|
||||
} = require('../../helpers/stripe')
|
||||
const { STRIPE_SECRET_KEY } = require('../../config/secrets')
|
||||
const { REGION } = require('./config')
|
||||
const {
|
||||
ALL_SUBSCRIPTION_PRICE_IDS,
|
||||
@@ -17,6 +18,7 @@ const {
|
||||
const { refsList, formatCoinPack, getSubscriptionMetaFromPrice } = require('./shared')
|
||||
|
||||
const CHECKOUT_UI_MODES = new Set(['hosted', 'embedded'])
|
||||
const STRIPE_FUNCTION_OPTIONS = { region: REGION, secrets: [STRIPE_SECRET_KEY] }
|
||||
|
||||
const sanitizePriceId = (value) => {
|
||||
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 {
|
||||
const uid = request?.auth?.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 {
|
||||
const uid = request?.auth?.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 {
|
||||
const uid = request?.auth?.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 {
|
||||
const uid = request?.auth?.uid
|
||||
if (!uid) {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
const { HttpsError, onCall } = require('firebase-functions/https')
|
||||
|
||||
const { getStripeClient, mapStripeErrorToHttps } = require('../../helpers/stripe')
|
||||
const { STRIPE_SECRET_KEY } = require('../../config/secrets')
|
||||
const { REGION } = require('./config')
|
||||
const { CANCELABLE_SUBSCRIPTION_STATUSES, ACTIVE_SUBSCRIPTION_STATUSES } = require('./constants')
|
||||
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 {
|
||||
const uid = request?.auth?.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 {
|
||||
const uid = request?.auth?.uid
|
||||
if (!uid) {
|
||||
|
||||
@@ -2,7 +2,7 @@ const admin = require('firebase-admin')
|
||||
const { FieldValue } = require('firebase-admin/firestore')
|
||||
|
||||
const { refList } = require('../../index')
|
||||
const { STRIPE_WEBHOOK_SECRET } = require('../../config/keys')
|
||||
const { STRIPE_WEBHOOK_SECRET } = require('../../config/secrets')
|
||||
const {
|
||||
SUBSCRIPTION_LEVEL_ALLOWANCES,
|
||||
SUBSCRIPTION_PRICE_METADATA,
|
||||
@@ -120,13 +120,7 @@ const resolveStripeWebhookSecret = () => {
|
||||
return cachedStripeWebhookSecret
|
||||
}
|
||||
|
||||
const envSecret =
|
||||
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
|
||||
const secret = STRIPE_WEBHOOK_SECRET.value().trim()
|
||||
if (!secret) {
|
||||
throw new Error('STRIPE_WEBHOOK_SECRET not configured')
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ const { HttpsError } = require('firebase-functions/https')
|
||||
|
||||
const { ORDER_TYPES, createOrderDocument } = require('../helpers/orders')
|
||||
const { getStripeClient } = require('../../helpers/stripe')
|
||||
const { STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET } = require('../../config/secrets')
|
||||
const { REGION } = require('./config')
|
||||
const {
|
||||
paymentsCollection,
|
||||
@@ -20,6 +21,10 @@ const {
|
||||
upsertPaymentDocument,
|
||||
} = require('./shared')
|
||||
const { PREMIUM_SUBSCRIPTION_STATUSES } = require('./constants')
|
||||
const STRIPE_WEBHOOK_OPTIONS = {
|
||||
region: REGION,
|
||||
secrets: [STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET],
|
||||
}
|
||||
|
||||
const isPaidCheckoutSession = (session) => {
|
||||
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') {
|
||||
res.status(405).send('Method Not Allowed')
|
||||
return
|
||||
|
||||
+18
-10
@@ -6,14 +6,26 @@ const { ORDER_TYPES, createOrderDocument } = require('./helpers/orders')
|
||||
const { deleteFolder } = require('../helpers/firebase')
|
||||
const { Resend } = require('resend')
|
||||
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 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_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') {
|
||||
res.set('Allow', 'GET')
|
||||
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 firstName = req.query.firstName || 'Toto'
|
||||
const lastName = req.query.lastName || 'Test'
|
||||
const { data, error } = await resendClient.emails.send({
|
||||
const { data, error } = await getResendClient().emails.send({
|
||||
from: WELCOME_EMAIL_FROM,
|
||||
to: [targetEmail],
|
||||
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 {
|
||||
const { email = '', firstName = '', lastName = '' } = event?.data?.data() || {}
|
||||
|
||||
@@ -62,13 +74,9 @@ exports.onUserCreated = onDocumentCreated('users/{userID}', async (event) => {
|
||||
)
|
||||
}
|
||||
if (email) {
|
||||
if (!resendClient) {
|
||||
console.warn('Resend API key not configured; skipping welcome email.')
|
||||
return
|
||||
}
|
||||
try {
|
||||
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,
|
||||
to: [email],
|
||||
subject: WELCOME_EMAIL_SUBJECT,
|
||||
|
||||
Reference in New Issue
Block a user