Back to Blog
Product | Stripe | GraphQL | Open Source

Open Source Stripe GraphQL API

28 September 2022
Transparent lines
Banner of Open Source Stripe GraphQL API

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.

Stripe GrahQL APIStripe GrahQL API

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!

Workspace ExampleWorkspace Example

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.

Image from https://hasura.io/blog/announcing-hasura-graphql-v1-3-0/Image from https://hasura.io/blog/announcing-hasura-graphql-v1-3-0/

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


_10
import { createStripeGraphQLServer } from '@nhost/stripe-graphql-js'
_10
_10
const server = createStripeGraphQLServer()
_10
_10
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:


_10
{{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:


_16
query {
_16
stripe {
_16
customer(id: "cus_xxx") {
_16
id
_16
invoices {
_16
data {
_16
id
_16
paid
_16
amountPaid
_16
amountDue
_16
hostedInvoiceUrl
_16
}
_16
}
_16
}
_16
}
_16
}

Example: Get all payment methods for a customer:


_19
query {
_19
stripe {
_19
customer(id: "cus_xxx") {
_19
id
_19
paymentMethods {
_19
data {
_19
id
_19
created
_19
card {
_19
brand
_19
expMonth
_19
expYear
_19
last4
_19
}
_19
}
_19
}
_19
}
_19
}
_19
}

Example: Get all subscriptions and the subscription's items (product name and cost) for a customer:


_30
{
_30
stripe {
_30
customer(id: "cus_xxx") {
_30
id
_30
subscriptions {
_30
data {
_30
id
_30
status
_30
items {
_30
data {
_30
id
_30
quantity
_30
price {
_30
product {
_30
id
_30
name
_30
defaultPrice {
_30
id
_30
currency
_30
unitAmount
_30
}
_30
}
_30
}
_30
}
_30
}
_30
}
_30
}
_30
}
_30
}
_30
}

Example: Get a billing portal session URL:


_10
mutation {
_10
stripe {
_10
createBillingPortalSession(customer: "cus_xxx") {
_10
id
_10
url
_10
}
_10
}
_10
}

Permissions

Permissions are managed in two ways:

  1. x-hasura-admin-secret header for full access.
  2. 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:

  1. If the request is made with a valid admin secret, isAdmin is true, we allow all operations.
  2. Get the user's id.
  3. Get user information from the database.
  4. 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.

_39
import { createStripeGraphQLServer } from '@nhost/stripe-graphql-js'
_39
_39
const isAllowed = (stripeCustomerId: string, context: Context) => {
_39
const { isAdmin, userClaims } = context
_39
_39
// allow requests if it has a valid `x-hasura-admin-secret`
_39
if (isAdmin) {
_39
return true
_39
}
_39
_39
// get user id
_39
const userId = userClaims['x-hasura-user-id']
_39
_39
// check if user is signed in
_39
if (!userId) {
_39
return false;
_39
}
_39
_39
// get more user information from the database
_39
const { user } = await gqlSDK.getUser({
_39
id: userId,
_39
});
_39
_39
if (!user) {
_39
return false;
_39
}
_39
_39
// check if the user is part of a workspace with the `stripeCustomerId`
_39
return user.workspaceMembers
_39
.some((workspaceMember) => {
_39
return workspaceMember.workspace.stripeCustomerId === stripeCustomerId;
_39
});
_39
}
_39
_39
const server = createStripeGraphQLServer({
_39
isAllowed
_39
})
_39
_39
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 the Authorization 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:

  1. Webhook (duplicated data)
  2. 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:


_42
const GET_CUSTOMER = `
_42
query {
_42
customer {
_42
id
_42
name
_42
stripeCustomerId
_42
}
_42
}`;
_42
_42
const [stripeData, setStripeData] = useState();
_42
const { data, loading } = useQuery(GET_CUSTOMER);
_42
_42
useEffect(() => {
_42
const getStripeData = async () => {
_42
if (!loading && data.customer) {
_42
// Note: Custom endpoint to manage data access and permissions.
_42
// This endpoint is not included in this example.
_42
const invoices = await httpClient.call(`/api/stripe/invoices?stripe-customer-id=data.customer.stripeCustomerId`);
_42
}
_42
}
_42
_42
getStripeData();
_42
);
_42
_42
if (loading || !stripeData) {
_42
return <div>Loading...</div>
_42
}
_42
_42
const { customer } = data;
_42
_42
return (
_42
<div>
_42
<div>{customer.name}'s invoices</div>
_42
<ul>
_42
{stripeData.invoices.map(invoice => {
_42
return (
_42
<li>{invoice.id} - <a href={invoice.hosted_invoice_url}>Link</a></li>
_42
)
_42
)}
_42
</ul>
_42
</div>
_42
);

Compare the example above with the new Stripe GraphQL API with Nhost:


_40
const GET_CUSTOMER = `
_40
query {
_40
customer {
_40
id
_40
name
_40
stripeCustomer {
_40
invoices {
_40
data {
_40
id
_40
created
_40
paid
_40
currency
_40
amountDue
_40
amountPaid
_40
}
_40
}
_40
}
_40
}
_40
}`;
_40
_40
const { data, loading } = useQuery(GET_CUSTOMER);
_40
_40
if (loading) {
_40
return <div>Loading...</div>
_40
}
_40
_40
const { customer } = data;
_40
_40
return (
_40
<div>
_40
<div>{customer.name}'s invoices</div>
_40
<ul>
_40
{customer.stipeCustomer.invoices.data.map(invoice => {
_40
return (
_40
<li>{invoice.id} - <a href={invoice.hosted_invoice_url}>Link</a></li>
_40
)
_40
)}
_40
</ul>
_40
</div>
_40
);

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.

PS. Support our Open Source work

Star us on GitHub ⭐

Share this post

Twitter LogoLinkedIn LogoFacebook Logo