@deiondz/better-auth-razorpay 1.0.0 → 2.0.0
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 +258 -49
- 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/lib/types.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Razorpay subscription response
|
|
3
|
-
* This represents the full subscription object returned by Razorpay API calls.
|
|
2
|
+
* Razorpay subscription response from the Razorpay API.
|
|
4
3
|
*/
|
|
5
4
|
export interface RazorpaySubscription {
|
|
6
5
|
id: string
|
|
@@ -29,33 +28,103 @@ export interface RazorpaySubscription {
|
|
|
29
28
|
remaining_count: string
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
/** Local subscription status aligned with Razorpay and plugin lifecycle. */
|
|
32
|
+
export type SubscriptionStatus =
|
|
33
|
+
| 'created'
|
|
34
|
+
| 'active'
|
|
35
|
+
| 'pending'
|
|
36
|
+
| 'halted'
|
|
37
|
+
| 'cancelled'
|
|
38
|
+
| 'completed'
|
|
39
|
+
| 'expired'
|
|
40
|
+
| 'trialing'
|
|
41
|
+
|
|
42
|
+
/** Local subscription record stored in the auth adapter. */
|
|
43
|
+
export interface SubscriptionRecord {
|
|
44
|
+
id: string
|
|
45
|
+
plan: string
|
|
46
|
+
referenceId: string
|
|
47
|
+
razorpayCustomerId?: string | null
|
|
48
|
+
razorpaySubscriptionId?: string | null
|
|
49
|
+
status: SubscriptionStatus
|
|
50
|
+
trialStart?: Date | null
|
|
51
|
+
trialEnd?: Date | null
|
|
52
|
+
periodStart?: Date | null
|
|
53
|
+
periodEnd?: Date | null
|
|
54
|
+
cancelAtPeriodEnd: boolean
|
|
55
|
+
seats: number
|
|
56
|
+
groupId?: string | null
|
|
57
|
+
createdAt: Date
|
|
58
|
+
updatedAt: Date
|
|
40
59
|
}
|
|
41
60
|
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
|
|
61
|
+
/** Plan limits (customizable per plan). */
|
|
62
|
+
export interface PlanLimits {
|
|
63
|
+
[key: string]: number
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Free trial configuration for a plan. */
|
|
67
|
+
export interface PlanFreeTrial {
|
|
68
|
+
days: number
|
|
69
|
+
onTrialStart?: (subscription: SubscriptionRecord) => Promise<void>
|
|
70
|
+
onTrialEnd?: (args: { subscription: SubscriptionRecord }) => Promise<void>
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Named plan with monthly/annual Razorpay plan IDs and optional trial. */
|
|
74
|
+
export interface RazorpayPlan {
|
|
75
|
+
name: string
|
|
76
|
+
monthlyPlanId: string
|
|
77
|
+
annualPlanId?: string
|
|
78
|
+
limits?: PlanLimits
|
|
79
|
+
freeTrial?: PlanFreeTrial
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Subscription plugin options (plans, callbacks, authorization). */
|
|
83
|
+
export interface SubscriptionOptions {
|
|
84
|
+
enabled: boolean
|
|
85
|
+
plans: RazorpayPlan[] | (() => Promise<RazorpayPlan[]>)
|
|
86
|
+
requireEmailVerification?: boolean
|
|
87
|
+
authorizeReference?: (args: {
|
|
88
|
+
user: { id: string; email?: string; name?: string; [key: string]: unknown }
|
|
89
|
+
referenceId: string
|
|
90
|
+
action: string
|
|
91
|
+
}) => Promise<boolean>
|
|
92
|
+
getSubscriptionCreateParams?: (args: {
|
|
93
|
+
user: { id: string; email?: string; name?: string; [key: string]: unknown }
|
|
94
|
+
session: unknown
|
|
95
|
+
plan: RazorpayPlan
|
|
96
|
+
subscription: SubscriptionRecord
|
|
97
|
+
}) => Promise<{ params?: Record<string, unknown> }>
|
|
98
|
+
onSubscriptionCreated?: (args: {
|
|
99
|
+
razorpaySubscription: RazorpaySubscription
|
|
100
|
+
subscription: SubscriptionRecord
|
|
101
|
+
plan: RazorpayPlan
|
|
102
|
+
}) => Promise<void>
|
|
103
|
+
onSubscriptionActivated?: (args: {
|
|
104
|
+
event: string
|
|
105
|
+
razorpaySubscription: RazorpaySubscription
|
|
106
|
+
subscription: SubscriptionRecord
|
|
107
|
+
plan: RazorpayPlan
|
|
108
|
+
}) => Promise<void>
|
|
109
|
+
onSubscriptionUpdate?: (args: { event: string; subscription: SubscriptionRecord }) => Promise<void>
|
|
110
|
+
onSubscriptionCancel?: (args: {
|
|
111
|
+
event: string
|
|
112
|
+
razorpaySubscription: RazorpaySubscription
|
|
113
|
+
subscription: SubscriptionRecord
|
|
114
|
+
}) => Promise<void>
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** User record shape used by the Razorpay plugin (customer ID on user). */
|
|
45
118
|
export interface RazorpayUserRecord {
|
|
46
119
|
id: string
|
|
47
120
|
email?: string
|
|
48
121
|
name?: string
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
subscriptionStatus?: string
|
|
52
|
-
subscriptionCurrentPeriodEnd?: Date | null
|
|
53
|
-
cancelAtPeriodEnd?: boolean
|
|
54
|
-
lastPaymentDate?: Date | null
|
|
55
|
-
nextBillingDate?: Date | null
|
|
122
|
+
razorpayCustomerId?: string | null
|
|
123
|
+
[key: string]: unknown
|
|
56
124
|
}
|
|
57
125
|
|
|
58
|
-
|
|
126
|
+
/** Razorpay webhook event types. */
|
|
127
|
+
export type RazorpayWebhookEvent =
|
|
59
128
|
| 'subscription.authenticated'
|
|
60
129
|
| 'subscription.activated'
|
|
61
130
|
| 'subscription.charged'
|
|
@@ -64,8 +133,9 @@ type RazorpayWebhookEvent =
|
|
|
64
133
|
| 'subscription.resumed'
|
|
65
134
|
| 'subscription.pending'
|
|
66
135
|
| 'subscription.halted'
|
|
136
|
+
| 'subscription.expired'
|
|
67
137
|
|
|
68
|
-
interface RazorpayWebhookPayload {
|
|
138
|
+
export interface RazorpayWebhookPayload {
|
|
69
139
|
event: RazorpayWebhookEvent
|
|
70
140
|
subscription: {
|
|
71
141
|
id: string
|
|
@@ -73,79 +143,62 @@ interface RazorpayWebhookPayload {
|
|
|
73
143
|
status: string
|
|
74
144
|
current_start?: number
|
|
75
145
|
current_end?: number
|
|
76
|
-
[key: string]: unknown
|
|
146
|
+
[key: string]: unknown
|
|
77
147
|
}
|
|
78
148
|
payment?: {
|
|
79
149
|
id: string
|
|
80
150
|
amount: number
|
|
81
151
|
currency: string
|
|
82
|
-
[key: string]: unknown
|
|
152
|
+
[key: string]: unknown
|
|
83
153
|
}
|
|
84
154
|
}
|
|
85
155
|
|
|
86
|
-
interface RazorpayWebhookContext {
|
|
156
|
+
export interface RazorpayWebhookContext {
|
|
87
157
|
userId: string
|
|
88
|
-
user: {
|
|
89
|
-
id: string
|
|
90
|
-
email: string
|
|
91
|
-
name: string
|
|
92
|
-
[key: string]: unknown // Allow other user fields
|
|
93
|
-
}
|
|
158
|
+
user: { id: string; email?: string; name?: string; [key: string]: unknown }
|
|
94
159
|
}
|
|
95
160
|
|
|
96
|
-
|
|
97
|
-
* Callback function invoked after webhook events are processed.
|
|
98
|
-
* Can be used for any custom logic: emails, notifications, analytics, integrations, etc.
|
|
99
|
-
*/
|
|
100
|
-
type OnWebhookEventCallback = (
|
|
161
|
+
export type OnWebhookEventCallback = (
|
|
101
162
|
payload: RazorpayWebhookPayload,
|
|
102
163
|
context: RazorpayWebhookContext
|
|
103
164
|
) => Promise<void>
|
|
104
165
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
166
|
+
/** Main plugin options: client, webhook secret, customer creation, subscription config, callbacks. */
|
|
167
|
+
export interface RazorpayPluginOptions {
|
|
168
|
+
/** Initialized Razorpay client instance. */
|
|
169
|
+
razorpayClient: import('razorpay')
|
|
170
|
+
/** Webhook secret for signature verification. */
|
|
171
|
+
razorpayWebhookSecret?: string
|
|
172
|
+
/** Create Razorpay customer when user signs up. Default: false. */
|
|
173
|
+
createCustomerOnSignUp?: boolean
|
|
174
|
+
/** Called after a Razorpay customer is created. */
|
|
175
|
+
onCustomerCreate?: (args: {
|
|
176
|
+
user: RazorpayUserRecord
|
|
177
|
+
razorpayCustomer: { id: string; [key: string]: unknown }
|
|
178
|
+
}) => Promise<void>
|
|
179
|
+
/** Custom params (e.g. notes) when creating Razorpay customer. */
|
|
180
|
+
getCustomerCreateParams?: (args: {
|
|
181
|
+
user: RazorpayUserRecord
|
|
182
|
+
session: unknown
|
|
183
|
+
}) => Promise<{ params?: Record<string, unknown> }>
|
|
184
|
+
/** Subscription feature config (plans, callbacks). */
|
|
185
|
+
subscription?: SubscriptionOptions
|
|
186
|
+
/** Global callback for all processed webhook events. */
|
|
187
|
+
onEvent?: (event: { event: string; [key: string]: unknown }) => Promise<void>
|
|
188
|
+
/** Legacy: callback after webhook events are processed (payload + context). */
|
|
115
189
|
onWebhookEvent?: OnWebhookEventCallback
|
|
116
190
|
}
|
|
117
191
|
|
|
118
|
-
/**
|
|
119
|
-
* Standard success response structure for Razorpay API endpoints.
|
|
120
|
-
*/
|
|
121
192
|
export interface RazorpaySuccessResponse<T = unknown> {
|
|
122
193
|
success: true
|
|
123
194
|
data: T
|
|
124
195
|
}
|
|
125
196
|
|
|
126
|
-
/**
|
|
127
|
-
* Standard error response structure for Razorpay API endpoints.
|
|
128
|
-
*/
|
|
129
197
|
export interface RazorpayErrorResponse {
|
|
130
198
|
success: false
|
|
131
|
-
error: {
|
|
132
|
-
code: string
|
|
133
|
-
description: string
|
|
134
|
-
[key: string]: unknown // Allow additional error metadata
|
|
135
|
-
}
|
|
199
|
+
error: { code: string; description: string; [key: string]: unknown }
|
|
136
200
|
}
|
|
137
201
|
|
|
138
|
-
/**
|
|
139
|
-
* Union type for all Razorpay API responses.
|
|
140
|
-
*/
|
|
141
202
|
export type RazorpayApiResponse<T = unknown> =
|
|
142
203
|
| RazorpaySuccessResponse<T>
|
|
143
204
|
| RazorpayErrorResponse
|
|
144
|
-
|
|
145
|
-
export type {
|
|
146
|
-
RazorpayPluginOptions,
|
|
147
|
-
RazorpayWebhookEvent,
|
|
148
|
-
RazorpayWebhookPayload,
|
|
149
|
-
RazorpayWebhookContext,
|
|
150
|
-
OnWebhookEventCallback,
|
|
151
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deiondz/better-auth-razorpay",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Better Auth plugin for Razorpay subscriptions and payments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -15,11 +15,17 @@
|
|
|
15
15
|
"types": "./client.ts",
|
|
16
16
|
"import": "./client.ts",
|
|
17
17
|
"default": "./client.ts"
|
|
18
|
+
},
|
|
19
|
+
"./hooks": {
|
|
20
|
+
"types": "./client/hooks.ts",
|
|
21
|
+
"import": "./client/hooks.ts",
|
|
22
|
+
"default": "./client/hooks.ts"
|
|
18
23
|
}
|
|
19
24
|
},
|
|
20
25
|
"files": [
|
|
21
26
|
"index.ts",
|
|
22
27
|
"client.ts",
|
|
28
|
+
"client",
|
|
23
29
|
"api",
|
|
24
30
|
"lib",
|
|
25
31
|
"README.md",
|
|
@@ -43,13 +49,17 @@
|
|
|
43
49
|
"url": "git+https://github.com/deiondz/better-auth-razorpay.git"
|
|
44
50
|
},
|
|
45
51
|
"peerDependencies": {
|
|
46
|
-
"
|
|
52
|
+
"@tanstack/react-query": "^5.0.0",
|
|
53
|
+
"better-auth": "^1.0.0",
|
|
54
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
47
55
|
},
|
|
48
56
|
"dependencies": {
|
|
49
57
|
"razorpay": "^2.9.2",
|
|
50
58
|
"zod": "^3.23.0"
|
|
51
59
|
},
|
|
52
60
|
"devDependencies": {
|
|
61
|
+
"@tanstack/react-query": "^5.0.0",
|
|
62
|
+
"react": "^18.0.0",
|
|
53
63
|
"typescript": "^5.0.0"
|
|
54
64
|
},
|
|
55
65
|
"engines": {
|
package/api/get-subscription.ts
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { createAuthEndpoint, sessionMiddleware } from 'better-auth/api'
|
|
2
|
-
import type Razorpay from 'razorpay'
|
|
3
|
-
import {
|
|
4
|
-
handleRazorpayError,
|
|
5
|
-
type RazorpaySubscription,
|
|
6
|
-
type RazorpayUserRecord,
|
|
7
|
-
} from '../lib'
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Retrieves the current subscription details for the authenticated user.
|
|
11
|
-
*
|
|
12
|
-
* @param razorpay - The Razorpay instance initialized with API credentials
|
|
13
|
-
* @returns A Better Auth endpoint handler
|
|
14
|
-
*
|
|
15
|
-
* @remarks
|
|
16
|
-
* This endpoint:
|
|
17
|
-
* - Requires user authentication via session
|
|
18
|
-
* - Fetches subscription details from Razorpay API
|
|
19
|
-
* - Includes cancellation status and period end information
|
|
20
|
-
* - Returns null if user has no active subscription
|
|
21
|
-
* - Provides detailed error messages in development mode
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* Response (success):
|
|
25
|
-
* ```json
|
|
26
|
-
* {
|
|
27
|
-
* "success": true,
|
|
28
|
-
* "data": {
|
|
29
|
-
* "id": "sub_1234567890",
|
|
30
|
-
* "status": "active",
|
|
31
|
-
* "plan_id": "plan_1234567890",
|
|
32
|
-
* "cancel_at_period_end": false,
|
|
33
|
-
* ...
|
|
34
|
-
* }
|
|
35
|
-
* }
|
|
36
|
-
* ```
|
|
37
|
-
*/
|
|
38
|
-
export const getSubscription = (razorpay: Razorpay) =>
|
|
39
|
-
createAuthEndpoint(
|
|
40
|
-
'/razorpay/get-subscription',
|
|
41
|
-
{ method: 'GET', use: [sessionMiddleware] },
|
|
42
|
-
async (_ctx) => {
|
|
43
|
-
try {
|
|
44
|
-
// Get user ID from session
|
|
45
|
-
const userId = _ctx.context.session?.user?.id
|
|
46
|
-
|
|
47
|
-
if (!userId) {
|
|
48
|
-
return {
|
|
49
|
-
success: false,
|
|
50
|
-
error: {
|
|
51
|
-
code: 'UNAUTHORIZED',
|
|
52
|
-
description: 'User not authenticated',
|
|
53
|
-
},
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Get user record to check subscription status
|
|
58
|
-
const user = (await _ctx.context.adapter.findOne({
|
|
59
|
-
model: 'user',
|
|
60
|
-
where: [{ field: 'id', value: userId }],
|
|
61
|
-
})) as RazorpayUserRecord | null
|
|
62
|
-
|
|
63
|
-
if (!user) {
|
|
64
|
-
return {
|
|
65
|
-
success: false,
|
|
66
|
-
error: {
|
|
67
|
-
code: 'USER_NOT_FOUND',
|
|
68
|
-
description: 'User not found',
|
|
69
|
-
},
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Check subscription status from user table
|
|
74
|
-
const subscriptionId = user.subscriptionId
|
|
75
|
-
|
|
76
|
-
if (!subscriptionId) {
|
|
77
|
-
return {
|
|
78
|
-
success: true,
|
|
79
|
-
data: null,
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Fetch full subscription details from Razorpay API
|
|
84
|
-
try {
|
|
85
|
-
const subscription = (await razorpay.subscriptions.fetch(
|
|
86
|
-
subscriptionId
|
|
87
|
-
)) as RazorpaySubscription
|
|
88
|
-
|
|
89
|
-
// Read cancellation status from user table
|
|
90
|
-
const cancelAtPeriodEnd = user.cancelAtPeriodEnd ?? false
|
|
91
|
-
const subscriptionCurrentPeriodEnd = user.subscriptionCurrentPeriodEnd
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
success: true,
|
|
95
|
-
data: {
|
|
96
|
-
id: subscription.id,
|
|
97
|
-
entity: subscription.entity,
|
|
98
|
-
plan_id: subscription.plan_id,
|
|
99
|
-
status: subscription.status,
|
|
100
|
-
current_start: subscription.current_start,
|
|
101
|
-
current_end: subscription.current_end,
|
|
102
|
-
ended_at: subscription.ended_at,
|
|
103
|
-
quantity: subscription.quantity,
|
|
104
|
-
notes: subscription.notes,
|
|
105
|
-
charge_at: subscription.charge_at,
|
|
106
|
-
start_at: subscription.start_at,
|
|
107
|
-
end_at: subscription.end_at,
|
|
108
|
-
auth_attempts: subscription.auth_attempts,
|
|
109
|
-
total_count: subscription.total_count,
|
|
110
|
-
paid_count: subscription.paid_count,
|
|
111
|
-
customer_notify: subscription.customer_notify,
|
|
112
|
-
created_at: subscription.created_at,
|
|
113
|
-
expire_by: subscription.expire_by,
|
|
114
|
-
short_url: subscription.short_url,
|
|
115
|
-
has_scheduled_changes: subscription.has_scheduled_changes,
|
|
116
|
-
change_scheduled_at: subscription.change_scheduled_at,
|
|
117
|
-
source: subscription.source,
|
|
118
|
-
offer_id: subscription.offer_id,
|
|
119
|
-
remaining_count: subscription.remaining_count,
|
|
120
|
-
cancel_at_period_end: cancelAtPeriodEnd,
|
|
121
|
-
subscription_current_period_end: subscriptionCurrentPeriodEnd
|
|
122
|
-
? Math.floor(new Date(subscriptionCurrentPeriodEnd).getTime() / 1000)
|
|
123
|
-
: null,
|
|
124
|
-
},
|
|
125
|
-
}
|
|
126
|
-
} catch (razorpayError) {
|
|
127
|
-
// Handle Razorpay-specific errors with subscription ID context
|
|
128
|
-
const isDev = process.env.NODE_ENV === 'development'
|
|
129
|
-
|
|
130
|
-
// Extract error message from Razorpay error
|
|
131
|
-
let errorMessage = 'Failed to fetch subscription'
|
|
132
|
-
let errorCode = 'SUBSCRIPTION_FETCH_FAILED'
|
|
133
|
-
|
|
134
|
-
if (razorpayError && typeof razorpayError === 'object') {
|
|
135
|
-
// Razorpay error format: { error: { code: string, description: string } }
|
|
136
|
-
if ('error' in razorpayError) {
|
|
137
|
-
const razorpayErr = razorpayError as {
|
|
138
|
-
error?: { code?: string; description?: string }
|
|
139
|
-
}
|
|
140
|
-
errorCode = razorpayErr.error?.code || errorCode
|
|
141
|
-
errorMessage = razorpayErr.error?.description || errorMessage
|
|
142
|
-
} else if ('message' in razorpayError) {
|
|
143
|
-
errorMessage = (razorpayError as { message: string }).message
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Check if error is specifically about subscription not existing
|
|
148
|
-
const isNotFoundError =
|
|
149
|
-
errorMessage.toLowerCase().includes('does not exist') ||
|
|
150
|
-
errorMessage.toLowerCase().includes('not found') ||
|
|
151
|
-
errorCode === 'BAD_REQUEST_ERROR'
|
|
152
|
-
|
|
153
|
-
// Include subscription ID in error for debugging
|
|
154
|
-
const description = isDev
|
|
155
|
-
? `${errorMessage} (Subscription ID: ${subscriptionId})`
|
|
156
|
-
: isNotFoundError
|
|
157
|
-
? 'The subscription could not be found. This may indicate the subscription was deleted or the ID is invalid. Please contact support if this issue persists.'
|
|
158
|
-
: 'Unable to retrieve subscription information. Please try again or contact support if this issue persists.'
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
success: false,
|
|
162
|
-
error: {
|
|
163
|
-
code: errorCode,
|
|
164
|
-
description,
|
|
165
|
-
// Include subscription ID in metadata for development
|
|
166
|
-
...(isDev && { subscriptionId }),
|
|
167
|
-
},
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
} catch (error) {
|
|
171
|
-
return handleRazorpayError(error)
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
)
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { createAuthEndpoint, sessionMiddleware } from 'better-auth/api'
|
|
2
|
-
import type Razorpay from 'razorpay'
|
|
3
|
-
import {
|
|
4
|
-
handleRazorpayError,
|
|
5
|
-
pauseSubscriptionSchema,
|
|
6
|
-
type RazorpaySubscription,
|
|
7
|
-
type RazorpaySubscriptionRecord,
|
|
8
|
-
} from '../lib'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Pauses an active subscription.
|
|
12
|
-
*
|
|
13
|
-
* @param razorpay - The Razorpay instance initialized with API credentials
|
|
14
|
-
* @returns A Better Auth endpoint handler
|
|
15
|
-
*
|
|
16
|
-
* @remarks
|
|
17
|
-
* This endpoint:
|
|
18
|
-
* - Requires user authentication via session
|
|
19
|
-
* - Verifies subscription ownership before pausing
|
|
20
|
-
* - Pauses the subscription via Razorpay API
|
|
21
|
-
* - Updates subscription and user records with paused status
|
|
22
|
-
* - Paused subscriptions can be resumed later
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* Request body:
|
|
26
|
-
* ```json
|
|
27
|
-
* {
|
|
28
|
-
* "subscription_id": "sub_1234567890"
|
|
29
|
-
* }
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
export const pauseSubscription = (razorpay: Razorpay) =>
|
|
33
|
-
createAuthEndpoint(
|
|
34
|
-
'/razorpay/pause-subscription',
|
|
35
|
-
{ method: 'POST', use: [sessionMiddleware] },
|
|
36
|
-
async (_ctx) => {
|
|
37
|
-
try {
|
|
38
|
-
// Validate input using Zod schema
|
|
39
|
-
const validatedInput = pauseSubscriptionSchema.parse(_ctx.body)
|
|
40
|
-
|
|
41
|
-
// Get user ID from session
|
|
42
|
-
const userId = _ctx.context.session?.user?.id
|
|
43
|
-
|
|
44
|
-
if (!userId) {
|
|
45
|
-
return {
|
|
46
|
-
success: false,
|
|
47
|
-
error: {
|
|
48
|
-
code: 'UNAUTHORIZED',
|
|
49
|
-
description: 'User not authenticated',
|
|
50
|
-
},
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Get subscription record to verify it belongs to the user
|
|
55
|
-
const subscriptionRecord = (await _ctx.context.adapter.findOne({
|
|
56
|
-
model: 'razorpaySubscription',
|
|
57
|
-
where: [{ field: 'subscriptionId', value: validatedInput.subscription_id }],
|
|
58
|
-
})) as RazorpaySubscriptionRecord | null
|
|
59
|
-
|
|
60
|
-
if (!subscriptionRecord) {
|
|
61
|
-
return {
|
|
62
|
-
success: false,
|
|
63
|
-
error: {
|
|
64
|
-
code: 'SUBSCRIPTION_NOT_FOUND',
|
|
65
|
-
description: 'Subscription not found',
|
|
66
|
-
},
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Verify that the subscription belongs to the authenticated user
|
|
71
|
-
const subscriptionUserId = subscriptionRecord.userId
|
|
72
|
-
if (subscriptionUserId !== userId) {
|
|
73
|
-
return {
|
|
74
|
-
success: false,
|
|
75
|
-
error: {
|
|
76
|
-
code: 'UNAUTHORIZED',
|
|
77
|
-
description: 'Subscription does not belong to authenticated user',
|
|
78
|
-
},
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Pause subscription via Razorpay API
|
|
83
|
-
const subscription = (await razorpay.subscriptions.pause(
|
|
84
|
-
validatedInput.subscription_id
|
|
85
|
-
)) as RazorpaySubscription
|
|
86
|
-
|
|
87
|
-
// Update subscription status in database
|
|
88
|
-
await _ctx.context.adapter.update({
|
|
89
|
-
model: 'razorpaySubscription',
|
|
90
|
-
where: [{ field: 'subscriptionId', value: validatedInput.subscription_id }],
|
|
91
|
-
update: { status: subscription.status },
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
// Update user table with subscription status
|
|
95
|
-
await _ctx.context.adapter.update({
|
|
96
|
-
model: 'user',
|
|
97
|
-
where: [{ field: 'id', value: userId }],
|
|
98
|
-
update: {
|
|
99
|
-
data: {
|
|
100
|
-
subscriptionStatus: subscription.status,
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
success: true,
|
|
107
|
-
data: {
|
|
108
|
-
id: subscription.id,
|
|
109
|
-
entity: subscription.entity,
|
|
110
|
-
plan_id: subscription.plan_id,
|
|
111
|
-
status: subscription.status,
|
|
112
|
-
current_start: subscription.current_start,
|
|
113
|
-
current_end: subscription.current_end,
|
|
114
|
-
ended_at: subscription.ended_at,
|
|
115
|
-
quantity: subscription.quantity,
|
|
116
|
-
notes: subscription.notes,
|
|
117
|
-
charge_at: subscription.charge_at,
|
|
118
|
-
start_at: subscription.start_at,
|
|
119
|
-
end_at: subscription.end_at,
|
|
120
|
-
auth_attempts: subscription.auth_attempts,
|
|
121
|
-
total_count: subscription.total_count,
|
|
122
|
-
paid_count: subscription.paid_count,
|
|
123
|
-
customer_notify: subscription.customer_notify,
|
|
124
|
-
created_at: subscription.created_at,
|
|
125
|
-
expire_by: subscription.expire_by,
|
|
126
|
-
short_url: subscription.short_url,
|
|
127
|
-
has_scheduled_changes: subscription.has_scheduled_changes,
|
|
128
|
-
change_scheduled_at: subscription.change_scheduled_at,
|
|
129
|
-
source: subscription.source,
|
|
130
|
-
offer_id: subscription.offer_id,
|
|
131
|
-
remaining_count: subscription.remaining_count,
|
|
132
|
-
},
|
|
133
|
-
}
|
|
134
|
-
} catch (error) {
|
|
135
|
-
return handleRazorpayError(error)
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
)
|