mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-09 20: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": {
|
"dependencies": {
|
||||||
|
"defu": "^6.1.4",
|
||||||
"zod": "^4.1.5"
|
"zod": "^4.1.5"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import type {
|
|||||||
} from "./types";
|
} from "./types";
|
||||||
import { getPlanByName, getPlanByPriceInfo, getPlans } from "./utils";
|
import { getPlanByName, getPlanByPriceInfo, getPlans } from "./utils";
|
||||||
import { getSchema } from "./schema";
|
import { getSchema } from "./schema";
|
||||||
|
import { defu } from "defu";
|
||||||
|
|
||||||
const STRIPE_ERROR_CODES = {
|
const STRIPE_ERROR_CODES = {
|
||||||
SUBSCRIPTION_NOT_FOUND: "Subscription not found",
|
SUBSCRIPTION_NOT_FOUND: "Subscription not found",
|
||||||
@@ -1299,13 +1300,27 @@ export const stripe = <O extends StripeOptions>(options: O) => {
|
|||||||
create: {
|
create: {
|
||||||
async after(user, ctx) {
|
async after(user, ctx) {
|
||||||
if (ctx && options.createCustomerOnSignUp) {
|
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,
|
email: user.email,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
metadata: {
|
metadata: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
extraCreateParams,
|
||||||
|
);
|
||||||
|
const stripeCustomer =
|
||||||
|
await client.customers.create(params);
|
||||||
await ctx.context.internalAdapter.updateUser(user.id, {
|
await ctx.context.internalAdapter.updateUser(user.id, {
|
||||||
stripeCustomerId: stripeCustomer.id,
|
stripeCustomerId: stripeCustomer.id,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1496,4 +1496,247 @@ describe("stripe", async () => {
|
|||||||
email: "newemail@example.com",
|
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
|
* @returns
|
||||||
*/
|
*/
|
||||||
getCustomerCreateParams?: (
|
getCustomerCreateParams?: (
|
||||||
data: {
|
user: User,
|
||||||
user: User;
|
|
||||||
session: Session;
|
|
||||||
},
|
|
||||||
ctx: GenericEndpointContext,
|
ctx: GenericEndpointContext,
|
||||||
) => Promise<{}>;
|
) => Promise<Partial<Stripe.CustomerCreateParams>>;
|
||||||
/**
|
/**
|
||||||
* Subscriptions
|
* Subscriptions
|
||||||
*/
|
*/
|
||||||
|
|||||||
11
pnpm-lock.yaml
generated
11
pnpm-lock.yaml
generated
@@ -1154,6 +1154,9 @@ importers:
|
|||||||
|
|
||||||
packages/stripe:
|
packages/stripe:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
defu:
|
||||||
|
specifier: ^6.1.4
|
||||||
|
version: 6.1.4
|
||||||
zod:
|
zod:
|
||||||
specifier: ^4.1.5
|
specifier: ^4.1.5
|
||||||
version: 4.1.5
|
version: 4.1.5
|
||||||
@@ -14935,7 +14938,7 @@ snapshots:
|
|||||||
postcss: 8.4.49
|
postcss: 8.4.49
|
||||||
resolve-from: 5.0.0
|
resolve-from: 5.0.0
|
||||||
optionalDependencies:
|
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:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -15020,7 +15023,7 @@ snapshots:
|
|||||||
'@expo/json-file': 10.0.7
|
'@expo/json-file': 10.0.7
|
||||||
'@react-native/normalize-colors': 0.81.4
|
'@react-native/normalize-colors': 0.81.4
|
||||||
debug: 4.4.1
|
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
|
resolve-from: 5.0.0
|
||||||
semver: 7.7.2
|
semver: 7.7.2
|
||||||
xml2js: 0.6.0
|
xml2js: 0.6.0
|
||||||
@@ -19179,7 +19182,7 @@ snapshots:
|
|||||||
resolve-from: 5.0.0
|
resolve-from: 5.0.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@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:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -20741,7 +20744,7 @@ snapshots:
|
|||||||
|
|
||||||
expo-keep-awake@15.0.7(expo@54.0.10)(react@19.1.1):
|
expo-keep-awake@15.0.7(expo@54.0.10)(react@19.1.1):
|
||||||
dependencies:
|
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
|
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):
|
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