#jonas_unexpected
1 messages ¡ Page 1 of 1 (latest)
đ Welcome to your new thread!
â˛ď¸ We'll be here soon! Typically we respond in a few minutes, but sometimes we might take a bit longer if the server is busy or if you have a particularly tricky question.
âąď¸ We close idle threads, which makes them read-only. Once a thread is closed it won't be reopened, but you can always start a new thread if you have another question.
đ This thread will always be available, even after it's closed. You can find it again using Discord's search, or you can save this link: https://discord.com/channels/841573134531821608/1348763016744927262
đ Have more to share? Add more details, code, screenshots, videos, etc. below.
Hi there
Can you provide more details? It'll be helpful if you can share a Customer ID and clarify how you integrate with Stripe, e.g. Payment Links, Checkout, PaymentElement, etc.
This is my backend code in NestJS:
async createCheckoutSession(request: Request, planName: Role) {
const priceId = await getPlanAndPrice(this.stripe, planName);
const session = await this.stripe.checkout.sessions
.create({
ui_mode: 'embedded',
payment_method_types: ['card', 'paypal'],
line_items: [
{
price: priceId.price,
quantity: 1,
},
],
mode: 'subscription',
billing_address_collection: 'required',
automatic_tax: { enabled: true },
return_url: `${request.headers.origin}/return?session_id={CHECKOUT_SESSION_ID}`,
})
.catch(() => {
throw new ConflictException('Error creating checkout session');
});
return { sessionId: session.id, client_secret: session.client_secret };
}
async getSessionStatus(sessionId: string, userId: number) {
try {
const session = await this.stripe.checkout.sessions.retrieve(sessionId);
const customer = {
id: session.customer,
customer_details: session.customer_details,
};
const subscription = session.subscription;
const subscriptionInfo = await this.stripe.subscriptions.retrieve(
typeof subscription === 'string' ? subscription : subscription.id,
);
const product = await this.stripe.products.retrieve(
subscriptionInfo.items.data[0].plan.product as string,
);
console.log('[STRIPE subscription]: ', subscription);
console.log('[STRIPE subscriptionInfo]: ', subscriptionInfo);
//Update user role and subscription
if (session.payment_status === 'paid') {
await handleSubscription(
this.prisma,
userId,
customer.id as string,
subscriptionInfo.id,
product.name.toLocaleUpperCase() as Role,
);
}
const { phone, tax_exempt, tax_ids, ...customer_details } =
customer.customer_details;
return {
status: session.payment_status,
customer: customer_details,
product: { name: product.name, price: session.amount_total / 100 },
};
} catch (error) {
if (error.type === 'StripeInvalidRequestError')
throw new NotFoundException('Session not found');
console.log('[STRIPE getSessionStatus]: ', error);
throw new InternalServerErrorException('Error getting session status');
}
}
This is my Frontend code
"use client";
import { FC, useEffect, useState } from "react";
import { loadStripe } from "@stripe/stripe-js";
import {
EmbeddedCheckoutProvider,
EmbeddedCheckout,
} from "@stripe/react-stripe-js";
import { STRIPE_PUBLIC_KEY } from "@/lib/constants";
import { Role } from "@/types/user";
import ApiClient from "@/api";
interface CheckoutProps {
plan: Role | null;
}
const apiClient = new ApiClient();
const Checkout: FC<CheckoutProps> = ({ plan }) => {
const [clientSecret, setClientSecret] = useState<string | null>(null);
const stripePromise = loadStripe(STRIPE_PUBLIC_KEY);
useEffect(() => {
apiClient.stripe.helper.createCheckoutSession(plan).then((response) => {
if (response.status) {
setClientSecret(response.data.client_secret);
}
});
}, [plan]);
return (
<EmbeddedCheckoutProvider stripe={stripePromise} options={{ clientSecret }}>
<div className="text-center">
<h1 className="text-4xl font-bold mb-4 text-blackho">
Subscribe to {plan}
</h1>
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
You are about to subscribe to the {plan} plan. Please enter your
payment details below to proceed.
</p>
<div className="mt-8">
<EmbeddedCheckout />
</div>
</div>
</EmbeddedCheckoutProvider>
);
};
export default Checkout;
helper.ts:
async createCheckoutSession(plan: Role | null) {
return axios
.post(`payment/create-checkout-session?plan=${plan}`)
.then((response) => {
if (response.status !== 201) return { data: null, status: false };
const data = response.data;
return { data: data, status: true };
}
Okay, you're creating Checkout Sessions in subscription mode. These Checkout Sessions will automatically create a Customer object with the details included in the session (e.g. email address)
yeah
Email addresses on Stripe are not unique. This means that it's possible for multiple Customer objects to be created with the same email address. I'm sure if you click into those three Customers in your Dashboard, you'll see they'll all have unique IDs that begin with cus_
If your goal is to create a Subscription for an existing Customer, you should include a Customer ID as the value for customer when creating the Checkout Session: https://docs.stripe.com/api/checkout/sessions/create#create_checkout_session-customer
yes they have others IDs. But how am I supposed to look at the billing history of a user if a new customer is created again and again?
If a Customer object exists for a given customer of yours, you should use their Customer ID when creating the Checkout Session if you want to associate payment from that Sesssion (either a one-off payment or a Subscription) with their existing Customer object on Stripe.
It's not possible to merge existing Customer objects into one so if some of your customers already have multiple Customer objects on Stripe, you'll need to handle any history manually/look at all of the Customer objects together on your end
So I have to program the logic behind it myself first. I can't just set this up in the Stripe dashboard?
What logic exactly?
That I bind several invoices to one customer
Stripe can handle this for you for all new customers going forward. You'd just need to ensure any Checkout Sessions you create for existing customers include their Customer ID.
How do I integrate the customer id into the payment process?
For any existing customers, you're kind of stuck with multiple customer objects if they've already been created. If you want to use one customer object going forward, you'll essentially need to pick one as the "real" one then cancel any Subscriptions on the other customer objects and recreate them on the "real" one. I recommend recreating this scenario and testing this in test mode first to make sure you get it right
Ok then once again to conclude.
What I want: I want each subscription to be linked to the same customer and not create a new customer with the same data for each new subscription.
It is not possible to implement this directly in Stripe. Is that right?
That means I have to program the logic into my program myself?
It is possible but yes, you'll also need some logic on your end.
So is this the rigth way?
async createCheckoutSession(request: Request, planName: Role) {
const priceId = await getPlanAndPrice(this.stripe, planName);
const customerId = 'cus_1234567890';
const session = await this.stripe.checkout.sessions
.create({
ui_mode: 'embedded',
payment_method_types: ['card', 'paypal'],
line_items: [
{
price: priceId.price,
quantity: 1,
},
],
mode: 'subscription',
billing_address_collection: 'required',
automatic_tax: { enabled: true },
customer: customerId,
return_url: `${request.headers.origin}/return?session_id={CHECKOUT_SESSION_ID}`,
})
.catch(() => {
throw new ConflictException('Error creating checkout session');
});
return { sessionId: session.id, client_secret: session.client_secret };
}
Is cus_1234567890 just a placeholder in this case?
yes
Got it, so yes, that's what you want
Lets' say I'm a new customer of yours. If I land on your shop and go to check out, there should ideally be a way for me to register on your site. When I register, you should have some logic to create some user on your end and also create a Stripe Customer object. You should keep track of the mapping between your user ID and the Stripe Customer ID.
If I return to your shop in a month because I want to place a similar order, the Checkout Session you create after I log in should include customer: cus_1234567890 if cus_1234567890 is the ID of the Customer object that was created when I first registered
This is my prisma.schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum Role {
ADMIN
VIP
PREMIUM
USER
}
model User {
id Int @id @default(autoincrement())
username String
email String @unique
password String
role Role @default(USER)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
clientKey ClientKey?
Subscription Subscription?
}
model ClientKey {
id Int @id @default(autoincrement())
key String @unique
createdAt DateTime @default(now())
userId Int @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Subscription {
id String @id @default(cuid())
customerId String
subscriptionId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId Int @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
When the users makes a payment then the Subscription is created
export const handleSubscription = async (
prisma: PrismaService,
userId: number,
customerId: string,
subscriptionId: string,
role: Role,
) => {
const user = await prisma.user.findUnique({ where: { id: userId } });
if (!user) throw new NotFoundException('User not found');
await prisma.subscription.upsert({
where: {
userId: userId,
},
create: {
userId: userId,
customerId: customerId,
subscriptionId: subscriptionId,
},
update: {
customerId: customerId,
subscriptionId: subscriptionId,
},
});
await prisma.user.update({
where: {
id: userId,
},
data: {
role: role,
},
});
};
What's your question?
@lethal dagger let me know if you can clarify any remaining questions you have about this functionality