Open Source Stripe GraphQL API
28 September 2022
Today we’re launching an open source Stripe GraphQL API that makes it possible to query data from your database and Stripe account in a single GraphQL query. You can start using it today with our official npm package, @nhost/stripe-graphql-js
, and get a ready-to-use Stripe GraphQL API server.
Note:
@nhost/stripe-graphql-js
creates an unofficial open-source Stripe GraphQL API. The source code is available on GitHub.
How does it work?
The generated Stripe GraphQL API can be hosted using a Serverless Function and added as a Hasura Remote Schema. After that, a GraphQL relationship is created between your data in Postgres and Stripe using Hasura Data Federation.
We’ll explain how it works in more detail. But first…
Why did we build this?
Developers are using Nhost to build SaaS applications and Stripe to handle payments.
The problem is that some customer data lives in the Postgres database while other data is in Stripe. One of the selling points of GraphQL is being able to fetch all data required in a single request regardless of where that data is coming from. This is why having to manually combine data from Postgres and Stripe is not ideal.
We faced this first hand while building our dashboard - every workspace in Nhost is a Stripe customer. In the workspace view, we want to show data from both our database and Stripe accounts.
It would be great if we could make a single GraphQL query to show this information.
Well, now we can!
With GraphQL, it’s possible to query data from any data source. Not only a database.
Now, with the package being introduced, it’s possible to get data from both the Postgres database and Stripe using a single GraphQL query.
This is called Data Federation and is a powerful concept to unify data through a single GraphQL API.
Get Started
This is how you get started.
Serverless Function
First, create a Serverless Function to host and scale the Stripe GraphQL API. Here’s a minimal example of the Stripe GraphQL server:
functions/graphql/stripe.ts
import { createStripeGraphQLServer } from '@nhost/stripe-graphql-js'
const server = createStripeGraphQLServer()
export default server
That’s it. The GraphQL API is ready to be added as a Remote Schema.
Remote Schema in Hasura
You can now head to Remote Schemas in the Hasura Console and add the Stripe GraphQL API previously created. This way, the Stripe GraphQL API will be available in your main GraphQL API. This is called schema stitching.
The Stripe GraphQL API will available at:
{{NHOST_BACKEND_URL}}/v1/functions/graphql/stripe
We're using here a System Environment Variable,
NHOST_BACKEND_URL
, to make sure the URL works both locally and in production.
Hasura Data Federation Relationship
Finally, we can now create a relationship from an existing table in the database to Stripe.
Usually, you would have a stripe_customer_id
column in some table and from that column, create a relationship to a customer in Stripe.
You now have the Stripe GraphQL API set up and configured. It’s added as a Remote Schema in Hasura, and there is a relationship between the stripe_customer_id
column and the customer id in Stripe.
Almost like magic, it’s now possible to make GraphQL queries to get data from both the database and Stripe.
GraphQL Objects
The following GraphQL object types are available:
At the moment, not all fields are available on all objects, but most are. We’ll continue the work to make the Stripe GraphQL API as complete as possible.
If you want to help us, the code is open source and available on GitHub - we would be more than happy to accept contributions.
Examples
Here are some examples of GraphQL queries and mutations to show the power of the Stripe GraphQL API.
Example: Get all invoices for a customer:
query {
stripe {
customer(id: "cus_xxx") {
id
invoices {
data {
id
paid
amountPaid
amountDue
hostedInvoiceUrl
}
}
}
}
}
Example: Get all payment methods for a customer:
query {
stripe {
customer(id: "cus_xxx") {
id
paymentMethods {
data {
id
created
card {
brand
expMonth
expYear
last4
}
}
}
}
}
}
Example: Get all subscriptions and the subscription’s items (product name and cost) for a customer:
{
stripe {
customer(id: "cus_xxx") {
id
subscriptions {
data {
id
status
items {
data {
id
quantity
price {
product {
id
name
defaultPrice {
id
currency
unitAmount
}
}
}
}
}
}
}
}
}
}
Example: Get a billing portal session URL:
mutation {
stripe {
createBillingPortalSession(customer: "cus_xxx") {
id
url
}
}
}
Permissions
Permissions are managed in two ways:
x-hasura-admin-secret
header for full access.isAllowed
function for granular permissions.
The isAllowed
function checks if a current operation is allowed, like interacting with a customer in Stripe.
Here’s an example that uses the isAllowed
function and does the following:
- If the request is made with a valid admin secret,
isAdmin
istrue
, we allow all operations. - Get the user’s id.
- Get user information from the database.
- Loop over the user’s workspaces to see if the user belongs to a workspace with the same stripe customer id as the
stripeCustomerId
for the operation.
import { createStripeGraphQLServer } from '@nhost/stripe-graphql-js'
const isAllowed = (stripeCustomerId: string, context: Context) => {
const { isAdmin, userClaims } = context
// allow requests if it has a valid `x-hasura-admin-secret`
if (isAdmin) {
return true
}
// get user id
const userId = userClaims['x-hasura-user-id']
// check if user is signed in
if (!userId) {
return false;
}
// get more user information from the database
const { user } = await gqlSDK.getUser({
id: userId,
});
if (!user) {
return false;
}
// check if the user is part of a workspace with the `stripeCustomerId`
return user.workspaceMembers
.some((workspaceMember) => {
return workspaceMember.workspace.stripeCustomerId === stripeCustomerId;
});
}
const server = createStripeGraphQLServer({
isAllowed
})
export default server
As you see, the isAllowed
function allows you to create a custom function to manage permissions in your Stripe GraphQL API.
How we built the Stripe GraphQL API
This Stripe GraphQL API would not have been possible without some excellent open-source libraries.
The Stripe GraphQL API schema is built using Pothos, a GraphQL Schema builder for TypeScript. Using the type definitions from Stripe it was easy to create the Stripe GraphQL schema.
Btw, Nhost is a proud sponsor of Pothos.
We also used the awesome GraphQL Yoga, a fully-featured GraphQL Server built-in TypeScript for the actual GraphQL Server. The core of GraphQL Yoga implements W3C Fetch API and can run on any JS environment - perfect for Serverless Functions.
GraphQL Yoga is maintained by The Guild, a team that does fantastic work for the GraphQL community.
These two tools were terrific to work with and made it easy to create a custom GraphQL API like this.
Permissions were possible because of:
- Context to read the request headers and write a custom context for each GraphQL requests
- Hasura Remote Schema that forwards all headers from the client. Specifically the
Authorization
header. - System environment variables make the
NHOST_JWT_SECRET
available in all Serverless Functions to verify the access token from theAuthorization
header request header.
This way, it’s possible to identify and verify the user making the request and write custom code to manage permissions.
Alternatives
What would developers have to do to solve this issue if it wasn’t for this new Stripe GraphQL API?
There are two alternatives:
- Webhook (duplicated data)
- Direct Access using REST
Let’s go a bit deeper into both of these alternatives to understand their drawbacks.
Webhook (duplicated data)
It’s possible to set up a webhook and have Stripe send a webhook if anything changes at Stripe. For E.g. if a new customer is created, a subscription is deleted, or a payment method is updated.
Once anything changes in Stripe, you must receive that event via a webhook and store the information in your database. Effectively duplicating the data in Stripe.
Duplicating data is generally a bad idea and leads to synchronization problems.
- What if your webhook is down?
- How do you get the initial set of your Stripe data?
Also, if you sync Stripe’s data to your database now, you’re responsible for data format, backups, scaling, etc. Things that Stripe is probably handling better.
Direct Access using REST
One alternative is to get the data from Stripe using Stripe’s REST API whenever necessary. The problem is that if your app uses GraphQL, you now have two ways of interacting with your data.
With REST, you need a separate client, and it’s impossible to create relationships between your data in GraphQL and Stripe.
Here’s a short example of how you would have to combine GraphQL and Stripe REST:
const GET_CUSTOMER = `
query {
customer {
id
name
stripeCustomerId
}
}`;
const [stripeData, setStripeData] = useState();
const { data, loading } = useQuery(GET_CUSTOMER);
useEffect(() => {
const getStripeData = async () => {
if (!loading && data.customer) {
// Note: Custom endpoint to manage data access and permissions.
// This endpoint is not included in this example.
const invoices = await httpClient.call(`/api/stripe/invoices?stripe-customer-id=data.customer.stripeCustomerId`);
}
}
getStripeData();
);
if (loading || !stripeData) {
return <div>Loading...</div>
}
const { customer } = data;
return (
<div>
<div>{customer.name}'s invoices</div>
<ul>
{stripeData.invoices.map(invoice => {
return (
<li>{invoice.id} - <a href={invoice.hosted_invoice_url}>Link</a></li>
)
)}
</ul>
</div>
);
Compare the example above with the new Stripe GraphQL API with Nhost:
const GET_CUSTOMER = `
query {
customer {
id
name
stripeCustomer {
invoices {
data {
id
created
paid
currency
amountDue
amountPaid
}
}
}
}
}`;
const { data, loading } = useQuery(GET_CUSTOMER);
if (loading) {
return <div>Loading...</div>
}
const { customer } = data;
return (
<div>
<div>{customer.name}'s invoices</div>
<ul>
{customer.stipeCustomer.invoices.data.map(invoice => {
return (
<li>{invoice.id} - <a href={invoice.hosted_invoice_url}>Link</a></li>
)
)}
</ul>
</div>
);
What’s next
The @nhost/stripe-graphql-js
package makes it easier for developers building SaaS applications to integrate Stripe and payments into their applications.
We’ll continue to make the Stripe GraphQL API package as complete as possible. The code is open source and available on GitHub, and it’s easy to contribute. The Stripe API is extensive, so there is a lot of work still left to do.
We’ll also work on making it easier to build custom GraphQL APIs for your application with Serverless Functions and Hasura Remote Schema. Something that we’ve only scratched the surface off.