@deiondz/better-auth-razorpay 1.0.0 → 2.0.1

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.
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Client-side types for Razorpay plugin hooks and API responses.
3
+ */
4
+
5
+ import type { SubscriptionRecord } from '../lib/types'
6
+
7
+ /** Plan summary returned by GET /razorpay/get-plans (client-safe shape). */
8
+ export interface PlanSummary {
9
+ name: string
10
+ monthlyPlanId: string
11
+ annualPlanId?: string
12
+ limits?: Record<string, number>
13
+ freeTrial?: { days: number }
14
+ }
15
+
16
+ /** Response shape for get-plans (success). */
17
+ export interface GetPlansResponse {
18
+ success: true
19
+ data: PlanSummary[]
20
+ }
21
+
22
+ /** Response shape for subscription/list (success). */
23
+ export interface ListSubscriptionsResponse {
24
+ success: true
25
+ data: SubscriptionRecord[]
26
+ }
27
+
28
+ /** Response shape for create-or-update (success). */
29
+ export interface CreateOrUpdateSubscriptionResponse {
30
+ success: true
31
+ data: {
32
+ checkoutUrl: string
33
+ subscriptionId: string
34
+ razorpaySubscriptionId: string
35
+ }
36
+ }
37
+
38
+ /** Response shape for cancel (success). */
39
+ export interface CancelSubscriptionResponse {
40
+ success: true
41
+ data: {
42
+ id: string
43
+ status: string
44
+ plan_id: string
45
+ current_end?: number
46
+ ended_at?: number | null
47
+ }
48
+ }
49
+
50
+ /** Response shape for restore (success). */
51
+ export interface RestoreSubscriptionResponse {
52
+ success: true
53
+ data: { id: string; status: string }
54
+ }
55
+
56
+ /** Error shape from plugin API. */
57
+ export interface RazorpayApiError {
58
+ success: false
59
+ error: { code: string; description: string; [key: string]: unknown }
60
+ }
61
+
62
+ /** Any plugin API response (success or error). */
63
+ export type RazorpayApiResult<T = unknown> =
64
+ | { success: true; data: T }
65
+ | RazorpayApiError
66
+
67
+ /**
68
+ * Minimal auth client interface for Razorpay hooks.
69
+ * Compatible with Better Auth's createAuthClient() return type.
70
+ */
71
+ export interface RazorpayAuthClient {
72
+ api: {
73
+ get: (
74
+ path: string,
75
+ options?: { query?: Record<string, string> }
76
+ ) => Promise<RazorpayApiResult<unknown>>
77
+ post: (
78
+ path: string,
79
+ options?: { body?: Record<string, unknown> }
80
+ ) => Promise<RazorpayApiResult<unknown>>
81
+ }
82
+ }
83
+
84
+ /** Input for create-or-update subscription. */
85
+ export interface CreateOrUpdateSubscriptionInput {
86
+ plan: string
87
+ annual?: boolean
88
+ seats?: number
89
+ subscriptionId?: string
90
+ successUrl?: string
91
+ disableRedirect?: boolean
92
+ }
93
+
94
+ /** Input for cancel subscription. */
95
+ export interface CancelSubscriptionInput {
96
+ subscriptionId: string
97
+ immediately?: boolean
98
+ }
99
+
100
+ /** Input for restore subscription. */
101
+ export interface RestoreSubscriptionInput {
102
+ subscriptionId: string
103
+ }
104
+
105
+ /** Input for list subscriptions (query). */
106
+ export interface ListSubscriptionsInput {
107
+ referenceId?: string
108
+ }
package/index.ts CHANGED
@@ -1,123 +1,156 @@
1
1
  import type { BetterAuthPlugin } from 'better-auth'
2
- import Razorpay from 'razorpay'
3
2
  import {
4
3
  cancelSubscription,
4
+ createOrUpdateSubscription,
5
5
  getPlans,
6
- getSubscription,
7
- pauseSubscription,
8
- resumeSubscription,
9
- subscribe,
10
- verifyPayment,
6
+ listSubscriptions,
7
+ restoreSubscription,
11
8
  webhook,
12
9
  } from './api'
13
- import type { RazorpayPluginOptions } from './lib'
10
+ import type { RazorpayPluginOptions, RazorpayUserRecord } from './lib'
14
11
 
15
12
  /**
16
13
  * Razorpay plugin for Better Auth.
17
14
  *
18
- * Provides subscription management functionality including:
19
- * - Creating subscriptions
20
- * - Managing subscription lifecycle (pause, resume, cancel)
21
- * - Payment verification
22
- * - Webhook handling for subscription events
23
- * - Plan retrieval
15
+ * Aligns with the subscription flow from the community plugin:
16
+ * - Subscription: create-or-update (checkout URL), cancel, restore, list
17
+ * - Customer: optional creation on sign-up, callbacks and params
18
+ * - Webhooks: subscription events (activated, cancelled, expired, etc.) with optional callbacks
19
+ * - Plans: named plans with monthly/annual IDs, limits, free trial
24
20
  *
25
- * @param options - Plugin configuration options
26
- * @param options.keyId - Razorpay key ID (required)
27
- * @param options.keySecret - Razorpay key secret (required)
28
- * @param options.webhookSecret - Webhook secret for signature verification (optional)
29
- * @param options.plans - Array of plan IDs from Razorpay dashboard (required)
30
- * @param options.onWebhookEvent - Optional callback for custom webhook event handling
31
- * @returns Better Auth plugin configuration
32
- *
33
- * @throws {Error} If keyId or keySecret are not provided
34
- *
35
- * @example
36
- * ```typescript
37
- * import { razorpayPlugin } from '@better-auth/razorpay'
38
- *
39
- * const auth = betterAuth({
40
- * plugins: [
41
- * razorpayPlugin({
42
- * keyId: process.env.RAZORPAY_KEY_ID!,
43
- * keySecret: process.env.RAZORPAY_KEY_SECRET!,
44
- * webhookSecret: process.env.RAZORPAY_WEBHOOK_SECRET,
45
- * plans: ['plan_1234567890', 'plan_0987654321'],
46
- * }),
47
- * ],
48
- * })
49
- * ```
21
+ * @param options - Plugin configuration
22
+ * @param options.razorpayClient - Initialized Razorpay instance (key_id, key_secret)
23
+ * @param options.razorpayWebhookSecret - Webhook secret for signature verification
24
+ * @param options.createCustomerOnSignUp - Create Razorpay customer when user signs up (default: false)
25
+ * @param options.onCustomerCreate - Callback after customer is created
26
+ * @param options.getCustomerCreateParams - Custom params when creating customer
27
+ * @param options.subscription - Subscription config (enabled, plans, callbacks, authorizeReference)
28
+ * @param options.onEvent - Global callback for all webhook events
50
29
  */
51
30
  export const razorpayPlugin = (options: RazorpayPluginOptions) => {
52
- const { keyId, keySecret, webhookSecret, plans, onWebhookEvent } = options
31
+ const {
32
+ razorpayClient,
33
+ razorpayWebhookSecret,
34
+ createCustomerOnSignUp = false,
35
+ onCustomerCreate,
36
+ getCustomerCreateParams,
37
+ subscription: subOpts,
38
+ onEvent,
39
+ } = options
53
40
 
54
- if (!keyId || !keySecret) {
55
- throw new Error('Razorpay keyId and keySecret are required')
41
+ if (!razorpayClient) {
42
+ throw new Error('Razorpay plugin: razorpayClient is required')
56
43
  }
57
44
 
58
- // Initialize Razorpay instance once in plugin closure
59
- // This instance is accessible to all endpoints via closure scope
60
- const razorpay = new Razorpay({
61
- key_id: keyId,
62
- key_secret: keySecret,
63
- })
45
+ const razorpay = razorpayClient as import('razorpay')
64
46
 
65
- return {
47
+ const plugin = {
66
48
  id: 'razorpay-plugin',
67
49
 
68
50
  schema: {
69
51
  user: {
70
52
  fields: {
71
- subscriptionId: { type: 'string', required: false },
72
- subscriptionPlanId: { type: 'string', required: false },
73
- subscriptionStatus: { type: 'string', required: false },
74
- subscriptionCurrentPeriodEnd: { type: 'date', required: false },
75
- cancelAtPeriodEnd: { type: 'boolean', required: false },
76
- lastPaymentDate: { type: 'date', required: false },
77
- nextBillingDate: { type: 'date', required: false },
53
+ razorpayCustomerId: { type: 'string', required: false },
78
54
  },
79
55
  },
80
- razorpayCustomer: {
56
+ subscription: {
81
57
  fields: {
82
- userId: { type: 'string', unique: true },
83
- razorpayCustomerId: { type: 'string', unique: true },
84
- },
85
- },
86
- razorpaySubscription: {
87
- fields: {
88
- userId: { type: 'string' },
89
- subscriptionId: { type: 'string' },
90
- planId: { type: 'string' },
91
- status: { type: 'string' },
58
+ id: { type: 'string', required: true },
59
+ plan: { type: 'string', required: true },
60
+ referenceId: { type: 'string', required: true },
61
+ razorpayCustomerId: { type: 'string', required: false },
62
+ razorpaySubscriptionId: { type: 'string', required: false },
63
+ status: { type: 'string', required: true },
64
+ trialStart: { type: 'date', required: false },
65
+ trialEnd: { type: 'date', required: false },
66
+ periodStart: { type: 'date', required: false },
67
+ periodEnd: { type: 'date', required: false },
68
+ cancelAtPeriodEnd: { type: 'boolean', required: false },
69
+ seats: { type: 'number', required: false },
70
+ groupId: { type: 'string', required: false },
71
+ createdAt: { type: 'date', required: true },
72
+ updatedAt: { type: 'date', required: true },
92
73
  },
93
74
  },
94
75
  },
95
76
 
96
77
  endpoints: {
97
- subscribe: subscribe(razorpay, plans),
98
- 'get-plans': getPlans(razorpay, plans),
99
- 'verify-payment': verifyPayment(keySecret),
100
- webhook: webhook(webhookSecret, onWebhookEvent),
101
- 'get-subscription': getSubscription(razorpay),
102
- 'pause-subscription': pauseSubscription(razorpay),
103
- 'cancel-subscription': cancelSubscription(razorpay),
104
- 'resume-subscription': resumeSubscription(razorpay),
78
+ 'subscription/create-or-update': createOrUpdateSubscription(razorpay, {
79
+ subscription: subOpts,
80
+ createCustomerOnSignUp,
81
+ }),
82
+ 'subscription/cancel': cancelSubscription(razorpay),
83
+ 'subscription/restore': restoreSubscription(razorpay),
84
+ 'subscription/list': listSubscriptions({ subscription: subOpts }),
85
+ 'get-plans': getPlans({ subscription: subOpts }),
86
+ webhook: webhook(razorpayWebhookSecret, options.onWebhookEvent ?? undefined, {
87
+ subscription: subOpts,
88
+ onEvent,
89
+ }),
105
90
  },
106
- } satisfies BetterAuthPlugin
91
+
92
+ databaseHooks: createCustomerOnSignUp
93
+ ? {
94
+ user: {
95
+ create: {
96
+ after: async (
97
+ user: RazorpayUserRecord & { id: string },
98
+ ctx: { context?: { adapter?: { update: (p: unknown) => Promise<unknown> } }; adapter?: { update: (p: unknown) => Promise<unknown> }; session?: unknown }
99
+ ) => {
100
+ const adapter = ctx.context?.adapter ?? (ctx as { adapter?: { update: (p: unknown) => Promise<unknown> } }).adapter
101
+ if (!adapter?.update) return
102
+ try {
103
+ const params: { name?: string; email?: string; contact?: string; [key: string]: unknown } = {
104
+ name: user.name ?? user.email ?? 'Customer',
105
+ email: user.email ?? undefined,
106
+ }
107
+ if (getCustomerCreateParams) {
108
+ const extra = await getCustomerCreateParams({
109
+ user: user as RazorpayUserRecord,
110
+ session: ctx.session,
111
+ })
112
+ if (extra?.params && typeof extra.params === 'object') {
113
+ Object.assign(params, extra.params)
114
+ }
115
+ }
116
+ const customer = await razorpay.customers.create(params)
117
+ await adapter.update({
118
+ model: 'user',
119
+ where: [{ field: 'id', value: user.id }],
120
+ update: { data: { razorpayCustomerId: customer.id } },
121
+ })
122
+ if (onCustomerCreate) {
123
+ await onCustomerCreate({
124
+ user: user as RazorpayUserRecord,
125
+ razorpayCustomer: { id: customer.id, ...(customer as unknown as Record<string, unknown>) },
126
+ })
127
+ }
128
+ } catch (err) {
129
+ console.error('[better-auth-razorpay] Create customer on sign-up failed:', err)
130
+ }
131
+ },
132
+ },
133
+ },
134
+ }
135
+ : undefined,
136
+ }
137
+
138
+ return plugin as BetterAuthPlugin
107
139
  }
108
140
 
109
- // Re-export types for external usage
110
141
  export type {
111
142
  OnWebhookEventCallback,
112
143
  RazorpayApiResponse,
113
144
  RazorpayErrorResponse,
145
+ RazorpayPlan,
114
146
  RazorpayPluginOptions,
115
147
  RazorpaySubscription,
116
- RazorpaySubscriptionRecord,
117
148
  RazorpaySuccessResponse,
118
149
  RazorpayUserRecord,
119
150
  RazorpayWebhookContext,
120
151
  RazorpayWebhookEvent,
121
152
  RazorpayWebhookPayload,
153
+ SubscriptionRecord,
154
+ SubscriptionStatus,
122
155
  } from './lib'
123
156
  export type { WebhookResult } from './api/webhook'
package/lib/index.ts CHANGED
@@ -1,22 +1,28 @@
1
1
  export { handleRazorpayError } from './error-handler'
2
2
  export {
3
3
  cancelSubscriptionSchema,
4
- getPlansSchema,
5
- pauseSubscriptionSchema,
6
- resumeSubscriptionSchema,
4
+ createOrUpdateSubscriptionSchema,
5
+ listSubscriptionsSchema,
6
+ restoreSubscriptionSchema,
7
7
  subscribeSchema,
8
8
  verifyPaymentSchema,
9
9
  } from './schemas'
10
+ export type { CreateOrUpdateSubscriptionInput } from './schemas'
10
11
  export type {
11
12
  OnWebhookEventCallback,
13
+ PlanFreeTrial,
14
+ PlanLimits,
12
15
  RazorpayApiResponse,
13
16
  RazorpayErrorResponse,
17
+ RazorpayPlan,
14
18
  RazorpayPluginOptions,
15
19
  RazorpaySubscription,
16
- RazorpaySubscriptionRecord,
17
20
  RazorpaySuccessResponse,
18
21
  RazorpayUserRecord,
19
22
  RazorpayWebhookContext,
20
23
  RazorpayWebhookEvent,
21
24
  RazorpayWebhookPayload,
25
+ SubscriptionOptions,
26
+ SubscriptionRecord,
27
+ SubscriptionStatus,
22
28
  } from './types'
package/lib/schemas.ts CHANGED
@@ -1,58 +1,34 @@
1
1
  import { z } from 'zod'
2
2
 
3
- // Schema for getPlans validation (GET requests don't have a body, so optional)
4
- const getPlansSchema = z.object({}).strict().optional()
5
-
6
- // Schema for subscribe validation
7
- const subscribeSchema = z.object({
8
- plan_id: z.string().min(1, 'Plan ID is required'),
9
- total_count: z.number().int().min(1, 'Total count must be at least 1').optional().default(12),
10
- quantity: z.number().int().min(1, 'Quantity must be at least 1').optional().default(1),
11
- start_at: z.number().int().optional(),
12
- expire_by: z.number().int().optional(),
13
- customer_notify: z.boolean().optional().default(true),
14
- addons: z
15
- .array(
16
- z.object({
17
- item: z.object({
18
- name: z.string().min(1, 'Addon name is required'),
19
- amount: z.number().int().positive('Addon amount must be positive'),
20
- currency: z.string().min(1, 'Addon currency is required'),
21
- }),
22
- })
23
- )
24
- .optional(),
25
- offer_id: z.string().optional(),
26
- notes: z.record(z.string(), z.string()).optional(),
3
+ export const createOrUpdateSubscriptionSchema = z.object({
4
+ plan: z.string().min(1, 'Plan name is required'),
5
+ annual: z.boolean().optional().default(false),
6
+ seats: z.number().int().min(1).optional().default(1),
7
+ subscriptionId: z.string().optional(),
8
+ successUrl: z.string().url().optional(),
9
+ disableRedirect: z.boolean().optional().default(false),
27
10
  })
28
11
 
29
- // Schema for verify payment validation
30
- const verifyPaymentSchema = z.object({
31
- razorpay_payment_id: z.string().min(1, 'Payment ID is required'),
32
- razorpay_subscription_id: z.string().min(1, 'Subscription ID is required'),
33
- razorpay_signature: z.string().min(1, 'Signature is required'),
12
+ export const cancelSubscriptionSchema = z.object({
13
+ subscriptionId: z.string().min(1, 'Subscription ID (local) is required'),
14
+ immediately: z.boolean().optional().default(false),
34
15
  })
35
16
 
36
- // Schema for pause subscription validation
37
- const pauseSubscriptionSchema = z.object({
38
- subscription_id: z.string().min(1, 'Subscription ID is required'),
17
+ export const restoreSubscriptionSchema = z.object({
18
+ subscriptionId: z.string().min(1, 'Subscription ID (local) is required'),
39
19
  })
40
20
 
41
- // Schema for cancel subscription validation
42
- const cancelSubscriptionSchema = z.object({
43
- subscription_id: z.string().min(1, 'Subscription ID is required'),
21
+ export const listSubscriptionsSchema = z.object({
22
+ referenceId: z.string().optional(),
44
23
  })
45
24
 
46
- // Schema for resume subscription validation
47
- const resumeSubscriptionSchema = z.object({
48
- subscription_id: z.string().min(1, 'Subscription ID is required'),
25
+ export const verifyPaymentSchema = z.object({
26
+ razorpay_payment_id: z.string().min(1, 'Payment ID is required'),
27
+ razorpay_subscription_id: z.string().min(1, 'Subscription ID is required'),
28
+ razorpay_signature: z.string().min(1, 'Signature is required'),
49
29
  })
50
30
 
51
31
  export {
52
- getPlansSchema,
53
- subscribeSchema,
54
- verifyPaymentSchema,
55
- pauseSubscriptionSchema,
56
- cancelSubscriptionSchema,
57
- resumeSubscriptionSchema,
32
+ createOrUpdateSubscriptionSchema as subscribeSchema,
58
33
  }
34
+ export type CreateOrUpdateSubscriptionInput = z.infer<typeof createOrUpdateSubscriptionSchema>