Skip to content

Instantly share code, notes, and snippets.

@christianalfoni
Created September 12, 2019 11:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save christianalfoni/7e1652982df96d85a2a80e12a55a2ae6 to your computer and use it in GitHub Desktop.
Save christianalfoni/7e1652982df96d85a2a80e12a55a2ae6 to your computer and use it in GitHub Desktop.
import {
OnCallWithServices,
ResponseError,
OnRequestWithServices,
} from './services'
import * as Stripe from 'stripe'
export const subscribe: OnCallWithServices<{
token: string
plans: string[]
}> = async ({ token, plans }, { user, firestore, stripe }) => {
if (!user.email) {
return {
error: ResponseError.MISSING_EMAIL,
}
}
if (!plans.length) {
return {
error: ResponseError.MISSING_PLANS,
}
}
const familyReference = firestore.collection('families').doc()
const profileSnapshot = await firestore
.collection('profiles')
.doc(user.uid)
.get()
const profile = profileSnapshot.data()
if (!profile) {
return {
error: ResponseError.MISSING_PROFILE,
}
}
const getCustomer = async () => {
const stripeId = profile && profile.stripeId
if (stripeId && token) {
return stripe.customers.update(stripeId, {
source: token,
})
} else if (token) {
return stripe.customers.create({
email: user.email,
source: token,
})
}
return stripe.customers.retrieve(stripeId)
}
const updateProfile = async (
customer: Stripe.customers.ICustomer,
subscription: Stripe.subscriptions.ISubscription
) => {
return firestore
.collection('profiles')
.doc(user.uid)
.set(
{
sources: customer.sources ? customer.sources.data : null,
stripeId: customer.id,
subscriptionId: subscription.id,
subscriptionStatus: subscription.status,
plans,
},
{
merge: true,
}
)
}
const getPlanUpdates = () => {
const planUpdates: Array<{ plan: string; deleted?: boolean }> = []
const existingPlans: string[] = profile.plans || []
for (const existingPlan of existingPlans) {
if (!plans.includes(existingPlan)) {
planUpdates.push({
plan: existingPlan,
deleted: true,
})
}
}
for (const plan of plans) {
if (!existingPlans.includes(plan)) {
planUpdates.push({
plan: plan,
})
}
}
return planUpdates
}
const getSubscription = async (customer: Stripe.customers.ICustomer) => {
const subscriptionId = profile && profile.subscriptionId
if (subscriptionId) {
return stripe.subscriptions.update(subscriptionId, {
items: getPlanUpdates(),
expand: ['latest_invoice.payment_intent'],
})
}
return stripe.subscriptions.create({
customer: customer.id,
items: getPlanUpdates(),
expand: ['latest_invoice.payment_intent'],
})
}
try {
const customer = await getCustomer()
const subscription = await getSubscription(customer)
await updateProfile(customer, subscription)
if (
!subscription.latest_invoice ||
!subscription.latest_invoice.payment_intent
) {
return {
error: ResponseError.UNKNOWN,
}
}
let paymentIntent = subscription.latest_invoice.payment_intent
if (paymentIntent.status === 'requires_payment_method') {
return {
error: ResponseError.REQUIRES_PAYMENT_METHOD,
}
}
if (paymentIntent.status === 'requires_action') {
return {
paymentIntentSecret:
subscription.latest_invoice.payment_intent.client_secret,
}
}
if (
token &&
profile.subscriptionId &&
subscription.status === 'incomplete'
) {
const invoice = await stripe.invoices.pay(
subscription.latest_invoice.id,
{
expand: ['payment_intent'],
}
)
paymentIntent = invoice.payment_intent
? invoice.payment_intent
: paymentIntent
}
await Promise.all([
// Set default family data
familyReference.set({
profileUids: [user.uid],
ownerUid: user.uid,
}),
// Update profile,
firestore
.collection('profiles')
.doc(user.uid)
.set(
{
subscriptionStatus: 'active',
familyUid: familyReference.id,
},
{
merge: true,
}
),
])
} catch (error) {
// If something fails, remove the family
await familyReference.delete()
return {
error: error.message,
}
}
return {}
}
export const recurringPayment: OnRequestWithServices = async (
req,
res,
{ firestore }
) => {
const hook = req.body.type
const data = req.body.data.object
try {
if (!data) throw new Error('missing data on recurring payment')
const profilesSnapshot = await firestore
.collection('profiles')
.where('stripeId', '==', data.customer)
.get()
const profile = profilesSnapshot.docs[0]
if (!profile || !profile.exists) {
throw new Error('missing profile on recurring payment, ' + data.customer)
}
if (hook === 'invoice.payment_succeeded') {
await profile.ref.update({ subscriptionStatus: 'active' })
}
if (hook === 'invoice.payment_failed') {
await profile.ref.update({ subscriptionStatus: 'incomplete' })
}
res.status(200).send('OK')
} catch (error) {
res.status(400).send('error - ' + hook + ': ' + error.message)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment