@deiondz/better-auth-razorpay 2.0.2 → 2.0.4

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 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
- ### Better Auth Client Methods
673
+ ### Preferred: authClient.razorpay.* (method-based API)
672
674
 
673
- The plugin integrates with Better Auth's client API. Use `authClient.api` for all endpoint calls:
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 request
679
- const plans = await authClient.api.get('/razorpay/get-plans')
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
- // POST request (create or update subscription)
682
- const result = await authClient.api.post('/razorpay/subscription/create-or-update', {
683
- body: { plan: 'Starter', annual: false, seats: 1 },
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**: you can use it with `useQuery`/`useMutation` and `authClient.api` in any way you like. We provide optional pre-built hooks below; if you prefer a different setup, build your own hooks around the same endpoints (e.g. `authClient.api.get('/razorpay/get-plans')`, `authClient.api.post('/razorpay/subscription/create-or-update', { body })`, etc.).
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. "Plan not found in configured plans"**
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
- **2. "Webhook signature verification failed"**
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
- **3. "Subscription already exists"**
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
- **4. "User not authenticated"**
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
- **5. "Subscription not found"**
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
- **6. Database Schema Issues**
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
- **7. Type Errors**
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 = await client.api.get(`${BASE}/get-plans`)
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 query: Record<string, string> = {}
62
- if (input?.referenceId) query.referenceId = input.referenceId
63
- const path = `${BASE}/subscription/list`
64
- const res =
65
- Object.keys(query).length > 0
66
- ? await client.api.get(path, { query })
67
- : await client.api.get(path)
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 = await client.api.post(`${BASE}/subscription/create-or-update`, {
78
- body: input as unknown as Record<string, unknown>,
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 = await client.api.post(`${BASE}/subscription/cancel`, {
90
- body: input as unknown as Record<string, unknown>,
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 = await client.api.post(`${BASE}/subscription/restore`, {
102
- body: input as unknown as Record<string, unknown>,
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 = await client.api.post(`${BASE}/verify-payment`, {
114
- body: input as unknown as Record<string, unknown>,
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
- * Compatible with Better Auth's createAuthClient() return type.
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
package/lib/types.ts CHANGED
@@ -75,6 +75,7 @@ export interface RazorpayPlan {
75
75
  name: string
76
76
  monthlyPlanId: string
77
77
  annualPlanId?: string
78
+ description?: string
78
79
  limits?: PlanLimits
79
80
  freeTrial?: PlanFreeTrial
80
81
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deiondz/better-auth-razorpay",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "Better Auth plugin for Razorpay subscriptions and payments",
5
5
  "type": "module",
6
6
  "main": "./index.ts",