Problem
We want to allow users to purchase a Premium subscription to our app, to be billed monthly.
Solution
We can use Stripe to:
Tokenize the user’s payment method on the client.
Accept the token on our backend
Create a Customer in Stripe
Subscribe that Customer to our Product
Implementation
Stripe Dashboard
Create a new Product
Create a new Pricing Plan for that Product
We’ll need the Plan ID (eg.
plan_FLyf9o01u2bJZU
)
GraphQL
Schema
Update our updateRole
mutation to accept a stripeToken
updateRole(newRole: Role!, stripeToken: String): User!
Resolver
updateRole
needs to do a series of things, in order:
1. Create a Customer in Stripe
stripe.customers.create({ | |
description: `Customer for ${me.email}`, | |
email: me.email, | |
source: stripeToken | |
}); |
2. Create a Subscription in Stripe for that Customer
stripe.subscriptions.create({ | |
customer: customer.id, | |
items: [{ plan: "plan_FLyf9o01u2bJZU" }] | |
}); |
3. Update the user’s role in the database
const result = await models.User.update( | |
{ | |
// what to update: | |
role: newRole | |
}, | |
{ | |
// where to update | |
where: { id: me.id }, | |
// options | |
returning: true, // return data after db update | |
plain: true // don't return extra stuff | |
} | |
); | |
// result is an array with [affectedRows, data]. We want the data (user); | |
const updatedUser = result[1]; // return this later |
4. Reset the cookie so the JWT includes the new role
const token = await createToken(updatedUser, process.env.APP_SECRET); | |
res.cookie("token", token, { | |
httpOnly: true, | |
maxAge: 1000 * 60 * 60 * 24 * 365, | |
}); |
5. Return the updated user from our resolver
return updatedUser;
Putting it all together
Here’s the code for the resolver in full. It’s pretty long-winded in it’s first iteration, but it gets the job done. Later we’ll want to add more code to handle different cases.
{ | |
Mutation: { | |
updateRole: async ( | |
parent, | |
{ newRole, stripeToken }, | |
{ models, me, res } | |
) => { | |
if (!me) { | |
throw new Error("Please log in first"); | |
} | |
// TODO: If newRole === db.user.role, "you already have a {newRole} account." | |
if ((newRole === "PREMIUM")) { | |
// 1. Create a customer | |
const customerReturn = stripe.customers.create( | |
{ | |
description: `Customer for ${me.email}`, | |
email: me.email, | |
source: stripeToken | |
}, | |
function(err, customer) { | |
// asynchronously called | |
console.log("err: ", err); | |
if (customer) { | |
console.log("customer: ", customer); | |
// Create the subscription and attempt payment. | |
stripe.subscriptions.create( | |
{ | |
customer: customer.id, | |
items: [{ plan: "plan_FLyf9o01u2bJZU" }] | |
}, | |
function(err, subscription) { | |
// asynchronously called | |
if (err) { | |
console.log("err: ", err); | |
} | |
if (subscription) { | |
console.log("subscription: ", subscription); | |
} | |
} | |
); | |
} | |
} | |
); | |
console.log("customerReturn: ", customerReturn); | |
// Update the DB user role | |
const result = await models.User.update( | |
{ | |
// what to update: | |
role: newRole | |
}, | |
{ | |
// where to update | |
where: { id: me.id }, | |
// options | |
returning: true, // return data after db update | |
plain: true // don't return extra stuff | |
} | |
); | |
// result is an array with [affectedRows, data]. We want the data (user); | |
const updatedUser = result[1]; // return this at the end | |
// reset the cookie, now with jwt containing updated user role | |
const token = await createToken(updatedUser, process.env.APP_SECRET); | |
res.cookie("token", token, { | |
httpOnly: true, | |
maxAge: 1000 * 60 * 60 * 24 * 365 // 1 year cookie | |
}); | |
// return user | |
return updatedUser; | |
} | |
if ((newRole = "BASIC")) { | |
// Update Stripe | |
// Update DB | |
} | |
} | |
}, | |
} | |
} |