fix: 添加better auth 接口配置
This commit is contained in:
5
app/auth.tsx
Normal file
5
app/auth.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
135
lib/auth.ts
135
lib/auth.ts
@@ -1,27 +1,114 @@
|
|||||||
import { createAuthClient } from "better-auth/react";
|
import 'reflect-metadata'
|
||||||
import { usernameClient, phoneNumberClient, emailOTPClient, genericOAuthClient, adminClient, organizationClient, jwtClient } from 'better-auth/client/plugins'
|
|
||||||
import * as SecureStore from "expo-secure-store";
|
|
||||||
import { expoClient } from "@better-auth/expo/client";
|
|
||||||
|
|
||||||
import loomartPlugin from './plugins/loomart-plugin';
|
import { expoClient } from '@better-auth/expo/client'
|
||||||
|
import { createSkerClientPlugin } from '@repo/sdk'
|
||||||
|
import {
|
||||||
|
adminClient,
|
||||||
|
emailOTPClient,
|
||||||
|
genericOAuthClient,
|
||||||
|
jwtClient,
|
||||||
|
organizationClient,
|
||||||
|
phoneNumberClient,
|
||||||
|
usernameClient,
|
||||||
|
} from 'better-auth/client/plugins'
|
||||||
|
import { createAuthClient } from 'better-auth/react'
|
||||||
|
|
||||||
|
import { fetchWithLogger } from './fetch-logger'
|
||||||
|
import type { Subscription } from './plugins/stripe'
|
||||||
|
import { stripeClient } from './plugins/stripe-plugin'
|
||||||
|
import { storage } from './storage'
|
||||||
|
import type { ApiError } from './types'
|
||||||
|
|
||||||
|
export interface CreditBalance {
|
||||||
|
tokenUsage: number
|
||||||
|
remainingTokenBalance: number
|
||||||
|
currentCreditBalance: number
|
||||||
|
unitPrice: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubscriptionListItem extends Subscription {
|
||||||
|
type?: 'metered' | 'licenced'
|
||||||
|
limits?: Record<string, number>
|
||||||
|
creditBalance?: CreditBalance
|
||||||
|
highestTierPlan?: string
|
||||||
|
highestTierCredits?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubscriptionListParams {
|
||||||
|
query?: {
|
||||||
|
referenceId?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubscriptionRestoreParams {
|
||||||
|
referenceId?: string
|
||||||
|
subscriptionId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubscriptionRestoreResponse {
|
||||||
|
success: boolean
|
||||||
|
subscription: {
|
||||||
|
id: string
|
||||||
|
stripeSubscriptionId: string
|
||||||
|
cancelAtPeriodEnd: boolean
|
||||||
|
periodEnd?: Date
|
||||||
|
status: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BillingPortalParams {
|
||||||
|
locale?: string
|
||||||
|
referenceId?: string
|
||||||
|
returnUrl?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BillingPortalResponse {
|
||||||
|
url: string
|
||||||
|
redirect: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISubscription {
|
||||||
|
list: (params?: SubscriptionListParams) => Promise<{ data?: SubscriptionListItem[]; error?: ApiError }>
|
||||||
|
restore: (params?: SubscriptionRestoreParams) => Promise<{ data?: SubscriptionRestoreResponse; error?: ApiError }>
|
||||||
|
billingPortal: (params?: BillingPortalParams) => Promise<{ data?: BillingPortalResponse; error?: ApiError }>
|
||||||
|
}
|
||||||
|
export const getAuthToken = async () => (await storage.getItem('bestaibest.better-auth.session_token')) || ''
|
||||||
|
|
||||||
|
export const setAuthToken = async (token: string) => {
|
||||||
|
await storage.setItem(`bestaibest.better-auth.session_token`, token)
|
||||||
|
}
|
||||||
export const authClient = createAuthClient({
|
export const authClient = createAuthClient({
|
||||||
baseURL: "http://localhost:14333/api/auth",
|
baseURL: 'https://api.mixvideo.bowong.cc',
|
||||||
trustedOrigins: ["duooomi://"],
|
trustedOrigins: ['duooomi://', 'https://api.mixvideo.bowong.cc'],
|
||||||
storage: SecureStore,
|
storage,
|
||||||
scheme: "duooomi",
|
scheme: 'duooomi',
|
||||||
plugins: [
|
fetchOptions: {
|
||||||
loomartPlugin(),
|
auth: {
|
||||||
usernameClient(),
|
type: 'Bearer',
|
||||||
phoneNumberClient(),
|
token: async () => {
|
||||||
emailOTPClient(),
|
const Authorization = await getAuthToken()
|
||||||
genericOAuthClient(),
|
return Authorization
|
||||||
jwtClient(),
|
},
|
||||||
adminClient(),
|
},
|
||||||
organizationClient(),
|
customFetchImpl: fetchWithLogger as typeof fetch,
|
||||||
expoClient({
|
},
|
||||||
storage: SecureStore
|
plugins: [
|
||||||
})
|
createSkerClientPlugin(),
|
||||||
],
|
stripeClient(),
|
||||||
});
|
usernameClient(),
|
||||||
|
phoneNumberClient(),
|
||||||
|
emailOTPClient(),
|
||||||
|
genericOAuthClient(),
|
||||||
|
jwtClient(),
|
||||||
|
adminClient(),
|
||||||
|
organizationClient(),
|
||||||
|
expoClient({
|
||||||
|
storage: storage as any,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
export const { signIn, signUp, signOut, useSession, $Infer, loomart, admin } = authClient;
|
export const { signIn, signUp, signOut, useSession, $Infer, admin, forgetPassword, resetPassword, emailOtp } =
|
||||||
|
authClient
|
||||||
|
|
||||||
|
export const subscription: ISubscription = Reflect.get(authClient, 'subscription')
|
||||||
|
|||||||
101
lib/fetch-logger.ts
Normal file
101
lib/fetch-logger.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import { router } from 'expo-router'
|
||||||
|
|
||||||
|
import { storage } from './storage'
|
||||||
|
|
||||||
|
interface FetchLoggerOptions {
|
||||||
|
enableLogging?: boolean
|
||||||
|
logRequest?: boolean
|
||||||
|
logResponse?: boolean
|
||||||
|
logError?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOptions: FetchLoggerOptions = {
|
||||||
|
enableLogging: __DEV__,
|
||||||
|
logRequest: true,
|
||||||
|
logResponse: true,
|
||||||
|
logError: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalFetch = global.fetch
|
||||||
|
|
||||||
|
export const createFetchWithLogger = (options: FetchLoggerOptions = {}) => {
|
||||||
|
const config = { ...defaultOptions, ...options }
|
||||||
|
|
||||||
|
return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
||||||
|
const startTime = Date.now()
|
||||||
|
const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url
|
||||||
|
const method = init?.method || 'GET'
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = await storage.getItem('token')
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
init = {
|
||||||
|
...init,
|
||||||
|
headers: {
|
||||||
|
...init?.headers,
|
||||||
|
authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await originalFetch(input, init)
|
||||||
|
|
||||||
|
if (response.status === 401) {
|
||||||
|
console.warn('🔐 401 未授权,跳转到登录页')
|
||||||
|
// router.replace('/auth')
|
||||||
|
router.push('/auth')
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
} catch (error) {
|
||||||
|
const duration = Date.now() - startTime
|
||||||
|
|
||||||
|
if (config.enableLogging && config.logError) {
|
||||||
|
console.group(`❌ [FETCH 错误] ${method} ${url}`)
|
||||||
|
console.error('📍 URL:', url)
|
||||||
|
console.error('🔧 Method:', method)
|
||||||
|
console.error('⏱️ Duration:', `${duration}ms`)
|
||||||
|
|
||||||
|
if (init?.headers) {
|
||||||
|
console.error('📋 Request Headers:', JSON.stringify(init.headers, null, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (init?.body) {
|
||||||
|
try {
|
||||||
|
const bodyContent = typeof init.body === 'string' ? init.body : JSON.stringify(init.body)
|
||||||
|
console.error('📦 Request Body:', bodyContent)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('📦 Request Body: [无法序列化]')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error('💥 Error Type:', error?.constructor?.name || 'Unknown')
|
||||||
|
console.error('💥 Error Message:', error instanceof Error ? error.message : String(error))
|
||||||
|
|
||||||
|
if (error instanceof Error && error.stack) {
|
||||||
|
console.error('📚 Stack Trace:', error.stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error && typeof error === 'object') {
|
||||||
|
console.error('🔍 Error Details:', JSON.stringify(error, Object.getOwnPropertyNames(error), 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
console.groupEnd()
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchWithLogger = createFetchWithLogger()
|
||||||
|
|
||||||
|
export const setupGlobalFetchLogger = (options?: FetchLoggerOptions) => {
|
||||||
|
const loggedFetch = createFetchWithLogger(options)
|
||||||
|
global.fetch = loggedFetch as typeof fetch
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
global.fetch = originalFetch
|
||||||
|
}
|
||||||
|
}
|
||||||
24
lib/i18n.ts
24
lib/i18n.ts
@@ -1,11 +1,11 @@
|
|||||||
import i18n from 'i18next'
|
import i18n from 'i18next'
|
||||||
import { initReactI18next } from 'react-i18next'
|
import { initReactI18next } from 'react-i18next'
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
|
||||||
import { NativeModules, Platform } from 'react-native'
|
import { NativeModules, Platform } from 'react-native'
|
||||||
|
import { storage } from './storage'
|
||||||
|
|
||||||
// 导入语言资源
|
// 导入语言资源
|
||||||
import zhCN from '../locales/zh-CN.json'
|
|
||||||
import enUS from '../locales/en-US.json'
|
import enUS from '../locales/en-US.json'
|
||||||
|
import zhCN from '../locales/zh-CN.json'
|
||||||
|
|
||||||
// 定义支持的语言
|
// 定义支持的语言
|
||||||
export const SUPPORTED_LANGUAGES = {
|
export const SUPPORTED_LANGUAGES = {
|
||||||
@@ -52,7 +52,7 @@ const languageDetector = {
|
|||||||
detect: async (callback: (lng: string) => void) => {
|
detect: async (callback: (lng: string) => void) => {
|
||||||
try {
|
try {
|
||||||
// 1. 优先使用用户保存的语言设置
|
// 1. 优先使用用户保存的语言设置
|
||||||
const savedLanguage = await AsyncStorage.getItem('app_language')
|
const savedLanguage = await storage.getItem('app_language')
|
||||||
if (savedLanguage) {
|
if (savedLanguage) {
|
||||||
// 兼容旧的语言代码格式
|
// 兼容旧的语言代码格式
|
||||||
if (savedLanguage === 'zh' || savedLanguage === 'zh-CN') {
|
if (savedLanguage === 'zh' || savedLanguage === 'zh-CN') {
|
||||||
@@ -63,7 +63,7 @@ const languageDetector = {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 如果没有保存的语言,尝试使用系统语言
|
// 2. 如果没有保存的语言,尝试使用系统语言
|
||||||
const systemLang = getSystemLanguage()
|
const systemLang = getSystemLanguage()
|
||||||
callback(systemLang)
|
callback(systemLang)
|
||||||
@@ -73,11 +73,11 @@ const languageDetector = {
|
|||||||
callback('zh-CN')
|
callback('zh-CN')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
init: () => {},
|
init: () => { },
|
||||||
cacheUserLanguage: async (lng: string) => {
|
cacheUserLanguage: async (lng: string) => {
|
||||||
try {
|
try {
|
||||||
// AsyncStorage 在 Android 和 iOS 上都正常工作
|
// AsyncStorage 在 Android 和 iOS 上都正常工作
|
||||||
await AsyncStorage.setItem('app_language', lng)
|
await storage.setItem('app_language', lng)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving language:', error)
|
console.error('Error saving language:', error)
|
||||||
}
|
}
|
||||||
@@ -97,25 +97,25 @@ i18n.use(languageDetector)
|
|||||||
translation: enUS,
|
translation: enUS,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// 默认语言
|
// 默认语言
|
||||||
fallbackLng: 'zh-CN',
|
fallbackLng: 'zh-CN',
|
||||||
|
|
||||||
// 调试模式(开发环境开启)
|
// 调试模式(开发环境开启)
|
||||||
debug: false,
|
debug: false,
|
||||||
|
|
||||||
// 兼容性配置:使用 v3 格式处理复数,避免 Android 端 Intl.PluralRules 不支持的问题
|
// 兼容性配置:使用 v3 格式处理复数,避免 Android 端 Intl.PluralRules 不支持的问题
|
||||||
compatibilityJSON: 'v3',
|
compatibilityJSON: 'v3',
|
||||||
|
|
||||||
// 插值配置
|
// 插值配置
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false, // React已经处理了XSS防护
|
escapeValue: false, // React已经处理了XSS防护
|
||||||
},
|
},
|
||||||
|
|
||||||
// 命名空间配置
|
// 命名空间配置
|
||||||
defaultNS: 'translation',
|
defaultNS: 'translation',
|
||||||
ns: ['translation'],
|
ns: ['translation'],
|
||||||
|
|
||||||
// 键值分隔符
|
// 键值分隔符
|
||||||
keySeparator: '.',
|
keySeparator: '.',
|
||||||
nsSeparator: ':',
|
nsSeparator: ':',
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import type { loomart } from "./loomart";
|
|
||||||
export default () => {
|
|
||||||
return {
|
|
||||||
id: "loomart",
|
|
||||||
$InferServerPlugin: {} as ReturnType<typeof loomart>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
7333
lib/plugins/loomart.d.ts
vendored
7333
lib/plugins/loomart.d.ts
vendored
File diff suppressed because it is too large
Load Diff
37
lib/plugins/stripe-plugin.ts
Normal file
37
lib/plugins/stripe-plugin.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import type { BetterAuthClientPlugin } from "better-auth";
|
||||||
|
import type { stripe } from "./stripe";
|
||||||
|
|
||||||
|
export const stripeClient = <
|
||||||
|
O extends {
|
||||||
|
subscription: boolean;
|
||||||
|
},
|
||||||
|
>(
|
||||||
|
options?: O,
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
id: "stripe-client",
|
||||||
|
$InferServerPlugin: {} as ReturnType<
|
||||||
|
typeof stripe<
|
||||||
|
O["subscription"] extends true
|
||||||
|
? {
|
||||||
|
stripeClient: any;
|
||||||
|
stripeWebhookSecret: string;
|
||||||
|
subscription: {
|
||||||
|
enabled: true;
|
||||||
|
plans: [];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
stripeClient: any;
|
||||||
|
stripeWebhookSecret: string;
|
||||||
|
}
|
||||||
|
>
|
||||||
|
>,
|
||||||
|
pathMethods: {
|
||||||
|
"/subscription/restore": "POST",
|
||||||
|
"/subscription/billing-portal": "POST",
|
||||||
|
"/alipay/app-pay": "POST",
|
||||||
|
"/alipay/auth-info": "POST",
|
||||||
|
},
|
||||||
|
} satisfies BetterAuthClientPlugin;
|
||||||
|
};
|
||||||
422
lib/plugins/stripe.d.ts
vendored
Normal file
422
lib/plugins/stripe.d.ts
vendored
Normal file
@@ -0,0 +1,422 @@
|
|||||||
|
import { GenericEndpointContext, User, Session, InferOptionSchema, BetterAuthPlugin } from 'better-auth';
|
||||||
|
import Stripe from 'stripe';
|
||||||
|
|
||||||
|
declare const subscriptions: {
|
||||||
|
subscription: {
|
||||||
|
fields: {
|
||||||
|
plan: {
|
||||||
|
type: "string";
|
||||||
|
required: true;
|
||||||
|
};
|
||||||
|
referenceId: {
|
||||||
|
type: "string";
|
||||||
|
required: true;
|
||||||
|
};
|
||||||
|
stripeCustomerId: {
|
||||||
|
type: "string";
|
||||||
|
required: false;
|
||||||
|
};
|
||||||
|
stripeSubscriptionId: {
|
||||||
|
type: "string";
|
||||||
|
required: false;
|
||||||
|
unique: true;
|
||||||
|
};
|
||||||
|
status: {
|
||||||
|
type: "string";
|
||||||
|
defaultValue: string;
|
||||||
|
};
|
||||||
|
periodStart: {
|
||||||
|
type: "date";
|
||||||
|
required: false;
|
||||||
|
};
|
||||||
|
periodEnd: {
|
||||||
|
type: "date";
|
||||||
|
required: false;
|
||||||
|
};
|
||||||
|
trialStart: {
|
||||||
|
type: "date";
|
||||||
|
required: false;
|
||||||
|
};
|
||||||
|
trialEnd: {
|
||||||
|
type: "date";
|
||||||
|
required: false;
|
||||||
|
};
|
||||||
|
cancelAtPeriodEnd: {
|
||||||
|
type: "boolean";
|
||||||
|
required: false;
|
||||||
|
defaultValue: false;
|
||||||
|
};
|
||||||
|
seats: {
|
||||||
|
type: "number";
|
||||||
|
required: false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
declare const user: {
|
||||||
|
user: {
|
||||||
|
fields: {
|
||||||
|
stripeCustomerId: {
|
||||||
|
type: "string";
|
||||||
|
required: false;
|
||||||
|
};
|
||||||
|
metadata: {
|
||||||
|
type: "json";
|
||||||
|
required: false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type StripePlan = {
|
||||||
|
/**
|
||||||
|
* Monthly price id
|
||||||
|
*/
|
||||||
|
priceId?: string;
|
||||||
|
/**
|
||||||
|
* To use lookup key instead of price id
|
||||||
|
*
|
||||||
|
* https://docs.stripe.com/products-prices/
|
||||||
|
* manage-prices#lookup-keys
|
||||||
|
*/
|
||||||
|
lookupKey?: string;
|
||||||
|
/**
|
||||||
|
* A yearly discount price id
|
||||||
|
*
|
||||||
|
* useful when you want to offer a discount for
|
||||||
|
* yearly subscription
|
||||||
|
*/
|
||||||
|
annualDiscountPriceId?: string;
|
||||||
|
/**
|
||||||
|
* To use lookup key instead of price id
|
||||||
|
*
|
||||||
|
* https://docs.stripe.com/products-prices/
|
||||||
|
* manage-prices#lookup-keys
|
||||||
|
*/
|
||||||
|
annualDiscountLookupKey?: string;
|
||||||
|
/**
|
||||||
|
* Plan name
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* Limits for the plan
|
||||||
|
*/
|
||||||
|
limits?: Record<string, number>;
|
||||||
|
/**
|
||||||
|
* Plan group name
|
||||||
|
*
|
||||||
|
* useful when you want to group plans or
|
||||||
|
* when a user can subscribe to multiple plans.
|
||||||
|
*/
|
||||||
|
group?: string;
|
||||||
|
/**
|
||||||
|
* Free trial days
|
||||||
|
*/
|
||||||
|
freeTrial?: {
|
||||||
|
/**
|
||||||
|
* Number of days
|
||||||
|
*/
|
||||||
|
days: number;
|
||||||
|
/**
|
||||||
|
* A function that will be called when the trial
|
||||||
|
* starts.
|
||||||
|
*
|
||||||
|
* @param subscription
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
onTrialStart?: (subscription: Subscription) => Promise<void>;
|
||||||
|
/**
|
||||||
|
* A function that will be called when the trial
|
||||||
|
* ends
|
||||||
|
*
|
||||||
|
* @param subscription - Subscription
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
onTrialEnd?: (data: {
|
||||||
|
subscription: Subscription;
|
||||||
|
}, ctx: GenericEndpointContext) => Promise<void>;
|
||||||
|
/**
|
||||||
|
* A function that will be called when the trial
|
||||||
|
* expired.
|
||||||
|
* @param subscription - Subscription
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
onTrialExpired?: (subscription: Subscription, ctx: GenericEndpointContext) => Promise<void>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
type actionType = 'upgrade-subscription' | 'list-subscription' | 'cancel-subscription' | 'restore-subscription' | 'meter-event' | 'credit-topup' | 'adjust-meter-event' | 'meter-event-summary' | 'create-subscription' | 'get-pricing-table' | 'billing-portal';
|
||||||
|
interface Subscription {
|
||||||
|
/**
|
||||||
|
* Database identifier
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* The plan name
|
||||||
|
*/
|
||||||
|
plan: string;
|
||||||
|
/**
|
||||||
|
* Stripe customer id
|
||||||
|
*/
|
||||||
|
stripeCustomerId?: string;
|
||||||
|
/**
|
||||||
|
* Stripe subscription id
|
||||||
|
*/
|
||||||
|
stripeSubscriptionId?: string;
|
||||||
|
/**
|
||||||
|
* Trial start date
|
||||||
|
*/
|
||||||
|
trialStart?: Date;
|
||||||
|
/**
|
||||||
|
* Trial end date
|
||||||
|
*/
|
||||||
|
trialEnd?: Date;
|
||||||
|
/**
|
||||||
|
* Price Id for the subscription
|
||||||
|
*/
|
||||||
|
priceId?: string;
|
||||||
|
/**
|
||||||
|
* To what reference id the subscription belongs to
|
||||||
|
* @example
|
||||||
|
* - userId for a user
|
||||||
|
* - workspace id for a saas platform
|
||||||
|
* - website id for a hosting platform
|
||||||
|
*
|
||||||
|
* @default - userId
|
||||||
|
*/
|
||||||
|
referenceId: string;
|
||||||
|
/**
|
||||||
|
* Subscription status
|
||||||
|
*/
|
||||||
|
status: 'active' | 'canceled' | 'incomplete' | 'incomplete_expired' | 'past_due' | 'paused' | 'trialing' | 'unpaid';
|
||||||
|
/**
|
||||||
|
* The billing cycle start date
|
||||||
|
*/
|
||||||
|
periodStart?: Date;
|
||||||
|
/**
|
||||||
|
* The billing cycle end date
|
||||||
|
*/
|
||||||
|
periodEnd?: Date;
|
||||||
|
/**
|
||||||
|
* Cancel at period end
|
||||||
|
*/
|
||||||
|
cancelAtPeriodEnd?: boolean;
|
||||||
|
/**
|
||||||
|
* A field to group subscriptions so you can have multiple subscriptions
|
||||||
|
* for one reference id
|
||||||
|
*/
|
||||||
|
groupId?: string;
|
||||||
|
/**
|
||||||
|
* Number of seats for the subscription (useful for team plans)
|
||||||
|
*/
|
||||||
|
seats?: number;
|
||||||
|
}
|
||||||
|
interface AlipayNotifyData {
|
||||||
|
notify_time: string;
|
||||||
|
notify_type: string;
|
||||||
|
notify_id: string;
|
||||||
|
app_id: string;
|
||||||
|
charset: string;
|
||||||
|
version: string;
|
||||||
|
sign_type: string;
|
||||||
|
sign: string;
|
||||||
|
trade_no: string;
|
||||||
|
out_trade_no: string;
|
||||||
|
out_biz_no?: string;
|
||||||
|
buyer_id?: string;
|
||||||
|
buyer_logon_id?: string;
|
||||||
|
seller_id?: string;
|
||||||
|
seller_email?: string;
|
||||||
|
trade_status: 'WAIT_BUYER_PAY' | 'TRADE_CLOSED' | 'TRADE_SUCCESS' | 'TRADE_FINISHED';
|
||||||
|
total_amount: string;
|
||||||
|
receipt_amount?: string;
|
||||||
|
invoice_amount?: string;
|
||||||
|
buyer_pay_amount?: string;
|
||||||
|
point_amount?: string;
|
||||||
|
refund_fee?: string;
|
||||||
|
subject?: string;
|
||||||
|
body?: string;
|
||||||
|
gmt_create?: string;
|
||||||
|
gmt_payment?: string;
|
||||||
|
gmt_refund?: string;
|
||||||
|
gmt_close?: string;
|
||||||
|
fund_bill_list?: string;
|
||||||
|
passback_params?: string;
|
||||||
|
voucher_detail_list?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
interface StripeOptions {
|
||||||
|
/**
|
||||||
|
* Stripe Client
|
||||||
|
*/
|
||||||
|
stripeClient: Stripe;
|
||||||
|
/**
|
||||||
|
* Stripe Webhook Secret
|
||||||
|
*
|
||||||
|
* @description Stripe webhook secret key
|
||||||
|
*/
|
||||||
|
stripeWebhookSecret: string;
|
||||||
|
/**
|
||||||
|
* Enable customer creation when a user signs up
|
||||||
|
*/
|
||||||
|
createCustomerOnSignUp?: boolean;
|
||||||
|
/**
|
||||||
|
* A callback to run after a customer has been created
|
||||||
|
* @param customer - Customer Data
|
||||||
|
* @param stripeCustomer - Stripe Customer Data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
onCustomerCreate?: (data: {
|
||||||
|
stripeCustomer: Stripe.Customer;
|
||||||
|
user: User & {
|
||||||
|
stripeCustomerId: string;
|
||||||
|
};
|
||||||
|
}, ctx: GenericEndpointContext) => Promise<void>;
|
||||||
|
/**
|
||||||
|
* A custom function to get the customer create
|
||||||
|
* params
|
||||||
|
* @param data - data containing user and session
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
getCustomerCreateParams?: (user: User, ctx: GenericEndpointContext) => Promise<Partial<Stripe.CustomerCreateParams>>;
|
||||||
|
/**
|
||||||
|
* Subscriptions
|
||||||
|
*/
|
||||||
|
subscription?: {
|
||||||
|
enabled: boolean;
|
||||||
|
/**
|
||||||
|
* Subscription Configuration
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* List of plan
|
||||||
|
*/
|
||||||
|
plans: StripePlan[] | (() => StripePlan[] | Promise<StripePlan[]>);
|
||||||
|
/**
|
||||||
|
* Require email verification before a user is allowed to upgrade
|
||||||
|
* their subscriptions
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
requireEmailVerification?: boolean;
|
||||||
|
/**
|
||||||
|
* A callback to run after a user has subscribed to a package
|
||||||
|
* @param event - Stripe Event
|
||||||
|
* @param subscription - Subscription Data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
onSubscriptionComplete?: (data: {
|
||||||
|
event: Stripe.Event;
|
||||||
|
stripeSubscription: Stripe.Subscription;
|
||||||
|
subscription: Subscription;
|
||||||
|
plan: StripePlan;
|
||||||
|
}, ctx: GenericEndpointContext) => Promise<void>;
|
||||||
|
/**
|
||||||
|
* A callback to run after a user is about to cancel their subscription
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
onSubscriptionUpdate?: (data: {
|
||||||
|
event: Stripe.Event;
|
||||||
|
subscription: Subscription;
|
||||||
|
}) => Promise<void>;
|
||||||
|
/**
|
||||||
|
* A callback to run after a user is about to cancel their subscription
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
onSubscriptionCancel?: (data: {
|
||||||
|
event?: Stripe.Event;
|
||||||
|
subscription: Subscription;
|
||||||
|
stripeSubscription: Stripe.Subscription;
|
||||||
|
cancellationDetails?: Stripe.Subscription.CancellationDetails | null;
|
||||||
|
}) => Promise<void>;
|
||||||
|
/**
|
||||||
|
* A function to check if the reference id is valid
|
||||||
|
* and belongs to the user
|
||||||
|
*
|
||||||
|
* @param data - data containing user, session and referenceId
|
||||||
|
* @param ctx - the context object
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
authorizeReference?: (data: {
|
||||||
|
user: User & Record<string, any>;
|
||||||
|
session: Session & Record<string, any>;
|
||||||
|
referenceId: string;
|
||||||
|
action: actionType;
|
||||||
|
}, ctx: GenericEndpointContext) => Promise<boolean>;
|
||||||
|
/**
|
||||||
|
* A callback to run after a user has deleted their subscription
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
onSubscriptionDeleted?: (data: {
|
||||||
|
event: Stripe.Event;
|
||||||
|
stripeSubscription: Stripe.Subscription;
|
||||||
|
subscription: Subscription;
|
||||||
|
}) => Promise<void>;
|
||||||
|
/**
|
||||||
|
* parameters for session create params
|
||||||
|
*
|
||||||
|
* @param data - data containing user, session and plan
|
||||||
|
* @param ctx - the context object
|
||||||
|
*/
|
||||||
|
getCheckoutSessionParams?: (data: {
|
||||||
|
user: User & Record<string, any>;
|
||||||
|
session: Session & Record<string, any>;
|
||||||
|
plan: StripePlan;
|
||||||
|
subscription: Subscription;
|
||||||
|
}, ctx: GenericEndpointContext) => Promise<{
|
||||||
|
params?: Stripe.Checkout.SessionCreateParams;
|
||||||
|
options?: Stripe.RequestOptions;
|
||||||
|
}> | {
|
||||||
|
params?: Stripe.Checkout.SessionCreateParams;
|
||||||
|
options?: Stripe.RequestOptions;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Enable organization subscription
|
||||||
|
*/
|
||||||
|
organization?: {
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* A callback to run after a stripe event is received
|
||||||
|
* @param event - Stripe Event
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
onEvent?: (event: Stripe.Event) => Promise<void>;
|
||||||
|
/**
|
||||||
|
* A callback to run after a one-time payment checkout is completed
|
||||||
|
* (e.g., credit top-up)
|
||||||
|
* @param checkoutSession - Stripe Checkout Session
|
||||||
|
* @param ctx - Generic Endpoint Context
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
onCheckoutComplete?: (data: {
|
||||||
|
session: Stripe.Checkout.Session;
|
||||||
|
}, ctx: GenericEndpointContext) => Promise<void>;
|
||||||
|
/**
|
||||||
|
* Schema for the stripe plugin
|
||||||
|
*/
|
||||||
|
schema?: InferOptionSchema<typeof subscriptions & typeof user>;
|
||||||
|
/**
|
||||||
|
* Pricing table config
|
||||||
|
*/
|
||||||
|
pricingTable?: {
|
||||||
|
id: string;
|
||||||
|
key: string;
|
||||||
|
meteredPriceId: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Alipay Configuration
|
||||||
|
*/
|
||||||
|
alipay?: {
|
||||||
|
endpoint: string;
|
||||||
|
/**
|
||||||
|
* Callback when Alipay sends a webhook notification
|
||||||
|
* @param data The notification parameters from Alipay
|
||||||
|
*/
|
||||||
|
beforeNotify?: (data: AlipayNotifyData, ctx: GenericEndpointContext) => Promise<void>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const stripe: <O extends StripeOptions>(options: O) => BetterAuthPlugin;
|
||||||
|
|
||||||
|
export { stripe };
|
||||||
|
export type { StripePlan, Subscription };
|
||||||
15
lib/storage.native.ts
Normal file
15
lib/storage.native.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import * as SecureStore from "expo-secure-store";
|
||||||
|
|
||||||
|
export const storage = {
|
||||||
|
async getItem(key: string): Promise<string | null> {
|
||||||
|
return await SecureStore.getItemAsync(key);
|
||||||
|
},
|
||||||
|
|
||||||
|
async setItem(key: string, value: string): Promise<void> {
|
||||||
|
await SecureStore.setItemAsync(key, value);
|
||||||
|
},
|
||||||
|
|
||||||
|
async removeItem(key: string): Promise<void> {
|
||||||
|
await SecureStore.deleteItemAsync(key);
|
||||||
|
},
|
||||||
|
};
|
||||||
17
lib/storage.ts
Normal file
17
lib/storage.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
declare const window: any;
|
||||||
|
export const storage = {
|
||||||
|
async getItem(key: string): Promise<string | null> {
|
||||||
|
if (typeof window === "undefined") return null;
|
||||||
|
return window.localStorage.getItem(key);
|
||||||
|
},
|
||||||
|
|
||||||
|
async setItem(key: string, value: string): Promise<void> {
|
||||||
|
if (typeof window === "undefined") return;
|
||||||
|
window.localStorage.setItem(key, value);
|
||||||
|
},
|
||||||
|
|
||||||
|
async removeItem(key: string): Promise<void> {
|
||||||
|
if (typeof window === "undefined") return;
|
||||||
|
window.localStorage.removeItem(key);
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user