@deiondz/better-auth-razorpay 2.0.2 → 2.0.3
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/README.md +43 -17
- package/client/hooks.ts +40 -26
- package/client/types.ts +28 -1
- package/client.ts +97 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,7 +112,9 @@ export const auth = betterAuth({
|
|
|
112
112
|
})
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
3. **Add Client Plugin**
|
|
115
|
+
3. **Add Client Plugin (required to avoid 404s)**
|
|
116
|
+
|
|
117
|
+
Add the Razorpay client plugin so requests use the correct paths. **Without it, calls like `authClient.api.get('/razorpay/get-plans')` can hit wrong URLs (e.g. `POST /api/auth/api/get` 404).** The plugin exposes `authClient.razorpay.*` methods that use the plugin’s route map.
|
|
116
118
|
|
|
117
119
|
```typescript
|
|
118
120
|
// src/lib/auth-client.ts
|
|
@@ -668,23 +670,42 @@ Handle Razorpay webhook events (automatically called by Razorpay).
|
|
|
668
670
|
|
|
669
671
|
## Client Usage
|
|
670
672
|
|
|
671
|
-
###
|
|
673
|
+
### Preferred: authClient.razorpay.* (method-based API)
|
|
672
674
|
|
|
673
|
-
|
|
675
|
+
When you add `razorpayClientPlugin()` to `createAuthClient({ plugins: [...] })`, the client gets `authClient.razorpay` with explicit methods. **Use these so requests hit the correct paths** (avoids 404s like `POST /api/auth/api/get`):
|
|
674
676
|
|
|
675
677
|
```typescript
|
|
676
678
|
import { authClient } from '@/lib/auth-client'
|
|
677
679
|
|
|
678
|
-
// GET
|
|
679
|
-
const
|
|
680
|
+
// GET plans
|
|
681
|
+
const plansRes = await authClient.razorpay.getPlans()
|
|
682
|
+
if (plansRes.success) console.log(plansRes.data)
|
|
683
|
+
|
|
684
|
+
// List subscriptions
|
|
685
|
+
const listRes = await authClient.razorpay.listSubscriptions({ referenceId: 'optional' })
|
|
680
686
|
|
|
681
|
-
//
|
|
682
|
-
const result = await authClient.
|
|
683
|
-
|
|
687
|
+
// Create or update subscription (returns checkoutUrl for Razorpay payment page)
|
|
688
|
+
const result = await authClient.razorpay.createOrUpdateSubscription({
|
|
689
|
+
plan: 'Starter',
|
|
690
|
+
annual: false,
|
|
691
|
+
seats: 1,
|
|
692
|
+
})
|
|
693
|
+
if (result.success) window.location.href = result.data.checkoutUrl
|
|
694
|
+
|
|
695
|
+
// Cancel, restore, verify payment
|
|
696
|
+
await authClient.razorpay.cancelSubscription({ subscriptionId: 'sub_xxx', immediately: false })
|
|
697
|
+
await authClient.razorpay.restoreSubscription({ subscriptionId: 'sub_xxx' })
|
|
698
|
+
await authClient.razorpay.verifyPayment({
|
|
699
|
+
razorpay_payment_id: 'pay_xxx',
|
|
700
|
+
razorpay_subscription_id: 'sub_xxx',
|
|
701
|
+
razorpay_signature: '...',
|
|
684
702
|
})
|
|
685
|
-
// result.data.checkoutUrl -> redirect user to Razorpay payment page
|
|
686
703
|
```
|
|
687
704
|
|
|
705
|
+
### Fallback: authClient.api (only if client plugin is not used)
|
|
706
|
+
|
|
707
|
+
If you do not add the client plugin, you can use `authClient.api.get/post` with the Razorpay paths. **This can lead to 404s** (e.g. `POST /api/auth/api/get`) depending on how your auth client resolves paths. Prefer adding the client plugin and using `authClient.razorpay.*` instead.
|
|
708
|
+
|
|
688
709
|
### Type Safety
|
|
689
710
|
|
|
690
711
|
Infer types from your auth configuration:
|
|
@@ -704,7 +725,7 @@ type User = typeof authClient.$Infer.Session.user
|
|
|
704
725
|
|
|
705
726
|
## TanStack Query Hooks
|
|
706
727
|
|
|
707
|
-
The plugin works with **TanStack Query
|
|
728
|
+
The plugin works with **TanStack Query**. We provide optional pre-built hooks that accept your auth client; when you use `razorpayClientPlugin()`, the hooks call `authClient.razorpay.*` so requests hit the correct paths. If you prefer a different setup, use `authClient.razorpay.getPlans()`, `authClient.razorpay.createOrUpdateSubscription(...)`, etc., or build your own hooks around those methods.
|
|
708
729
|
|
|
709
730
|
To use our pre-built hooks, install peer dependencies:
|
|
710
731
|
|
|
@@ -1357,38 +1378,43 @@ async function initializeRazorpayCheckout(subscriptionId: string) {
|
|
|
1357
1378
|
|
|
1358
1379
|
### Common Issues
|
|
1359
1380
|
|
|
1360
|
-
**1. "
|
|
1381
|
+
**1. "POST /api/auth/api/get 404" or Razorpay requests returning 404**
|
|
1382
|
+
- Add the **client plugin** to your auth client: `createAuthClient({ plugins: [razorpayClientPlugin(), ...] })`
|
|
1383
|
+
- Use **method-based API** instead of `authClient.api.get/post`: call `authClient.razorpay.getPlans()`, `authClient.razorpay.createOrUpdateSubscription(...)`, etc., so requests use the plugin’s route map and hit the correct paths
|
|
1384
|
+
- The TanStack hooks (`usePlans`, `useSubscriptions`, etc.) use `authClient.razorpay` when present, so they work correctly once the client plugin is added
|
|
1385
|
+
|
|
1386
|
+
**2. "Plan not found in configured plans"**
|
|
1361
1387
|
- Ensure the plan ID exists in Razorpay dashboard
|
|
1362
1388
|
- Add the plan ID to the `plans` array in plugin configuration
|
|
1363
1389
|
- Re-run Better Auth CLI after updating plans
|
|
1364
1390
|
|
|
1365
|
-
**
|
|
1391
|
+
**3. "Webhook signature verification failed"**
|
|
1366
1392
|
- Verify webhook secret matches Razorpay dashboard
|
|
1367
1393
|
- Ensure webhook URL is correct: `https://yourdomain.com/api/auth/razorpay/webhook`
|
|
1368
1394
|
- Check that request body is not modified
|
|
1369
1395
|
- Verify `x-razorpay-signature` header is present
|
|
1370
1396
|
|
|
1371
|
-
**
|
|
1397
|
+
**4. "Subscription already exists"**
|
|
1372
1398
|
- User already has an active subscription
|
|
1373
1399
|
- Cancel or pause existing subscription first
|
|
1374
1400
|
- Check subscription status before creating new one
|
|
1375
1401
|
|
|
1376
|
-
**
|
|
1402
|
+
**5. "User not authenticated"**
|
|
1377
1403
|
- Ensure user is logged in via Better Auth
|
|
1378
1404
|
- Check session middleware is properly configured
|
|
1379
1405
|
- Verify `sessionMiddleware` is used in endpoint configuration
|
|
1380
1406
|
|
|
1381
|
-
**
|
|
1407
|
+
**6. "Subscription not found"**
|
|
1382
1408
|
- Subscription may have been deleted
|
|
1383
1409
|
- Check subscription ID is correct
|
|
1384
1410
|
- Verify subscription belongs to the user
|
|
1385
1411
|
|
|
1386
|
-
**
|
|
1412
|
+
**7. Database Schema Issues**
|
|
1387
1413
|
- Run `npx @better-auth/cli@latest migrate` after adding plugin
|
|
1388
1414
|
- For Prisma/Drizzle: Run `npx @better-auth/cli@latest generate`
|
|
1389
1415
|
- Check that user additional fields are properly configured
|
|
1390
1416
|
|
|
1391
|
-
**
|
|
1417
|
+
**8. Type Errors**
|
|
1392
1418
|
- Ensure you're using `createAuthClient<typeof auth>()` for type inference
|
|
1393
1419
|
- Import types from `@deiondz/better-auth-razorpay`
|
|
1394
1420
|
- Check that plugin is properly exported
|
package/client/hooks.ts
CHANGED
|
@@ -46,73 +46,86 @@ function assertSuccess<T>(res: unknown): asserts res is { success: true; data: T
|
|
|
46
46
|
throw new Error('Invalid response')
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/** Fetch plans (GET /razorpay/get-plans). */
|
|
49
|
+
/** Fetch plans (GET /razorpay/get-plans). Prefers client.razorpay when available to avoid 404s. */
|
|
50
50
|
async function fetchPlans(client: RazorpayAuthClient): Promise<PlanSummary[]> {
|
|
51
|
-
const res =
|
|
51
|
+
const res = client.razorpay
|
|
52
|
+
? await client.razorpay.getPlans()
|
|
53
|
+
: await client.api.get(`${BASE}/get-plans`)
|
|
52
54
|
assertSuccess<PlanSummary[]>(res)
|
|
53
55
|
return res.data
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
/** Fetch subscriptions list (GET /razorpay/subscription/list). */
|
|
58
|
+
/** Fetch subscriptions list (GET /razorpay/subscription/list). Prefers client.razorpay when available. */
|
|
57
59
|
async function fetchSubscriptions(
|
|
58
60
|
client: RazorpayAuthClient,
|
|
59
61
|
input?: ListSubscriptionsInput
|
|
60
62
|
): Promise<ListSubscriptionsResponse['data']> {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
const res = client.razorpay
|
|
64
|
+
? await client.razorpay.listSubscriptions(input)
|
|
65
|
+
: (() => {
|
|
66
|
+
const query: Record<string, string> = {}
|
|
67
|
+
if (input?.referenceId) query.referenceId = input.referenceId
|
|
68
|
+
const path = `${BASE}/subscription/list`
|
|
69
|
+
return Object.keys(query).length > 0
|
|
70
|
+
? client.api.get(path, { query })
|
|
71
|
+
: client.api.get(path)
|
|
72
|
+
})()
|
|
68
73
|
assertSuccess<ListSubscriptionsResponse['data']>(res)
|
|
69
74
|
return res.data
|
|
70
75
|
}
|
|
71
76
|
|
|
72
|
-
/** Create or update subscription (POST /razorpay/subscription/create-or-update). */
|
|
77
|
+
/** Create or update subscription (POST /razorpay/subscription/create-or-update). Prefers client.razorpay when available. */
|
|
73
78
|
async function createOrUpdateSubscription(
|
|
74
79
|
client: RazorpayAuthClient,
|
|
75
80
|
input: CreateOrUpdateSubscriptionInput
|
|
76
81
|
): Promise<CreateOrUpdateSubscriptionResponse['data']> {
|
|
77
|
-
const res =
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
const res = client.razorpay
|
|
83
|
+
? await client.razorpay.createOrUpdateSubscription(input)
|
|
84
|
+
: await client.api.post(`${BASE}/subscription/create-or-update`, {
|
|
85
|
+
body: input as unknown as Record<string, unknown>,
|
|
86
|
+
})
|
|
80
87
|
assertSuccess<CreateOrUpdateSubscriptionResponse['data']>(res)
|
|
81
88
|
return res.data
|
|
82
89
|
}
|
|
83
90
|
|
|
84
|
-
/** Cancel subscription (POST /razorpay/subscription/cancel). */
|
|
91
|
+
/** Cancel subscription (POST /razorpay/subscription/cancel). Prefers client.razorpay when available. */
|
|
85
92
|
async function cancelSubscription(
|
|
86
93
|
client: RazorpayAuthClient,
|
|
87
94
|
input: CancelSubscriptionInput
|
|
88
95
|
): Promise<CancelSubscriptionResponse['data']> {
|
|
89
|
-
const res =
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
const res = client.razorpay
|
|
97
|
+
? await client.razorpay.cancelSubscription(input)
|
|
98
|
+
: await client.api.post(`${BASE}/subscription/cancel`, {
|
|
99
|
+
body: input as unknown as Record<string, unknown>,
|
|
100
|
+
})
|
|
92
101
|
assertSuccess<CancelSubscriptionResponse['data']>(res)
|
|
93
102
|
return res.data
|
|
94
103
|
}
|
|
95
104
|
|
|
96
|
-
/** Restore subscription (POST /razorpay/subscription/restore). */
|
|
105
|
+
/** Restore subscription (POST /razorpay/subscription/restore). Prefers client.razorpay when available. */
|
|
97
106
|
async function restoreSubscription(
|
|
98
107
|
client: RazorpayAuthClient,
|
|
99
108
|
input: RestoreSubscriptionInput
|
|
100
109
|
): Promise<RestoreSubscriptionResponse['data']> {
|
|
101
|
-
const res =
|
|
102
|
-
|
|
103
|
-
|
|
110
|
+
const res = client.razorpay
|
|
111
|
+
? await client.razorpay.restoreSubscription(input)
|
|
112
|
+
: await client.api.post(`${BASE}/subscription/restore`, {
|
|
113
|
+
body: input as unknown as Record<string, unknown>,
|
|
114
|
+
})
|
|
104
115
|
assertSuccess<RestoreSubscriptionResponse['data']>(res)
|
|
105
116
|
return res.data
|
|
106
117
|
}
|
|
107
118
|
|
|
108
|
-
/** Verify payment (POST /razorpay/verify-payment). */
|
|
119
|
+
/** Verify payment (POST /razorpay/verify-payment). Prefers client.razorpay when available. */
|
|
109
120
|
async function verifyPayment(
|
|
110
121
|
client: RazorpayAuthClient,
|
|
111
122
|
input: VerifyPaymentInput
|
|
112
123
|
): Promise<VerifyPaymentResponse['data']> {
|
|
113
|
-
const res =
|
|
114
|
-
|
|
115
|
-
|
|
124
|
+
const res = client.razorpay
|
|
125
|
+
? await client.razorpay.verifyPayment(input)
|
|
126
|
+
: await client.api.post(`${BASE}/verify-payment`, {
|
|
127
|
+
body: input as unknown as Record<string, unknown>,
|
|
128
|
+
})
|
|
116
129
|
assertSuccess<VerifyPaymentResponse['data']>(res)
|
|
117
130
|
return res.data
|
|
118
131
|
}
|
|
@@ -228,6 +241,7 @@ export type UseRestoreSubscriptionOptions = UseMutationOptions<
|
|
|
228
241
|
// Re-export client types for convenience when importing from this entry
|
|
229
242
|
export type {
|
|
230
243
|
RazorpayAuthClient,
|
|
244
|
+
RazorpayClientActions,
|
|
231
245
|
PlanSummary,
|
|
232
246
|
CreateOrUpdateSubscriptionInput,
|
|
233
247
|
CancelSubscriptionInput,
|
package/client/types.ts
CHANGED
|
@@ -64,9 +64,34 @@ export type RazorpayApiResult<T = unknown> =
|
|
|
64
64
|
| { success: true; data: T }
|
|
65
65
|
| RazorpayApiError
|
|
66
66
|
|
|
67
|
+
/** Razorpay API actions from the client plugin (authClient.razorpay). Use these so requests hit the correct paths. */
|
|
68
|
+
export interface RazorpayClientActions {
|
|
69
|
+
getPlans: (fetchOptions?: { query?: Record<string, string> }) => Promise<RazorpayApiResult<PlanSummary[]>>
|
|
70
|
+
listSubscriptions: (
|
|
71
|
+
input?: ListSubscriptionsInput,
|
|
72
|
+
fetchOptions?: { query?: Record<string, string> }
|
|
73
|
+
) => Promise<RazorpayApiResult<ListSubscriptionsResponse['data']>>
|
|
74
|
+
createOrUpdateSubscription: (
|
|
75
|
+
input: CreateOrUpdateSubscriptionInput,
|
|
76
|
+
fetchOptions?: { body?: Record<string, unknown> }
|
|
77
|
+
) => Promise<RazorpayApiResult<CreateOrUpdateSubscriptionResponse['data']>>
|
|
78
|
+
cancelSubscription: (
|
|
79
|
+
input: CancelSubscriptionInput,
|
|
80
|
+
fetchOptions?: { body?: Record<string, unknown> }
|
|
81
|
+
) => Promise<RazorpayApiResult<CancelSubscriptionResponse['data']>>
|
|
82
|
+
restoreSubscription: (
|
|
83
|
+
input: RestoreSubscriptionInput,
|
|
84
|
+
fetchOptions?: { body?: Record<string, unknown> }
|
|
85
|
+
) => Promise<RazorpayApiResult<RestoreSubscriptionResponse['data']>>
|
|
86
|
+
verifyPayment: (
|
|
87
|
+
input: VerifyPaymentInput,
|
|
88
|
+
fetchOptions?: { body?: Record<string, unknown> }
|
|
89
|
+
) => Promise<RazorpayApiResult<VerifyPaymentResponse['data']>>
|
|
90
|
+
}
|
|
91
|
+
|
|
67
92
|
/**
|
|
68
93
|
* Minimal auth client interface for Razorpay hooks.
|
|
69
|
-
*
|
|
94
|
+
* When using the client plugin (razorpayClientPlugin()), authClient.razorpay is set and hooks use it so requests hit the correct paths (avoids 404s from api.get/post).
|
|
70
95
|
*/
|
|
71
96
|
export interface RazorpayAuthClient {
|
|
72
97
|
api: {
|
|
@@ -79,6 +104,8 @@ export interface RazorpayAuthClient {
|
|
|
79
104
|
options?: { body?: Record<string, unknown> }
|
|
80
105
|
) => Promise<RazorpayApiResult<unknown>>
|
|
81
106
|
}
|
|
107
|
+
/** Set when razorpayClientPlugin() is used in createAuthClient({ plugins: [razorpayClientPlugin()] }). Prefer these methods over api.get/post. */
|
|
108
|
+
razorpay?: RazorpayClientActions
|
|
82
109
|
}
|
|
83
110
|
|
|
84
111
|
/** Input for create-or-update subscription. */
|
package/client.ts
CHANGED
|
@@ -1,8 +1,105 @@
|
|
|
1
1
|
import type { BetterAuthClientPlugin } from 'better-auth/client'
|
|
2
2
|
import type { razorpayPlugin } from './index'
|
|
3
|
+
import type {
|
|
4
|
+
PlanSummary,
|
|
5
|
+
CreateOrUpdateSubscriptionInput,
|
|
6
|
+
CancelSubscriptionInput,
|
|
7
|
+
RestoreSubscriptionInput,
|
|
8
|
+
ListSubscriptionsInput,
|
|
9
|
+
VerifyPaymentInput,
|
|
10
|
+
ListSubscriptionsResponse,
|
|
11
|
+
CreateOrUpdateSubscriptionResponse,
|
|
12
|
+
CancelSubscriptionResponse,
|
|
13
|
+
RestoreSubscriptionResponse,
|
|
14
|
+
VerifyPaymentResponse,
|
|
15
|
+
RazorpayApiResult,
|
|
16
|
+
} from './client/types'
|
|
3
17
|
|
|
18
|
+
type FetchFn = (
|
|
19
|
+
path: string,
|
|
20
|
+
options?: {
|
|
21
|
+
method?: string
|
|
22
|
+
body?: Record<string, unknown>
|
|
23
|
+
query?: Record<string, string>
|
|
24
|
+
}
|
|
25
|
+
) => Promise<RazorpayApiResult<unknown>>
|
|
26
|
+
|
|
27
|
+
const PATHS = {
|
|
28
|
+
getPlans: '/razorpay/get-plans',
|
|
29
|
+
listSubscriptions: '/razorpay/subscription/list',
|
|
30
|
+
createOrUpdateSubscription: '/razorpay/subscription/create-or-update',
|
|
31
|
+
cancelSubscription: '/razorpay/subscription/cancel',
|
|
32
|
+
restoreSubscription: '/razorpay/subscription/restore',
|
|
33
|
+
verifyPayment: '/razorpay/verify-payment',
|
|
34
|
+
} as const
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Razorpay client plugin for Better Auth.
|
|
38
|
+
* Exposes authClient.razorpay.* so requests use the correct paths and avoid 404s from api.get/post.
|
|
39
|
+
* Add to createAuthClient: plugins: [razorpayClientPlugin()]
|
|
40
|
+
*/
|
|
4
41
|
export const razorpayClientPlugin = () =>
|
|
5
42
|
({
|
|
6
43
|
id: 'razorpay-plugin',
|
|
7
44
|
$InferServerPlugin: {} as ReturnType<typeof razorpayPlugin>,
|
|
45
|
+
getActions: ($fetch: FetchFn) => ({
|
|
46
|
+
razorpay: {
|
|
47
|
+
getPlans: (fetchOptions?: Parameters<FetchFn>[1]) =>
|
|
48
|
+
$fetch(PATHS.getPlans, { method: 'GET', ...fetchOptions }) as Promise<
|
|
49
|
+
RazorpayApiResult<PlanSummary[]>
|
|
50
|
+
>,
|
|
51
|
+
|
|
52
|
+
listSubscriptions: (
|
|
53
|
+
input?: ListSubscriptionsInput,
|
|
54
|
+
fetchOptions?: Parameters<FetchFn>[1]
|
|
55
|
+
) =>
|
|
56
|
+
$fetch(PATHS.listSubscriptions, {
|
|
57
|
+
method: 'GET',
|
|
58
|
+
query: input?.referenceId ? { referenceId: input.referenceId } : undefined,
|
|
59
|
+
...fetchOptions,
|
|
60
|
+
}) as Promise<RazorpayApiResult<ListSubscriptionsResponse['data']>>,
|
|
61
|
+
|
|
62
|
+
createOrUpdateSubscription: (
|
|
63
|
+
input: CreateOrUpdateSubscriptionInput,
|
|
64
|
+
fetchOptions?: Parameters<FetchFn>[1]
|
|
65
|
+
) =>
|
|
66
|
+
$fetch(PATHS.createOrUpdateSubscription, {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
body: input as unknown as Record<string, unknown>,
|
|
69
|
+
...fetchOptions,
|
|
70
|
+
}) as Promise<
|
|
71
|
+
RazorpayApiResult<CreateOrUpdateSubscriptionResponse['data']>
|
|
72
|
+
>,
|
|
73
|
+
|
|
74
|
+
cancelSubscription: (
|
|
75
|
+
input: CancelSubscriptionInput,
|
|
76
|
+
fetchOptions?: Parameters<FetchFn>[1]
|
|
77
|
+
) =>
|
|
78
|
+
$fetch(PATHS.cancelSubscription, {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
body: input as unknown as Record<string, unknown>,
|
|
81
|
+
...fetchOptions,
|
|
82
|
+
}) as Promise<RazorpayApiResult<CancelSubscriptionResponse['data']>>,
|
|
83
|
+
|
|
84
|
+
restoreSubscription: (
|
|
85
|
+
input: RestoreSubscriptionInput,
|
|
86
|
+
fetchOptions?: Parameters<FetchFn>[1]
|
|
87
|
+
) =>
|
|
88
|
+
$fetch(PATHS.restoreSubscription, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
body: input as unknown as Record<string, unknown>,
|
|
91
|
+
...fetchOptions,
|
|
92
|
+
}) as Promise<RazorpayApiResult<RestoreSubscriptionResponse['data']>>,
|
|
93
|
+
|
|
94
|
+
verifyPayment: (
|
|
95
|
+
input: VerifyPaymentInput,
|
|
96
|
+
fetchOptions?: Parameters<FetchFn>[1]
|
|
97
|
+
) =>
|
|
98
|
+
$fetch(PATHS.verifyPayment, {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
body: input as unknown as Record<string, unknown>,
|
|
101
|
+
...fetchOptions,
|
|
102
|
+
}) as Promise<RazorpayApiResult<VerifyPaymentResponse['data']>>,
|
|
103
|
+
},
|
|
104
|
+
}),
|
|
8
105
|
}) satisfies BetterAuthClientPlugin
|