Next.js Discord

Discord Forum

How to refresh pages where subscription is checked after stripe payment (any event) is successful

Answered
Nebelung posted this in #help-forum
Open in Discord
NebelungOP
Stuck with revalidation... Currently, after submitting payment i redirect user to the protected page but the access is not refreshed until i refresh the page manually. Please, how can I, after redirection or even clicking protected page link from the successful payment page have the refreshed data? Right now I see Preview...

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    // if can't confirm don't allow form submission
    if (!canConfirm) {
      e.preventDefault();
      return;
    }
    e.preventDefault();
    //  confirm() method returns a Promise that resolves to an object with one of the following types
    //  { session: CheckoutSession }
    //  { error: StripeError }
    confirm().then((result) => {
      if (result.session) {
        return router.push('/payment-success');
      } else {
        setMessageBody(result.error.message || 'An error occurred');
      }
    });
  };

The subscription validation on protected page looks like this:
const supabase = createClient();
const { data: subscription, error } = await supabase
    .from('subscriptions')
    .select('*, prices(*, products(*))')
    .in('status', ['trialing', 'active'])
    .maybeSingle();

  if (error) {
    console.log(error);
  } 
  if (subscription) {
    return <ProtectedPage/>
  } else {
    return <Preview/>
  }
Answered by Ray
I think you should subscribe to the database change on the subscriptions table
View full answer

51 Replies

NebelungOP
Basically I can go from page to page clicking <Links/> and none of these will refresh the access to pages. only browser refresh does it...
NebelungOP
I’m thinking I should refresh data with the middleware? Is that the correct approach?
Round sardinella
Have you tried calling router.refresh?
NebelungOP
@Round sardinella hey! if i do that in the url for custom checkout it indeed refreshes the data nicely but i'm stuck on the same page with refreshed payment forms.

So the payment happens at this url: http://localhost:3000/checkout-custom-stripe?clientSecret=cs_test_b1b8BM(...). If I do router.refresh(); on success I'm staying on this page. I would like to redirect the user on success and refresh the page which seems to be the tricky part here.
Round sardinella
Gotcha. If you redirect then just call router.refresh, do you eventually see the correct UI (without having to do a browser reload)?
NebelungOP
like this?
 if (result.session) {
    return router.push('/').then(() => router.refresh());
 }
NebelungOP
I tried this but I get this error: TypeError: Cannot read properties of undefined (reading 'then')
NebelungOP
@Round sardinella do you have time to just let me know how can i solve this? seems like i can't do then.(... on router.
@Nebelung <@165684525882540035> do you have time to just let me know how can i solve this? seems like i can't do then.(... on router.
router.push() doesn't return a promise, I think you can call them like this
 if (result.session) {
    router.push('/')
    router.refresh();
 }
NebelungOP
hey @Ray it didn't throw an error at me this time but it didn't refresh the information i fetch on the server. it still needs that hard reload. Is there a way to maybe call revalidate data i fetch from supabase?

This is my page where i fetch the data:
import Pricing from '@/components/ui/Pricing/Pricing';
import { createClient } from '@/utils/supabase/server';

export default async function PricingPage() {
  const supabase = createClient();

  const {
    data: { user }
  } = await supabase.auth.getUser();

  const { data: subscription, error } = await supabase
    .from('subscriptions')
    .select('*, prices(*, products(*))')
    .in('status', ['trialing', 'active'])
    .maybeSingle();

  if (error) {
    console.log(error);
  }

  const { data: products } = await supabase
    .from('products')
    .select('*, prices(*)')
    .eq('active', true)
    .eq('prices.active', true)
    .order('metadata->index')
    .order('unit_amount', { referencedTable: 'prices' });

  return (
    <Pricing
      user={user}
      products={products ?? []}
      subscription={subscription}
    />
  );
}
NebelungOP
In Pricing yes.
so the flow is /pricing > /payment-success > /pricing?
@Ray so the flow is /pricing > /payment-success > /pricing?
NebelungOP
I simplified this flow a little bit. I handle payment in a modal on pricing page. so on pricing page on clicking on the product i make stripe session and open modal with react.state. in modal i have some stripe elements and on form submission i listen for result.session. here i want to refresh the subscription data.
here is the code for Pricing: https://github.com/szymonhernik/lc-dev/blob/custom_stripe/components/ui/Pricing/Pricing.tsx
sorry for this change! i had to simplify the payment flow because before i was passing clientSecret which wasn't secure and open a way to open multiple subscriptions.
NebelungOP
The best solution i figured out on my own is this. It has a drawback of seeing not updated data in the background of modal and walking around nextjs functionality by calling window.location.reload(); on Close link.
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
...
confirm().then((result) => {
  setIsSubmitting(false);
  if (result.session) {
    router.refresh()
    setIsSuccess(true);
...}

return (
    <>
      {!isSuccess ? (all the payment forms) : (
         <div>
          <p>Your payment was successful.</p>
            <Link href="/account">Go to account</Link>
            <a
              href="/"
              onClick={(e) => {
                e.preventDefault();
                window.location.reload();
              }}
            >
              Close
            </a>
          </div>
        </div>
    )
    </>
I still don't get it
so you make the payment on /pricing and show the result?
NebelungOP
I do, yes
then click close to show the new data right?
NebelungOP
on pricing i click on one of the product, it creates a session so i can pay for it and on success i want to refresh the data.
So currently on success i show this message. but you can see the data in the background didn't refresh, although i called router.refresh() because it should be "manage" if the user has subscription.
I'm afraid this solution where i need to click Close which is a tag with e.preventDefault() window.location.reload(); isn't the best approach and i wonder how to refresh that data in the background on successful payment (if it's possible).
<div>
          <p>Your payment was successful.</p>
            <Link href="/account">Go to account</Link>
            <a
              href="/"
              onClick={(e) => {
                router.refresh()
                router.push("/pricing")
              }}
            >
              Close
            </a>
          </div>
        </div>
NebelungOP
that nicely solved the need to use window.location.reload() It didn't help refreshing the data in the background though.
do you get new data from the subscription?
NebelungOP
it didn't refresh the data...
btw pricing is my on my home page so i'm just doing this router.push(/?ts=${Date.now()});
are you still subscribing to supabase for new data
  const { data: subscription, error } = await supabase
    .from('subscriptions')
    .select('*, prices(*, products(*))')
    .in('status', ['trialing', 'active'])
    .maybeSingle();
NebelungOP
Yes this is app/page.tsx
import Pricing from '@/components/ui/Pricing/Pricing';
import { createClient } from '@/utils/supabase/server';

export default async function PricingPage() {
  const supabase = createClient();
...

  const { data: subscription, error } = await supabase
    .from('subscriptions')
    .select('*, prices(*, products(*))')
    .in('status', ['trialing', 'active'])
    .maybeSingle();
...

  return (
    <Pricing
      user={user}
      products={products ?? []}
      subscription={subscription}
    />
  );
}
NebelungOP
No, it returns null many times if i console log it. only on page refresh i get subscription object.
@Nebelung No, it returns null many times if i console log it. only on page refresh i get subscription object.
the subscription should return the new data when the payment success right?
oh nm, it is a query
NebelungOP
when the payment success i get only data of the payment session
i have api/webhook to listen for 'checkout.session.completed'. Maybe i can listen for changes in subscriptions table and then fetch again if something changes? Just trying to brainstorm something...
case 'checkout.session.completed':
          const checkoutSession = event.data.object as Stripe.Checkout.Session;
          console.log('checkoutSession', checkoutSession);
          if (checkoutSession.mode === 'subscription') {
            const subscriptionId = checkoutSession.subscription;
            await manageSubscriptionStatusChange(
              subscriptionId as string,
              checkoutSession.customer as string,
              true
            );
          }
          break;
Answer
in the Pricing component
NebelungOP
I'll try that! Thanks a lot for debugging with me 🙂
NebelungOP
:))
Added this in Pricing component and it updates the subscriptions in the background. Merci @Ray
useEffect(() => {
    const channel = supabase
      .channel('supabase_realtime')
      .on(
        'postgres_changes',
        {
          event: '*',
          schema: 'public',
          table: 'subscriptions'
        },
        () => {
          router.refresh();

        }
      )
      .subscribe();

    return () => {
      supabase.removeChannel(channel);
    };
  }, [supabase, router]);
NebelungOP
without router.refresh() it doesn't work.
yeah I just realize you are still querying the data on the page
NebelungOP
yes! i removed router.refresh() from a tag and from the check in result.session.
NebelungOP
One more question. Do you know if that's gonna refetch the subscription data for the user also if other users update their subscriptions? Is there a way to listen to changes only by this user?
@Nebelung I’m thinking I should refresh data with the middleware? Is that the correct approach?
Alligator mississippiensis
honestly at this point i gave up on using router.refresh, since it seems to only work with onClick events (as far as i know)

nowadays im using window.location.href = "your-path"
@Alligator mississippiensis honestly at this point i gave up on using router.refresh, since it seems to only work with onClick events (as far as i know) nowadays im using window.location.href = "your-path"
router.refresh should work.
the reason why this case doesn't work is the new data need to wait until the webhook finish