@deiondz/better-auth-razorpay 2.0.0 → 2.0.2

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
@@ -50,13 +50,13 @@ The Razorpay plugin provides a subscription management solution aligned with the
50
50
  1. **Install the Package**
51
51
 
52
52
  ```bash
53
- npm install better-auth-razorpay
53
+ npm install @deiondz/better-auth-razorpay
54
54
  # or
55
- yarn add better-auth-razorpay
55
+ yarn add @deiondz/better-auth-razorpay
56
56
  # or
57
- pnpm add better-auth-razorpay
57
+ pnpm add @deiondz/better-auth-razorpay
58
58
  # or
59
- bun add better-auth-razorpay
59
+ bun add @deiondz/better-auth-razorpay
60
60
  ```
61
61
 
62
62
  The package includes `razorpay` and `zod` as dependencies.
@@ -67,7 +67,7 @@ The package includes `razorpay` and `zod` as dependencies.
67
67
  // src/lib/auth.ts (or your auth configuration file)
68
68
  import Razorpay from 'razorpay'
69
69
  import { betterAuth } from 'better-auth'
70
- import { razorpayPlugin } from 'better-auth-razorpay'
70
+ import { razorpayPlugin } from '@deiondz/better-auth-razorpay'
71
71
 
72
72
  const razorpayClient = new Razorpay({
73
73
  key_id: process.env.RAZORPAY_KEY_ID!,
@@ -84,6 +84,7 @@ export const auth = betterAuth({
84
84
  razorpayPlugin({
85
85
  razorpayClient,
86
86
  razorpayWebhookSecret: process.env.RAZORPAY_WEBHOOK_SECRET,
87
+ razorpayKeySecret: process.env.RAZORPAY_KEY_SECRET, // optional: enables verify-payment endpoint
87
88
  createCustomerOnSignUp: true, // optional
88
89
  subscription: {
89
90
  enabled: true,
@@ -116,7 +117,7 @@ export const auth = betterAuth({
116
117
  ```typescript
117
118
  // src/lib/auth-client.ts
118
119
  import { createAuthClient } from 'better-auth/react'
119
- import { razorpayClientPlugin } from 'better-auth-razorpay/client'
120
+ import { razorpayClientPlugin } from '@deiondz/better-auth-razorpay/client'
120
121
  import type { auth } from './auth'
121
122
 
122
123
  export const authClient = createAuthClient<typeof auth>({
@@ -141,6 +142,7 @@ BETTER_AUTH_URL=https://yourdomain.com
141
142
  RAZORPAY_KEY_ID=rzp_test_xxxxxxxxxxxx
142
143
  RAZORPAY_KEY_SECRET=your_secret_key
143
144
  RAZORPAY_WEBHOOK_SECRET=your_webhook_secret
145
+ # Pass RAZORPAY_KEY_SECRET as razorpayKeySecret in plugin options to enable the verify-payment endpoint (same as client key, not webhook secret).
144
146
  ```
145
147
 
146
148
  5. **Run Database Migration**
@@ -163,6 +165,7 @@ npx @better-auth/cli@latest generate
163
165
  interface RazorpayPluginOptions {
164
166
  razorpayClient: Razorpay // Required: Initialized Razorpay instance (key_id, key_secret)
165
167
  razorpayWebhookSecret?: string // Optional: Webhook secret for signature verification
168
+ razorpayKeySecret?: string // Optional: API key secret for payment signature verification; when set, enables POST /razorpay/verify-payment (same secret as Razorpay client, not webhook secret)
166
169
  createCustomerOnSignUp?: boolean // Optional: Create Razorpay customer on user sign-up (default: false)
167
170
  onCustomerCreate?: (args) => Promise<void>
168
171
  getCustomerCreateParams?: (args) => Promise<{ params?: Record<string, unknown> }>
@@ -197,6 +200,7 @@ Example: using callbacks in your config:
197
200
  razorpayPlugin({
198
201
  razorpayClient,
199
202
  razorpayWebhookSecret: process.env.RAZORPAY_WEBHOOK_SECRET,
203
+ razorpayKeySecret: process.env.RAZORPAY_KEY_SECRET, // optional: enables verify-payment endpoint
200
204
  createCustomerOnSignUp: true,
201
205
  onCustomerCreate: async ({ user, razorpayCustomer }) => {
202
206
  console.log(`Razorpay customer created for user ${user.id}: ${razorpayCustomer.id}`)
@@ -429,7 +433,7 @@ if (response.success && response.data) {
429
433
 
430
434
  ### 4. Verify Payment
431
435
 
432
- Verify payment signature after Razorpay checkout completion.
436
+ Verify payment signature after Razorpay checkout completion. This endpoint is **only registered when `razorpayKeySecret`** is set in plugin options. Use the same API key secret as your Razorpay client (not the webhook secret).
433
437
 
434
438
  **Endpoint:** `POST /api/auth/razorpay/verify-payment`
435
439
 
@@ -484,6 +488,7 @@ const handlePaymentSuccess = async (razorpayResponse: {
484
488
  - `SIGNATURE_VERIFICATION_FAILED` - Invalid payment signature
485
489
  - `UNAUTHORIZED` - User not authenticated
486
490
  - `SUBSCRIPTION_NOT_FOUND` - Subscription record not found
491
+ - `FORBIDDEN` - Subscription does not belong to authenticated user
487
492
 
488
493
  ---
489
494
 
@@ -708,12 +713,12 @@ npm install @tanstack/react-query react
708
713
  # or yarn / pnpm / bun
709
714
  ```
710
715
 
711
- Import from `better-auth-razorpay/hooks` and pass your Better Auth client as the first argument:
716
+ Import from `@deiondz/better-auth-razorpay/hooks` and pass your Better Auth client as the first argument:
712
717
 
713
718
  ```tsx
714
719
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
715
720
  import { createAuthClient } from 'better-auth/react'
716
- import { razorpayClientPlugin } from 'better-auth-razorpay/client'
721
+ import { razorpayClientPlugin } from '@deiondz/better-auth-razorpay/client'
717
722
  import {
718
723
  usePlans,
719
724
  useSubscriptions,
@@ -721,8 +726,8 @@ import {
721
726
  useCancelSubscription,
722
727
  useRestoreSubscription,
723
728
  razorpayQueryKeys,
724
- } from 'better-auth-razorpay/hooks'
725
- import type { CreateOrUpdateSubscriptionInput } from 'better-auth-razorpay/hooks'
729
+ } from '@deiondz/better-auth-razorpay/hooks'
730
+ import type { CreateOrUpdateSubscriptionInput } from '@deiondz/better-auth-razorpay/hooks'
726
731
 
727
732
  const queryClient = new QueryClient()
728
733
  // auth from your server config (e.g. import type { auth } from './auth')
@@ -804,7 +809,7 @@ function SubscriptionUI() {
804
809
  **Query keys** (for manual invalidation or prefetching):
805
810
 
806
811
  ```ts
807
- import { razorpayQueryKeys } from 'better-auth-razorpay/hooks'
812
+ import { razorpayQueryKeys } from '@deiondz/better-auth-razorpay/hooks'
808
813
 
809
814
  razorpayQueryKeys.plans() // ['razorpay', 'plans']
810
815
  razorpayQueryKeys.subscriptions() // ['razorpay', 'subscriptions', 'me']
@@ -888,7 +893,7 @@ import type {
888
893
  RazorpayErrorResponse,
889
894
  RazorpayPluginOptions,
890
895
  OnWebhookEventCallback,
891
- } from 'better-auth-razorpay'
896
+ } from '@deiondz/better-auth-razorpay'
892
897
  ```
893
898
 
894
899
  ### Response Types
@@ -1385,7 +1390,7 @@ async function initializeRazorpayCheckout(subscriptionId: string) {
1385
1390
 
1386
1391
  **7. Type Errors**
1387
1392
  - Ensure you're using `createAuthClient<typeof auth>()` for type inference
1388
- - Import types from `better-auth-razorpay`
1393
+ - Import types from `@deiondz/better-auth-razorpay`
1389
1394
  - Check that plugin is properly exported
1390
1395
 
1391
1396
  ## Resources
package/api/index.ts CHANGED
@@ -3,4 +3,5 @@ export { createOrUpdateSubscription } from './create-or-update-subscription'
3
3
  export { getPlans } from './get-plans'
4
4
  export { listSubscriptions } from './list-subscriptions'
5
5
  export { restoreSubscription } from './restore-subscription'
6
+ export { verifyPayment } from './verify-payment'
6
7
  export { webhook } from './webhook'
@@ -0,0 +1,87 @@
1
+ import { createHmac } from 'node:crypto'
2
+ import { createAuthEndpoint, sessionMiddleware } from 'better-auth/api'
3
+ import {
4
+ handleRazorpayError,
5
+ verifyPaymentSchema,
6
+ type SubscriptionRecord,
7
+ } from '../lib'
8
+
9
+ /**
10
+ * POST /api/auth/razorpay/verify-payment
11
+ * Verifies payment signature after Razorpay subscription checkout completion.
12
+ * Requires razorpayKeySecret to be set in plugin options.
13
+ */
14
+ export const verifyPayment = (keySecret: string) =>
15
+ createAuthEndpoint(
16
+ '/razorpay/verify-payment',
17
+ { method: 'POST', use: [sessionMiddleware] },
18
+ async (ctx) => {
19
+ try {
20
+ const body = verifyPaymentSchema.parse(ctx.body)
21
+ const { razorpay_payment_id, razorpay_subscription_id, razorpay_signature } = body
22
+
23
+ const generatedSignature = createHmac('sha256', keySecret)
24
+ .update(`${razorpay_payment_id}|${razorpay_subscription_id}`)
25
+ .digest('hex')
26
+
27
+ if (generatedSignature !== razorpay_signature) {
28
+ return {
29
+ success: false,
30
+ error: {
31
+ code: 'SIGNATURE_VERIFICATION_FAILED',
32
+ description: 'Payment signature verification failed',
33
+ },
34
+ }
35
+ }
36
+
37
+ const userId = ctx.context.session?.user?.id
38
+ if (!userId) {
39
+ return {
40
+ success: false,
41
+ error: { code: 'UNAUTHORIZED', description: 'User not authenticated' },
42
+ }
43
+ }
44
+
45
+ const record = (await ctx.context.adapter.findOne({
46
+ model: 'subscription',
47
+ where: [{ field: 'razorpaySubscriptionId', value: razorpay_subscription_id }],
48
+ })) as SubscriptionRecord | null
49
+
50
+ if (!record) {
51
+ return {
52
+ success: false,
53
+ error: { code: 'SUBSCRIPTION_NOT_FOUND', description: 'Subscription not found' },
54
+ }
55
+ }
56
+
57
+ if (record.referenceId !== userId) {
58
+ return {
59
+ success: false,
60
+ error: { code: 'FORBIDDEN', description: 'Subscription does not belong to you' },
61
+ }
62
+ }
63
+
64
+ await ctx.context.adapter.update({
65
+ model: 'subscription',
66
+ where: [{ field: 'razorpaySubscriptionId', value: razorpay_subscription_id }],
67
+ update: {
68
+ data: {
69
+ status: 'pending',
70
+ updatedAt: new Date(),
71
+ },
72
+ },
73
+ })
74
+
75
+ return {
76
+ success: true,
77
+ data: {
78
+ message: 'Payment verified successfully',
79
+ payment_id: razorpay_payment_id,
80
+ subscription_id: razorpay_subscription_id,
81
+ },
82
+ }
83
+ } catch (error) {
84
+ return handleRazorpayError(error)
85
+ }
86
+ }
87
+ )
package/client/hooks.ts CHANGED
@@ -17,11 +17,13 @@ import type {
17
17
  CancelSubscriptionInput,
18
18
  RestoreSubscriptionInput,
19
19
  ListSubscriptionsInput,
20
+ VerifyPaymentInput,
20
21
  GetPlansResponse,
21
22
  ListSubscriptionsResponse,
22
23
  CreateOrUpdateSubscriptionResponse,
23
24
  CancelSubscriptionResponse,
24
25
  RestoreSubscriptionResponse,
26
+ VerifyPaymentResponse,
25
27
  RazorpayApiError,
26
28
  } from './types'
27
29
 
@@ -103,6 +105,18 @@ async function restoreSubscription(
103
105
  return res.data
104
106
  }
105
107
 
108
+ /** Verify payment (POST /razorpay/verify-payment). */
109
+ async function verifyPayment(
110
+ client: RazorpayAuthClient,
111
+ input: VerifyPaymentInput
112
+ ): Promise<VerifyPaymentResponse['data']> {
113
+ const res = await client.api.post(`${BASE}/verify-payment`, {
114
+ body: input as unknown as Record<string, unknown>,
115
+ })
116
+ assertSuccess<VerifyPaymentResponse['data']>(res)
117
+ return res.data
118
+ }
119
+
106
120
  export type UsePlansOptions = Omit<
107
121
  UseQueryOptions<PlanSummary[], Error, PlanSummary[], readonly string[]>,
108
122
  'queryKey' | 'queryFn'
@@ -219,11 +233,13 @@ export type {
219
233
  CancelSubscriptionInput,
220
234
  RestoreSubscriptionInput,
221
235
  ListSubscriptionsInput,
236
+ VerifyPaymentInput,
222
237
  GetPlansResponse,
223
238
  ListSubscriptionsResponse,
224
239
  CreateOrUpdateSubscriptionResponse,
225
240
  CancelSubscriptionResponse,
226
241
  RestoreSubscriptionResponse,
242
+ VerifyPaymentResponse,
227
243
  RazorpayApiError,
228
244
  RazorpayApiResult,
229
245
  } from './types'
@@ -246,3 +262,30 @@ export function useRestoreSubscription(
246
262
  },
247
263
  })
248
264
  }
265
+
266
+ export type UseVerifyPaymentOptions = UseMutationOptions<
267
+ VerifyPaymentResponse['data'],
268
+ Error,
269
+ VerifyPaymentInput,
270
+ unknown
271
+ >
272
+
273
+ /**
274
+ * Verify payment signature after Razorpay checkout success.
275
+ * Call with the payload from the Razorpay success handler (razorpay_payment_id, razorpay_subscription_id, razorpay_signature).
276
+ * Invalidates subscriptions list on success.
277
+ */
278
+ export function useVerifyPayment(
279
+ client: RazorpayAuthClient | null | undefined,
280
+ options?: UseVerifyPaymentOptions
281
+ ) {
282
+ const queryClient = useQueryClient()
283
+ return useMutation({
284
+ mutationFn: (input: VerifyPaymentInput) => verifyPayment(client!, input),
285
+ ...options,
286
+ onSuccess: (data, variables, onMutateResult, context) => {
287
+ queryClient.invalidateQueries({ queryKey: razorpayQueryKeys.subscriptions() })
288
+ options?.onSuccess?.(data, variables, onMutateResult, context)
289
+ },
290
+ })
291
+ }
package/client/types.ts CHANGED
@@ -106,3 +106,20 @@ export interface RestoreSubscriptionInput {
106
106
  export interface ListSubscriptionsInput {
107
107
  referenceId?: string
108
108
  }
109
+
110
+ /** Input for verify-payment (Razorpay checkout success callback payload). */
111
+ export interface VerifyPaymentInput {
112
+ razorpay_payment_id: string
113
+ razorpay_subscription_id: string
114
+ razorpay_signature: string
115
+ }
116
+
117
+ /** Response shape for verify-payment (success). */
118
+ export interface VerifyPaymentResponse {
119
+ success: true
120
+ data: {
121
+ message: string
122
+ payment_id: string
123
+ subscription_id: string
124
+ }
125
+ }
package/index.ts CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  getPlans,
6
6
  listSubscriptions,
7
7
  restoreSubscription,
8
+ verifyPayment,
8
9
  webhook,
9
10
  } from './api'
10
11
  import type { RazorpayPluginOptions, RazorpayUserRecord } from './lib'
@@ -21,6 +22,7 @@ import type { RazorpayPluginOptions, RazorpayUserRecord } from './lib'
21
22
  * @param options - Plugin configuration
22
23
  * @param options.razorpayClient - Initialized Razorpay instance (key_id, key_secret)
23
24
  * @param options.razorpayWebhookSecret - Webhook secret for signature verification
25
+ * @param options.razorpayKeySecret - API key secret for payment signature verification (optional; when set, enables POST /razorpay/verify-payment)
24
26
  * @param options.createCustomerOnSignUp - Create Razorpay customer when user signs up (default: false)
25
27
  * @param options.onCustomerCreate - Callback after customer is created
26
28
  * @param options.getCustomerCreateParams - Custom params when creating customer
@@ -31,6 +33,7 @@ export const razorpayPlugin = (options: RazorpayPluginOptions) => {
31
33
  const {
32
34
  razorpayClient,
33
35
  razorpayWebhookSecret,
36
+ razorpayKeySecret,
34
37
  createCustomerOnSignUp = false,
35
38
  onCustomerCreate,
36
39
  getCustomerCreateParams,
@@ -83,6 +86,9 @@ export const razorpayPlugin = (options: RazorpayPluginOptions) => {
83
86
  'subscription/restore': restoreSubscription(razorpay),
84
87
  'subscription/list': listSubscriptions({ subscription: subOpts }),
85
88
  'get-plans': getPlans({ subscription: subOpts }),
89
+ ...(razorpayKeySecret
90
+ ? { 'verify-payment': verifyPayment(razorpayKeySecret) }
91
+ : {}),
86
92
  webhook: webhook(razorpayWebhookSecret, options.onWebhookEvent ?? undefined, {
87
93
  subscription: subOpts,
88
94
  onEvent,
package/lib/types.ts CHANGED
@@ -169,6 +169,8 @@ export interface RazorpayPluginOptions {
169
169
  razorpayClient: import('razorpay')
170
170
  /** Webhook secret for signature verification. */
171
171
  razorpayWebhookSecret?: string
172
+ /** API key secret for payment signature verification. When set, enables POST /razorpay/verify-payment (same secret as Razorpay client, not webhook secret). */
173
+ razorpayKeySecret?: string
172
174
  /** Create Razorpay customer when user signs up. Default: false. */
173
175
  createCustomerOnSignUp?: boolean
174
176
  /** Called after a Razorpay customer is created. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deiondz/better-auth-razorpay",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Better Auth plugin for Razorpay subscriptions and payments",
5
5
  "type": "module",
6
6
  "main": "./index.ts",