#DeMe

1 messages ยท Page 1 of 1 (latest)

open wadiBOT
sick granite
#

How do you integrate with Stripe currently? Are you using Checkout Session or Payment Element?

candid wigeon
#

Next JS with tRPC using CheckoutSession

sick granite
candid wigeon
#

Thanks! I appreciate it

sick granite
#

No problem! Happy to help ๐Ÿ˜„

candid wigeon
#

One more question, using createCheckout that takes in a priceID, how can I get the metadata from the product to show on the confirmation page.

More specifically how can I tell stripe what product to retrieve the metadata for I am taken to the success url page?

sick granite
#

Which confirmation page are you referring to? Are you referring to the Stripe one, or your own page that is set in success_url?

#

Can you share how your expected flow should look like?

candid wigeon
#

For sure, here's my stripe related router that runs the createCheckoutSession:

 createCheckoutSession: publicProcedure
    .input(
      z.object({
        priceId: z.string(),
        // productName: z.string(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { stripe, userId, prisma, req } = ctx;

      if (!userId) {
        throw new Error("No user id");
      }

      const customerId = await getOrCreateStripeCustomerIdForUser({
        prisma,
        stripe,
        userId,
      });

      console.log(customerId, "customerId");
      if (!customerId) {
        throw new Error("Could not create customer");
      }

      // the base url is different depending on if we are in development or production
      const baseUrl =
        env.NODE_ENV === "development"
          ? `http://${req.headers.host ?? "localhost:3000"}`
          : `https://${req.headers.host ?? env.NEXTAUTH_URL}`;

      // Stripe Checkout Session
      const checkoutSession = await stripe.checkout.sessions.create({
        customer: customerId as string,
        client_reference_id: userId!,
        payment_method_types: ["card"],
        mode: "payment",
        // if using subscriptions
        // mode: "subscription",
        line_items: [
          {
            // price: env.STRIPE_PRICE_ID,
            price: input.priceId,
            quantity: 1,
          },
        ],
        // lead the user to the stripe confirmed page if they successfully purchase
        // success_url: `${baseUrl}/store/orderSuccessful/${input.productName}`,
        success_url: `${baseUrl}/store/orderSuccessful`,
        // redirect the user back to the item page if they cancel the purchase
        cancel_url: `${baseUrl}/store`,
        // if using subscriptions
        // subscription_data: {
        //   metadata: {
        //     userId: userId,
        //   },
        // },
      });

      if (!checkoutSession) {
        throw new Error("Could not create checkout session");
      }

      return { checkoutUrl: checkoutSession.url };
    }),
#

My button that runs the trpc call:

 <button
        className="btn-primary btn bg-primary"
        onClick={async () => {
          if (!user.isSignedIn) {
            handleBuyNow();
          } else if (user.isSignedIn) {
            // create a checkout session
            const { checkoutUrl } = await createCheckoutSession({
              priceId: product.default_price,
            });
            if (checkoutUrl) {
              void push(checkoutUrl);
            }
          }
        }}
      >
        Leave a Tip
      </button>
#

And then the orderSuccessful page which I get the url from in the trpc call "success_url":

import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheckCircle } from "@fortawesome/free-solid-svg-icons";
import { useRouter } from "next/router";
import { GetStaticProps } from "next";
import { generateSSGHelper } from "~/server/helpers/ssgHelper";
import { api } from "~/utils/api";
import { usePathname } from "next/navigation";

const OrderSuccessful = () => {
  const pathname = usePathname();
  // I want everything after the /`${baseUrl}/store/orderSuccessful
  const path = pathname.split("/").pop();
  console.log(path, "path");

  const { data: downloadUrls } = api.store.getS3DownloadUrl.useQuery();
  console.log(downloadUrls, "s3BucketDownloadLink");
  // const postPurchaseDownloadLink = downloadUrls;

  const router = useRouter();



  return (
    <div className="flex min-h-screen flex-col justify-center bg-gray-100 py-6 sm:py-12">
      <div className="relative mx-auto py-3 text-center sm:max-w-xl">
        <span className="text-6xl text-green-500">
          <FontAwesomeIcon icon={faCheckCircle} />
        </span>
        <h1 className="text-2xl font-semibold text-gray-900">
          Purchase Successful!
        </h1>
        <p className="text-gray-500">
          Thank you for your order. We hope you enjoy your purchase!
        </p>
        <p>Here's your download link: {url}</p>
        <div className="mt-6">
          <button
            onClick={() => {
              router.push("/");
            }}
            className="btn-primary btn mx-2"
          >
            Back to Home
          </button>
          <button
            onClick={() => {
              router.push("/store");
            }}
            className="btn-secondary btn mx-2"
          >
            Continue Shopping
          </button>
        </div>
      </div>

      <div className="mt-12">
        <h2 className="mb-4 text-center text-xl font-semibold text-gray-900">
          Other users also bought:
        </h2>
        <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
          {mappedProducts.map((product) => (
            <div key={product.id} className="rounded-lg bg-white p-4 shadow-md">
              <img
                className="mb-4 h-48 w-full rounded object-cover"
                src={product.images}
                alt={product.title}
              />
              <h3 className="text-lg font-semibold text-gray-900">
                {product.name}
              </h3>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

export default OrderSuccessful;

export const getStaticProps: GetStaticProps = async ({ req, res }: any) => {
  // use SSG to get the data from the server
  const ssg = generateSSGHelper(req, res);

  // get recent store activities for the carousel's top news
  await ssg.homePage.getRecentStoreActivities.prefetch();

  // return the props
  return {
    props: {
      trpcState: ssg.dehydrate(),
    },
  };
};
#

So button -> tRPC that runs createCheckoutSession that ends up return a success_url -> orderSuccessful Page

sick granite
#

Yup, so how does this s3 link come into place? Where do you expect from it?

candid wigeon
#

If I end up using metadata for the stripe product I'd place it in there, and then I would need to retrieve it to be able to use it in the orderSuccess Page

sick granite
#

Thanks for sharing! Is there only one s3 link per Checkout Session or it's possible to have multiple s3 link in a single Checkout Session?

candid wigeon
#

Just one s3 link for now.

Later on I plan to let users purchase multiple items at a time so there could be multiple s3 links, or a list. But for now, just one link is fine

sick granite
#

There are 2 ways I can think of:

  1. Store the s3 links and the corresponding Checkout Session ID in your database. When the customer is returned to your success_url, your system will retrieve the s3 link with the given Checkout Session ID
  2. Set s3 link(s) in the metadata in the Checkout Session creation request (not product or price). When the customer is returned to success_url, your system will make a Checkout Session retrieval request to get the metadata stored on the Checkout Session.