@deiondz/better-auth-razorpay 2.0.1 → 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
@@ -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,
@@ -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
 
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.1",
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",