pay for music
This commit is contained in:
@@ -152,6 +152,85 @@ const createSubscriptionCheckoutSession = onCall({ region: REGION }, async (requ
|
||||
}
|
||||
})
|
||||
|
||||
const createSongDownloadCheckoutSession = onCall({ region: REGION }, async (request) => {
|
||||
try {
|
||||
const uid = request?.auth?.uid
|
||||
if (!uid) {
|
||||
throw new HttpsError('unauthenticated', 'Connecte-toi pour télécharger ton morceau.')
|
||||
}
|
||||
|
||||
const rawProjectId = request?.data?.projectId
|
||||
const projectId = typeof rawProjectId === 'string' ? rawProjectId.trim() : ''
|
||||
if (!projectId) {
|
||||
throw new HttpsError('invalid-argument', 'Un identifiant de projet est requis.')
|
||||
}
|
||||
|
||||
const stripe = getStripeClient()
|
||||
|
||||
const { lineItems } = await buildCheckoutLineItems(
|
||||
[
|
||||
{
|
||||
label: 'Téléchargement Musicland',
|
||||
unitAmount: 199,
|
||||
currency: 'eur',
|
||||
quantity: 1,
|
||||
isRenewable: false,
|
||||
},
|
||||
],
|
||||
{ stripe }
|
||||
)
|
||||
|
||||
const uiMode = resolveCheckoutUiMode(request)
|
||||
const shouldProvideReturnUrls = uiMode !== 'embedded'
|
||||
const { successUrl, cancelUrl } = shouldProvideReturnUrls
|
||||
? getReturnUrls(request?.data?.returnUrls)
|
||||
: { successUrl: null, cancelUrl: null }
|
||||
|
||||
const { customerId } = await ensureStripeCustomer({
|
||||
uid,
|
||||
stripe,
|
||||
refsList,
|
||||
createIfMissing: true,
|
||||
})
|
||||
|
||||
if (!customerId) {
|
||||
throw new HttpsError(
|
||||
'failed-precondition',
|
||||
'Impossible de retrouver le client Stripe associé.'
|
||||
)
|
||||
}
|
||||
|
||||
const session = await stripe.checkout.sessions.create(
|
||||
withCheckoutNavigationParams(
|
||||
{
|
||||
mode: 'payment',
|
||||
customer: customerId,
|
||||
line_items: lineItems,
|
||||
allow_promotion_codes: false,
|
||||
metadata: {
|
||||
firebaseUID: uid,
|
||||
purchaseType: 'SONG_DOWNLOAD',
|
||||
projectId,
|
||||
},
|
||||
},
|
||||
{
|
||||
uiMode,
|
||||
successUrl,
|
||||
cancelUrl,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
return formatCheckoutSessionResponse(session)
|
||||
} catch (error) {
|
||||
console.error('[subscription-createSongDownloadCheckoutSession] error', error)
|
||||
if (error instanceof HttpsError) {
|
||||
throw error
|
||||
}
|
||||
throw mapStripeErrorToHttps(error, "Impossible de créer la session d'achat.")
|
||||
}
|
||||
})
|
||||
|
||||
const createCoinPackCheckoutSession = onCall({ region: REGION }, async (request) => {
|
||||
try {
|
||||
const uid = request?.auth?.uid
|
||||
@@ -272,6 +351,7 @@ const createCoinPackCheckoutSession = onCall({ region: REGION }, async (request)
|
||||
|
||||
module.exports = {
|
||||
createSubscriptionCheckoutSession,
|
||||
createSongDownloadCheckoutSession,
|
||||
createCoinPackCheckoutSession,
|
||||
resolveCheckoutUiMode,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
const { listSubscriptionPlans, listCoinPacks } = require('./catalog')
|
||||
const { createSubscriptionCheckoutSession, createCoinPackCheckoutSession } = require('./checkout')
|
||||
const {
|
||||
createSubscriptionCheckoutSession,
|
||||
createSongDownloadCheckoutSession,
|
||||
createCoinPackCheckoutSession,
|
||||
} = require('./checkout')
|
||||
const { cancelActiveSubscription, getActiveSubscription } = require('./management')
|
||||
const { handleStripeWebhook } = require('./webhooks')
|
||||
const { processAnnualSubscriptionAllowances } = require('./schedule')
|
||||
@@ -7,6 +11,7 @@ const { processAnnualSubscriptionAllowances } = require('./schedule')
|
||||
module.exports = {
|
||||
listSubscriptionPlans,
|
||||
createSubscriptionCheckoutSession,
|
||||
createSongDownloadCheckoutSession,
|
||||
cancelActiveSubscription,
|
||||
getActiveSubscription,
|
||||
listCoinPacks,
|
||||
|
||||
@@ -6,6 +6,7 @@ const { getStripeClient } = require('../../helpers/stripe')
|
||||
const { REGION } = require('./config')
|
||||
const {
|
||||
paymentsCollection,
|
||||
refsList,
|
||||
resolveStripeWebhookSecret,
|
||||
getServerTimestamp,
|
||||
toFirestoreTimestamp,
|
||||
@@ -149,6 +150,91 @@ const handleCheckoutSessionCompleted = async (session, event, { stripe } = {}) =
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
session.mode === 'payment' &&
|
||||
(session.payment_status === 'paid' || session.payment_status === 'no_payment_required') &&
|
||||
session.metadata?.purchaseType === 'SONG_DOWNLOAD'
|
||||
) {
|
||||
const rawProjectId = session.metadata?.projectId
|
||||
const projectId = typeof rawProjectId === 'string' ? rawProjectId.trim() : ''
|
||||
|
||||
if (projectId && paymentDocRef) {
|
||||
let paymentSnapshot = null
|
||||
try {
|
||||
paymentSnapshot = await paymentDocRef.get()
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
'[subscription-handleCheckoutSessionCompleted] Unable to read payment doc',
|
||||
session.id,
|
||||
error?.message || error
|
||||
)
|
||||
}
|
||||
|
||||
const alreadyGranted = Boolean(
|
||||
paymentSnapshot?.exists && paymentSnapshot.data()?.downloadGrantedAt
|
||||
)
|
||||
|
||||
if (!alreadyGranted) {
|
||||
const projectRef = refsList?.projects?.doc(projectId) || null
|
||||
let shouldGrant = true
|
||||
|
||||
if (projectRef) {
|
||||
try {
|
||||
const projectSnapshot = await projectRef.get()
|
||||
if (!projectSnapshot.exists) {
|
||||
shouldGrant = false
|
||||
} else {
|
||||
const projectData = projectSnapshot.data() || {}
|
||||
const ownerId =
|
||||
typeof projectData?.userId === 'string' ? projectData.userId.trim() : ''
|
||||
const targetUserId = uid || firebaseUid || userRef?.id || null
|
||||
if (ownerId && targetUserId && ownerId !== targetUserId) {
|
||||
console.warn(
|
||||
'[subscription-handleCheckoutSessionCompleted] Project owner mismatch',
|
||||
projectId
|
||||
)
|
||||
shouldGrant = false
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
'[subscription-handleCheckoutSessionCompleted] Unable to read project',
|
||||
projectId,
|
||||
error?.message || error
|
||||
)
|
||||
}
|
||||
} else {
|
||||
shouldGrant = false
|
||||
}
|
||||
|
||||
if (shouldGrant && projectRef) {
|
||||
await projectRef.set(
|
||||
{
|
||||
downloadPurchase: {
|
||||
status: 'paid',
|
||||
paymentId: session.id || null,
|
||||
paymentIntentId:
|
||||
typeof session.payment_intent === 'string' ? session.payment_intent : null,
|
||||
amount: session.amount_total ?? null,
|
||||
currency: session.currency || null,
|
||||
paidAt: getServerTimestamp(),
|
||||
},
|
||||
},
|
||||
{ merge: true }
|
||||
)
|
||||
|
||||
await paymentDocRef.set(
|
||||
{
|
||||
downloadGrantedAt: getServerTimestamp(),
|
||||
downloadProjectId: projectId,
|
||||
},
|
||||
{ merge: true }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleCustomerSubscriptionEvent = async (subscription, event, { stripe } = {}) => {
|
||||
|
||||
Reference in New Issue
Block a user