@deiondz/better-auth-razorpay 2.0.5 → 2.0.7
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/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/client/hooks.d.ts +87 -0
- package/dist/client/hooks.js +182 -0
- package/dist/client/hooks.js.map +1 -0
- package/dist/client.d.ts +32 -0
- package/dist/client.js +49 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +4921 -0
- package/dist/index.js.map +1 -0
- package/dist/types-B25gyPpX.d.ts +208 -0
- package/dist/types-JYoruGio.d.ts +146 -0
- package/package.json +17 -18
- package/api/cancel-subscription.ts +0 -90
- package/api/create-or-update-subscription.ts +0 -254
- package/api/get-plans.ts +0 -36
- package/api/index.ts +0 -7
- package/api/list-subscriptions.ts +0 -79
- package/api/restore-subscription.ts +0 -79
- package/api/verify-payment.ts +0 -87
- package/api/webhook.ts +0 -305
- package/client/hooks.ts +0 -352
- package/client/types.ts +0 -154
- package/client.ts +0 -105
- package/index.ts +0 -162
- package/lib/error-handler.ts +0 -99
- package/lib/index.ts +0 -28
- package/lib/schemas.ts +0 -34
- package/lib/types.ts +0 -207
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
import { createAuthEndpoint, sessionMiddleware } from 'better-auth/api'
|
|
2
|
-
import type Razorpay from 'razorpay'
|
|
3
|
-
import {
|
|
4
|
-
createOrUpdateSubscriptionSchema,
|
|
5
|
-
handleRazorpayError,
|
|
6
|
-
type RazorpayPlan,
|
|
7
|
-
type RazorpayPluginOptions,
|
|
8
|
-
type RazorpaySubscription,
|
|
9
|
-
type RazorpayUserRecord,
|
|
10
|
-
type SubscriptionRecord,
|
|
11
|
-
} from '../lib'
|
|
12
|
-
|
|
13
|
-
async function resolvePlans(
|
|
14
|
-
plans: RazorpayPlan[] | (() => Promise<RazorpayPlan[]>)
|
|
15
|
-
): Promise<RazorpayPlan[]> {
|
|
16
|
-
return typeof plans === 'function' ? plans() : plans
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function toLocalStatus(razorpayStatus: string): SubscriptionRecord['status'] {
|
|
20
|
-
const map: Record<string, SubscriptionRecord['status']> = {
|
|
21
|
-
created: 'created',
|
|
22
|
-
authenticated: 'pending',
|
|
23
|
-
active: 'active',
|
|
24
|
-
pending: 'pending',
|
|
25
|
-
halted: 'halted',
|
|
26
|
-
cancelled: 'cancelled',
|
|
27
|
-
completed: 'completed',
|
|
28
|
-
expired: 'expired',
|
|
29
|
-
}
|
|
30
|
-
return map[razorpayStatus] ?? 'pending'
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* POST /api/auth/razorpay/subscription/create-or-update
|
|
35
|
-
* Creates a new subscription or updates an existing one (plan/quantity).
|
|
36
|
-
* Returns checkoutUrl for Razorpay payment page, or updated subscription for updates.
|
|
37
|
-
*/
|
|
38
|
-
export const createOrUpdateSubscription = (
|
|
39
|
-
razorpay: Razorpay,
|
|
40
|
-
options: Pick<
|
|
41
|
-
RazorpayPluginOptions,
|
|
42
|
-
'subscription' | 'createCustomerOnSignUp'
|
|
43
|
-
>
|
|
44
|
-
) =>
|
|
45
|
-
createAuthEndpoint(
|
|
46
|
-
'/razorpay/subscription/create-or-update',
|
|
47
|
-
{ method: 'POST', use: [sessionMiddleware] },
|
|
48
|
-
async (ctx) => {
|
|
49
|
-
try {
|
|
50
|
-
const body = createOrUpdateSubscriptionSchema.parse(ctx.body)
|
|
51
|
-
const subOpts = options.subscription
|
|
52
|
-
if (!subOpts?.enabled) {
|
|
53
|
-
return {
|
|
54
|
-
success: false,
|
|
55
|
-
error: { code: 'SUBSCRIPTION_DISABLED', description: 'Subscription feature is disabled' },
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const plans = await resolvePlans(subOpts.plans)
|
|
60
|
-
const plan = plans.find((p) => p.name === body.plan)
|
|
61
|
-
if (!plan) {
|
|
62
|
-
return {
|
|
63
|
-
success: false,
|
|
64
|
-
error: { code: 'PLAN_NOT_FOUND', description: `Plan "${body.plan}" not found` },
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const planId = body.annual && plan.annualPlanId ? plan.annualPlanId : plan.monthlyPlanId
|
|
69
|
-
const userId = ctx.context.session?.user?.id
|
|
70
|
-
if (!userId) {
|
|
71
|
-
return {
|
|
72
|
-
success: false,
|
|
73
|
-
error: { code: 'UNAUTHORIZED', description: 'User not authenticated' },
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const user = (await ctx.context.adapter.findOne({
|
|
78
|
-
model: 'user',
|
|
79
|
-
where: [{ field: 'id', value: userId }],
|
|
80
|
-
})) as RazorpayUserRecord | null
|
|
81
|
-
if (!user) {
|
|
82
|
-
return {
|
|
83
|
-
success: false,
|
|
84
|
-
error: { code: 'USER_NOT_FOUND', description: 'User not found' },
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (subOpts.requireEmailVerification && user.email) {
|
|
89
|
-
// If your Better Auth setup has emailVerified, check it here
|
|
90
|
-
// const verified = (user as { emailVerified?: boolean }).emailVerified
|
|
91
|
-
// if (!verified) return { success: false, error: { code: 'EMAIL_NOT_VERIFIED', ... } }
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (subOpts.authorizeReference) {
|
|
95
|
-
const allowed = await subOpts.authorizeReference({
|
|
96
|
-
user: user as { id: string; email?: string; name?: string; [key: string]: unknown },
|
|
97
|
-
referenceId: userId,
|
|
98
|
-
action: 'create-or-update',
|
|
99
|
-
})
|
|
100
|
-
if (!allowed) {
|
|
101
|
-
return {
|
|
102
|
-
success: false,
|
|
103
|
-
error: { code: 'FORBIDDEN', description: 'Not authorized to manage this subscription' },
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const now = new Date()
|
|
109
|
-
const generateId = ctx.context.generateId as
|
|
110
|
-
| ((options: { model: string; size?: number }) => string | false)
|
|
111
|
-
| undefined
|
|
112
|
-
const generated =
|
|
113
|
-
typeof generateId === 'function'
|
|
114
|
-
? generateId({ model: 'subscription' })
|
|
115
|
-
: undefined
|
|
116
|
-
const localId =
|
|
117
|
-
(typeof generated === 'string' ? generated : undefined) ??
|
|
118
|
-
`sub_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`
|
|
119
|
-
|
|
120
|
-
// Update existing subscription (by local id)
|
|
121
|
-
if (body.subscriptionId) {
|
|
122
|
-
const existing = (await ctx.context.adapter.findOne({
|
|
123
|
-
model: 'subscription',
|
|
124
|
-
where: [{ field: 'id', value: body.subscriptionId }],
|
|
125
|
-
})) as SubscriptionRecord | null
|
|
126
|
-
if (!existing) {
|
|
127
|
-
return {
|
|
128
|
-
success: false,
|
|
129
|
-
error: { code: 'SUBSCRIPTION_NOT_FOUND', description: 'Subscription not found' },
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
if (existing.referenceId !== userId) {
|
|
133
|
-
return {
|
|
134
|
-
success: false,
|
|
135
|
-
error: { code: 'FORBIDDEN', description: 'Subscription does not belong to you' },
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
// For "update" we could call Razorpay subscription update API if needed.
|
|
139
|
-
// Here we treat update as "create new" is not required by GitHub README; they return "updated subscription".
|
|
140
|
-
const rpSub = existing.razorpaySubscriptionId
|
|
141
|
-
? ((await razorpay.subscriptions.fetch(existing.razorpaySubscriptionId)) as RazorpaySubscription)
|
|
142
|
-
: null
|
|
143
|
-
if (rpSub) {
|
|
144
|
-
return {
|
|
145
|
-
success: true,
|
|
146
|
-
data: {
|
|
147
|
-
checkoutUrl: rpSub.short_url,
|
|
148
|
-
subscription: {
|
|
149
|
-
id: existing.id,
|
|
150
|
-
plan: existing.plan,
|
|
151
|
-
status: existing.status,
|
|
152
|
-
razorpaySubscriptionId: existing.razorpaySubscriptionId,
|
|
153
|
-
cancelAtPeriodEnd: existing.cancelAtPeriodEnd,
|
|
154
|
-
periodEnd: existing.periodEnd,
|
|
155
|
-
seats: existing.seats,
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Create new subscription
|
|
163
|
-
const totalCount = body.annual ? 1 : 12
|
|
164
|
-
const subscriptionPayload: Parameters<Razorpay['subscriptions']['create']>[0] = {
|
|
165
|
-
plan_id: planId,
|
|
166
|
-
total_count: totalCount,
|
|
167
|
-
quantity: body.seats,
|
|
168
|
-
customer_notify: true,
|
|
169
|
-
notes: { referenceId: userId, planName: plan.name },
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (subOpts.getSubscriptionCreateParams) {
|
|
173
|
-
const tempSub: SubscriptionRecord = {
|
|
174
|
-
id: '',
|
|
175
|
-
plan: plan.name,
|
|
176
|
-
referenceId: userId,
|
|
177
|
-
status: 'created',
|
|
178
|
-
cancelAtPeriodEnd: false,
|
|
179
|
-
seats: body.seats,
|
|
180
|
-
createdAt: now,
|
|
181
|
-
updatedAt: now,
|
|
182
|
-
}
|
|
183
|
-
const extra = await subOpts.getSubscriptionCreateParams({
|
|
184
|
-
user: user as { id: string; email?: string; name?: string; [key: string]: unknown },
|
|
185
|
-
session: ctx.context.session,
|
|
186
|
-
plan,
|
|
187
|
-
subscription: tempSub,
|
|
188
|
-
})
|
|
189
|
-
if (extra?.params?.notes && typeof extra.params.notes === 'object') {
|
|
190
|
-
subscriptionPayload.notes = { ...subscriptionPayload.notes, ...extra.params.notes }
|
|
191
|
-
}
|
|
192
|
-
if (extra?.params && typeof extra.params === 'object') {
|
|
193
|
-
Object.assign(subscriptionPayload, extra.params)
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const rpSubscription = (await razorpay.subscriptions.create(
|
|
198
|
-
subscriptionPayload
|
|
199
|
-
)) as RazorpaySubscription
|
|
200
|
-
|
|
201
|
-
const subscriptionRecord: Omit<SubscriptionRecord, 'id'> & { id: string } = {
|
|
202
|
-
id: localId,
|
|
203
|
-
plan: plan.name,
|
|
204
|
-
referenceId: userId,
|
|
205
|
-
razorpayCustomerId: user.razorpayCustomerId ?? null,
|
|
206
|
-
razorpaySubscriptionId: rpSubscription.id,
|
|
207
|
-
status: toLocalStatus(rpSubscription.status),
|
|
208
|
-
trialStart: null,
|
|
209
|
-
trialEnd: null,
|
|
210
|
-
periodStart: rpSubscription.current_start
|
|
211
|
-
? new Date(rpSubscription.current_start * 1000)
|
|
212
|
-
: null,
|
|
213
|
-
periodEnd: rpSubscription.current_end
|
|
214
|
-
? new Date(rpSubscription.current_end * 1000)
|
|
215
|
-
: null,
|
|
216
|
-
cancelAtPeriodEnd: false,
|
|
217
|
-
seats: body.seats,
|
|
218
|
-
groupId: null,
|
|
219
|
-
createdAt: now,
|
|
220
|
-
updatedAt: now,
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
await ctx.context.adapter.create({
|
|
224
|
-
model: 'subscription',
|
|
225
|
-
data: subscriptionRecord,
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
if (subOpts.onSubscriptionCreated) {
|
|
229
|
-
await subOpts.onSubscriptionCreated({
|
|
230
|
-
razorpaySubscription: rpSubscription,
|
|
231
|
-
subscription: subscriptionRecord as SubscriptionRecord,
|
|
232
|
-
plan,
|
|
233
|
-
})
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const checkoutUrl = body.disableRedirect
|
|
237
|
-
? rpSubscription.short_url
|
|
238
|
-
: body.successUrl
|
|
239
|
-
? `${rpSubscription.short_url}?redirect=${encodeURIComponent(body.successUrl)}`
|
|
240
|
-
: rpSubscription.short_url
|
|
241
|
-
|
|
242
|
-
return {
|
|
243
|
-
success: true,
|
|
244
|
-
data: {
|
|
245
|
-
checkoutUrl,
|
|
246
|
-
subscriptionId: localId,
|
|
247
|
-
razorpaySubscriptionId: rpSubscription.id,
|
|
248
|
-
},
|
|
249
|
-
}
|
|
250
|
-
} catch (error) {
|
|
251
|
-
return handleRazorpayError(error)
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
)
|
package/api/get-plans.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { createAuthEndpoint } from 'better-auth/api'
|
|
2
|
-
import { handleRazorpayError, type RazorpayPlan, type RazorpayPluginOptions } from '../lib'
|
|
3
|
-
|
|
4
|
-
async function resolvePlans(
|
|
5
|
-
plans: RazorpayPlan[] | (() => Promise<RazorpayPlan[]>)
|
|
6
|
-
): Promise<RazorpayPlan[]> {
|
|
7
|
-
return typeof plans === 'function' ? plans() : plans
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* GET /api/auth/razorpay/get-plans
|
|
12
|
-
* Returns the configured subscription plans (name, monthlyPlanId, annualPlanId, limits, freeTrial).
|
|
13
|
-
* Does not call Razorpay API.
|
|
14
|
-
*/
|
|
15
|
-
export const getPlans = (options: Pick<RazorpayPluginOptions, 'subscription'>) =>
|
|
16
|
-
createAuthEndpoint('/razorpay/get-plans', { method: 'GET' }, async (_ctx) => {
|
|
17
|
-
try {
|
|
18
|
-
const subOpts = options.subscription
|
|
19
|
-
if (!subOpts?.enabled) {
|
|
20
|
-
return { success: true, data: [] }
|
|
21
|
-
}
|
|
22
|
-
const plans = await resolvePlans(subOpts.plans)
|
|
23
|
-
return {
|
|
24
|
-
success: true,
|
|
25
|
-
data: plans.map((p) => ({
|
|
26
|
-
name: p.name,
|
|
27
|
-
monthlyPlanId: p.monthlyPlanId,
|
|
28
|
-
annualPlanId: p.annualPlanId,
|
|
29
|
-
limits: p.limits,
|
|
30
|
-
freeTrial: p.freeTrial ? { days: p.freeTrial.days } : undefined,
|
|
31
|
-
})),
|
|
32
|
-
}
|
|
33
|
-
} catch (error) {
|
|
34
|
-
return handleRazorpayError(error)
|
|
35
|
-
}
|
|
36
|
-
})
|
package/api/index.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export { cancelSubscription } from './cancel-subscription'
|
|
2
|
-
export { createOrUpdateSubscription } from './create-or-update-subscription'
|
|
3
|
-
export { getPlans } from './get-plans'
|
|
4
|
-
export { listSubscriptions } from './list-subscriptions'
|
|
5
|
-
export { restoreSubscription } from './restore-subscription'
|
|
6
|
-
export { verifyPayment } from './verify-payment'
|
|
7
|
-
export { webhook } from './webhook'
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { createAuthEndpoint, sessionMiddleware } from 'better-auth/api'
|
|
2
|
-
import {
|
|
3
|
-
handleRazorpayError,
|
|
4
|
-
listSubscriptionsSchema,
|
|
5
|
-
type RazorpayPluginOptions,
|
|
6
|
-
type SubscriptionRecord,
|
|
7
|
-
} from '../lib'
|
|
8
|
-
|
|
9
|
-
const ACTIVE_STATUSES: SubscriptionRecord['status'][] = [
|
|
10
|
-
'active',
|
|
11
|
-
'trialing',
|
|
12
|
-
'pending',
|
|
13
|
-
'created',
|
|
14
|
-
]
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* GET /api/auth/razorpay/subscription/list
|
|
18
|
-
* Lists active and trialing subscriptions for the current user (or referenceId if authorized).
|
|
19
|
-
*/
|
|
20
|
-
export const listSubscriptions = (
|
|
21
|
-
options: Pick<RazorpayPluginOptions, 'subscription'>
|
|
22
|
-
) =>
|
|
23
|
-
createAuthEndpoint(
|
|
24
|
-
'/razorpay/subscription/list',
|
|
25
|
-
{ method: 'GET', use: [sessionMiddleware] },
|
|
26
|
-
async (ctx) => {
|
|
27
|
-
try {
|
|
28
|
-
const query = listSubscriptionsSchema.parse(ctx.query ?? {})
|
|
29
|
-
const userId = ctx.context.session?.user?.id
|
|
30
|
-
if (!userId) {
|
|
31
|
-
return {
|
|
32
|
-
success: false,
|
|
33
|
-
error: { code: 'UNAUTHORIZED', description: 'User not authenticated' },
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const referenceId = query.referenceId ?? userId
|
|
38
|
-
if (referenceId !== userId && options.subscription?.authorizeReference) {
|
|
39
|
-
const user = (await ctx.context.adapter.findOne({
|
|
40
|
-
model: 'user',
|
|
41
|
-
where: [{ field: 'id', value: userId }],
|
|
42
|
-
})) as { id: string; email?: string; name?: string } | null
|
|
43
|
-
if (!user) {
|
|
44
|
-
return {
|
|
45
|
-
success: false,
|
|
46
|
-
error: { code: 'USER_NOT_FOUND', description: 'User not found' },
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
const allowed = await options.subscription.authorizeReference({
|
|
50
|
-
user: user as { id: string; email?: string; name?: string; [key: string]: unknown },
|
|
51
|
-
referenceId,
|
|
52
|
-
action: 'list',
|
|
53
|
-
})
|
|
54
|
-
if (!allowed) {
|
|
55
|
-
return {
|
|
56
|
-
success: false,
|
|
57
|
-
error: { code: 'FORBIDDEN', description: 'Not authorized to list this user\'s subscriptions' },
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const list = (await ctx.context.adapter.findMany({
|
|
63
|
-
model: 'subscription',
|
|
64
|
-
where: [{ field: 'referenceId', value: referenceId }],
|
|
65
|
-
})) as SubscriptionRecord[] | null
|
|
66
|
-
|
|
67
|
-
const subscriptions = (list ?? []).filter((s) =>
|
|
68
|
-
ACTIVE_STATUSES.includes(s.status)
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
success: true,
|
|
73
|
-
data: subscriptions,
|
|
74
|
-
}
|
|
75
|
-
} catch (error) {
|
|
76
|
-
return handleRazorpayError(error)
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
)
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { createAuthEndpoint, sessionMiddleware } from 'better-auth/api'
|
|
2
|
-
import type Razorpay from 'razorpay'
|
|
3
|
-
import {
|
|
4
|
-
handleRazorpayError,
|
|
5
|
-
restoreSubscriptionSchema,
|
|
6
|
-
type SubscriptionRecord,
|
|
7
|
-
} from '../lib'
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* POST /api/auth/razorpay/subscription/restore
|
|
11
|
-
* Restores a subscription that was scheduled to cancel at period end.
|
|
12
|
-
*/
|
|
13
|
-
export const restoreSubscription = (razorpay: Razorpay) =>
|
|
14
|
-
createAuthEndpoint(
|
|
15
|
-
'/razorpay/subscription/restore',
|
|
16
|
-
{ method: 'POST', use: [sessionMiddleware] },
|
|
17
|
-
async (ctx) => {
|
|
18
|
-
try {
|
|
19
|
-
const body = restoreSubscriptionSchema.parse(ctx.body)
|
|
20
|
-
const userId = ctx.context.session?.user?.id
|
|
21
|
-
if (!userId) {
|
|
22
|
-
return {
|
|
23
|
-
success: false,
|
|
24
|
-
error: { code: 'UNAUTHORIZED', description: 'User not authenticated' },
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const record = (await ctx.context.adapter.findOne({
|
|
29
|
-
model: 'subscription',
|
|
30
|
-
where: [{ field: 'id', value: body.subscriptionId }],
|
|
31
|
-
})) as SubscriptionRecord | null
|
|
32
|
-
|
|
33
|
-
if (!record) {
|
|
34
|
-
return {
|
|
35
|
-
success: false,
|
|
36
|
-
error: { code: 'SUBSCRIPTION_NOT_FOUND', description: 'Subscription not found' },
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
if (record.referenceId !== userId) {
|
|
40
|
-
return {
|
|
41
|
-
success: false,
|
|
42
|
-
error: { code: 'FORBIDDEN', description: 'Subscription does not belong to you' },
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const rpId = record.razorpaySubscriptionId
|
|
47
|
-
if (!rpId) {
|
|
48
|
-
return {
|
|
49
|
-
success: false,
|
|
50
|
-
error: { code: 'INVALID_STATE', description: 'No Razorpay subscription linked' },
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Razorpay: resume a paused subscription (or cancel scheduled cancellation)
|
|
55
|
-
const subscription = await razorpay.subscriptions.resume(rpId)
|
|
56
|
-
|
|
57
|
-
await ctx.context.adapter.update({
|
|
58
|
-
model: 'subscription',
|
|
59
|
-
where: [{ field: 'id', value: body.subscriptionId }],
|
|
60
|
-
update: {
|
|
61
|
-
data: {
|
|
62
|
-
cancelAtPeriodEnd: false,
|
|
63
|
-
updatedAt: new Date(),
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
success: true,
|
|
70
|
-
data: {
|
|
71
|
-
id: (subscription as { id: string }).id,
|
|
72
|
-
status: (subscription as { status: string }).status,
|
|
73
|
-
},
|
|
74
|
-
}
|
|
75
|
-
} catch (error) {
|
|
76
|
-
return handleRazorpayError(error)
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
)
|
package/api/verify-payment.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
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
|
-
)
|