Customer Webhook
updatedCustomerWebhook.ts
updatedCustomerWebhook.tsimport { firestore, admin } from "../utils/firebase"
import stripe, { Stripe } from "../utils/stripe"
import * as express from "express"
const router = express.Router()
router.post("/", (req: any, res) => {
let event
try {
// check stripe event signature
event = stripe.webhooks.constructEvent(
req.rawBody,
req.headers["stripe-signature"],
process.env.YOUR_WEBHOOK_SIGNING_SECRET // visible on the Stripe Dashboard (https://dashboard.stripe.com/webhooks)
)
} catch (error) {
reportError(error, { request: req, details: "stripe-signature check error" }, req)
return res.status(400).send({ error })
}
if (!event || !event.type || !event.data?.object) return res.status(400).send({ event, error: "Missing data" })
const { previous_attributes } = event.data
const { metadata = {}, email, id: stripeCustomerId } = event.data.object as Stripe.Customer
const previousEmail = previous_attributes?.email
const emailWasUpdated = previousEmail && previousEmail !== email
const { billing_shared_subscription_id: sharedSubscriptionId } = metadata
const userDeleted = event.type === "customer.deleted"
return Promise.resolve()
.then((): any => {
// if user has a shared subscription, update the subscription
// this webhook is triggered for each user of a shared subscription
// when the status of the shared subscription has changed
if (sharedSubscriptionId) {
return stripe.subscriptions
.retrieve(sharedSubscriptionId)
.then((stripeSubscription: Stripe.Subscription) => {
return firestore
.collection("users")
.where("email", "==", email.toLowerCase())
.get()
.then((querySnapshot) => {
if (querySnapshot.empty) {
// user is not in the database yet
// aka the user was assigned a shared subscription license before he created an account
// on the app
return
}
// get first matching user
const matchingUser = querySnapshot.docs[0]
const userId = matchingUser.id
const subscription = userDeleted
? admin.firestore.FieldValue.delete() // delete subscription if customer is deleted
: // add shared subscription to new user
{
stripeCustomerId,
id: sharedSubscriptionId,
status: stripeSubscription.status,
plan: stripeSubscription.items?.data[0]?.price?.id ?? null,
trialEnd: stripeSubscription.trial_end,
periodEnd: stripeSubscription.current_period_end,
cancelAtPeriodEnd: stripeSubscription.cancel_at_period_end
}
return firestore.collection("users").doc(userId).set(
{
subscription
},
{ merge: true }
)
})
.then(() => {
// if the shared subscription customer updated his email
// delete old subscription
if (emailWasUpdated) {
firestore
.collection("users")
.where("email", "==", previousEmail.toLowerCase())
.get()
.then((querySnapshot): any => {
if (querySnapshot.empty) {
return Promise.resolve() // user not found, nothing to delete
}
const matchingUser = querySnapshot.docs[0]
const userId = matchingUser.id
// remove subscription from previous user
return firestore.collection("users").doc(userId).set(
{
subscription: admin.firestore.FieldValue.delete()
},
{ merge: true }
)
})
}
})
.then(
() =>
`user shared subscription was updated. ${
emailWasUpdated
? `Subscription was transferred from ${previousEmail} to ${email}`
: ""
} `
)
})
}
if (emailWasUpdated) {
// user is transferring ownership of his subscriptions
// the user is not a member of a shared subscription
return stripe.subscriptions
.list({
customer: stripeCustomerId,
status: "active"
})
.then((subscriptions) => {
if (subscriptions.data.length === 0) {
// no active subscriptions found for customer
return
}
const subscription = subscriptions.data[0]
return firestore
.collection("users")
.where("email", "==", email.toLowerCase())
.get()
.then((querySnapshot) => {
if (querySnapshot.empty) {
throw Error(`user not found: ${email}`)
}
// get first matching user for new email address
const matchingUser = querySnapshot.docs[0]
const userId = matchingUser.id
// add subscription to new user
return firestore
.collection("users")
.doc(userId)
.set(
{
subscription: {
stripeCustomerId,
id: subscription.id,
status: subscription.status,
plan: subscription.items?.data[0]?.price?.id ?? null,
trialEnd: subscription.trial_end,
periodEnd: subscription.current_period_end,
cancelAtPeriodEnd: subscription.cancel_at_period_end
}
},
{ merge: true }
)
.then(() => {
// delete old subscription from the user who transferred the subscription
firestore
.collection("users")
.where("email", "==", previousEmail.toLowerCase())
.get()
.then((querySnapshot): any => {
if (querySnapshot.empty) {
return Promise.resolve() // user not found, nothing to delete
}
const matchingUser = querySnapshot.docs[0]
const userId = matchingUser.id
// remove subscription from previous user
return firestore.collection("users").doc(userId).set(
{
subscription: admin.firestore.FieldValue.delete()
},
{ merge: true }
)
})
})
})
.then(() => `user subscription was transferred from ${previousEmail} to ${email}`)
})
}
return Promise.resolve("user has no shared subscription and email was not updated. nothing to do.")
})
.then((status) => res.status(200).send({ status }))
.catch((error) => {
console.error(`>> WEBHOOK ERROR: ${error}`)
res.status(400).send({ event, error })
})
})
export default router
Last updated