mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-06 12:27:44 +00:00
fix(stripe): getCustomerCreateParams not actually being called (#5019)
Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
@@ -43,6 +43,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"defu": "^6.1.4",
|
||||
"zod": "^4.1.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -26,6 +26,7 @@ import type {
|
||||
} from "./types";
|
||||
import { getPlanByName, getPlanByPriceInfo, getPlans } from "./utils";
|
||||
import { getSchema } from "./schema";
|
||||
import { defu } from "defu";
|
||||
|
||||
const STRIPE_ERROR_CODES = {
|
||||
SUBSCRIPTION_NOT_FOUND: "Subscription not found",
|
||||
@@ -1299,13 +1300,27 @@ export const stripe = <O extends StripeOptions>(options: O) => {
|
||||
create: {
|
||||
async after(user, ctx) {
|
||||
if (ctx && options.createCustomerOnSignUp) {
|
||||
const stripeCustomer = await client.customers.create({
|
||||
let extraCreateParams: Partial<Stripe.CustomerCreateParams> =
|
||||
{};
|
||||
if (options.getCustomerCreateParams) {
|
||||
extraCreateParams = await options.getCustomerCreateParams(
|
||||
user,
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
|
||||
const params: Stripe.CustomerCreateParams = defu(
|
||||
{
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
metadata: {
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
},
|
||||
extraCreateParams,
|
||||
);
|
||||
const stripeCustomer =
|
||||
await client.customers.create(params);
|
||||
await ctx.context.internalAdapter.updateUser(user.id, {
|
||||
stripeCustomerId: stripeCustomer.id,
|
||||
});
|
||||
|
||||
@@ -1496,4 +1496,247 @@ describe("stripe", async () => {
|
||||
email: "newemail@example.com",
|
||||
});
|
||||
});
|
||||
|
||||
describe("getCustomerCreateParams", () => {
|
||||
it("should call getCustomerCreateParams and merge with default params", async () => {
|
||||
const getCustomerCreateParamsMock = vi
|
||||
.fn()
|
||||
.mockResolvedValue({ metadata: { customField: "customValue" } });
|
||||
|
||||
const testOptions = {
|
||||
...stripeOptions,
|
||||
createCustomerOnSignUp: true,
|
||||
getCustomerCreateParams: getCustomerCreateParamsMock,
|
||||
} satisfies StripeOptions;
|
||||
|
||||
const testAuth = betterAuth({
|
||||
database: memory,
|
||||
baseURL: "http://localhost:3000",
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
plugins: [stripe(testOptions)],
|
||||
});
|
||||
|
||||
const testAuthClient = createAuthClient({
|
||||
baseURL: "http://localhost:3000",
|
||||
plugins: [bearer(), stripeClient({ subscription: true })],
|
||||
fetchOptions: {
|
||||
customFetchImpl: async (url, init) =>
|
||||
testAuth.handler(new Request(url, init)),
|
||||
},
|
||||
});
|
||||
|
||||
// Sign up a user
|
||||
const userRes = await testAuthClient.signUp.email(
|
||||
{
|
||||
email: "custom-params@email.com",
|
||||
password: "password",
|
||||
name: "Custom User",
|
||||
},
|
||||
{
|
||||
throw: true,
|
||||
},
|
||||
);
|
||||
|
||||
// Verify getCustomerCreateParams was called
|
||||
expect(getCustomerCreateParamsMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: userRes.user.id,
|
||||
email: "custom-params@email.com",
|
||||
name: "Custom User",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
context: expect.any(Object),
|
||||
}),
|
||||
);
|
||||
|
||||
// Verify customer was created with merged params
|
||||
expect(mockStripe.customers.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
email: "custom-params@email.com",
|
||||
name: "Custom User",
|
||||
metadata: expect.objectContaining({
|
||||
userId: userRes.user.id,
|
||||
customField: "customValue",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should use getCustomerCreateParams to add custom address", async () => {
|
||||
const getCustomerCreateParamsMock = vi.fn().mockResolvedValue({
|
||||
address: {
|
||||
line1: "123 Main St",
|
||||
city: "San Francisco",
|
||||
state: "CA",
|
||||
postal_code: "94111",
|
||||
country: "US",
|
||||
},
|
||||
});
|
||||
|
||||
const testOptions = {
|
||||
...stripeOptions,
|
||||
createCustomerOnSignUp: true,
|
||||
getCustomerCreateParams: getCustomerCreateParamsMock,
|
||||
} satisfies StripeOptions;
|
||||
|
||||
const testAuth = betterAuth({
|
||||
database: memory,
|
||||
baseURL: "http://localhost:3000",
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
plugins: [stripe(testOptions)],
|
||||
});
|
||||
|
||||
const testAuthClient = createAuthClient({
|
||||
baseURL: "http://localhost:3000",
|
||||
plugins: [bearer(), stripeClient({ subscription: true })],
|
||||
fetchOptions: {
|
||||
customFetchImpl: async (url, init) =>
|
||||
testAuth.handler(new Request(url, init)),
|
||||
},
|
||||
});
|
||||
|
||||
// Sign up a user
|
||||
await testAuthClient.signUp.email(
|
||||
{
|
||||
email: "address-user@email.com",
|
||||
password: "password",
|
||||
name: "Address User",
|
||||
},
|
||||
{
|
||||
throw: true,
|
||||
},
|
||||
);
|
||||
|
||||
// Verify customer was created with address
|
||||
expect(mockStripe.customers.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
email: "address-user@email.com",
|
||||
name: "Address User",
|
||||
address: {
|
||||
line1: "123 Main St",
|
||||
city: "San Francisco",
|
||||
state: "CA",
|
||||
postal_code: "94111",
|
||||
country: "US",
|
||||
},
|
||||
metadata: expect.objectContaining({
|
||||
userId: expect.any(String),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should properly merge nested objects using defu", async () => {
|
||||
const getCustomerCreateParamsMock = vi.fn().mockResolvedValue({
|
||||
metadata: {
|
||||
customField: "customValue",
|
||||
anotherField: "anotherValue",
|
||||
},
|
||||
phone: "+1234567890",
|
||||
});
|
||||
|
||||
const testOptions = {
|
||||
...stripeOptions,
|
||||
createCustomerOnSignUp: true,
|
||||
getCustomerCreateParams: getCustomerCreateParamsMock,
|
||||
} satisfies StripeOptions;
|
||||
|
||||
const testAuth = betterAuth({
|
||||
database: memory,
|
||||
baseURL: "http://localhost:3000",
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
plugins: [stripe(testOptions)],
|
||||
});
|
||||
|
||||
const testAuthClient = createAuthClient({
|
||||
baseURL: "http://localhost:3000",
|
||||
plugins: [bearer(), stripeClient({ subscription: true })],
|
||||
fetchOptions: {
|
||||
customFetchImpl: async (url, init) =>
|
||||
testAuth.handler(new Request(url, init)),
|
||||
},
|
||||
});
|
||||
|
||||
// Sign up a user
|
||||
const userRes = await testAuthClient.signUp.email(
|
||||
{
|
||||
email: "merge-test@email.com",
|
||||
password: "password",
|
||||
name: "Merge User",
|
||||
},
|
||||
{
|
||||
throw: true,
|
||||
},
|
||||
);
|
||||
|
||||
// Verify customer was created with properly merged params
|
||||
// defu merges objects and preserves all fields
|
||||
expect(mockStripe.customers.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
email: "merge-test@email.com",
|
||||
name: "Merge User",
|
||||
phone: "+1234567890",
|
||||
metadata: {
|
||||
userId: userRes.user.id,
|
||||
customField: "customValue",
|
||||
anotherField: "anotherValue",
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should work without getCustomerCreateParams", async () => {
|
||||
// This test ensures backward compatibility
|
||||
const testOptions = {
|
||||
...stripeOptions,
|
||||
createCustomerOnSignUp: true,
|
||||
// No getCustomerCreateParams provided
|
||||
} satisfies StripeOptions;
|
||||
|
||||
const testAuth = betterAuth({
|
||||
database: memory,
|
||||
baseURL: "http://localhost:3000",
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
plugins: [stripe(testOptions)],
|
||||
});
|
||||
|
||||
const testAuthClient = createAuthClient({
|
||||
baseURL: "http://localhost:3000",
|
||||
plugins: [bearer(), stripeClient({ subscription: true })],
|
||||
fetchOptions: {
|
||||
customFetchImpl: async (url, init) =>
|
||||
testAuth.handler(new Request(url, init)),
|
||||
},
|
||||
});
|
||||
|
||||
// Sign up a user
|
||||
const userRes = await testAuthClient.signUp.email(
|
||||
{
|
||||
email: "no-custom-params@email.com",
|
||||
password: "password",
|
||||
name: "Default User",
|
||||
},
|
||||
{
|
||||
throw: true,
|
||||
},
|
||||
);
|
||||
|
||||
// Verify customer was created with default params only
|
||||
expect(mockStripe.customers.create).toHaveBeenCalledWith({
|
||||
email: "no-custom-params@email.com",
|
||||
name: "Default User",
|
||||
metadata: {
|
||||
userId: userRes.user.id,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -199,12 +199,9 @@ export interface StripeOptions {
|
||||
* @returns
|
||||
*/
|
||||
getCustomerCreateParams?: (
|
||||
data: {
|
||||
user: User;
|
||||
session: Session;
|
||||
},
|
||||
user: User,
|
||||
ctx: GenericEndpointContext,
|
||||
) => Promise<{}>;
|
||||
) => Promise<Partial<Stripe.CustomerCreateParams>>;
|
||||
/**
|
||||
* Subscriptions
|
||||
*/
|
||||
|
||||
11
pnpm-lock.yaml
generated
11
pnpm-lock.yaml
generated
@@ -1154,6 +1154,9 @@ importers:
|
||||
|
||||
packages/stripe:
|
||||
dependencies:
|
||||
defu:
|
||||
specifier: ^6.1.4
|
||||
version: 6.1.4
|
||||
zod:
|
||||
specifier: ^4.1.5
|
||||
version: 4.1.5
|
||||
@@ -14935,7 +14938,7 @@ snapshots:
|
||||
postcss: 8.4.49
|
||||
resolve-from: 5.0.0
|
||||
optionalDependencies:
|
||||
expo: 54.0.10(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.8)(graphql@16.11.0)(react-native@0.81.4(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.2))(@react-native/metro-config@0.81.0(@babel/core@7.28.4))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1)
|
||||
expo: 54.0.10(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.8)(graphql@16.11.0)(react-native@0.80.2(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1)
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
@@ -15020,7 +15023,7 @@ snapshots:
|
||||
'@expo/json-file': 10.0.7
|
||||
'@react-native/normalize-colors': 0.81.4
|
||||
debug: 4.4.1
|
||||
expo: 54.0.10(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.8)(graphql@16.11.0)(react-native@0.81.4(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.2))(@react-native/metro-config@0.81.0(@babel/core@7.28.4))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1)
|
||||
expo: 54.0.10(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.8)(graphql@16.11.0)(react-native@0.80.2(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1)
|
||||
resolve-from: 5.0.0
|
||||
semver: 7.7.2
|
||||
xml2js: 0.6.0
|
||||
@@ -19179,7 +19182,7 @@ snapshots:
|
||||
resolve-from: 5.0.0
|
||||
optionalDependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
expo: 54.0.10(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.8)(graphql@16.11.0)(react-native@0.81.4(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.2))(@react-native/metro-config@0.81.0(@babel/core@7.28.4))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1)
|
||||
expo: 54.0.10(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.8)(graphql@16.11.0)(react-native@0.80.2(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1)
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- supports-color
|
||||
@@ -20741,7 +20744,7 @@ snapshots:
|
||||
|
||||
expo-keep-awake@15.0.7(expo@54.0.10)(react@19.1.1):
|
||||
dependencies:
|
||||
expo: 54.0.10(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.8)(graphql@16.11.0)(react-native@0.81.4(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.2))(@react-native/metro-config@0.81.0(@babel/core@7.28.4))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1)
|
||||
expo: 54.0.10(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.8)(graphql@16.11.0)(react-native@0.80.2(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1)
|
||||
react: 19.1.1
|
||||
|
||||
expo-linking@7.1.7(expo@54.0.10)(react-native@0.80.2(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1):
|
||||
|
||||
Reference in New Issue
Block a user