tunji
tunji

Reputation: 11

in my stripe app, i want to link one email to one customer,so any recurring payment does not keep dusplicating customers, but stripe keeps duplicating

so my product is a subscription service, which grants users access to a service on my website.I've created the product on stripe, and i added the payment link to my website:

<a
   className="bg-red-600 text-white py-2 px-4 rounded inline-block"
  target="_blank"
  href={`https://buy.stripe.com/test_bIY15h1Jp5eg6409AA?prefilled_email=${session.user.email}`}
 > Pay Now

i've written my webhook.ts code in my api, that upon successful payment grant the user access, and upon subscription not being paid, revoke access. It works to an extent; during testing after i cancel a subscription and i try subscribing again with that same email(i add the customerId to the user database), stripe creates another customer instead of using that same customer and its causing issues

my webhookk.ts:

import { NextApiResponse, NextApiRequest } from "next";
import Stripe from "stripe";
import mongooseConnect from "@/lib/mongooseConnect";
import User from "@/models/User";
import Order from "@/models/Order";
import { buffer } from "micro";

const stripe = new Stripe(process.env.STRIPE_SK as string);

const webhookSecret = process.env.STRIPE_ENDPOINT_SECRET as string;

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  
  await mongooseConnect();

  const sig = req.headers['stripe-signature'];

  let event: Stripe.Event;

  try {
    event = stripe.webhooks.constructEvent(await buffer(req), sig!, webhookSecret);
  } catch (err: any) {
    console.error(`Webhook signature verification failed. ${err.message}`);
    return res.status(400).json({ error: err.message });
  }

  const eventType = event.type;

  try {
    switch (eventType) {
     
      case 'checkout.session.completed': {
        const session = event.data.object as Stripe.Checkout.Session;
        const email = session.customer_details?.email;
      
        if (email) {
          let user = await User.findOne({ email });
      
          let subscription;
          if (typeof session.subscription === 'string') {
            subscription = await stripe.subscriptions.retrieve(session.subscription);
          } else {
            subscription = session.subscription as Stripe.Subscription;
          }
      
          const priceId = subscription?.items.data[0]?.price?.id || null;
      
          if (!user) {
            // Create a new user if none exists
            user = await User.create({
              email,
              name: session.customer_details?.name || 'Unknown',
              stripeCustomerId: session.customer as string,
              priceId,
              hasAccess: true,
            });
          } else {
            // Update existing user with Stripe Customer ID if it doesn't exist
            if (!user.stripeCustomerId) {
              user.stripeCustomerId = session.customer as string;
            }
            // Ensure user has access
            user.hasAccess = true;
            user.priceId = priceId;
            await user.save();
          }
      
          // Create an order record
          await Order.create({
            user: user._id,
            name: user.name,
            email: user.email,
            stripeCustomerId: user.stripeCustomerId,
            paid: true,
          });
        }
        break;
      }
      

      case 'customer.subscription.deleted': {
        const subscription = event.data.object as Stripe.Subscription;

        const user = await User.findOne({
          stripeCustomerId: subscription.customer as string,
        });

        if (user) {
          // Revoke access
          user.hasAccess = false;
          await user.save();
        }

        break;
      }

      default:
        console.log(`Unhandled event type ${event.type}`);
    }
  } catch (e: any) {
    console.error(`Stripe error: ${e.message} | EVENT TYPE: ${eventType}`);
  }

  return res.status(200).json({ received: true });
}

export const config = {
  api: {
    bodyParser: false,
  },
};

my model/user.ts:

import mongoose, { Schema, model, models } from "mongoose";

const userSchema = new Schema({
  name: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: false, default: null },
  image: { type: String, required: false },
  stripeCustomerId: { type: String, required: false, default: null },
  hasAccess: {type: Boolean, required: true, default: false},
  priceId: { type: String, required: false, default: null },
  emailVerified: { type: Boolean, default: false },
}, {timestamps: true});

const User = models.User || model("User", userSchema);

export default User;

it works,but after a subscription is canceled and you try paying again, it creates a new customer in stripe dashboard, and it mess up the code, any solution to customer duplication?

Upvotes: -1

Views: 30

Answers (1)

qichuan
qichuan

Reputation: 2031

You should create (or retrieve if there's an existing one) a customer first, and pass the customer to the checkout session creation API, so that Stripe will associate the newly created subscription with this customer instead of creating a new customer. Refer to the API reference for more details

Upvotes: 0

Related Questions