@deiondz/better-auth-razorpay 2.0.15 → 2.0.16

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
@@ -328,7 +328,8 @@ The plugin automatically creates the following database models via Better Auth's
328
328
  - `razorpayCustomerId` (string, optional) — Razorpay customer ID when customer creation is enabled.
329
329
 
330
330
  **`subscription`**
331
- - `id`, `plan`, `referenceId`, `razorpayCustomerId`, `razorpaySubscriptionId`, `status`, `trialStart`, `trialEnd`, `periodStart`, `periodEnd`, `cancelAtPeriodEnd`, `seats`, `groupId`, `createdAt`, `updatedAt`
331
+ - Primary key: **`id`** (generated by the adapter/database when not provided; the plugin does not pass `id` on create, so the adapter uses its `generateId` or the DB’s default, e.g. UUID or MongoDB `_id`). MongoDB adapters should map `id` `_id` per the [create-a-db-adapter](https://better-auth.com/docs/guides/create-a-db-adapter) guide.
332
+ - Fields: `id`, `plan`, `planId`, `referenceId`, `razorpayCustomerId`, `razorpaySubscriptionId`, `status`, `trialStart`, `trialEnd`, `periodStart`, `periodEnd`, `cancelAtPeriodEnd`, `seats`, `groupId`, `createdAt`, `updatedAt`
332
333
  - `status` values: `created`, `active`, `pending`, `halted`, `cancelled`, `completed`, `expired`, `trialing`
333
334
 
334
335
  ### Database Adapters
@@ -342,6 +343,17 @@ The plugin works with all Better Auth database adapters:
342
343
 
343
344
  **Important:** Better Auth uses adapter model names, NOT underlying table names. If your Prisma model is `User` mapping to table `users`, use the model name in configuration.
344
345
 
346
+ ### Primary key and MongoDB
347
+
348
+ Following [Better Auth’s adapter guide](https://better-auth.com/docs/guides/create-a-db-adapter): the plugin always uses the field name **`id`** for the subscription primary key. The subscription **id is generated by the adapter/database** (the plugin does not pass `id` on create and does not use `forceAllowId`), so the adapter uses its `generateId` or the DB’s default (e.g. PostgreSQL `gen_random_uuid()`, MongoDB `_id`).
349
+
350
+ - **SQL / Prisma / Drizzle:** The adapter or DB generates the id; no extra config.
351
+ - **MongoDB:** Better Auth recommends mapping `id` ↔ `_id` so that:
352
+ - On **input** (create/update), `id` is stored as `_id`.
353
+ - On **output** (findOne/findMany), `_id` is returned as `id`.
354
+
355
+ The official `mongodbAdapter` from `better-auth/adapters/mongodb` applies this mapping for all models (including plugin models). If you use a custom MongoDB adapter, configure it with the same mapping (e.g. `mapKeysTransformInput: { id: "_id" }` and `mapKeysTransformOutput: { _id: "id" }` per the [create-a-db-adapter](https://better-auth.com/docs/guides/create-a-db-adapter) guide) so subscription create/update/webhook work correctly.
356
+
345
357
  ## API Endpoints
346
358
 
347
359
  All endpoints are prefixed with `/api/auth/razorpay/` (or your configured `basePath`).
package/dist/index.js CHANGED
@@ -4229,6 +4229,11 @@ function toLocalStatus(razorpayStatus) {
4229
4229
  };
4230
4230
  return map[razorpayStatus] ?? "pending";
4231
4231
  }
4232
+ function getPrimaryKey(record) {
4233
+ if (record.id != null && record.id !== "") return { value: record.id, field: "id" };
4234
+ if (record._id != null && record._id !== "") return { value: record._id, field: "_id" };
4235
+ return { value: "", field: "id" };
4236
+ }
4232
4237
  var createOrUpdateSubscription = (razorpay, options) => createAuthEndpoint2(
4233
4238
  "/razorpay/subscription/create-or-update",
4234
4239
  { method: "POST", use: [sessionMiddleware2] },
@@ -4286,9 +4291,6 @@ var createOrUpdateSubscription = (razorpay, options) => createAuthEndpoint2(
4286
4291
  }
4287
4292
  }
4288
4293
  const now = /* @__PURE__ */ new Date();
4289
- const generateId = ctx.context.generateId;
4290
- const generated = typeof generateId === "function" ? generateId({ model: "subscription" }) : void 0;
4291
- const localId = (typeof generated === "string" ? generated : void 0) ?? `sub_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
4292
4294
  if (body.subscriptionId) {
4293
4295
  const existing = await ctx.context.adapter.findOne({
4294
4296
  model: "subscription",
@@ -4361,8 +4363,9 @@ var createOrUpdateSubscription = (razorpay, options) => createAuthEndpoint2(
4361
4363
  notes: { referenceId: userId, planName: plan.name }
4362
4364
  };
4363
4365
  if (subOpts.getSubscriptionCreateParams) {
4366
+ const tempSubPk = appTrialSub ? getPrimaryKey(appTrialSub) : null;
4364
4367
  const tempSub = {
4365
- id: appTrialSub?.id ?? "",
4368
+ id: tempSubPk?.value ?? "",
4366
4369
  plan: plan.name,
4367
4370
  planId,
4368
4371
  referenceId: userId,
@@ -4392,9 +4395,16 @@ var createOrUpdateSubscription = (razorpay, options) => createAuthEndpoint2(
4392
4395
  const periodEnd = rpSubscription.current_end ? new Date(rpSubscription.current_end * 1e3) : null;
4393
4396
  const newStatus = toLocalStatus(rpSubscription.status);
4394
4397
  if (appTrialSub) {
4398
+ const trialPk = getPrimaryKey(appTrialSub);
4399
+ if (!trialPk.value) {
4400
+ return {
4401
+ success: false,
4402
+ error: { code: "INVALID_TRIAL", description: "Trial subscription has no primary key" }
4403
+ };
4404
+ }
4395
4405
  await ctx.context.adapter.update({
4396
4406
  model: "subscription",
4397
- where: [{ field: "id", value: appTrialSub.id }],
4407
+ where: [{ field: trialPk.field, value: trialPk.value }],
4398
4408
  update: {
4399
4409
  data: {
4400
4410
  plan: plan.name,
@@ -4412,6 +4422,7 @@ var createOrUpdateSubscription = (razorpay, options) => createAuthEndpoint2(
4412
4422
  if (subOpts.onSubscriptionCreated) {
4413
4423
  const updatedRecord = {
4414
4424
  ...appTrialSub,
4425
+ id: trialPk.value,
4415
4426
  plan: plan.name,
4416
4427
  planId,
4417
4428
  razorpaySubscriptionId: rpSubscription.id,
@@ -4429,7 +4440,7 @@ var createOrUpdateSubscription = (razorpay, options) => createAuthEndpoint2(
4429
4440
  });
4430
4441
  }
4431
4442
  const data2 = {
4432
- subscriptionId: appTrialSub.id,
4443
+ subscriptionId: trialPk.value,
4433
4444
  razorpaySubscriptionId: rpSubscription.id
4434
4445
  };
4435
4446
  if (!body.embed) {
@@ -4437,8 +4448,7 @@ var createOrUpdateSubscription = (razorpay, options) => createAuthEndpoint2(
4437
4448
  }
4438
4449
  return { success: true, data: data2 };
4439
4450
  }
4440
- const subscriptionRecord = {
4441
- id: localId,
4451
+ const subscriptionData = {
4442
4452
  plan: plan.name,
4443
4453
  planId,
4444
4454
  referenceId: userId,
@@ -4455,11 +4465,28 @@ var createOrUpdateSubscription = (razorpay, options) => createAuthEndpoint2(
4455
4465
  createdAt: now,
4456
4466
  updatedAt: now
4457
4467
  };
4458
- await ctx.context.adapter.create({
4468
+ const createdRaw = await ctx.context.adapter.create({
4459
4469
  model: "subscription",
4460
- data: subscriptionRecord,
4461
- forceAllowId: true
4470
+ data: subscriptionData
4462
4471
  });
4472
+ const created = createdRaw;
4473
+ let createdId;
4474
+ if (created != null) {
4475
+ if (typeof created.id === "string" && created.id !== "") {
4476
+ createdId = created.id;
4477
+ } else if (typeof created._id === "string" && created._id !== "") {
4478
+ createdId = created._id;
4479
+ } else if (created._id != null) {
4480
+ createdId = String(created._id);
4481
+ }
4482
+ }
4483
+ if (createdId == null || createdId === "") {
4484
+ return {
4485
+ success: false,
4486
+ error: { code: "CREATE_FAILED", description: "Subscription record was created but no id was returned" }
4487
+ };
4488
+ }
4489
+ const subscriptionRecord = { ...subscriptionData, id: createdId };
4463
4490
  if (subOpts.onSubscriptionCreated) {
4464
4491
  await subOpts.onSubscriptionCreated({
4465
4492
  razorpaySubscription: rpSubscription,
@@ -4468,7 +4495,7 @@ var createOrUpdateSubscription = (razorpay, options) => createAuthEndpoint2(
4468
4495
  });
4469
4496
  }
4470
4497
  const data = {
4471
- subscriptionId: localId,
4498
+ subscriptionId: createdId,
4472
4499
  razorpaySubscriptionId: rpSubscription.id
4473
4500
  };
4474
4501
  if (!body.embed) {
@@ -4740,17 +4767,59 @@ function toLocalStatus2(razorpayStatus) {
4740
4767
  };
4741
4768
  return map[razorpayStatus] ?? "pending";
4742
4769
  }
4743
- var updateSubscriptionRecord = async (adapter, subscriptionRecordId, data) => {
4744
- await adapter.update({
4770
+ var WEBHOOK_DEBUG = process.env.NODE_ENV === "development" || process.env.RAZORPAY_WEBHOOK_DEBUG === "true";
4771
+ var log = (msg, data) => {
4772
+ if (WEBHOOK_DEBUG) {
4773
+ const payload = data ? ` ${JSON.stringify(data)}` : "";
4774
+ console.log(`[razorpay-webhook]${payload ? ` ${msg}` : msg}`, payload || "");
4775
+ }
4776
+ };
4777
+ var updateSubscriptionRecord = async (adapter, subscriptionRecordId, whereField, data) => {
4778
+ const updateData = { ...data, updatedAt: /* @__PURE__ */ new Date() };
4779
+ const params = {
4745
4780
  model: "subscription",
4746
- where: [{ field: "id", value: subscriptionRecordId }],
4747
- update: { data: { ...data, updatedAt: /* @__PURE__ */ new Date() } }
4781
+ where: [{ field: whereField, value: subscriptionRecordId }],
4782
+ update: { data: updateData }
4783
+ };
4784
+ log("updateSubscriptionRecord call", {
4785
+ subscriptionRecordId,
4786
+ whereField,
4787
+ dataKeys: Object.keys(updateData)
4748
4788
  });
4789
+ try {
4790
+ await adapter.update(params);
4791
+ log("updateSubscriptionRecord success", { subscriptionRecordId, whereField });
4792
+ } catch (err) {
4793
+ console.error("[razorpay-webhook] updateSubscriptionRecord failed", {
4794
+ subscriptionRecordId,
4795
+ whereField,
4796
+ error: err instanceof Error ? err.message : String(err),
4797
+ stack: err instanceof Error ? err.stack : void 0
4798
+ });
4799
+ throw err;
4800
+ }
4749
4801
  };
4802
+ function getSubscriptionPrimaryKey(record) {
4803
+ if (record.id != null && record.id !== "") {
4804
+ return { value: record.id, field: "id" };
4805
+ }
4806
+ if (record._id != null && record._id !== "") {
4807
+ return { value: record._id, field: "_id" };
4808
+ }
4809
+ return { value: "", field: "id" };
4810
+ }
4750
4811
  var createStatusHandler = (status, extraFields) => async (adapter, _razorpaySubscriptionId, record, subscription) => {
4812
+ const primaryKey = getSubscriptionPrimaryKey(record);
4813
+ if (!primaryKey.value) {
4814
+ console.error("[razorpay-webhook] record has no id or _id", {
4815
+ recordKeys: Object.keys(record),
4816
+ razorpaySubscriptionId: record.razorpaySubscriptionId
4817
+ });
4818
+ throw new Error("Subscription record has no primary key (id or _id)");
4819
+ }
4751
4820
  const periodStart = subscription.current_start ? new Date(subscription.current_start * 1e3) : null;
4752
4821
  const periodEnd = subscription.current_end ? new Date(subscription.current_end * 1e3) : null;
4753
- await updateSubscriptionRecord(adapter, record.id, {
4822
+ await updateSubscriptionRecord(adapter, primaryKey.value, primaryKey.field, {
4754
4823
  status,
4755
4824
  planId: subscription.plan_id,
4756
4825
  ...periodStart !== null && { periodStart },
@@ -4853,11 +4922,22 @@ async function processWebhookEvent(adapter, rawBody, fallbackBody, onWebhookEven
4853
4922
  where: [{ field: "razorpaySubscriptionId", value: subscriptionEntity.id }]
4854
4923
  });
4855
4924
  if (!record) {
4925
+ log("record not found", { razorpaySubscriptionId: subscriptionEntity.id });
4856
4926
  return {
4857
4927
  success: false,
4858
4928
  message: isDev ? `Subscription record not found for ${subscriptionEntity.id}` : "Subscription record not found"
4859
4929
  };
4860
4930
  }
4931
+ const primaryKey = getSubscriptionPrimaryKey(record);
4932
+ log("record found", {
4933
+ event,
4934
+ razorpaySubscriptionId: subscriptionEntity.id,
4935
+ recordId: primaryKey.value,
4936
+ whereField: primaryKey.field,
4937
+ hasId: "id" in record && record.id != null,
4938
+ has_id: "_id" in record && record._id != null,
4939
+ status: record.status
4940
+ });
4861
4941
  const userId = record.referenceId;
4862
4942
  if (!userId) {
4863
4943
  return {
@@ -4872,7 +4952,17 @@ async function processWebhookEvent(adapter, rawBody, fallbackBody, onWebhookEven
4872
4952
  message: isDev ? `Unhandled event: ${event}` : "Unhandled webhook event"
4873
4953
  };
4874
4954
  }
4875
- await handler(adapter, subscriptionEntity.id, record, subscriptionEntity);
4955
+ log("calling handler", { event, recordId: primaryKey.value });
4956
+ try {
4957
+ await handler(adapter, subscriptionEntity.id, record, subscriptionEntity);
4958
+ } catch (handlerError) {
4959
+ console.error("[razorpay-webhook] handler failed", {
4960
+ event,
4961
+ recordId: primaryKey.value,
4962
+ error: handlerError instanceof Error ? handlerError.message : String(handlerError)
4963
+ });
4964
+ throw handlerError;
4965
+ }
4876
4966
  if (pluginOptions?.onEvent) {
4877
4967
  try {
4878
4968
  await pluginOptions.onEvent({ event, ...payload });
@@ -5040,9 +5130,7 @@ var razorpayPlugin = (options) => {
5040
5130
  const now = /* @__PURE__ */ new Date();
5041
5131
  const trialEnd = new Date(now.getTime() + trialOnSignUp.days * 24 * 60 * 60 * 1e3);
5042
5132
  const planName = trialOnSignUp.planName ?? "Trial";
5043
- const localId = `sub_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
5044
5133
  const subscriptionRecord = {
5045
- id: localId,
5046
5134
  plan: planName,
5047
5135
  referenceId: user.id,
5048
5136
  razorpayCustomerId: customer.id,
@@ -5060,8 +5148,7 @@ var razorpayPlugin = (options) => {
5060
5148
  };
5061
5149
  await adapter.create({
5062
5150
  model: "subscription",
5063
- data: subscriptionRecord,
5064
- forceAllowId: true
5151
+ data: subscriptionRecord
5065
5152
  });
5066
5153
  }
5067
5154
  } catch (err) {