@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.
- package/README.md +266 -57
- package/api/cancel-subscription.ts +35 -80
- package/api/create-or-update-subscription.ts +254 -0
- package/api/get-plans.ts +26 -53
- package/api/index.ts +3 -5
- package/api/list-subscriptions.ts +79 -0
- package/api/restore-subscription.ts +79 -0
- package/api/webhook.ts +153 -121
- package/client/hooks.ts +248 -0
- package/client/types.ts +108 -0
- package/index.ts +110 -77
- package/lib/index.ts +10 -4
- package/lib/schemas.ts +20 -44
- package/lib/types.ts +121 -68
- package/package.json +12 -2
- package/api/get-subscription.ts +0 -174
- package/api/pause-subscription.ts +0 -138
- package/api/resume-subscription.ts +0 -150
- package/api/subscribe.ts +0 -188
- package/api/verify-payment.ts +0 -129
package/client/types.ts
ADDED
|
@@ -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
|
-
|
|
7
|
-
|
|
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
|
-
*
|
|
19
|
-
* -
|
|
20
|
-
* -
|
|
21
|
-
* -
|
|
22
|
-
* -
|
|
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
|
|
26
|
-
* @param options.
|
|
27
|
-
* @param options.
|
|
28
|
-
* @param options.
|
|
29
|
-
* @param options.
|
|
30
|
-
* @param options.
|
|
31
|
-
* @
|
|
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 {
|
|
31
|
+
const {
|
|
32
|
+
razorpayClient,
|
|
33
|
+
razorpayWebhookSecret,
|
|
34
|
+
createCustomerOnSignUp = false,
|
|
35
|
+
onCustomerCreate,
|
|
36
|
+
getCustomerCreateParams,
|
|
37
|
+
subscription: subOpts,
|
|
38
|
+
onEvent,
|
|
39
|
+
} = options
|
|
53
40
|
|
|
54
|
-
if (!
|
|
55
|
-
throw new Error('Razorpay
|
|
41
|
+
if (!razorpayClient) {
|
|
42
|
+
throw new Error('Razorpay plugin: razorpayClient is required')
|
|
56
43
|
}
|
|
57
44
|
|
|
58
|
-
|
|
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
|
-
|
|
47
|
+
const plugin = {
|
|
66
48
|
id: 'razorpay-plugin',
|
|
67
49
|
|
|
68
50
|
schema: {
|
|
69
51
|
user: {
|
|
70
52
|
fields: {
|
|
71
|
-
|
|
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
|
-
|
|
56
|
+
subscription: {
|
|
81
57
|
fields: {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
'
|
|
102
|
-
'
|
|
103
|
-
'
|
|
104
|
-
'
|
|
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
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
53
|
-
subscribeSchema,
|
|
54
|
-
verifyPaymentSchema,
|
|
55
|
-
pauseSubscriptionSchema,
|
|
56
|
-
cancelSubscriptionSchema,
|
|
57
|
-
resumeSubscriptionSchema,
|
|
32
|
+
createOrUpdateSubscriptionSchema as subscribeSchema,
|
|
58
33
|
}
|
|
34
|
+
export type CreateOrUpdateSubscriptionInput = z.infer<typeof createOrUpdateSubscriptionSchema>
|