Many sellers rely on marketplaces and payment processing platforms, like Stripe, to sell their goods. If you’re creating a marketplace that lets sellers sell products using your platform, you’ll need to use Stripe Connect to Connect people’s Stripe accounts to your Stripe accounts if you want to make 15% of every transaction on your platform.

In this tutorial, you’ll learn how to Connect a user’s Stripe account to your platform using next.js and Stripe Connect. You’ll also learn how Stripe Connect’s network hooks work.

What is Stripe Connect?

The Stripe Connect Payment onboarding process helps developers access their users’ Stripe accounts to create products, edit products, sell products, and more — all while providing an application fee to the platform.

Stripe Connect Account type

There are two main types of Stripe Connect accounts, standard and Fast.

Stripe Express is the premier tier as it gives platforms more control over the flow of money and is more expensive for platforms as their monthly fee is $2 per active connected account. Medium, Quora, and other advanced marketplaces use this Stripe connection implementation.

Stripe Standard connects Stripe accounts to a platform’s Stripe accounts, but gives the platform limited control. It’s free to use, and you don’t have to pay Stripe for the application fees you earn. Substack, for example, uses it.

Another Stripe Connect implementation is called Stripe Custom Connected Accounts, which is used by companies that need to implement complex and customized payment systems. Companies like Shopify and Lyft use it to pay their users and merchants. Stripe custom Connected Accounts provide granular control over the payment onboarding experience. In this article, we will not cover custom accounts.

Learn more about the differences between these accounts here.

In this tutorial, we will focus on connecting accounts using the Stripe standard, but the process is almost the same for Stripe Express accounts.

Create a Stripe standard account using the OAuth process

Here is a quick demonstration of how we built the project using OAuth Flow.

When you enter the demo and clickConnected to the Stripe“, you’ll enter a Stripe login page. After logging in and connecting with a valid account, you’ll see a full analysis of the Stripe account, like this.

Whenever you offer users the option to connect to your Stripe account, if you want to pay them through your platform, or if you want to charge an application fee, you’ll need to consider the following.

  1. Is the payment function enabled on this account?
  2. Can the account charge its customers?
  3. Have you submitted all details about the Stripe account?
  4. If something about the Stripe account doesn’t look right, you’ll need to allow users to unlink their Stripe account so they can try to connect to another account. If everything looks right and the Stripe account is connected, you will need to ensure that users cannot unlink the Stripe account to prevent errors and conflicts. Whether unlinking is allowed depends on various factors. We will discuss them later.

To work with connected accounts using Webhooks (tutorial later in this article), you may want to check the four points mentioned above.

Test Stripe on the development server

First, we need to get your Stripe account up and running. (You can view the full GitHub source code here.)

Get a Stripe distributable key and a secret key from your Stripe dashboard. If you don’t already have a valid Stripe account, you can try it out in beta mode. Make sure you save these keys – you’ll need them in the following steps.

Now, go to the Stripe connection Settings, scroll to the very bottom, and get your OAuth client ID.

Be sure to add [https://localhost:3001](https://localhost:3001) as the redirect URL (you don’t need to make it the default), or if you are using another URL for development and testing. I provide you with the development port 3001 for using the resource library.

Note: Make sure all of these keys are from test mode or live mode; Don’t lump them together!

Now, clone or downloadThis repository,Then copy and rename it.env.sample.env.developmentAnd fill in such a certificate (replacement key).

NEXT_PUBLIC_BASE_URL=http://localhost:3001
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_***
STRIPE_SECRET_KEY=sk_test_***
NEXT_PUBLIC_STRIPE_OAUTH_CLIENT_ID=ca_***

Copy the code

Next, run NPM Install, followed by NPM run dev. And just like that, you now have his job! Be sure to test with your Stripe account.

Create and verify the Stripe account ID

Next, we need to create, connect, and validate the account ID.

When you click the “Connect to Stripe” button, you’ll be redirected to the link.

https://dashboard.stripe.com/oauth/authorize?response_type=code&client_id=<OAUTH_CLIENT_ID>&scope=read_write&redirect_ur i=http://localhost:3001Copy the code

You can access this link yourself by replacing

with your OAuth client ID.

Tip: If you want to specify another redirect URL (from the one you have in the Stripe Connect setup), you can do so as well.

Please note that when a user logs in with their Stripe account on that particular link, it means they give you permission to connect their Stripe to your platform.

You will now need to validate the consent on the server side.

When a user logs in with Stripe via your Stripe OAuth link, they are returned to the specified redirect_URI, some properties in the URL, like this.

https://localhost:3001/?scope=read_write&code=ac_***
Copy the code

So, when a user agrees to connect their Stripe account to your OAuth link, Stripe sends you a redirected URL with code that you now need to use in the background to verify consent and successfully connect to the account.

This is easy to verify. You just need to make this little request in the background – striple.oauth.token. If the request gives you an account ID, the connection is successful.

Now that you have your account ID, you can store it anywhere so that you can access the information later and perform various Stripe operations on the account. Now, let’s lay the groundwork.

Create a stylized button

Next, create a classic Stripe-style “Stripe Connect “button.

We use gravitation-Components. Here’s what the button looks like.

Now redirect people to your Stripe OAuth URL on the button’s onClick event, like this.

<button
  type="button"
  className="stripe-connect"
  onClick={() => {
    if (window) {
      const url = `https://dashboard.stripe.com/oauth/authorize?response_type=code&client_id=${
        process.env.NEXT_PUBLIC_STRIPE_OAUTH_CLIENT_ID
      }&scope=read_write&redirect_uri=${
        process.env.NEXT_PUBLIC_BASE_URL
      }`;
      window.document.location.href = url;
    }
  }}
>
  <span>Connect with Stripe</span>
</button>

Copy the code

To make this work, make sure you double-check your.env.development file to make sure all credentials are correct, and rerun NPM run dev if you run into trouble.

Scope = READ_write Allows you to agree to write and read data from a user’s Stripe account.

Create a back-end API endpoint

Now, let’s make an API on the back end that validates the code Stripe sends in the URL bar after the user returns from your OAuth page. You can refer to the final version of this file in the repository, which is responsible for validating the code it received in the request body.

Note: The main logic also uses some other utility functions, so let you check all of this in your repository.

Let’s start/pages/API/verifyStripe ts of this code.

import { NextApiHandler } from 'next'; import handleErrors from '@/api/middlewares/handleErrors'; import createError from '@/api/utils/createError'; const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); const handler: NextApiHandler = async (req, res) => { const body = req.body; switch (req.method) { case 'POST': const result = await stripe.oauth .token({ grant_type: 'authorization_code', code: body? .code, }) .catch((err: unknown) => { throw createError(400, `${(err as any)? .message}`); }); res .status(200) .json({ oauth: result }); break; default: throw createError(405, 'Method Not Allowed'); }}; export const config = { api: { bodyParser: { sizeLimit: '1mb', }, }, }; export default handleErrors(handler);Copy the code

In this case, if the correct code is provided in the request…

const result = await stripe.oauth .token({ grant_type: 'authorization_code', code: body? .code, }) .catch((err: unknown) => { throw createError(400, `${(err as any)? .message}`); });Copy the code

. You’ll get this result from Stripe.

{
  "token_type": "bearer",
  "stripe_publishable_key": "{PUBLISHABLE_KEY}",
  "scope": "read_write",
  "livemode": false,
  "stripe_user_id": "{ACCOUNT_ID}",
  "refresh_token": "{REFRESH_TOKEN}",
  "access_token": "{ACCESS_TOKEN}"
}
Copy the code

If incorrect or out of date code is provided to the API, it will also report an error.

Keep in mind that code expires in seconds, so when the client receives this code, validate it immediately upon loading.

Now that you have a basic API endpoint, you’re ready to be pinged by the Next. Js client!

Use React and Next-js to authenticate a connected account on the server side

Let’s create a server-side request from the Next-.js home page if it receives the code parameter in the URL.

It should look like this /pages/index.ts.

import React from 'react'; import fetch from 'isomorphic-unfetch'; import HomeView from '@/views/Home'; const Home = (props) => { return <HomeView data={props} />; }; export const getServerSideProps = async ({ req, }) => { const body = req? .__NEXT_INIT_QUERY; if (! body? .code) { return { props: { data: null, req: body } }; } let response; try { response = await fetch( process.env.NEXT_PUBLIC_BASE_URL + '/api/verifyStripe', { method: 'POST', body: JSON.stringify(body), headers: { 'Content-Type': 'application/json', }, }, ).then((res) => res.json()); } catch (error) { return { props: { data: { error }, req: body } }; } return { props: { data: response, req: body } }; }; export default Home;Copy the code

When you export a function getServerSideProps from the Next. Js page file, Next. Js runs the function on the server. You can return the received data in the getServerSideProps function, so the React component can receive the received data as props.

You can learn more about how Next. Js SSR works here.

Note: You can also make a request in useEffect() and then update the state of the client after retrieving the data.

What if your URL has? Code =ac_***, you want to get that parameter from the URL. Next. Js provides you with req? The main argument in the.__next_init_query object.

{
  code: "ac_***",
  scope: "read_write"
}

Copy the code

With isomorphic-unfetch, we extract data from our API by providing it with a code in the request.

await fetch(
  process.env.NEXT_PUBLIC_BASE_URL + '/api/verifyStripe', // our API endpoint
  {
    method: 'POST',
    body: JSON.stringify(body), // '{"code": "ac_***", "scope": "read_write"}'
    headers: {
      'Content-Type': 'application/json',
    },
  },
).then((res) => res.json());
Copy the code

If you check/pages/API/verifyStripe ts, you will see the API returns {request: result}.

The result contains the Stripe account ID, which we can now use in the React component props by returning this response in getServerSideProps, at /pages/index.ts.

return { props: { data: response, req: body } };

Copy the code

In the line above, we dump all the data we got from the API to data. The REQ item provides URL parameter data, which you can now access in the React component.

Now, let’s use the data retrieved from the Data Prop passed through the React component.

const Home = (props) => { return <HomeView data={props} />; }; const HomeView = ({ data }) => { return ( <> <button type="button" className="stripe-connect" onClick={() => { if (window) { const url = `https://dashboard.stripe.com/oauth/authorize?response_type=code&client_id=${ process.env.NEXT_PUBLIC_STRIPE_OAUTH_CLIENT_ID }&scope=read_write&redirect_uri=${ process.env.NEXT_PUBLIC_BASE_URL }`; window.document.location.href = url; } }} > <span>Connect with Stripe</span> </button> {data? .req? .code? .startsWith('ac_') && ( <> <div className="fetchedData"> <h3>Fetched data</h3> <pre>{JSON.stringify(data, null, 2)}</pre> </div> </> )} </> ); };Copy the code

Note that if the URL bar has a code parameter string starting with ac_, the front end will display the data fetched from the back end in the

 element.

Now, try clicking the Stripe button and registering. When you are redirected from the Stripe OAuth page to the home page, you should see a printed success message, something like this.

{ "data": { "oauth": { "access_token": "sk*****4m", "livemode": false, "refresh_token": "r*****Oib6W", "token_type": "bearer", "stripe_publishable_key": "pk_test_51***tfPe", "stripe_user_id": "acct_1******YHsmb", "scope": "Read_write}}", "the req" : {" scope ":" read_write ", "state" : "13.036056350529645", "code" : "ac_JP8TFZTmFg1GUnPnJmTII2PTOJYaeBCD" } }Copy the code

Cool! You have now retrieved the front-end data from the/API /verifyStripe endpoint.

Obtain Stripe account information

You may have noticed something missing. You have the account ID, but you don’t have the account information.

Let us also from/pages/API/verifyStripe ts.

import { NextApiHandler } from 'next'; import handleErrors from '@/api/middlewares/handleErrors'; import createError from '@/api/utils/createError'; const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); const handler: NextApiHandler = async (req, res) => { const body = req.body; switch (req.method) { case 'POST': const result = await stripe.oauth .token({ grant_type: 'authorization_code', code: body? .code, }) .catch((err) => { throw createError(400, `${err? .message}`); }); // We get the Account ID from `result.stripe_user_id`, // let's fetch more account details using the ID. const account = await stripe.accounts ? .retrieve(result? .stripe_user_id) ? .catch((err) => { throw createError(400, `${err? .message}`); }); // Here we get the important details of the account. const accountAnalysis = { hasConnectedAccount: !! account? .id, // Check if account ID received is actually connected or exists. accountId: account? .id, hasCompletedProcess: account? .details_submitted, isValid: account?.charges_enabled && account?.payouts_enabled, displayName: account?.settings?.dashboard?.display_name || account?.display_name || null, country: account?.country, currency: account?.default_currency, }; // boolean - Once the account is connected, should we let it unlink? const shouldAllowUnlink = accountAnalysis? .hasConnectedAccount && (! accountAnalysis? .isValid || ! accountAnalysis? .hasCompletedProcess || ! accountAnalysis? .displayName); res .status(200) .json({ account, oauth: result, accountAnalysis, shouldAllowUnlink }); break; default: throw createError(405, 'Method Not Allowed'); }}; export const config = { api: { bodyParser: { sizeLimit: '1mb', }, }, }; export default handleErrors(handler);Copy the code

Now, refresh the home page and log into Stripe again, and you’ll see that you now have more important information than ever before.

Let’s use the captured data in the foreground to update your Stripe connect button with dynamic text.

<button type="button" className="stripe-connect" disabled={! data? .data? .shouldAllowUnlink} onClick={() => { if (window) { const url = `https://dashboard.stripe.com/oauth/authorize?response_type=code&client_id=${ process.env.NEXT_PUBLIC_STRIPE_OAUTH_CLIENT_ID }&scope=read_write&state=${Math.random() * 100}&redirect_uri=${ process.env.NEXT_PUBLIC_BASE_URL }`; window.document.location.href = url; } }} > {data? .data? .account? .id ? ( <span>Connected: {data? .data? .account? .display_name}</span> ) : ( <span>Connect with Stripe</span> )} </button>Copy the code

This will check if you have an account ID in the Data item. If you have, it will Display: Connected: Display Name, otherwise, Connect with Stripe.

Here, we will also disable ={! data? .data? .shouldAllowUnlink} item added to button. You’ll learn what shouldAllowUnlink is in the next section of this article.

We can also display account information like this.

Const YES = < > ✅ & have spent &nbsp; Yes.</>; Const NO = < > ❌ & have spent &nbsp; No.</>; const HomeView: React.FC<{ data? ; }> = ({ data }) => { return ( <> <h1>Stripe Connect Demo</h1> ... {data? .data? .account? .id && ( <> <div className="accountAnalysis"> <div> <h3>Payouts Enabled? </h3> <h2>{data? .data? .account? .payouts_enabled ? YES : NO}</h2> </div> <div> <h3>Charges Enabled? </h3> <h2>{data? .data? .account? .charges_enabled ? YES : NO}</h2> </div> <div> <h3>Details Submitted? </h3> <h2>{data? .data? .account? .details_submitted ? YES : NO}</h2> </div> </div> <div className="allowUnlink"> <h3>Allow Unlink?</h3> <p> When users connect their Stripe account,  and if it is incomplete or invalid, you might want to let them unlink. </p> <h2>{data?.data?.shouldAllowUnlink ? YES : NO}</h2> </div> </> )} .... < / a >); }Copy the code

This is the result.

Just like that, you have successfully implemented basic Stripe OAuth!

Identify and optimize Stripe account effectiveness factors

These factors are important to ensure a better KYC process.

1. Should we allow unlinking from Stripe accounts?

Let’s see what it means to unlink a Stripe account.

When a user connects to their Stripe account, you get an account ID that you can store in the database for future access.

Now, imagine that you have a large number of products, customers, and subscriptions through connected accounts.

If your account is running smoothly, you don’t want your users to be able to disconnect their Stripe connection from your platform user interface through your account. You also don’t want them to change their Stripe account, or there will be problems.

However, you do want them to be able to disconnect and reconnect the account if the account is invalid, if payments are disabled, if charges are disabled, or if the account is from a country or currency that you do not support.

Please remember that. Regardless, users can always disconnect your platform from their Stripe dashboard. But you still have to decide if you want to offer this option to users on your platform.

In our demo example, we check if an account should be able to disconnect itself, like this (checkverifyStripe.ts)

const accountAnalysis = { hasConnectedAccount: !! account? .id, accountId: account? .id, hasCompletedProcess: account? .details_submitted, isValid: account?.charges_enabled && account?.payouts_enabled, displayName: account?.settings?.dashboard?.display_name || account?.display_name || null, country: account?.country, currency: account?.default_currency, }; // @type boolean const shouldAllowUnlink = accountAnalysis? .hasConnectedAccount && (! accountAnalysis? .isValid || ! accountAnalysis? .hasCompletedProcess || ! accountAnalysis? .displayName);Copy the code

In our example, if the account is connected and any of the following is false or NULL, we should allow unlinking the account.

  • account.charges_enabled
  • account.payouts_enabled
  • account.details_submitted
  • Account Display name

Note: Whether you allow unlinking depends on the needs of your platform. Make sure you adjust your logic accordingly.

2. Check whether the payment function is enabled.

Can your Stripe Connected account users withdraw funds from their Stripe balance to their bank account?

If you get account? .payouts_enabled is false, which means that the account has no valid bank account connected to the Stripe account.

Sometimes, this should not affect your platform, because you can offer your services even if your users’ connected accounts are not enabled for payment.

It depends on your usage. But ideally, you should ask users to enable it.

3. Is charging enabled?

Can your connected account users create charges?

If you get account? If charges_enabled is false, the account cannot be charged.

You will not be able to create charges on a user’s account if they are not enabled. If your platform relies on creating fees through its users’ Stripe accounts, you need to enable user fees.

Things like subscriptions, scheduling, and checkout can all rely on billing apis. Therefore, it is absolutely necessary in almost all use cases.

Have all the details been submitted?

This should be obvious.

When you create a Stripe account, it asks you for a lot of information: name, email, address, tax information, EIN code, bank account information, and more.

In order for a Stripe account to be fully functional and have all the necessary features, you want your user’s Stripe account to be fully verified by Stripe and properly submitted with all of its details.

If you get account? .detailS_submitted Is false, which means the account verification process is not yet complete or some details are not yet known to Stripe.

Always avoid trading with such accounts.

4. Check whether there are other requirements

When you verify a Stripe Connected account, immediately check the Account? . Requirements [currently_due]. If null, it means that the connected account is in good health.

If there are due requirements, critical account operations can be blocked later if the user does not comply with these requirements in a timely manner.

So when you find account? .requirements[currentLY_DUE], be sure to notify the user so they can take action.

Tip: Keep checking your connected accounts every four weeks and continuously optimize and collect data on the validity of your connected Stripe accounts.

Perform operations on accounts connected to the Stripe

You can easily perform operations using node.js and stripe Node packages.

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); await payment.products .create( { name: "New product", description: "Product created on a Connected Account", metadata: { userId: "<WHATEVER_CUSTOM_PLATFORM_USER_ID_YOU_HAVE>" } }, // Just add the following object as a second parameter on any Stripe operation. { stripeAccount: account? .id } ) .catch(async e => { throw new Error(e.message) })Copy the code

In the example above, you can create a Stripe product on the connected account. To do this, you simply provide an object with a stripeAccount ID in the optional second parameter of the Stripe Node API. Please note that if you do not provide the Stripe account ID, it will create the product on your Stripe account instead of the required connection account.

Remember: If a connected account is disconnected from your platform through the Stripe dashboard, or if the account is not valid enough, some operations may fail. Therefore, we recommend that you only operate on fully valid connected accounts.

You can do most other things with Stripe accounts that are connected using a similar approach.

Use Stripe Connect Webhooks

What is Webhook? It is an endpoint designed to receive events from (or through) other services when an event occurs.

What do Stripe Webhooks do?

Whenever an event occurs on a Stripe account, such as creating an invoice, or creating or updating a subscription, charge, or payment method, Stripe sends the appropriate information to your API endpoint so that you can complete the event from your side.

Here is a use case.

Your users pay for your software license on your Stripe checkout page. After submitting from the Stripe checkout page and returning to the home page, the user is greeted with a “successful payment” message.

But how do you know if the user is paying you? By using Stripe Webhooks. When a user makes a payment to you through your checkout page, Stripe will ping your Webhook API endpoint to tell you that the user has paid you for a product at a certain point in time. Now you can E-mail them the license and fulfill the order.

What is Stripe Connect Webhooks?

Basically, they are Webhooks for Stripe that signal your specified terminal whenever a selected event occurs on a connected account.

If you have a subscription or any other event running on a connected account, you will use Stripe Connect Webhooks.

Create a simple Stripe Connect Webhook

First, install the Stripe CLI. Then the terminal operation stripe listen – forward – connect to localhost: 3001 / API/connectWebhook.

If this is your first run, it will ask you to log in. Log in and run the command again.

Running this command gives you an endpoint secret starting with whsec_. It is now listening for all events that may occur in all of your Stripe connected accounts.

When it gets an event, it will ping you webhook in localhost: 3001 / API/connectWebhook.

In the.env.development and.env.production files, save the STRIPE_SECRET_KEY you received after running the CLI command and restart the server.

To create the webhook, please/pages/API/connectWebhook ts to create a file.

import { NextApiHandler } from 'next'; import handleErrors from '@/api/middlewares/handleErrors'; import createError from '@/api/utils/createError'; const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); const handler: NextApiHandler = async (req, res) => { const body = req.body; switch (req.method) { case 'POST': // Refers to STRIPE_ENDPOINT_SECRET variable in .env.development file const endpointSecret: string = process.env.STRIPE_ENDPOINT_SECRET // This is to verify if the request is coming from Stripe indeed. const sig = req.headers["stripe-signature"] let event try { event = stripe.webhooks.constructEvent(req?.body, sig, endpointSecret) } catch (err) { console.log(err.message) return res.status(401).send(`Webhook Error: ${err.message}`) } // Handle the checkout.session.completed event if (event.type === "checkout.session.completed") { const session = event?.data?.object // Fulfill the purchase if payment_status is "paid". if (session.payment_status === "paid") { try { // Do whatever here to fulful the purchase. // await fulfilThePurchase() // Or just observe the Session object console.log(session) } catch (err) { return res.status(400).send({ received: true, error: `${err}`, event }) } } } // Return a response to acknowledge receipt of the event res .status(200) .json({ received: true, event }); break; default: throw createError(405, 'Method Not Allowed'); }}; export const config = { api: { bodyParser: { sizeLimit: '1mb', }, }, }; export default handleErrors(handler);Copy the code

In the example above, you listening is checkout.session.com pleted events. You can choose to listen for more events occurring in your Stripe account or Stripe connected account. You can see a list of all the events here.

You can trigger these Webhooks from your dashboard by creating/editing products or updating subscriptions, depending on the events you are listening for.

To Test in development mode, use Stripe Test Triggers.

$ stripe trigger checkout.session.completed

Copy the code

You have now seen how Stripe Connect Webhooks work and how to test them in development mode.

To use this Webhook in Stripe production, go to the “Developer “>”Webhooks” section of the Stripe dashboard.

Next to the endpoint that receives events from the Connect application, click Add Endpoint.

Fill in the events you want to listen for and use the endpoint URL that you host online.

For deployment, I recommend Vercel because it’s free and easy to use – you can deploy your Next.js project simply by running the Vercel command from your terminal.

When you deploy, make sure that. Env.production has the correct variables for your Stripe account. You should also add them from your Vercel project dashboard.

Thank you for reading!

The postGetting started with Stripe Connect using Next.jsappeared first onLogRocket Blog.