@deiondz/better-auth-razorpay 2.0.12 → 2.0.14

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
@@ -219,6 +219,7 @@ interface RazorpayPluginOptions {
219
219
  razorpayKeySecret?: string // Optional: Razorpay API key secret; required when razorpayClient is not provided; when set, enables POST /razorpay/verify-payment
220
220
  razorpayWebhookSecret?: string // Optional: Webhook secret for signature verification
221
221
  createCustomerOnSignUp?: boolean // Optional: Create Razorpay customer on user sign-up (default: false)
222
+ trialOnSignUp?: { days: number; planName?: string } // Optional: When set with createCustomerOnSignUp, creates an app-level trial subscription at sign-up. Omit for no sign-up trial.
222
223
  onCustomerCreate?: (args) => Promise<void>
223
224
  getCustomerCreateParams?: (args) => Promise<{ params?: Record<string, unknown> }>
224
225
  subscription?: SubscriptionOptions // Optional: { enabled, plans, callbacks, authorizeReference, ... }
@@ -227,6 +228,17 @@ interface RazorpayPluginOptions {
227
228
  }
228
229
  ```
229
230
 
231
+ ### Optional: Trial on sign-up
232
+
233
+ When you want new users to get a **free trial** without adding a payment method first (e.g. 14 days, then they must subscribe):
234
+
235
+ - Set **both** `createCustomerOnSignUp: true` and `trialOnSignUp: { days: 14, planName: 'Trial' }` (or any `days` and display `planName`).
236
+ - On sign-up, the plugin creates a Razorpay customer and **one local subscription** with `status: 'trialing'`, `trialStart` / `trialEnd`, and no `razorpaySubscriptionId` (no Razorpay subscription until they subscribe).
237
+ - **Subscription list** returns this record; your app can show "Free trial — ends &lt;trialEnd&gt;" and gate features when `trialEnd < now`.
238
+ - The user can **subscribe anytime** via create-or-update (choose a plan, get checkout URL); the same record is updated to a paid subscription (plan, `razorpaySubscriptionId`, status from Razorpay).
239
+
240
+ **Configurable:** Omit `trialOnSignUp` for products that do not want sign-up trials (e.g. checkout-first or plan-based trial only). If `trialOnSignUp` is not set, behavior is unchanged.
241
+
230
242
  ### Callback functions
231
243
 
232
244
  The plugin supports the same callback hooks as the [community plugin](https://github.com/iamjasonkendrick/better-auth-razorpay). You can use them for emails, analytics, external systems, or custom logic.
@@ -254,6 +266,7 @@ razorpayPlugin({
254
266
  razorpayWebhookSecret: process.env.RAZORPAY_WEBHOOK_SECRET,
255
267
  razorpayKeySecret: process.env.RAZORPAY_KEY_SECRET, // optional: enables verify-payment endpoint
256
268
  createCustomerOnSignUp: true,
269
+ trialOnSignUp: { days: 14, planName: 'Trial' }, // optional: app-level trial at sign-up
257
270
  onCustomerCreate: async ({ user, razorpayCustomer }) => {
258
271
  console.log(`Razorpay customer created for user ${user.id}: ${razorpayCustomer.id}`)
259
272
  },
@@ -341,7 +354,7 @@ All endpoints are prefixed with `/api/auth/razorpay/` (or your configured `baseP
341
354
  | Cancel | `POST` | `subscription/cancel` | Cancel by local subscription ID. Body: `subscriptionId`, `immediately?`. |
342
355
  | Restore | `POST` | `subscription/restore` | Restore a subscription scheduled to cancel. Body: `subscriptionId`. |
343
356
  | List | `GET` | `subscription/list` | List active/trialing subscriptions. Query: `referenceId?` (default: current user). |
344
- | Get plans | `GET` | `get-plans` | Return configured plans (name, monthlyPlanId, annualPlanId, limits, freeTrial). |
357
+ | Get plans | `GET` | `get-plans` | Return configured plans with price details (name, monthlyPlanId, annualPlanId, limits, freeTrial, monthly/annual amount, currency, period from Razorpay). |
345
358
  | Webhook | `POST` | `webhook` | Razorpay webhook URL; configure in Razorpay Dashboard. |
346
359
 
347
360
  ### 1. Get Plans
@@ -350,6 +363,8 @@ Retrieve all configured subscription plans (from plugin config; no Razorpay API
350
363
 
351
364
  **Endpoint:** `GET /api/auth/razorpay/get-plans`
352
365
 
366
+ Each plan in the response includes optional **price details** (`monthly`, `annual`) when available from Razorpay: `amount` (smallest currency unit, e.g. paise/cents), `currency`, `period` (e.g. monthly, yearly), and `interval`. Omitted if the plan fetch fails or the variant is not configured.
367
+
353
368
  **Authentication:** Not required (public endpoint)
354
369
 
355
370
  **Response:**
@@ -357,7 +372,7 @@ Retrieve all configured subscription plans (from plugin config; no Razorpay API
357
372
  ```typescript
358
373
  {
359
374
  success: true,
360
- data: Array<{ name: string; monthlyPlanId: string; annualPlanId?: string; limits?: Record<string, number>; freeTrial?: { days: number } }>
375
+ data: Array<{ name: string; monthlyPlanId: string; annualPlanId?: string; limits?: Record<string, number>; freeTrial?: { days: number }; monthly?: { amount: number; currency: string; period: string; interval?: number }; annual?: { amount: number; currency: string; period: string; interval?: number } }>
361
376
  }
362
377
  ```
363
378
 
@@ -1,10 +1,10 @@
1
- import { S as SubscriptionRecord } from '../types-VZW_XCzQ.js';
1
+ import { S as SubscriptionRecord } from '../types-3dNrA0qB.js';
2
2
  import * as _tanstack_react_query from '@tanstack/react-query';
3
3
  import { UseMutationOptions, UseQueryOptions } from '@tanstack/react-query';
4
4
  import * as react from 'react';
5
5
  import { ReactNode } from 'react';
6
- import { c as RazorpayAuthClient, d as CancelSubscriptionResponse, a as CancelSubscriptionInput, e as CreateOrUpdateSubscriptionResponse, C as CreateOrUpdateSubscriptionInput, P as PlanSummary, f as RestoreSubscriptionResponse, b as RestoreSubscriptionInput, g as ListSubscriptionsResponse, h as VerifyPaymentResponse, V as VerifyPaymentInput, L as ListSubscriptionsInput } from '../types-BGWv0IJy.js';
7
- export { G as GetPlansResponse, i as RazorpayApiError, R as RazorpayApiResult, j as RazorpayClientActions } from '../types-BGWv0IJy.js';
6
+ import { c as RazorpayAuthClient, d as CancelSubscriptionResponse, a as CancelSubscriptionInput, e as CreateOrUpdateSubscriptionResponse, C as CreateOrUpdateSubscriptionInput, P as PlanSummary, f as RestoreSubscriptionResponse, b as RestoreSubscriptionInput, g as ListSubscriptionsResponse, h as VerifyPaymentResponse, V as VerifyPaymentInput, L as ListSubscriptionsInput } from '../types-CKR2ILS1.js';
7
+ export { G as GetPlansResponse, i as RazorpayApiError, R as RazorpayApiResult, j as RazorpayClientActions } from '../types-CKR2ILS1.js';
8
8
  import 'razorpay';
9
9
 
10
10
  /**
package/dist/client.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { S as SubscriptionRecord } from './types-VZW_XCzQ.js';
1
+ import { S as SubscriptionRecord } from './types-3dNrA0qB.js';
2
2
  import { razorpayPlugin } from './index.js';
3
- import { R as RazorpayApiResult, P as PlanSummary, L as ListSubscriptionsInput, C as CreateOrUpdateSubscriptionInput, a as CancelSubscriptionInput, b as RestoreSubscriptionInput, V as VerifyPaymentInput } from './types-BGWv0IJy.js';
3
+ import { R as RazorpayApiResult, P as PlanSummary, L as ListSubscriptionsInput, C as CreateOrUpdateSubscriptionInput, a as CancelSubscriptionInput, b as RestoreSubscriptionInput, V as VerifyPaymentInput } from './types-CKR2ILS1.js';
4
4
  import 'razorpay';
5
5
  import 'better-auth';
6
6
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { BetterAuthPlugin } from 'better-auth';
2
- import { R as RazorpayPluginOptions } from './types-VZW_XCzQ.js';
3
- export { O as OnWebhookEventCallback, a as RazorpayApiResponse, b as RazorpayErrorResponse, c as RazorpayPlan, d as RazorpaySubscription, e as RazorpaySuccessResponse, f as RazorpayUserRecord, g as RazorpayWebhookContext, h as RazorpayWebhookEvent, i as RazorpayWebhookPayload, S as SubscriptionRecord, j as SubscriptionStatus } from './types-VZW_XCzQ.js';
2
+ import { R as RazorpayPluginOptions } from './types-3dNrA0qB.js';
3
+ export { O as OnWebhookEventCallback, a as RazorpayApiResponse, b as RazorpayErrorResponse, c as RazorpayPlan, d as RazorpaySubscription, e as RazorpaySuccessResponse, f as RazorpayUserRecord, g as RazorpayWebhookContext, h as RazorpayWebhookEvent, i as RazorpayWebhookPayload, S as SubscriptionRecord, j as SubscriptionStatus } from './types-3dNrA0qB.js';
4
4
  import 'razorpay';
5
5
 
6
6
  interface WebhookResult {
@@ -23,6 +23,7 @@ interface WebhookResult {
23
23
  * @param options.razorpayKeySecret - Razorpay API key secret (required when razorpayClient is not provided; when set, also enables POST /razorpay/verify-payment)
24
24
  * @param options.razorpayWebhookSecret - Webhook secret for signature verification
25
25
  * @param options.createCustomerOnSignUp - Create Razorpay customer when user signs up (default: false)
26
+ * @param options.trialOnSignUp - Optional. When set with createCustomerOnSignUp, creates an app-level trial subscription at sign-up. { days, planName? }. Omit for no sign-up trial.
26
27
  * @param options.onCustomerCreate - Callback after customer is created
27
28
  * @param options.getCustomerCreateParams - Custom params when creating customer
28
29
  * @param options.subscription - Subscription config (enabled, plans, callbacks, authorizeReference)
package/dist/index.js CHANGED
@@ -4334,8 +4334,15 @@ var createOrUpdateSubscription = (razorpay, options) => createAuthEndpoint2(
4334
4334
  "created",
4335
4335
  "halted"
4336
4336
  ];
4337
- const hasActiveSubscription = (existingSubs ?? []).filter((s) => activeStatuses.includes(s.status)).length > 0;
4338
- if (hasActiveSubscription) {
4337
+ const subs = existingSubs ?? [];
4338
+ const activePaidSubs = subs.filter(
4339
+ (s) => activeStatuses.includes(s.status) && s.razorpaySubscriptionId
4340
+ );
4341
+ const appTrialSubs = subs.filter(
4342
+ (s) => s.status === "trialing" && !s.razorpaySubscriptionId
4343
+ );
4344
+ const appTrialSub = appTrialSubs.length === 1 ? appTrialSubs[0] : null;
4345
+ if (activePaidSubs.length > 0) {
4339
4346
  return {
4340
4347
  success: false,
4341
4348
  error: {
@@ -4354,7 +4361,7 @@ var createOrUpdateSubscription = (razorpay, options) => createAuthEndpoint2(
4354
4361
  };
4355
4362
  if (subOpts.getSubscriptionCreateParams) {
4356
4363
  const tempSub = {
4357
- id: "",
4364
+ id: appTrialSub?.id ?? "",
4358
4365
  plan: plan.name,
4359
4366
  referenceId: userId,
4360
4367
  status: "created",
@@ -4379,17 +4386,64 @@ var createOrUpdateSubscription = (razorpay, options) => createAuthEndpoint2(
4379
4386
  const rpSubscription = await razorpay.subscriptions.create(
4380
4387
  subscriptionPayload
4381
4388
  );
4389
+ const periodStart = rpSubscription.current_start ? new Date(rpSubscription.current_start * 1e3) : null;
4390
+ const periodEnd = rpSubscription.current_end ? new Date(rpSubscription.current_end * 1e3) : null;
4391
+ const newStatus = toLocalStatus(rpSubscription.status);
4392
+ if (appTrialSub) {
4393
+ await ctx.context.adapter.update({
4394
+ model: "subscription",
4395
+ where: [{ field: "id", value: appTrialSub.id }],
4396
+ update: {
4397
+ data: {
4398
+ plan: plan.name,
4399
+ razorpaySubscriptionId: rpSubscription.id,
4400
+ status: newStatus,
4401
+ trialEnd: now,
4402
+ periodStart,
4403
+ periodEnd,
4404
+ seats: body.seats,
4405
+ updatedAt: now
4406
+ }
4407
+ }
4408
+ });
4409
+ if (subOpts.onSubscriptionCreated) {
4410
+ const updatedRecord = {
4411
+ ...appTrialSub,
4412
+ plan: plan.name,
4413
+ razorpaySubscriptionId: rpSubscription.id,
4414
+ status: newStatus,
4415
+ trialEnd: now,
4416
+ periodStart,
4417
+ periodEnd,
4418
+ seats: body.seats,
4419
+ updatedAt: now
4420
+ };
4421
+ await subOpts.onSubscriptionCreated({
4422
+ razorpaySubscription: rpSubscription,
4423
+ subscription: updatedRecord,
4424
+ plan
4425
+ });
4426
+ }
4427
+ const data2 = {
4428
+ subscriptionId: appTrialSub.id,
4429
+ razorpaySubscriptionId: rpSubscription.id
4430
+ };
4431
+ if (!body.embed) {
4432
+ data2.checkoutUrl = body.disableRedirect ? rpSubscription.short_url : body.successUrl ? `${rpSubscription.short_url}?redirect=${encodeURIComponent(body.successUrl)}` : rpSubscription.short_url;
4433
+ }
4434
+ return { success: true, data: data2 };
4435
+ }
4382
4436
  const subscriptionRecord = {
4383
4437
  id: localId,
4384
4438
  plan: plan.name,
4385
4439
  referenceId: userId,
4386
4440
  razorpayCustomerId: user.razorpayCustomerId ?? null,
4387
4441
  razorpaySubscriptionId: rpSubscription.id,
4388
- status: toLocalStatus(rpSubscription.status),
4442
+ status: newStatus,
4389
4443
  trialStart: null,
4390
4444
  trialEnd: null,
4391
- periodStart: rpSubscription.current_start ? new Date(rpSubscription.current_start * 1e3) : null,
4392
- periodEnd: rpSubscription.current_end ? new Date(rpSubscription.current_end * 1e3) : null,
4445
+ periodStart,
4446
+ periodEnd,
4393
4447
  cancelAtPeriodEnd: false,
4394
4448
  seats: body.seats,
4395
4449
  groupId: null,
@@ -4427,24 +4481,48 @@ import { createAuthEndpoint as createAuthEndpoint3 } from "better-auth/api";
4427
4481
  async function resolvePlans2(plans) {
4428
4482
  return typeof plans === "function" ? plans() : plans;
4429
4483
  }
4430
- var getPlans = (options) => createAuthEndpoint3("/razorpay/get-plans", { method: "GET" }, async (_ctx) => {
4484
+ async function fetchPlanPrice(razorpay, planId) {
4485
+ try {
4486
+ const plan = await razorpay.plans.fetch(planId);
4487
+ const item = plan?.item;
4488
+ if (item && typeof item.amount === "number" && typeof item.currency === "string") {
4489
+ return {
4490
+ amount: item.amount,
4491
+ currency: item.currency,
4492
+ period: typeof plan.period === "string" ? plan.period : "monthly",
4493
+ interval: typeof plan.interval === "number" ? plan.interval : void 0
4494
+ };
4495
+ }
4496
+ } catch {
4497
+ }
4498
+ return void 0;
4499
+ }
4500
+ var getPlans = (razorpay, options) => createAuthEndpoint3("/razorpay/get-plans", { method: "GET" }, async (_ctx) => {
4431
4501
  try {
4432
4502
  const subOpts = options.subscription;
4433
4503
  if (!subOpts?.enabled) {
4434
4504
  return { success: true, data: [] };
4435
4505
  }
4436
4506
  const plans = await resolvePlans2(subOpts.plans);
4437
- return {
4438
- success: true,
4439
- data: plans.map((p) => ({
4440
- name: p.name,
4441
- monthlyPlanId: p.monthlyPlanId,
4442
- annualPlanId: p.annualPlanId,
4443
- description: p.description,
4444
- limits: p.limits,
4445
- freeTrial: p.freeTrial ? { days: p.freeTrial.days } : void 0
4446
- }))
4447
- };
4507
+ const data = await Promise.all(
4508
+ plans.map(async (p) => {
4509
+ const [monthly, annual] = await Promise.all([
4510
+ fetchPlanPrice(razorpay, p.monthlyPlanId),
4511
+ p.annualPlanId ? fetchPlanPrice(razorpay, p.annualPlanId) : Promise.resolve(void 0)
4512
+ ]);
4513
+ return {
4514
+ name: p.name,
4515
+ monthlyPlanId: p.monthlyPlanId,
4516
+ annualPlanId: p.annualPlanId,
4517
+ description: p.description,
4518
+ limits: p.limits,
4519
+ freeTrial: p.freeTrial ? { days: p.freeTrial.days } : void 0,
4520
+ monthly,
4521
+ annual
4522
+ };
4523
+ })
4524
+ );
4525
+ return { success: true, data };
4448
4526
  } catch (error) {
4449
4527
  return handleRazorpayError(error);
4450
4528
  }
@@ -4855,6 +4933,7 @@ var razorpayPlugin = (options) => {
4855
4933
  razorpayKeySecret,
4856
4934
  razorpayWebhookSecret,
4857
4935
  createCustomerOnSignUp = false,
4936
+ trialOnSignUp,
4858
4937
  onCustomerCreate,
4859
4938
  getCustomerCreateParams,
4860
4939
  subscription: subOpts,
@@ -4912,7 +4991,7 @@ var razorpayPlugin = (options) => {
4912
4991
  "subscription/cancel": cancelSubscription(razorpay),
4913
4992
  "subscription/restore": restoreSubscription(razorpay),
4914
4993
  "subscription/list": listSubscriptions({ subscription: subOpts }),
4915
- "get-plans": getPlans({ subscription: subOpts }),
4994
+ "get-plans": getPlans(razorpay, { subscription: subOpts }),
4916
4995
  ...razorpayKeySecret ? { "verify-payment": verifyPayment(razorpayKeySecret) } : {},
4917
4996
  webhook: webhook(razorpayWebhookSecret, options.onWebhookEvent ?? void 0, {
4918
4997
  subscription: subOpts,
@@ -4951,6 +5030,34 @@ var razorpayPlugin = (options) => {
4951
5030
  razorpayCustomer: { id: customer.id, ...customer }
4952
5031
  });
4953
5032
  }
5033
+ if (trialOnSignUp && typeof trialOnSignUp.days === "number" && trialOnSignUp.days > 0 && adapter.create) {
5034
+ const now = /* @__PURE__ */ new Date();
5035
+ const trialEnd = new Date(now.getTime() + trialOnSignUp.days * 24 * 60 * 60 * 1e3);
5036
+ const planName = trialOnSignUp.planName ?? "Trial";
5037
+ const localId = `sub_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
5038
+ const subscriptionRecord = {
5039
+ id: localId,
5040
+ plan: planName,
5041
+ referenceId: user.id,
5042
+ razorpayCustomerId: customer.id,
5043
+ razorpaySubscriptionId: null,
5044
+ status: "trialing",
5045
+ trialStart: now,
5046
+ trialEnd,
5047
+ periodStart: null,
5048
+ periodEnd: null,
5049
+ cancelAtPeriodEnd: false,
5050
+ seats: 1,
5051
+ groupId: null,
5052
+ createdAt: now,
5053
+ updatedAt: now
5054
+ };
5055
+ await adapter.create({
5056
+ model: "subscription",
5057
+ data: subscriptionRecord,
5058
+ forceAllowId: true
5059
+ });
5060
+ }
4954
5061
  } catch (err) {
4955
5062
  console.error("[better-auth-razorpay] Create customer on sign-up failed:", err);
4956
5063
  }