@better-auth/stripe 1.5.0-beta.1 → 1.5.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +15 -10
- package/LICENSE.md +15 -12
- package/dist/client.d.mts +47 -66
- package/dist/client.mjs +7 -3
- package/dist/client.mjs.map +1 -0
- package/dist/error-codes-CMowBCzF.mjs +30 -0
- package/dist/error-codes-CMowBCzF.mjs.map +1 -0
- package/dist/{index-DpiQGYLJ.d.mts → index-D9Pr9jIc.d.mts} +371 -188
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +720 -293
- package/dist/index.mjs.map +1 -0
- package/package.json +10 -10
- package/src/client.ts +2 -1
- package/src/error-codes.ts +17 -1
- package/src/hooks.ts +238 -57
- package/src/index.ts +149 -49
- package/src/metadata.ts +94 -0
- package/src/middleware.ts +91 -45
- package/src/routes.ts +699 -368
- package/src/schema.ts +40 -4
- package/src/types.ts +105 -20
- package/src/utils.ts +36 -1
- package/test/stripe-organization.test.ts +1993 -0
- package/{src → test}/stripe.test.ts +3775 -1393
- package/tsdown.config.ts +1 -0
- package/CHANGELOG.md +0 -22
- package/dist/error-codes-qqooUh6R.mjs +0 -16
package/src/metadata.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { defu } from "defu";
|
|
2
|
+
import type Stripe from "stripe";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Internal metadata fields for Stripe Customer.
|
|
6
|
+
*/
|
|
7
|
+
type CustomerInternalMetadata =
|
|
8
|
+
| { customerType: "user"; userId: string }
|
|
9
|
+
| { customerType: "organization"; organizationId: string };
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Internal metadata fields for Stripe Subscription/Checkout.
|
|
13
|
+
*/
|
|
14
|
+
type SubscriptionInternalMetadata = {
|
|
15
|
+
userId: string;
|
|
16
|
+
subscriptionId: string;
|
|
17
|
+
referenceId: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Customer metadata - set internal fields and extract typed fields.
|
|
22
|
+
*/
|
|
23
|
+
export const customerMetadata = {
|
|
24
|
+
/**
|
|
25
|
+
* Internal metadata keys for type-safe access.
|
|
26
|
+
*/
|
|
27
|
+
keys: {
|
|
28
|
+
userId: "userId",
|
|
29
|
+
organizationId: "organizationId",
|
|
30
|
+
customerType: "customerType",
|
|
31
|
+
} as const,
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create metadata with internal fields that cannot be overridden by user metadata.
|
|
35
|
+
* Uses `defu` which prioritizes the first argument.
|
|
36
|
+
*/
|
|
37
|
+
set(
|
|
38
|
+
internalFields: CustomerInternalMetadata,
|
|
39
|
+
...userMetadata: (Stripe.Emptyable<Stripe.MetadataParam> | undefined)[]
|
|
40
|
+
): Stripe.MetadataParam {
|
|
41
|
+
return defu(internalFields, ...userMetadata.filter(Boolean));
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Extract internal fields from Stripe metadata.
|
|
46
|
+
* Provides type-safe access to internal metadata keys.
|
|
47
|
+
*/
|
|
48
|
+
get(metadata: Stripe.Metadata | null | undefined) {
|
|
49
|
+
return {
|
|
50
|
+
userId: metadata?.userId,
|
|
51
|
+
organizationId: metadata?.organizationId,
|
|
52
|
+
customerType: metadata?.customerType as
|
|
53
|
+
| CustomerInternalMetadata["customerType"]
|
|
54
|
+
| undefined,
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Subscription/Checkout metadata - set internal fields and extract typed fields.
|
|
61
|
+
*/
|
|
62
|
+
export const subscriptionMetadata = {
|
|
63
|
+
/**
|
|
64
|
+
* Internal metadata keys for type-safe access.
|
|
65
|
+
*/
|
|
66
|
+
keys: {
|
|
67
|
+
userId: "userId",
|
|
68
|
+
subscriptionId: "subscriptionId",
|
|
69
|
+
referenceId: "referenceId",
|
|
70
|
+
} as const,
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Create metadata with internal fields that cannot be overridden by user metadata.
|
|
74
|
+
* Uses `defu` which prioritizes the first argument.
|
|
75
|
+
*/
|
|
76
|
+
set(
|
|
77
|
+
internalFields: SubscriptionInternalMetadata,
|
|
78
|
+
...userMetadata: (Stripe.Emptyable<Stripe.MetadataParam> | undefined)[]
|
|
79
|
+
): Stripe.MetadataParam {
|
|
80
|
+
return defu(internalFields, ...userMetadata.filter(Boolean));
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Extract internal fields from Stripe metadata.
|
|
85
|
+
* Provides type-safe access to internal metadata keys.
|
|
86
|
+
*/
|
|
87
|
+
get(metadata: Stripe.Metadata | null | undefined) {
|
|
88
|
+
return {
|
|
89
|
+
userId: metadata?.userId,
|
|
90
|
+
subscriptionId: metadata?.subscriptionId,
|
|
91
|
+
referenceId: metadata?.referenceId,
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
};
|
package/src/middleware.ts
CHANGED
|
@@ -1,58 +1,104 @@
|
|
|
1
1
|
import { createAuthMiddleware } from "@better-auth/core/api";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
2
|
+
import { APIError } from "@better-auth/core/error";
|
|
3
|
+
import { sessionMiddleware } from "better-auth/api";
|
|
4
|
+
import { STRIPE_ERROR_CODES } from "./error-codes";
|
|
5
|
+
import type {
|
|
6
|
+
AuthorizeReferenceAction,
|
|
7
|
+
CustomerType,
|
|
8
|
+
StripeCtxSession,
|
|
9
|
+
SubscriptionOptions,
|
|
10
|
+
} from "./types";
|
|
11
|
+
|
|
12
|
+
export const stripeSessionMiddleware = createAuthMiddleware(
|
|
13
|
+
{
|
|
14
|
+
use: [sessionMiddleware],
|
|
15
|
+
},
|
|
16
|
+
async (ctx) => {
|
|
17
|
+
const session = ctx.context.session as StripeCtxSession;
|
|
18
|
+
return {
|
|
19
|
+
session,
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
);
|
|
5
23
|
|
|
6
24
|
export const referenceMiddleware = (
|
|
7
25
|
subscriptionOptions: SubscriptionOptions,
|
|
8
|
-
action:
|
|
9
|
-
| "upgrade-subscription"
|
|
10
|
-
| "list-subscription"
|
|
11
|
-
| "cancel-subscription"
|
|
12
|
-
| "restore-subscription"
|
|
13
|
-
| "billing-portal",
|
|
26
|
+
action: AuthorizeReferenceAction,
|
|
14
27
|
) =>
|
|
15
28
|
createAuthMiddleware(async (ctx) => {
|
|
16
|
-
const
|
|
17
|
-
if (!
|
|
18
|
-
throw
|
|
29
|
+
const ctxSession = ctx.context.session as StripeCtxSession;
|
|
30
|
+
if (!ctxSession) {
|
|
31
|
+
throw APIError.from("UNAUTHORIZED", STRIPE_ERROR_CODES.UNAUTHORIZED);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const customerType: CustomerType =
|
|
35
|
+
ctx.body?.customerType || ctx.query?.customerType;
|
|
36
|
+
const explicitReferenceId = ctx.body?.referenceId || ctx.query?.referenceId;
|
|
37
|
+
|
|
38
|
+
if (customerType === "organization") {
|
|
39
|
+
// Organization subscriptions always require authorizeReference
|
|
40
|
+
if (!subscriptionOptions.authorizeReference) {
|
|
41
|
+
ctx.context.logger.error(
|
|
42
|
+
`Organization subscriptions require authorizeReference to be defined in your stripe plugin config.`,
|
|
43
|
+
);
|
|
44
|
+
throw APIError.from(
|
|
45
|
+
"BAD_REQUEST",
|
|
46
|
+
STRIPE_ERROR_CODES.ORGANIZATION_SUBSCRIPTION_NOT_ENABLED,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const referenceId =
|
|
51
|
+
explicitReferenceId || ctxSession.session.activeOrganizationId;
|
|
52
|
+
if (!referenceId) {
|
|
53
|
+
throw APIError.from(
|
|
54
|
+
"BAD_REQUEST",
|
|
55
|
+
STRIPE_ERROR_CODES.ORGANIZATION_REFERENCE_ID_REQUIRED,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
const isAuthorized = await subscriptionOptions.authorizeReference(
|
|
59
|
+
{
|
|
60
|
+
user: ctxSession.user,
|
|
61
|
+
session: ctxSession.session,
|
|
62
|
+
referenceId,
|
|
63
|
+
action,
|
|
64
|
+
},
|
|
65
|
+
ctx,
|
|
66
|
+
);
|
|
67
|
+
if (!isAuthorized) {
|
|
68
|
+
throw APIError.from("UNAUTHORIZED", STRIPE_ERROR_CODES.UNAUTHORIZED);
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
19
71
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
72
|
+
|
|
73
|
+
// User subscriptions - pass if no explicit referenceId
|
|
74
|
+
if (!explicitReferenceId) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Pass if referenceId is user id
|
|
79
|
+
if (explicitReferenceId === ctxSession.user.id) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!subscriptionOptions.authorizeReference) {
|
|
84
|
+
ctx.context.logger.error(
|
|
28
85
|
`Passing referenceId into a subscription action isn't allowed if subscription.authorizeReference isn't defined in your stripe plugin config.`,
|
|
29
86
|
);
|
|
30
|
-
throw
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
87
|
+
throw APIError.from(
|
|
88
|
+
"BAD_REQUEST",
|
|
89
|
+
STRIPE_ERROR_CODES.REFERENCE_ID_NOT_ALLOWED,
|
|
90
|
+
);
|
|
34
91
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
ctx
|
|
43
|
-
|
|
44
|
-
{
|
|
45
|
-
user: session.user,
|
|
46
|
-
session: session.session,
|
|
47
|
-
referenceId,
|
|
48
|
-
action,
|
|
49
|
-
},
|
|
50
|
-
ctx,
|
|
51
|
-
)) || sameReference
|
|
52
|
-
: true;
|
|
92
|
+
const isAuthorized = await subscriptionOptions.authorizeReference(
|
|
93
|
+
{
|
|
94
|
+
user: ctxSession.user,
|
|
95
|
+
session: ctxSession.session,
|
|
96
|
+
referenceId: explicitReferenceId,
|
|
97
|
+
action,
|
|
98
|
+
},
|
|
99
|
+
ctx,
|
|
100
|
+
);
|
|
53
101
|
if (!isAuthorized) {
|
|
54
|
-
throw
|
|
55
|
-
message: "Unauthorized",
|
|
56
|
-
});
|
|
102
|
+
throw APIError.from("UNAUTHORIZED", STRIPE_ERROR_CODES.UNAUTHORIZED);
|
|
57
103
|
}
|
|
58
104
|
});
|