v1.3.27-c: Add adjustment/summary action for meter event

This commit is contained in:
Yudi Xiao
2025-10-14 10:42:41 +08:00
parent 87846d7668
commit 0705a5f74d
4 changed files with 1792 additions and 1677 deletions

View File

@@ -1,7 +1,7 @@
{ {
"name": "@bowong/better-auth-stripe", "name": "@bowong/better-auth-stripe",
"author": "Bowong", "author": "Bowong",
"version": "1.3.27-a", "version": "1.3.27-c",
"main": "dist/index.cjs", "main": "dist/index.cjs",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [

File diff suppressed because it is too large Load Diff

View File

@@ -1,331 +1,336 @@
import type { import type {
GenericEndpointContext, GenericEndpointContext,
InferOptionSchema, InferOptionSchema,
Session, Session,
User, User,
} from "better-auth"; } from "better-auth";
import type Stripe from "stripe"; import type Stripe from "stripe";
import type { subscriptions, user } from "./schema"; import type {subscriptions, user} from "./schema";
export type StripePlan = { export type StripePlan = {
/** /**
* Monthly price id * Monthly price id
*/ */
priceId?: string; priceId?: string;
/** /**
* To use lookup key instead of price id * To use lookup key instead of price id
* *
* https://docs.stripe.com/products-prices/ * https://docs.stripe.com/products-prices/
* manage-prices#lookup-keys * manage-prices#lookup-keys
*/ */
lookupKey?: string; lookupKey?: string;
/** /**
* A yearly discount price id * A yearly discount price id
* *
* useful when you want to offer a discount for * useful when you want to offer a discount for
* yearly subscription * yearly subscription
*/ */
annualDiscountPriceId?: string; annualDiscountPriceId?: string;
/** /**
* To use lookup key instead of price id * To use lookup key instead of price id
* *
* https://docs.stripe.com/products-prices/ * https://docs.stripe.com/products-prices/
* manage-prices#lookup-keys * manage-prices#lookup-keys
*/ */
annualDiscountLookupKey?: string; annualDiscountLookupKey?: string;
/** /**
* Plan name * Plan name
*/ */
name: string; name: string;
/** /**
* Limits for the plan * Limits for the plan
*/ */
limits?: Record<string, number>; limits?: Record<string, number>;
/** /**
* Plan group name * Plan group name
* *
* useful when you want to group plans or * useful when you want to group plans or
* when a user can subscribe to multiple plans. * when a user can subscribe to multiple plans.
*/ */
group?: string; group?: string;
/** /**
* Free trial days * Free trial days
*/ */
freeTrial?: { freeTrial?: {
/** /**
* Number of days * Number of days
*/ */
days: number; days: number;
/** /**
* A function that will be called when the trial * A function that will be called when the trial
* starts. * starts.
* *
* @param subscription * @param subscription
* @returns * @returns
*/ */
onTrialStart?: (subscription: Subscription) => Promise<void>; onTrialStart?: (subscription: Subscription) => Promise<void>;
/** /**
* A function that will be called when the trial * A function that will be called when the trial
* ends * ends
* *
* @param subscription - Subscription * @param subscription - Subscription
* @returns * @returns
*/ */
onTrialEnd?: ( onTrialEnd?: (
data: { data: {
subscription: Subscription; subscription: Subscription;
}, },
ctx: GenericEndpointContext, ctx: GenericEndpointContext,
) => Promise<void>; ) => Promise<void>;
/** /**
* A function that will be called when the trial * A function that will be called when the trial
* expired. * expired.
* @param subscription - Subscription * @param subscription - Subscription
* @returns * @returns
*/ */
onTrialExpired?: ( onTrialExpired?: (
subscription: Subscription, subscription: Subscription,
ctx: GenericEndpointContext, ctx: GenericEndpointContext,
) => Promise<void>; ) => Promise<void>;
}; };
}; };
export type actionType =
| "upgrade-subscription"
| "list-subscription"
| "cancel-subscription"
| "restore-subscription"
| "meter-event"
| "adjust-meter-event"
| "meter-event-summary"
| "billing-portal";
export interface Subscription { export interface Subscription {
/** /**
* Database identifier * Database identifier
*/ */
id: string; id: string;
/** /**
* The plan name * The plan name
*/ */
plan: string; plan: string;
/** /**
* Stripe customer id * Stripe customer id
*/ */
stripeCustomerId?: string; stripeCustomerId?: string;
/** /**
* Stripe subscription id * Stripe subscription id
*/ */
stripeSubscriptionId?: string; stripeSubscriptionId?: string;
/** /**
* Trial start date * Trial start date
*/ */
trialStart?: Date; trialStart?: Date;
/** /**
* Trial end date * Trial end date
*/ */
trialEnd?: Date; trialEnd?: Date;
/** /**
* Price Id for the subscription * Price Id for the subscription
*/ */
priceId?: string; priceId?: string;
/** /**
* To what reference id the subscription belongs to * To what reference id the subscription belongs to
* @example * @example
* - userId for a user * - userId for a user
* - workspace id for a saas platform * - workspace id for a saas platform
* - website id for a hosting platform * - website id for a hosting platform
* *
* @default - userId * @default - userId
*/ */
referenceId: string; referenceId: string;
/** /**
* Subscription status * Subscription status
*/ */
status: status:
| "active" | "active"
| "canceled" | "canceled"
| "incomplete" | "incomplete"
| "incomplete_expired" | "incomplete_expired"
| "past_due" | "past_due"
| "paused" | "paused"
| "trialing" | "trialing"
| "unpaid"; | "unpaid";
/** /**
* The billing cycle start date * The billing cycle start date
*/ */
periodStart?: Date; periodStart?: Date;
/** /**
* The billing cycle end date * The billing cycle end date
*/ */
periodEnd?: Date; periodEnd?: Date;
/** /**
* Cancel at period end * Cancel at period end
*/ */
cancelAtPeriodEnd?: boolean; cancelAtPeriodEnd?: boolean;
/** /**
* A field to group subscriptions so you can have multiple subscriptions * A field to group subscriptions so you can have multiple subscriptions
* for one reference id * for one reference id
*/ */
groupId?: string; groupId?: string;
/** /**
* Number of seats for the subscription (useful for team plans) * Number of seats for the subscription (useful for team plans)
*/ */
seats?: number; seats?: number;
} }
export interface StripeOptions { export interface StripeOptions {
/** /**
* Stripe Client * Stripe Client
*/ */
stripeClient: Stripe; stripeClient: Stripe;
/** /**
* Stripe Webhook Secret * Stripe Webhook Secret
* *
* @description Stripe webhook secret key * @description Stripe webhook secret key
*/ */
stripeWebhookSecret: string; stripeWebhookSecret: string;
/** /**
* Enable customer creation when a user signs up * Enable customer creation when a user signs up
*/ */
createCustomerOnSignUp?: boolean; createCustomerOnSignUp?: boolean;
/** /**
* A callback to run after a customer has been created * A callback to run after a customer has been created
* @param customer - Customer Data * @param customer - Customer Data
* @param stripeCustomer - Stripe Customer Data * @param stripeCustomer - Stripe Customer Data
* @returns * @returns
*/ */
onCustomerCreate?: ( onCustomerCreate?: (
data: { data: {
stripeCustomer: Stripe.Customer; stripeCustomer: Stripe.Customer;
user: User & { stripeCustomerId: string }; user: User & { stripeCustomerId: string };
}, },
ctx: GenericEndpointContext, ctx: GenericEndpointContext,
) => Promise<void>; ) => Promise<void>;
/** /**
* A custom function to get the customer create * A custom function to get the customer create
* params * params
* @param data - data containing user and session * @param data - data containing user and session
* @returns * @returns
*/ */
getCustomerCreateParams?: ( getCustomerCreateParams?: (
user: User, user: User,
ctx: GenericEndpointContext, ctx: GenericEndpointContext,
) => Promise<Partial<Stripe.CustomerCreateParams>>; ) => Promise<Partial<Stripe.CustomerCreateParams>>;
/** /**
* Subscriptions * Subscriptions
*/ */
subscription?: { subscription?: {
enabled: boolean; enabled: boolean;
/** /**
* Subscription Configuration * Subscription Configuration
*/ */
/** /**
* List of plan * List of plan
*/ */
plans: StripePlan[] | (() => StripePlan[] | Promise<StripePlan[]>); plans: StripePlan[] | (() => StripePlan[] | Promise<StripePlan[]>);
/** /**
* Require email verification before a user is allowed to upgrade * Require email verification before a user is allowed to upgrade
* their subscriptions * their subscriptions
* *
* @default false * @default false
*/ */
requireEmailVerification?: boolean; requireEmailVerification?: boolean;
/** /**
* A callback to run after a user has subscribed to a package * A callback to run after a user has subscribed to a package
* @param event - Stripe Event * @param event - Stripe Event
* @param subscription - Subscription Data * @param subscription - Subscription Data
* @returns * @returns
*/ */
onSubscriptionComplete?: ( onSubscriptionComplete?: (
data: { data: {
event: Stripe.Event; event: Stripe.Event;
stripeSubscription: Stripe.Subscription; stripeSubscription: Stripe.Subscription;
subscription: Subscription; subscription: Subscription;
plan: StripePlan; plan: StripePlan;
}, },
ctx: GenericEndpointContext, ctx: GenericEndpointContext,
) => Promise<void>; ) => Promise<void>;
/** /**
* A callback to run after a user is about to cancel their subscription * A callback to run after a user is about to cancel their subscription
* @returns * @returns
*/ */
onSubscriptionUpdate?: (data: { onSubscriptionUpdate?: (data: {
event: Stripe.Event; event: Stripe.Event;
subscription: Subscription; subscription: Subscription;
}) => Promise<void>; }) => Promise<void>;
/** /**
* A callback to run after a user is about to cancel their subscription * A callback to run after a user is about to cancel their subscription
* @returns * @returns
*/ */
onSubscriptionCancel?: (data: { onSubscriptionCancel?: (data: {
event?: Stripe.Event; event?: Stripe.Event;
subscription: Subscription; subscription: Subscription;
stripeSubscription: Stripe.Subscription; stripeSubscription: Stripe.Subscription;
cancellationDetails?: Stripe.Subscription.CancellationDetails | null; cancellationDetails?: Stripe.Subscription.CancellationDetails | null;
}) => Promise<void>; }) => Promise<void>;
/** /**
* A function to check if the reference id is valid * A function to check if the reference id is valid
* and belongs to the user * and belongs to the user
* *
* @param data - data containing user, session and referenceId * @param data - data containing user, session and referenceId
* @param ctx - the context object * @param ctx - the context object
* @returns * @returns
*/ */
authorizeReference?: ( authorizeReference?: (
data: { data: {
user: User & Record<string, any>; user: User & Record<string, any>;
session: Session & Record<string, any>; session: Session & Record<string, any>;
referenceId: string; referenceId: string;
action: action: actionType;
| "upgrade-subscription" },
| "list-subscription" ctx: GenericEndpointContext,
| "cancel-subscription" ) => Promise<boolean>;
| "restore-subscription" /**
| "meter-event" * A callback to run after a user has deleted their subscription
| "billing-portal"; * @returns
}, */
ctx: GenericEndpointContext, onSubscriptionDeleted?: (data: {
) => Promise<boolean>; event: Stripe.Event;
/** stripeSubscription: Stripe.Subscription;
* A callback to run after a user has deleted their subscription subscription: Subscription;
* @returns }) => Promise<void>;
*/ /**
onSubscriptionDeleted?: (data: { * parameters for session create params
event: Stripe.Event; *
stripeSubscription: Stripe.Subscription; * @param data - data containing user, session and plan
subscription: Subscription; * @param ctx - the context object
}) => Promise<void>; */
/** getCheckoutSessionParams?: (
* parameters for session create params data: {
* user: User & Record<string, any>;
* @param data - data containing user, session and plan session: Session & Record<string, any>;
* @param ctx - the context object plan: StripePlan;
*/ subscription: Subscription;
getCheckoutSessionParams?: ( },
data: { ctx: GenericEndpointContext,
user: User & Record<string, any>; ) =>
session: Session & Record<string, any>; | Promise<{
plan: StripePlan; params?: Stripe.Checkout.SessionCreateParams;
subscription: Subscription; options?: Stripe.RequestOptions;
}, }>
ctx: GenericEndpointContext, | {
) => params?: Stripe.Checkout.SessionCreateParams;
| Promise<{ options?: Stripe.RequestOptions;
params?: Stripe.Checkout.SessionCreateParams; };
options?: Stripe.RequestOptions; /**
}> * Enable organization subscription
| { */
params?: Stripe.Checkout.SessionCreateParams; organization?: {
options?: Stripe.RequestOptions; enabled: boolean;
}; };
/** };
* Enable organization subscription /**
*/ * A callback to run after a stripe event is received
organization?: { * @param event - Stripe Event
enabled: boolean; * @returns
}; */
}; onEvent?: (event: Stripe.Event) => Promise<void>;
/** /**
* A callback to run after a stripe event is received * Schema for the stripe plugin
* @param event - Stripe Event */
* @returns schema?: InferOptionSchema<typeof subscriptions & typeof user>;
*/
onEvent?: (event: Stripe.Event) => Promise<void>;
/**
* Schema for the stripe plugin
*/
schema?: InferOptionSchema<typeof subscriptions & typeof user>;
} }
export interface InputSubscription extends Omit<Subscription, "id"> {} export interface InputSubscription extends Omit<Subscription, "id"> {
}

View File

@@ -1,30 +1,30 @@
import type { StripeOptions } from "./types"; import type {StripeOptions} from "./types";
export async function getPlans(options: StripeOptions) { export async function getPlans(options: StripeOptions) {
return typeof options?.subscription?.plans === "function" return typeof options?.subscription?.plans === "function"
? await options.subscription?.plans() ? await options.subscription?.plans()
: options.subscription?.plans; : options.subscription?.plans;
} }
export async function getPlanByPriceInfo( export async function getPlanByPriceInfo(
options: StripeOptions, options: StripeOptions,
priceId: string, priceId: string,
priceLookupKey: string | null, priceLookupKey: string | null,
) { ) {
return await getPlans(options).then((res) => return await getPlans(options).then((res) =>
res?.find( res?.find(
(plan) => (plan) =>
plan.priceId === priceId || plan.priceId === priceId ||
plan.annualDiscountPriceId === priceId || plan.annualDiscountPriceId === priceId ||
(priceLookupKey && (priceLookupKey &&
(plan.lookupKey === priceLookupKey || (plan.lookupKey === priceLookupKey ||
plan.annualDiscountLookupKey === priceLookupKey)), plan.annualDiscountLookupKey === priceLookupKey)),
), ),
); );
} }
export async function getPlanByName(options: StripeOptions, name: string) { export async function getPlanByName(options: StripeOptions, name: string) {
return await getPlans(options).then((res) => return await getPlans(options).then((res) =>
res?.find((plan) => plan.name.toLowerCase() === name.toLowerCase()), res?.find((plan) => plan.name.toLowerCase() === name.toLowerCase()),
); );
} }