skip to Main Content

I’m using the Stripe extension in Firebase to create subscriptions in a NextJS web app.

My goal is to create a link for a returning user to edit their payments in Stripe without authenticating again (they are already auth in my web app and Firebase recognizes the auth).

I’m using the test mode of Stripe and I have a test customer and test products.

I’ve tried

  • The Firebase Stripe extension library does not have any function which can just return a billing portal link: https://github.com/stripe/stripe-firebase-extensions/blob/next/firestore-stripe-web-sdk/markdown/firestore-stripe-payments.md

  • Use the NextJS recommended import of Stripe foudn in this Vercel blog

    First I setup the import for Stripe-JS: https://github.com/vercel/next.js/blob/758990dc06da4c2913f42fdfdacfe53e29e56593/examples/with-stripe-typescript/utils/get-stripejs.ts

    export default function Settings() {
    
      import stripe from "../../stripe_utils/get_stripejs"
    
      async function editDashboard() {
        const dashboardLink = await stripe.billingPortal.sessions.create({
          customer: "cus_XXX",
        })
      }
    
      console.log(dashboardLink.url)  
    
      return (
        <Button
          onClick={() => editDashboard()}>
          DEBUG: See payments
        </Button>
      )
    }
    

    This would result in an error:

    TypeError: Cannot read properties of undefined (reading 'sessions')
    
  • Use the stripe library. This seemed like the most promising solution but from what I read this is a backend library though I tried to use on the front end. There were no errors with this approach but I figure it hangs on the await

    import Stripe from "stripe"
    const stripe = new Stripe(process.env.STRIPE_SECRET)
    
    ... 
    
    const session = await stripe.billingPortal.sessions.create({
      customer: 'cus_XXX',
      return_url: 'https://example.com/account',
    })
    
    console.log(session.url)  // Does not reach here 
    
  • Use a pre-made Stripe link to redirect but the user will have to authenticate on Stripe using their email (this works but I would rather have a short-lived link from Stripe)

    <Button component={Link} to={"https://billing.stripe.com/p/login/XXX"}>
       Edit payment info on Stripe
    </Button>
    
  • Using POST HTTPS API call found at https://stripe.com/docs/api/authentication. Unlike the previous options, this optional will register a Stripe Dashboard Log event.

    const response = await fetch("https://api.stripe.com/v1/billing_portal/sessions", {
          method: 'POST', // *GET, POST, PUT, DELETE, etc.
          mode: 'cors', // no-cors, *cors, same-origin
          cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
          credentials: 'same-origin', // include, *same-origin, omit
          headers: {
            'Content-Type': 'application/json',
            'Authorization': 'bearer sk_test_XXX',
            'Content-Type': 'application/x-www-form-urlencoded',
          },
          redirect: 'follow', // manual, *follow, error
          referrerPolicy: 'no-referrer', // no-referrer, *client
          body: JSON.stringify(data), // body data type must match "Content-Type" header
        })
    
    

    The error is I’m missing some parameter parameter_missing -customer. So I’m closer to a resolution but I feel as if I should still be able to make the solution above work.

2

Answers


  1. Chosen as BEST ANSWER

    Given my case, I chose to call the API itself instead of the libraries provided:

    export default async function Stripe(payload, stripeEndpoint) {
      const _ENDPOINTS = [
        "/v1/billing_portal/sessions",
        "/v1/customers",
      ]
    let contentTypeHeader = "application/json"
      let body = JSON.stringify(payload)
    
       if _ENDPOINTS.includes(stripeEndpoint)) {
         contentTypeHeader = "application/x-www-form-urlencoded"
         body = Object.keys(payload).map(
           entry => entry + "=" + payload[entry]).join("&")
       }
    
       try {
         // Default options are marked with *
         const stripeResponse = await fetch("https://api.stripe.com" + stripeEndpoint, {
         method: "POST", // *GET, POST, PUT, DELETE, etc.
         headers: {
           "Authorization": "bearer " + STRIPE_PRIVATE_KEY,
           "Content-Type": contentTypeHeader,
         },
         redirect: "follow", // manual, *follow, error
         referrerPolicy: "no-referrer", // no-referrer, *client
         body: body, // body data type must match "Content-Type" header
       })
       return await stripeResponse.json()
    
       } catch (err) {
        console.error(err)
       }
    }
    

  2. You should use Stripe library to create a billing portal session (your 2nd approach), and you might want to check your Dashboard logs and set the endpoint to /v1/billing_portal/sessions so that you can see if there are any errors during portal session creation.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search