@alexasomba/better-auth-paystack 1.1.2 → 1.2.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/dist/index.mjs CHANGED
@@ -1,11 +1,10 @@
1
- import { defineErrorCodes } from "@better-auth/core/utils";
1
+ import { defineErrorCodes } from "@better-auth/core/utils/error-codes";
2
2
  import { defu } from "defu";
3
3
  import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api";
4
4
  import { HIDE_METADATA, logger } from "better-auth";
5
5
  import { APIError, getSessionFromCtx, originCheck, sessionMiddleware } from "better-auth/api";
6
6
  import * as z from "zod/v4";
7
7
  import { mergeSchema } from "better-auth/db";
8
-
9
8
  //#region src/paystack-sdk.ts
10
9
  function isOpenApiFetchResponse(value) {
11
10
  return value !== null && value !== void 0 && typeof value === "object" && ("data" in value || "error" in value || "response" in value);
@@ -73,6 +72,17 @@ function getPaystackOps(paystackClient) {
73
72
  if (paystackClient?.subscription_manageEmail !== void 0) return paystackClient.subscription_manageEmail({ params: { path: { code } } });
74
73
  return paystackClient?.subscription?.manage?.email?.(code, email);
75
74
  },
75
+ subscriptionUpdate: (params) => {
76
+ if (paystackClient?.subscription_update !== void 0) return paystackClient.subscription_update({
77
+ params: { path: { code: params.code } },
78
+ body: {
79
+ plan: params.plan,
80
+ authorization: params.authorization,
81
+ amount: params.amount
82
+ }
83
+ });
84
+ return paystackClient?.subscription?.update?.(params.code, params);
85
+ },
76
86
  transactionChargeAuthorization: (body) => {
77
87
  if (paystackClient?.transaction_chargeAuthorization !== void 0) return paystackClient.transaction_chargeAuthorization({ body });
78
88
  return paystackClient?.transaction?.chargeAuthorization?.(body);
@@ -106,7 +116,6 @@ function getPaystackOps(paystackClient) {
106
116
  }
107
117
  };
108
118
  }
109
-
110
119
  //#endregion
111
120
  //#region src/utils.ts
112
121
  async function getPlans(subscriptionOptions) {
@@ -179,8 +188,8 @@ async function syncProductQuantityFromPaystack(ctx, productName, paystackClient)
179
188
  value: productName.toLowerCase().replace(/\s+/g, "-")
180
189
  }]
181
190
  });
182
- if (localProduct?.paystackId === void 0 || localProduct?.paystackId === null || localProduct?.paystackId === "") {
183
- if (localProduct && localProduct.unlimited !== true && localProduct.quantity !== void 0 && localProduct.quantity > 0) await ctx.context.adapter.update({
191
+ if (localProduct?.paystackId === void 0 || localProduct.paystackId === null || localProduct.paystackId === "") {
192
+ if (localProduct !== null && localProduct.unlimited !== true && typeof localProduct.quantity === "number" && localProduct.quantity > 0) await ctx.context.adapter.update({
184
193
  model: "paystackProduct",
185
194
  update: {
186
195
  quantity: localProduct.quantity - 1,
@@ -207,7 +216,7 @@ async function syncProductQuantityFromPaystack(ctx, productName, paystackClient)
207
216
  }]
208
217
  });
209
218
  } catch {
210
- if (localProduct.unlimited !== true && localProduct.quantity !== void 0 && localProduct.quantity > 0) await ctx.context.adapter.update({
219
+ if (localProduct !== null && localProduct.unlimited !== true && typeof localProduct.quantity === "number" && localProduct.quantity > 0) await ctx.context.adapter.update({
211
220
  model: "paystackProduct",
212
221
  update: {
213
222
  quantity: localProduct.quantity - 1,
@@ -220,7 +229,51 @@ async function syncProductQuantityFromPaystack(ctx, productName, paystackClient)
220
229
  });
221
230
  }
222
231
  }
223
-
232
+ async function syncSubscriptionSeats(ctx, organizationId, options) {
233
+ if (options.subscription?.enabled !== true) return;
234
+ const adapter = ctx.context.adapter;
235
+ const subscription = await adapter.findOne({
236
+ model: "subscription",
237
+ where: [{
238
+ field: "referenceId",
239
+ value: organizationId
240
+ }]
241
+ });
242
+ if (subscription?.paystackSubscriptionCode === void 0 || subscription.paystackSubscriptionCode === null || subscription.paystackSubscriptionCode === "") return;
243
+ const plan = await getPlanByName(options, subscription.plan);
244
+ if (plan === null) return;
245
+ if (plan.seatAmount === void 0 && plan.seatPlanCode === void 0) return;
246
+ const quantity = (await adapter.findMany({
247
+ model: "member",
248
+ where: [{
249
+ field: "organizationId",
250
+ value: organizationId
251
+ }]
252
+ })).length;
253
+ let totalAmount = plan.amount ?? 0;
254
+ if (plan.seatAmount !== void 0 && plan.seatAmount !== null && typeof plan.seatAmount === "number") totalAmount += quantity * plan.seatAmount;
255
+ const ops = getPaystackOps(options.paystackClient);
256
+ try {
257
+ await ops.subscriptionUpdate({
258
+ code: subscription.paystackSubscriptionCode,
259
+ amount: totalAmount
260
+ });
261
+ await adapter.update({
262
+ model: "subscription",
263
+ where: [{
264
+ field: "id",
265
+ value: subscription.id
266
+ }],
267
+ update: {
268
+ seats: quantity,
269
+ updatedAt: /* @__PURE__ */ new Date()
270
+ }
271
+ });
272
+ } catch (e) {
273
+ const log = ctx.context.logger;
274
+ if (log !== void 0 && log !== null) log.error("Failed to sync subscription seats with Paystack", e);
275
+ }
276
+ }
224
277
  //#endregion
225
278
  //#region src/middleware.ts
226
279
  const referenceMiddleware = (options, action) => createAuthMiddleware(async (ctx) => {
@@ -259,7 +312,40 @@ const referenceMiddleware = (options, action) => createAuthMiddleware(async (ctx
259
312
  logger.error(`Passing referenceId into a subscription action isn't allowed if subscription.authorizeReference isn't defined in your paystack plugin config and matches no organization membership.`);
260
313
  throw new APIError("BAD_REQUEST", { message: "Passing referenceId isn't allowed without subscription.authorizeReference or valid organization membership." });
261
314
  });
262
-
315
+ //#endregion
316
+ //#region src/limits.ts
317
+ const getOrganizationSubscription = async (ctx, organizationId) => {
318
+ return await ctx.context.adapter.findOne({
319
+ model: "subscription",
320
+ where: [{
321
+ field: "referenceId",
322
+ value: organizationId
323
+ }]
324
+ });
325
+ };
326
+ const checkSeatLimit = async (ctx, organizationId, seatsToAdd = 1) => {
327
+ const subscription = await getOrganizationSubscription(ctx, organizationId);
328
+ if (subscription?.seats === void 0 || subscription.seats === null) return true;
329
+ const members = await ctx.context.adapter.findMany({
330
+ model: "member",
331
+ where: [{
332
+ field: "organizationId",
333
+ value: organizationId
334
+ }]
335
+ });
336
+ if (members.length + seatsToAdd > subscription.seats) throw new APIError("FORBIDDEN", { message: `Organization member limit reached. Used: ${members.length}, Max: ${subscription.seats}` });
337
+ return true;
338
+ };
339
+ const checkTeamLimit = async (ctx, organizationId, maxTeams) => {
340
+ if ((await ctx.context.adapter.findMany({
341
+ model: "team",
342
+ where: [{
343
+ field: "organizationId",
344
+ value: organizationId
345
+ }]
346
+ })).length >= maxTeams) throw new APIError("FORBIDDEN", { message: `Organization team limit reached. Max teams: ${maxTeams}` });
347
+ return true;
348
+ };
263
349
  //#endregion
264
350
  //#region src/routes.ts
265
351
  const PAYSTACK_ERROR_CODES = defineErrorCodes({
@@ -300,7 +386,7 @@ const paystackWebhook = (options) => {
300
386
  disableBody: true
301
387
  }, async (ctx) => {
302
388
  const request = ctx.requestClone ?? ctx.request;
303
- if (!request) throw new APIError("BAD_REQUEST", { message: "Request object is missing from context" });
389
+ if (request === void 0 || request === null) throw new APIError("BAD_REQUEST", { message: "Request object is missing from context" });
304
390
  const payload = await request.text();
305
391
  const signature = (ctx.headers ?? ctx.request?.headers)?.get("x-paystack-signature");
306
392
  if (signature === void 0 || signature === null || signature === "") throw new APIError("UNAUTHORIZED", {
@@ -342,7 +428,7 @@ const paystackWebhook = (options) => {
342
428
  value: reference
343
429
  }]
344
430
  });
345
- if (transaction?.product) await syncProductQuantityFromPaystack(ctx, transaction.product, options.paystackClient);
431
+ if (transaction?.product !== void 0 && transaction.product !== null && transaction.product !== "") await syncProductQuantityFromPaystack(ctx, transaction.product, options.paystackClient);
346
432
  } catch (e) {
347
433
  ctx.context.logger.warn("Failed to sync product quantity", e);
348
434
  }
@@ -398,11 +484,10 @@ const paystackWebhook = (options) => {
398
484
  value: planName
399
485
  });
400
486
  if (where.length > 0) {
401
- const matches = await ctx.context.adapter.findMany({
487
+ const subscription = (await ctx.context.adapter.findMany({
402
488
  model: "subscription",
403
489
  where
404
- });
405
- const subscription = matches !== void 0 && matches !== null ? matches[0] : void 0;
490
+ }))?.[0];
406
491
  if (subscription !== void 0 && subscription !== null) {
407
492
  await ctx.context.adapter.update({
408
493
  model: "subscription",
@@ -410,7 +495,7 @@ const paystackWebhook = (options) => {
410
495
  paystackSubscriptionCode: subscriptionCode,
411
496
  status: "active",
412
497
  updatedAt: /* @__PURE__ */ new Date(),
413
- periodEnd: payloadData?.next_payment_date !== void 0 && payloadData?.next_payment_date !== null && payloadData?.next_payment_date !== "" ? new Date(payloadData.next_payment_date) : void 0
498
+ periodEnd: payloadData?.next_payment_date !== void 0 && payloadData.next_payment_date !== null && payloadData.next_payment_date !== "" ? new Date(payloadData.next_payment_date) : void 0
414
499
  },
415
500
  where: [{
416
501
  field: "id",
@@ -455,8 +540,8 @@ const paystackWebhook = (options) => {
455
540
  });
456
541
  let newStatus = "canceled";
457
542
  const nextPaymentDate = data?.next_payment_date;
458
- const periodEnd = nextPaymentDate ? new Date(nextPaymentDate) : existing?.periodEnd ? new Date(existing.periodEnd) : void 0;
459
- if (periodEnd && periodEnd > /* @__PURE__ */ new Date()) newStatus = "active";
543
+ const periodEnd = nextPaymentDate !== void 0 && nextPaymentDate !== null && nextPaymentDate !== "" ? new Date(nextPaymentDate) : existing?.periodEnd !== void 0 ? new Date(existing.periodEnd) : void 0;
544
+ if (periodEnd !== void 0 && periodEnd > /* @__PURE__ */ new Date()) newStatus = "active";
460
545
  await ctx.context.adapter.update({
461
546
  model: "subscription",
462
547
  update: {
@@ -470,7 +555,7 @@ const paystackWebhook = (options) => {
470
555
  value: subscriptionCode
471
556
  }]
472
557
  });
473
- if (existing) await options.subscription.onSubscriptionCancel?.({
558
+ if (existing !== void 0 && existing !== null) await options.subscription.onSubscriptionCancel?.({
474
559
  event,
475
560
  subscription: {
476
561
  ...existing,
@@ -479,6 +564,31 @@ const paystackWebhook = (options) => {
479
564
  }, ctx);
480
565
  }
481
566
  }
567
+ if (eventName === "charge.success" || eventName === "invoice.update") {
568
+ const payloadData = data;
569
+ const subscriptionCode = payloadData?.subscription?.subscription_code ?? payloadData?.subscription_code;
570
+ if (subscriptionCode !== void 0 && subscriptionCode !== null && subscriptionCode !== "") {
571
+ const existingSub = await ctx.context.adapter.findOne({
572
+ model: "subscription",
573
+ where: [{
574
+ field: "paystackSubscriptionCode",
575
+ value: subscriptionCode
576
+ }]
577
+ });
578
+ if (existingSub?.pendingPlan !== void 0 && existingSub.pendingPlan !== null && existingSub.pendingPlan !== "") await ctx.context.adapter.update({
579
+ model: "subscription",
580
+ update: {
581
+ plan: existingSub.pendingPlan,
582
+ pendingPlan: null,
583
+ updatedAt: /* @__PURE__ */ new Date()
584
+ },
585
+ where: [{
586
+ field: "id",
587
+ value: existingSub.id
588
+ }]
589
+ });
590
+ }
591
+ }
482
592
  } catch (_e) {
483
593
  ctx.context.logger.error("Failed to sync Paystack webhook event", _e);
484
594
  }
@@ -495,7 +605,10 @@ const initializeTransactionBodySchema = z.object({
495
605
  metadata: z.record(z.string(), z.unknown()).optional(),
496
606
  referenceId: z.string().optional(),
497
607
  callbackURL: z.string().optional(),
498
- quantity: z.number().int().positive().optional()
608
+ quantity: z.number().int().positive().optional(),
609
+ scheduleAtPeriodEnd: z.boolean().optional(),
610
+ cancelAtPeriodEnd: z.boolean().optional(),
611
+ prorateAndCharge: z.boolean().optional()
499
612
  });
500
613
  const initializeTransaction = (options, path = "/paystack/initialize-transaction") => {
501
614
  const subscriptionOptions = options.subscription;
@@ -509,11 +622,11 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
509
622
  ] : [sessionMiddleware, originCheck]
510
623
  }, async (ctx) => {
511
624
  const paystack = getPaystackOps(options.paystackClient);
512
- const { plan: planName, product: productName, amount: bodyAmount, currency, email, metadata: extraMetadata, callbackURL, quantity } = ctx.body;
625
+ const { plan: planName, product: productName, amount: bodyAmount, currency, email, metadata: extraMetadata, callbackURL, quantity, scheduleAtPeriodEnd, cancelAtPeriodEnd, prorateAndCharge } = ctx.body;
513
626
  if (callbackURL !== void 0 && callbackURL !== null && callbackURL !== "") {
514
627
  const checkTrusted = () => {
515
628
  try {
516
- if (!callbackURL) return false;
629
+ if (callbackURL === void 0 || callbackURL === null || callbackURL === "") return false;
517
630
  if (callbackURL.startsWith("/")) return true;
518
631
  const baseUrl = ctx.context?.baseURL ?? ctx.request?.url ?? "";
519
632
  if (!baseUrl) return false;
@@ -523,7 +636,7 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
523
636
  return false;
524
637
  }
525
638
  };
526
- if (!checkTrusted()) throw new APIError("FORBIDDEN", {
639
+ if (checkTrusted() !== true) throw new APIError("FORBIDDEN", {
527
640
  message: "callbackURL is not a trusted origin.",
528
641
  status: 403
529
642
  });
@@ -531,16 +644,16 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
531
644
  const session = await getSessionFromCtx(ctx);
532
645
  if (!session) throw new APIError("UNAUTHORIZED");
533
646
  const user = session.user;
534
- if (subscriptionOptions?.enabled === true && subscriptionOptions.requireEmailVerification === true && !user.emailVerified) throw new APIError("BAD_REQUEST", {
647
+ if (subscriptionOptions?.enabled === true && subscriptionOptions.requireEmailVerification === true && user.emailVerified !== true) throw new APIError("BAD_REQUEST", {
535
648
  code: "EMAIL_VERIFICATION_REQUIRED",
536
- message: PAYSTACK_ERROR_CODES.EMAIL_VERIFICATION_REQUIRED
649
+ message: PAYSTACK_ERROR_CODES.EMAIL_VERIFICATION_REQUIRED.message
537
650
  });
538
651
  let plan;
539
652
  let product;
540
653
  if (planName !== void 0 && planName !== null && planName !== "") {
541
654
  if (subscriptionOptions?.enabled !== true) throw new APIError("BAD_REQUEST", { message: "Subscriptions are not enabled." });
542
655
  plan = await getPlanByName(options, planName) ?? void 0;
543
- if (!plan) {
656
+ if (plan === null || plan === void 0) {
544
657
  const nativePlan = await ctx.context.adapter.findOne({
545
658
  model: "paystackPlan",
546
659
  where: [{
@@ -548,7 +661,7 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
548
661
  value: planName
549
662
  }]
550
663
  });
551
- if (nativePlan) plan = nativePlan;
664
+ if (nativePlan !== void 0 && nativePlan !== null) plan = nativePlan;
552
665
  else plan = await ctx.context.adapter.findOne({
553
666
  model: "paystackPlan",
554
667
  where: [{
@@ -557,9 +670,9 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
557
670
  }]
558
671
  }) ?? void 0;
559
672
  }
560
- if (!plan) throw new APIError("BAD_REQUEST", {
673
+ if (plan === null || plan === void 0) throw new APIError("BAD_REQUEST", {
561
674
  code: "SUBSCRIPTION_PLAN_NOT_FOUND",
562
- message: PAYSTACK_ERROR_CODES.SUBSCRIPTION_PLAN_NOT_FOUND,
675
+ message: PAYSTACK_ERROR_CODES.SUBSCRIPTION_PLAN_NOT_FOUND.message,
563
676
  status: 400
564
677
  });
565
678
  } else if (productName !== void 0 && productName !== null && productName !== "") {
@@ -573,7 +686,7 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
573
686
  }]
574
687
  }) ?? void 0;
575
688
  }
576
- if (!product) throw new APIError("BAD_REQUEST", {
689
+ if (product === null || product === void 0) throw new APIError("BAD_REQUEST", {
577
690
  message: `Product '${productName}' not found.`,
578
691
  status: 400
579
692
  });
@@ -581,23 +694,77 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
581
694
  message: "Either 'plan', 'product', or 'amount' is required to initialize a transaction.",
582
695
  status: 400
583
696
  });
584
- const amount = bodyAmount ?? product?.price;
697
+ let amount = bodyAmount ?? product?.price;
585
698
  const finalCurrency = currency ?? product?.currency ?? plan?.currency ?? "NGN";
699
+ const referenceIdFromCtx = ctx.context.referenceId;
700
+ const referenceId = ctx.body.referenceId !== void 0 && ctx.body.referenceId !== null && ctx.body.referenceId !== "" ? ctx.body.referenceId : referenceIdFromCtx !== void 0 && referenceIdFromCtx !== null && referenceIdFromCtx !== "" ? referenceIdFromCtx : session.user.id;
701
+ if (plan && scheduleAtPeriodEnd === true) {
702
+ const existingSub = await getOrganizationSubscription(ctx, referenceId);
703
+ if (existingSub?.status === "active") {
704
+ await ctx.context.adapter.update({
705
+ model: "subscription",
706
+ where: [{
707
+ field: "id",
708
+ value: existingSub.id
709
+ }],
710
+ update: {
711
+ pendingPlan: plan.name,
712
+ updatedAt: /* @__PURE__ */ new Date()
713
+ }
714
+ });
715
+ return ctx.json({
716
+ status: "success",
717
+ message: "Plan change scheduled at period end.",
718
+ scheduled: true
719
+ });
720
+ }
721
+ }
722
+ if (cancelAtPeriodEnd === true) {
723
+ const existingSub = await getOrganizationSubscription(ctx, referenceId);
724
+ if (existingSub?.status === "active") {
725
+ await ctx.context.adapter.update({
726
+ model: "subscription",
727
+ where: [{
728
+ field: "id",
729
+ value: existingSub.id
730
+ }],
731
+ update: {
732
+ cancelAtPeriodEnd: true,
733
+ updatedAt: /* @__PURE__ */ new Date()
734
+ }
735
+ });
736
+ return ctx.json({
737
+ status: "success",
738
+ message: "Subscription cancellation scheduled at period end.",
739
+ scheduled: true
740
+ });
741
+ }
742
+ }
743
+ if (plan !== null && plan !== void 0 && (plan.seatAmount !== void 0 || "seatPriceId" in plan)) {
744
+ const members = await ctx.context.adapter.findMany({
745
+ model: "member",
746
+ where: [{
747
+ field: "organizationId",
748
+ value: referenceId
749
+ }]
750
+ });
751
+ const seatCount = members.length > 0 ? members.length : 1;
752
+ const quantityToUse = quantity ?? seatCount;
753
+ amount = (plan.amount ?? 0) + quantityToUse * (plan.seatAmount ?? plan.seatPriceId ?? 0);
754
+ }
586
755
  let url;
587
756
  let reference;
588
757
  let accessCode;
589
- const referenceIdFromCtx = ctx.context.referenceId;
590
- const referenceId = ctx.body.referenceId !== void 0 && ctx.body.referenceId !== null && ctx.body.referenceId !== "" ? ctx.body.referenceId : referenceIdFromCtx !== void 0 && referenceIdFromCtx !== null && referenceIdFromCtx !== "" ? referenceIdFromCtx : session.user.id;
591
758
  let trialStart;
592
759
  let trialEnd;
593
760
  if (plan?.freeTrial?.days !== void 0 && plan.freeTrial.days !== null && plan.freeTrial.days > 0) {
594
- if (!(await ctx.context.adapter.findMany({
761
+ if ((await ctx.context.adapter.findMany({
595
762
  model: "subscription",
596
763
  where: [{
597
764
  field: "referenceId",
598
765
  value: referenceId
599
766
  }]
600
- }))?.some((sub) => sub.trialStart !== void 0 && sub.trialStart !== null || sub.trialEnd !== void 0 && sub.trialEnd !== null || sub.status === "trialing")) {
767
+ }))?.some((sub) => sub.trialStart !== void 0 && sub.trialStart !== null || sub.trialEnd !== void 0 && sub.trialEnd !== null || sub.status === "trialing") !== true) {
601
768
  trialStart = /* @__PURE__ */ new Date();
602
769
  trialEnd = /* @__PURE__ */ new Date();
603
770
  trialEnd.setDate(trialEnd.getDate() + plan.freeTrial.days);
@@ -628,7 +795,7 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
628
795
  value: "owner"
629
796
  }]
630
797
  });
631
- if (ownerMember) {
798
+ if (ownerMember !== void 0 && ownerMember !== null) {
632
799
  const ownerUser = await ctx.context.adapter.findOne({
633
800
  model: "user",
634
801
  where: [{
@@ -646,7 +813,7 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
646
813
  userId: user.id,
647
814
  plan: plan?.name.toLowerCase(),
648
815
  product: product?.name.toLowerCase(),
649
- isTrial: !!trialStart,
816
+ isTrial: trialStart !== void 0 && trialStart !== null,
650
817
  trialEnd: trialEnd?.toISOString(),
651
818
  ...extraMetadata
652
819
  });
@@ -661,29 +828,108 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
661
828
  const ops = getPaystackOps(options.paystackClient);
662
829
  if (initBody.email !== void 0 && initBody.email !== null && initBody.email !== "") await ops.customerUpdate(paystackCustomerCode, { email: initBody.email });
663
830
  } catch (_e) {}
664
- if (plan) if (trialStart) initBody.amount = 5e3;
831
+ if (plan !== void 0 && plan !== null && prorateAndCharge === true) {
832
+ const existingSub = await getOrganizationSubscription(ctx, referenceId);
833
+ if (existingSub?.status === "active" && existingSub.paystackAuthorizationCode !== null && existingSub.paystackAuthorizationCode !== void 0 && existingSub.paystackSubscriptionCode !== null && existingSub.paystackSubscriptionCode !== void 0) {
834
+ const now = /* @__PURE__ */ new Date();
835
+ const periodEndLocal = existingSub.periodEnd ? new Date(existingSub.periodEnd) : new Date(now.getTime() + 720 * 60 * 60 * 1e3);
836
+ const periodStartLocal = existingSub.periodStart ? new Date(existingSub.periodStart) : now;
837
+ const totalDays = Math.max(1, Math.ceil((periodEndLocal.getTime() - periodStartLocal.getTime()) / (1e3 * 60 * 60 * 24)));
838
+ const remainingDays = Math.max(0, Math.ceil((periodEndLocal.getTime() - now.getTime()) / (1e3 * 60 * 60 * 24)));
839
+ let oldAmount = 0;
840
+ if (existingSub.plan !== void 0 && existingSub.plan !== null && existingSub.plan !== "") {
841
+ const oldPlan = await getPlanByName(options, existingSub.plan) ?? await ctx.context.adapter.findOne({
842
+ model: "paystackPlan",
843
+ where: [{
844
+ field: "name",
845
+ value: existingSub.plan
846
+ }]
847
+ });
848
+ if (oldPlan !== null && oldPlan !== void 0) {
849
+ const oldSeatCount = existingSub.seats ?? 1;
850
+ oldAmount = (oldPlan.amount ?? 0) + oldSeatCount * (oldPlan.seatAmount ?? oldPlan.seatPriceId ?? 0);
851
+ }
852
+ }
853
+ let membersCount = 1;
854
+ if (plan.seatAmount !== void 0 || plan.seatPriceId !== void 0) {
855
+ const members = await ctx.context.adapter.findMany({
856
+ model: "member",
857
+ where: [{
858
+ field: "organizationId",
859
+ value: referenceId
860
+ }]
861
+ });
862
+ membersCount = members.length > 0 ? members.length : 1;
863
+ }
864
+ const newSeatCount = quantity ?? existingSub.seats ?? membersCount;
865
+ const newAmount = (plan.amount ?? 0) + newSeatCount * (plan.seatAmount ?? plan.seatPriceId ?? 0);
866
+ const costDifference = newAmount - oldAmount;
867
+ if (costDifference > 0 && remainingDays > 0) {
868
+ const proratedAmount = Math.round(costDifference / totalDays * remainingDays);
869
+ if (proratedAmount >= 5e3) {
870
+ if (unwrapSdkResult(await getPaystackOps(options.paystackClient).transactionChargeAuthorization({
871
+ email: targetEmail,
872
+ amount: proratedAmount,
873
+ authorization_code: existingSub.paystackAuthorizationCode,
874
+ reference: `prorate_${Date.now()}_${Math.random().toString(36).substring(7)}`,
875
+ metadata: {
876
+ type: "proration",
877
+ referenceId,
878
+ newPlan: plan.name,
879
+ oldPlan: existingSub.plan,
880
+ remainingDays
881
+ }
882
+ }))?.status !== "success") throw new APIError("BAD_REQUEST", { message: "Failed to process prorated charge via saved authorization." });
883
+ }
884
+ }
885
+ await getPaystackOps(options.paystackClient).subscriptionUpdate({
886
+ code: existingSub.paystackSubscriptionCode,
887
+ amount: newAmount,
888
+ plan: plan.planCode
889
+ });
890
+ await ctx.context.adapter.update({
891
+ model: "subscription",
892
+ where: [{
893
+ field: "id",
894
+ value: existingSub.id
895
+ }],
896
+ update: {
897
+ plan: plan.name,
898
+ seats: newSeatCount,
899
+ updatedAt: /* @__PURE__ */ new Date()
900
+ }
901
+ });
902
+ return ctx.json({
903
+ status: "success",
904
+ message: "Subscription successfully upgraded with prorated charge.",
905
+ prorated: true
906
+ });
907
+ }
908
+ }
909
+ if (plan !== void 0 && plan !== null) if (trialStart !== void 0 && trialStart !== null) initBody.amount = 5e3;
665
910
  else {
666
911
  initBody.plan = plan.planCode;
667
912
  initBody.invoice_limit = plan.invoiceLimit;
668
- const planAmount = amount ?? plan.amount ?? 5e4;
669
- initBody.amount = Math.max(Math.round(planAmount), 5e4);
670
- if (quantity !== void 0 && quantity !== null && quantity > 0) initBody.amount = initBody.amount * quantity;
913
+ let finalAmount;
914
+ if (amount !== void 0 && amount !== null) {
915
+ finalAmount = amount;
916
+ initBody.quantity = 1;
917
+ } else finalAmount = (plan.amount ?? 0) * (quantity ?? 1);
918
+ initBody.amount = Math.max(Math.round(finalAmount), 5e3);
671
919
  }
672
920
  else {
673
921
  if (amount === void 0 || amount === null || amount === 0) throw new APIError("BAD_REQUEST", { message: "Amount is required for one-time payments" });
674
922
  initBody.amount = Math.round(amount);
675
923
  }
676
- const initRes = unwrapSdkResult(await paystack.transactionInitialize(initBody));
677
- let data = initRes !== void 0 && initRes !== null && typeof initRes === "object" && "status" in initRes && "data" in initRes ? initRes.data : initRes?.data ?? initRes;
678
- if (data !== void 0 && data !== null && typeof data === "object" && "status" in data && "data" in data) data = data.data;
679
- url = data?.authorization_url;
680
- reference = data?.reference;
681
- accessCode = data?.access_code;
924
+ const sdkRes = unwrapSdkResult(await paystack.transactionInitialize(initBody));
925
+ url = sdkRes?.authorization_url ?? sdkRes?.data?.authorization_url;
926
+ reference = sdkRes?.reference ?? sdkRes?.data?.reference;
927
+ accessCode = sdkRes?.access_code ?? sdkRes?.data?.access_code;
682
928
  } catch (error) {
683
929
  ctx.context.logger.error("Failed to initialize Paystack transaction", error);
684
930
  throw new APIError("BAD_REQUEST", {
685
931
  code: "FAILED_TO_INITIALIZE_TRANSACTION",
686
- message: error?.message ?? PAYSTACK_ERROR_CODES.FAILED_TO_INITIALIZE_TRANSACTION
932
+ message: error?.message ?? PAYSTACK_ERROR_CODES.FAILED_TO_INITIALIZE_TRANSACTION.message
687
933
  });
688
934
  }
689
935
  await ctx.context.adapter.create({
@@ -697,7 +943,7 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
697
943
  status: "pending",
698
944
  plan: plan?.name.toLowerCase(),
699
945
  product: product?.name.toLowerCase(),
700
- metadata: extraMetadata !== void 0 && extraMetadata !== null ? JSON.stringify(extraMetadata) : void 0,
946
+ metadata: extraMetadata !== void 0 && extraMetadata !== null && Object.keys(extraMetadata).length > 0 ? JSON.stringify(extraMetadata) : void 0,
701
947
  createdAt: /* @__PURE__ */ new Date(),
702
948
  updatedAt: /* @__PURE__ */ new Date()
703
949
  }
@@ -712,7 +958,7 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
712
958
  value: referenceId
713
959
  }]
714
960
  });
715
- if (org?.paystackCustomerCode !== void 0 && org?.paystackCustomerCode !== null && org.paystackCustomerCode !== "") storedCustomerCode = org.paystackCustomerCode;
961
+ if (org?.paystackCustomerCode !== void 0 && org.paystackCustomerCode !== null && org.paystackCustomerCode !== "") storedCustomerCode = org.paystackCustomerCode;
716
962
  }
717
963
  const newSubscription = await ctx.context.adapter.create({
718
964
  model: "subscription",
@@ -765,10 +1011,11 @@ const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
765
1011
  ctx.context.logger.error("Failed to verify Paystack transaction", error);
766
1012
  throw new APIError("BAD_REQUEST", {
767
1013
  code: "FAILED_TO_VERIFY_TRANSACTION",
768
- message: error?.message ?? PAYSTACK_ERROR_CODES.FAILED_TO_VERIFY_TRANSACTION
1014
+ message: error?.message ?? PAYSTACK_ERROR_CODES.FAILED_TO_VERIFY_TRANSACTION.message
769
1015
  });
770
1016
  }
771
- const data = unwrapSdkResult(verifyRes);
1017
+ const dataRaw = unwrapSdkResult(verifyRes);
1018
+ const data = dataRaw?.data ?? dataRaw;
772
1019
  const status = data?.status;
773
1020
  const reference = data?.reference ?? ctx.body.reference;
774
1021
  const paystackId = data?.id !== void 0 && data?.id !== null ? String(data.id) : void 0;
@@ -785,24 +1032,26 @@ const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
785
1032
  if (session !== null && session !== void 0 && referenceId !== session.user.id) {
786
1033
  const authRef = subscriptionOptions?.authorizeReference;
787
1034
  let authorized = false;
788
- if (authRef !== void 0 && authRef !== null) authorized = await authRef({
1035
+ if (authRef !== void 0) authorized = await authRef({
789
1036
  user: session.user,
790
1037
  session,
791
1038
  referenceId,
792
1039
  action: "verify-transaction"
793
1040
  }, ctx);
794
- else if (options.organization?.enabled === true) {
795
- const member = await ctx.context.adapter.findOne({
796
- model: "member",
797
- where: [{
798
- field: "userId",
799
- value: session.user.id
800
- }, {
801
- field: "organizationId",
802
- value: referenceId
803
- }]
804
- });
805
- if (member !== null && member !== void 0) authorized = true;
1041
+ if (authorized !== true) {
1042
+ if (options.organization?.enabled === true) {
1043
+ const member = await ctx.context.adapter.findOne({
1044
+ model: "member",
1045
+ where: [{
1046
+ field: "userId",
1047
+ value: session.user.id
1048
+ }, {
1049
+ field: "organizationId",
1050
+ value: referenceId
1051
+ }]
1052
+ });
1053
+ if (member !== void 0 && member !== null) authorized = true;
1054
+ }
806
1055
  }
807
1056
  if (!authorized) throw new APIError("UNAUTHORIZED");
808
1057
  }
@@ -813,7 +1062,7 @@ const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
813
1062
  status: "success",
814
1063
  paystackId,
815
1064
  ...data?.amount !== void 0 && data?.amount !== null ? { amount: data.amount } : {},
816
- ...data?.currency !== void 0 && data?.currency !== null ? { currency: data.currency } : {},
1065
+ ...data?.currency !== void 0 && data?.currency !== null && data?.currency !== "" ? { currency: data.currency } : {},
817
1066
  updatedAt: /* @__PURE__ */ new Date()
818
1067
  },
819
1068
  where: [{
@@ -823,28 +1072,35 @@ const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
823
1072
  });
824
1073
  const customer = data?.customer;
825
1074
  const paystackCustomerCodeFromPaystack = customer !== void 0 && customer !== null && typeof customer === "object" ? customer.customer_code : void 0;
826
- if (paystackCustomerCodeFromPaystack !== void 0 && paystackCustomerCodeFromPaystack !== null && paystackCustomerCodeFromPaystack !== "" && referenceId !== void 0 && referenceId !== null && referenceId !== "") if ((options.organization?.enabled === true && (referenceId.startsWith("org_") || await ctx.context.adapter.findOne({
827
- model: "organization",
828
- where: [{
829
- field: "id",
830
- value: referenceId
831
- }]
832
- }) !== null)) === true) await ctx.context.adapter.update({
833
- model: "organization",
834
- update: { paystackCustomerCode: paystackCustomerCodeFromPaystack },
835
- where: [{
836
- field: "id",
837
- value: referenceId
838
- }]
839
- });
840
- else await ctx.context.adapter.update({
841
- model: "user",
842
- update: { paystackCustomerCode: paystackCustomerCodeFromPaystack },
843
- where: [{
844
- field: "id",
845
- value: referenceId
846
- }]
847
- });
1075
+ if (paystackCustomerCodeFromPaystack !== void 0 && paystackCustomerCodeFromPaystack !== null && paystackCustomerCodeFromPaystack !== "" && referenceId !== void 0 && referenceId !== null && referenceId !== "") {
1076
+ let isOrg = options.organization?.enabled === true && typeof referenceId === "string" && referenceId.startsWith("org_");
1077
+ if (!isOrg && options.organization?.enabled === true) {
1078
+ const org = await ctx.context.adapter.findOne({
1079
+ model: "organization",
1080
+ where: [{
1081
+ field: "id",
1082
+ value: referenceId
1083
+ }]
1084
+ });
1085
+ isOrg = org !== null && org !== void 0;
1086
+ }
1087
+ if (isOrg === true) await ctx.context.adapter.update({
1088
+ model: "organization",
1089
+ update: { paystackCustomerCode: paystackCustomerCodeFromPaystack },
1090
+ where: [{
1091
+ field: "id",
1092
+ value: referenceId
1093
+ }]
1094
+ });
1095
+ else await ctx.context.adapter.update({
1096
+ model: "user",
1097
+ update: { paystackCustomerCode: paystackCustomerCodeFromPaystack },
1098
+ where: [{
1099
+ field: "id",
1100
+ value: referenceId
1101
+ }]
1102
+ });
1103
+ }
848
1104
  const transaction = await ctx.context.adapter.findOne({
849
1105
  model: "paystackTransaction",
850
1106
  where: [{
@@ -852,7 +1108,7 @@ const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
852
1108
  value: reference
853
1109
  }]
854
1110
  });
855
- if (transaction?.product) await syncProductQuantityFromPaystack(ctx, transaction.product, options.paystackClient);
1111
+ if (transaction?.product !== void 0 && transaction?.product !== null && transaction?.product !== "") await syncProductQuantityFromPaystack(ctx, transaction.product, options.paystackClient);
856
1112
  let isTrial = false;
857
1113
  let trialEnd;
858
1114
  let targetPlan;
@@ -865,17 +1121,17 @@ const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
865
1121
  }
866
1122
  let paystackSubscriptionCode;
867
1123
  if (isTrial === true && targetPlan !== void 0 && targetPlan !== null && targetPlan !== "" && trialEnd !== void 0 && trialEnd !== null && trialEnd !== "") {
868
- const email = (data?.customer)?.email;
1124
+ const email = data.customer?.email;
869
1125
  const planConfig = (await getPlans(subscriptionOptions)).find((p) => p.name.toLowerCase() === targetPlan?.toLowerCase());
870
- if (planConfig !== void 0 && (planConfig.planCode === void 0 || planConfig.planCode === null || planConfig.planCode === "")) paystackSubscriptionCode = `LOC_${reference}`;
871
- if (authorizationCode !== void 0 && authorizationCode !== null && authorizationCode !== "" && email !== void 0 && email !== null && email !== "" && planConfig?.planCode !== void 0 && planConfig?.planCode !== null && planConfig?.planCode !== "") {
872
- const subData = unwrapSdkResult(await paystack.subscriptionCreate({
1126
+ if (planConfig !== void 0 && planConfig !== null && (planConfig.planCode === void 0 || planConfig.planCode === null || planConfig.planCode === "")) paystackSubscriptionCode = `LOC_${reference}`;
1127
+ if (authorizationCode !== void 0 && authorizationCode !== null && authorizationCode !== "" && email !== void 0 && email !== null && email !== "" && planConfig?.planCode !== void 0 && planConfig.planCode !== null && planConfig.planCode !== "") {
1128
+ const subRes = unwrapSdkResult(await paystack.subscriptionCreate({
873
1129
  customer: email,
874
1130
  plan: planConfig.planCode,
875
1131
  authorization: authorizationCode,
876
1132
  start_date: trialEnd
877
1133
  }));
878
- paystackSubscriptionCode = (subData?.data ?? subData)?.subscription_code;
1134
+ paystackSubscriptionCode = (subRes?.data ?? subRes)?.subscription_code;
879
1135
  }
880
1136
  } else if (isTrial !== true) {
881
1137
  const planCodeFromPaystack = (data?.plan)?.plan_code;
@@ -890,15 +1146,15 @@ const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
890
1146
  }]
891
1147
  });
892
1148
  let targetSub;
893
- if (existingSubs && existingSubs.length > 0) targetSub = existingSubs.find((s) => !(referenceId !== void 0 && referenceId !== null && referenceId !== "") || s.referenceId === referenceId);
1149
+ if (existingSubs !== null && existingSubs !== void 0 && existingSubs.length > 0) targetSub = existingSubs.find((s) => referenceId === void 0 || referenceId === null || referenceId === "" || s.referenceId === referenceId);
894
1150
  let updatedSubscription = null;
895
1151
  if (targetSub !== void 0 && targetSub !== null) updatedSubscription = await ctx.context.adapter.update({
896
1152
  model: "subscription",
897
1153
  update: {
898
- status: isTrial === true ? "trialing" : "active",
1154
+ status: isTrial ? "trialing" : "active",
899
1155
  periodStart: /* @__PURE__ */ new Date(),
900
1156
  updatedAt: /* @__PURE__ */ new Date(),
901
- ...isTrial === true && trialEnd !== void 0 && trialEnd !== null && trialEnd !== "" ? {
1157
+ ...isTrial === true && trialEnd !== void 0 && trialEnd !== null ? {
902
1158
  trialStart: /* @__PURE__ */ new Date(),
903
1159
  trialEnd: new Date(trialEnd),
904
1160
  periodEnd: new Date(trialEnd)
@@ -922,20 +1178,6 @@ const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
922
1178
  } catch (e) {
923
1179
  ctx.context.logger.error("Failed to update transaction/subscription after verification", e);
924
1180
  }
925
- } else if (status === "failed" || status === "abandoned") try {
926
- await ctx.context.adapter.update({
927
- model: "paystackTransaction",
928
- update: {
929
- status,
930
- updatedAt: /* @__PURE__ */ new Date()
931
- },
932
- where: [{
933
- field: "reference",
934
- value: reference
935
- }]
936
- });
937
- } catch (e) {
938
- ctx.context.logger.error("Failed to update transaction status", e);
939
1181
  }
940
1182
  return ctx.json({
941
1183
  status,
@@ -961,7 +1203,7 @@ const listSubscriptions = (options) => {
961
1203
  if (!session) throw new APIError("UNAUTHORIZED");
962
1204
  const referenceIdPart = ctx.context.referenceId;
963
1205
  const queryRefId = ctx.query?.referenceId;
964
- const referenceId = referenceIdPart !== void 0 && referenceIdPart !== null && referenceIdPart !== "" ? referenceIdPart : queryRefId !== void 0 && queryRefId !== null && queryRefId !== "" ? queryRefId : session.user.id;
1206
+ const referenceId = referenceIdPart ?? queryRefId ?? session.user.id;
965
1207
  const res = await ctx.context.adapter.findMany({
966
1208
  model: "subscription",
967
1209
  where: [{
@@ -998,7 +1240,8 @@ const listTransactions = (options, path = "/paystack/list-transactions") => {
998
1240
  const enableDisableBodySchema = z.object({
999
1241
  referenceId: z.string().optional(),
1000
1242
  subscriptionCode: z.string(),
1001
- emailToken: z.string().optional()
1243
+ emailToken: z.string().optional(),
1244
+ atPeriodEnd: z.boolean().optional()
1002
1245
  });
1003
1246
  function decodeBase64UrlToString(value) {
1004
1247
  const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
@@ -1029,7 +1272,7 @@ const disablePaystackSubscription = (options, path = "/paystack/disable-subscrip
1029
1272
  referenceMiddleware(options, "disable-subscription")
1030
1273
  ] : [sessionMiddleware, originCheck]
1031
1274
  }, async (ctx) => {
1032
- const { subscriptionCode } = ctx.body;
1275
+ const { subscriptionCode, atPeriodEnd } = ctx.body;
1033
1276
  const paystack = getPaystackOps(options.paystackClient);
1034
1277
  try {
1035
1278
  if (subscriptionCode.startsWith("LOC_")) {
@@ -1040,12 +1283,12 @@ const disablePaystackSubscription = (options, path = "/paystack/disable-subscrip
1040
1283
  value: subscriptionCode
1041
1284
  }]
1042
1285
  });
1043
- if (sub) {
1286
+ if (sub !== null && sub !== void 0) {
1044
1287
  await ctx.context.adapter.update({
1045
1288
  model: "subscription",
1046
1289
  update: {
1047
- status: "active",
1048
- cancelAtPeriodEnd: true,
1290
+ status: atPeriodEnd === false ? "canceled" : "active",
1291
+ cancelAtPeriodEnd: atPeriodEnd !== false,
1049
1292
  updatedAt: /* @__PURE__ */ new Date()
1050
1293
  },
1051
1294
  where: [{
@@ -1061,15 +1304,15 @@ const disablePaystackSubscription = (options, path = "/paystack/disable-subscrip
1061
1304
  let nextPaymentDate;
1062
1305
  try {
1063
1306
  const fetchRes = unwrapSdkResult(await paystack.subscriptionFetch(subscriptionCode));
1064
- const data = fetchRes !== null && fetchRes !== void 0 && typeof fetchRes === "object" && "status" in fetchRes && "data" in fetchRes ? fetchRes.data : fetchRes?.data !== void 0 ? fetchRes.data : fetchRes;
1307
+ const data = fetchRes?.data ?? fetchRes;
1065
1308
  if (emailToken === void 0 || emailToken === null || emailToken === "") emailToken = data?.email_token;
1066
1309
  nextPaymentDate = data?.next_payment_date;
1067
1310
  } catch {}
1068
1311
  if (emailToken === void 0 || emailToken === null || emailToken === "") try {
1069
1312
  const linkRes = unwrapSdkResult(await paystack.subscriptionManageLink(subscriptionCode));
1070
- const data = linkRes !== null && linkRes !== void 0 && typeof linkRes === "object" && "status" in linkRes && "data" in linkRes ? linkRes.data : linkRes?.data !== void 0 ? linkRes.data : linkRes;
1071
- const link = typeof data === "string" ? data : data?.link;
1072
- if (link !== void 0 && link !== null && link !== "") emailToken = tryGetEmailTokenFromSubscriptionManageLink(link);
1313
+ const data = linkRes?.data ?? linkRes;
1314
+ const link = typeof data === "string" ? data : data.link;
1315
+ if (typeof link === "string" && link !== "") emailToken = tryGetEmailTokenFromSubscriptionManageLink(link);
1073
1316
  } catch {}
1074
1317
  if (emailToken === void 0 || emailToken === null || emailToken === "") throw new Error("Could not retrieve email_token for subscription disable.");
1075
1318
  await paystack.subscriptionDisable({
@@ -1084,11 +1327,11 @@ const disablePaystackSubscription = (options, path = "/paystack/disable-subscrip
1084
1327
  value: subscriptionCode
1085
1328
  }]
1086
1329
  });
1087
- if (sub) await ctx.context.adapter.update({
1330
+ if (sub !== void 0 && sub !== null) await ctx.context.adapter.update({
1088
1331
  model: "subscription",
1089
1332
  update: {
1090
- status: "active",
1091
- cancelAtPeriodEnd: true,
1333
+ status: atPeriodEnd === false ? "canceled" : "active",
1334
+ cancelAtPeriodEnd: atPeriodEnd !== false,
1092
1335
  periodEnd,
1093
1336
  updatedAt: /* @__PURE__ */ new Date()
1094
1337
  },
@@ -1103,7 +1346,7 @@ const disablePaystackSubscription = (options, path = "/paystack/disable-subscrip
1103
1346
  ctx.context.logger.error("Failed to disable subscription", error);
1104
1347
  throw new APIError("BAD_REQUEST", {
1105
1348
  code: "FAILED_TO_DISABLE_SUBSCRIPTION",
1106
- message: error?.message ?? PAYSTACK_ERROR_CODES.FAILED_TO_DISABLE_SUBSCRIPTION
1349
+ message: error?.message ?? PAYSTACK_ERROR_CODES.FAILED_TO_DISABLE_SUBSCRIPTION.message
1107
1350
  });
1108
1351
  }
1109
1352
  });
@@ -1124,13 +1367,13 @@ const enablePaystackSubscription = (options, path = "/paystack/enable-subscripti
1124
1367
  let emailToken = ctx.body.emailToken;
1125
1368
  if (emailToken === void 0 || emailToken === null || emailToken === "") try {
1126
1369
  const fetchRes = unwrapSdkResult(await paystack.subscriptionFetch(subscriptionCode));
1127
- emailToken = (fetchRes !== null && fetchRes !== void 0 && typeof fetchRes === "object" && "status" in fetchRes && "data" in fetchRes ? fetchRes.data : fetchRes?.data !== void 0 ? fetchRes.data : fetchRes)?.email_token;
1370
+ emailToken = (fetchRes?.data ?? fetchRes)?.email_token;
1128
1371
  } catch {}
1129
1372
  if (emailToken === void 0 || emailToken === null || emailToken === "") try {
1130
1373
  const linkRes = unwrapSdkResult(await paystack.subscriptionManageLink(subscriptionCode));
1131
- const data = linkRes !== null && linkRes !== void 0 && "status" in linkRes && "data" in linkRes ? linkRes.data : linkRes?.data !== void 0 ? linkRes.data : linkRes;
1132
- const link = typeof data === "string" ? data : data?.link;
1133
- if (link !== void 0 && link !== null && link !== "") emailToken = tryGetEmailTokenFromSubscriptionManageLink(link);
1374
+ const data = linkRes?.data ?? linkRes;
1375
+ const link = typeof data === "string" ? data : data.link;
1376
+ if (typeof link === "string" && link !== "") emailToken = tryGetEmailTokenFromSubscriptionManageLink(link);
1134
1377
  } catch {}
1135
1378
  if (emailToken === void 0 || emailToken === null || emailToken === "") throw new APIError("BAD_REQUEST", { message: "Could not retrieve email_token for subscription enable." });
1136
1379
  await paystack.subscriptionEnable({
@@ -1153,7 +1396,7 @@ const enablePaystackSubscription = (options, path = "/paystack/enable-subscripti
1153
1396
  ctx.context.logger.error("Failed to enable subscription", error);
1154
1397
  throw new APIError("BAD_REQUEST", {
1155
1398
  code: "FAILED_TO_ENABLE_SUBSCRIPTION",
1156
- message: error?.message ?? PAYSTACK_ERROR_CODES.FAILED_TO_ENABLE_SUBSCRIPTION
1399
+ message: error?.message ?? PAYSTACK_ERROR_CODES.FAILED_TO_ENABLE_SUBSCRIPTION.message
1157
1400
  });
1158
1401
  }
1159
1402
  });
@@ -1174,9 +1417,9 @@ const getSubscriptionManageLink = (options, path = "/paystack/get-subscription-m
1174
1417
  const paystack = getPaystackOps(options.paystackClient);
1175
1418
  try {
1176
1419
  const res = unwrapSdkResult(await paystack.subscriptionManageLink(subscriptionCode));
1177
- const data = res !== null && res !== void 0 && typeof res === "object" && "status" in res && "data" in res ? res.data : res?.data !== void 0 ? res.data : res;
1178
- const link = typeof data === "string" ? data : data?.link;
1179
- return ctx.json({ link });
1420
+ const data = res?.data ?? res;
1421
+ const link = typeof data === "string" ? data : data.link;
1422
+ return ctx.json({ link: typeof link === "string" ? link : null });
1180
1423
  } catch (error) {
1181
1424
  ctx.context.logger.error("Failed to get subscription manage link", error);
1182
1425
  throw new APIError("BAD_REQUEST", { message: error?.message ?? "Failed to get subscription manage link" });
@@ -1195,16 +1438,14 @@ const syncProducts = (options) => {
1195
1438
  disableBody: true,
1196
1439
  use: [sessionMiddleware]
1197
1440
  }, async (ctx) => {
1198
- console.error("DEBUG: syncProducts endpoint hit!");
1199
1441
  const paystack = getPaystackOps(options.paystackClient);
1200
1442
  try {
1201
- const res = unwrapSdkResult(await paystack.productList());
1202
- const productsData = res !== null && typeof res === "object" && "status" in res && "data" in res ? res.data : res?.data ?? res;
1203
- if (!Array.isArray(productsData)) return ctx.json({
1204
- status: "success",
1205
- count: 0
1206
- });
1207
- for (const product of productsData) {
1443
+ const dataRaw = unwrapSdkResult(await paystack.productList());
1444
+ const productsDataRaw = dataRaw?.data ?? dataRaw;
1445
+ if (!Array.isArray(productsDataRaw)) return ctx.json({ products: [] });
1446
+ const productsData = productsDataRaw;
1447
+ for (const productRaw of productsData) {
1448
+ const product = productRaw;
1208
1449
  const paystackId = String(product.id);
1209
1450
  const existing = await ctx.context.adapter.findOne({
1210
1451
  model: "paystackProduct",
@@ -1213,21 +1454,21 @@ const syncProducts = (options) => {
1213
1454
  value: paystackId
1214
1455
  }]
1215
1456
  });
1216
- const productData = {
1217
- name: product.name,
1218
- description: product.description,
1219
- price: product.price,
1220
- currency: product.currency,
1221
- quantity: product.quantity,
1222
- unlimited: product.unlimited,
1457
+ const productFields = {
1458
+ name: typeof product.name === "string" ? product.name : "",
1459
+ description: typeof product.description === "string" ? product.description : "",
1460
+ price: typeof product.price === "number" ? product.price : 0,
1461
+ currency: typeof product.currency === "string" ? product.currency : "",
1462
+ quantity: typeof product.quantity === "number" ? product.quantity : 0,
1463
+ unlimited: product.unlimited === true,
1223
1464
  paystackId,
1224
- slug: product.slug ?? product.name.toLowerCase().replace(/\s+/g, "-"),
1225
- metadata: product.metadata ? JSON.stringify(product.metadata) : void 0,
1465
+ slug: typeof product.slug === "string" && product.slug !== "" ? product.slug : typeof product.name === "string" ? product.name.toLowerCase().replace(/\s+/g, "-") : "",
1466
+ metadata: product.metadata !== void 0 && product.metadata !== null ? JSON.stringify(product.metadata) : void 0,
1226
1467
  updatedAt: /* @__PURE__ */ new Date()
1227
1468
  };
1228
- if (existing) await ctx.context.adapter.update({
1469
+ if (existing !== null && existing !== void 0) await ctx.context.adapter.update({
1229
1470
  model: "paystackProduct",
1230
- update: productData,
1471
+ update: productFields,
1231
1472
  where: [{
1232
1473
  field: "id",
1233
1474
  value: existing.id
@@ -1236,7 +1477,7 @@ const syncProducts = (options) => {
1236
1477
  else await ctx.context.adapter.create({
1237
1478
  model: "paystackProduct",
1238
1479
  data: {
1239
- ...productData,
1480
+ ...productFields,
1240
1481
  createdAt: /* @__PURE__ */ new Date()
1241
1482
  }
1242
1483
  });
@@ -1270,7 +1511,7 @@ const syncPlans = (options) => {
1270
1511
  const paystack = getPaystackOps(options.paystackClient);
1271
1512
  try {
1272
1513
  const res = unwrapSdkResult(await paystack.planList());
1273
- const plansData = res !== null && typeof res === "object" && "status" in res && "data" in res ? res.data : res?.data ?? res;
1514
+ const plansData = res?.data ?? res;
1274
1515
  if (!Array.isArray(plansData)) return ctx.json({
1275
1516
  status: "success",
1276
1517
  count: 0
@@ -1292,10 +1533,10 @@ const syncPlans = (options) => {
1292
1533
  interval: plan.interval,
1293
1534
  planCode: plan.plan_code,
1294
1535
  paystackId,
1295
- metadata: plan.metadata ? JSON.stringify(plan.metadata) : void 0,
1536
+ metadata: plan.metadata !== void 0 && plan.metadata !== null ? JSON.stringify(plan.metadata) : void 0,
1296
1537
  updatedAt: /* @__PURE__ */ new Date()
1297
1538
  };
1298
- if (existing) await ctx.context.adapter.update({
1539
+ if (existing !== void 0 && existing !== null) await ctx.context.adapter.update({
1299
1540
  model: "paystackPlan",
1300
1541
  update: planData,
1301
1542
  where: [{
@@ -1365,10 +1606,10 @@ const chargeRecurringSubscription = (options) => {
1365
1606
  value: subscriptionId
1366
1607
  }]
1367
1608
  });
1368
- if (subscription === null || subscription === void 0) throw new APIError("NOT_FOUND", { message: "Subscription not found" });
1609
+ if (subscription === void 0 || subscription === null) throw new APIError("NOT_FOUND", { message: "Subscription not found" });
1369
1610
  if (subscription.paystackAuthorizationCode === void 0 || subscription.paystackAuthorizationCode === null || subscription.paystackAuthorizationCode === "") throw new APIError("BAD_REQUEST", { message: "No authorization code found for this subscription" });
1370
1611
  const plan = (await getPlans(options.subscription)).find((p) => p.name.toLowerCase() === subscription.plan.toLowerCase());
1371
- if (plan === void 0 || plan === null) throw new APIError("NOT_FOUND", { message: "Plan not found" });
1612
+ if (!plan) throw new APIError("NOT_FOUND", { message: "Plan not found" });
1372
1613
  const amount = bodyAmount ?? plan.amount;
1373
1614
  if (amount === void 0 || amount === null) throw new APIError("BAD_REQUEST", { message: "Plan amount is not defined" });
1374
1615
  let email;
@@ -1407,7 +1648,7 @@ const chargeRecurringSubscription = (options) => {
1407
1648
  message: `Amount ${amount} is less than the minimum required for ${finalCurrency}.`,
1408
1649
  status: 400
1409
1650
  });
1410
- const data = unwrapSdkResult(await getPaystackOps(options.paystackClient).transactionChargeAuthorization({
1651
+ const dataRaw = unwrapSdkResult(await getPaystackOps(options.paystackClient).transactionChargeAuthorization({
1411
1652
  email,
1412
1653
  amount,
1413
1654
  authorization_code: subscription.paystackAuthorizationCode,
@@ -1418,8 +1659,8 @@ const chargeRecurringSubscription = (options) => {
1418
1659
  plan: plan.name
1419
1660
  }
1420
1661
  }));
1421
- const chargeData = data?.data ?? data;
1422
- if (chargeData?.status === "success") {
1662
+ const chargeData = dataRaw?.data ?? dataRaw;
1663
+ if (chargeData?.status === "success" || dataRaw?.status === "success") {
1423
1664
  const now = /* @__PURE__ */ new Date();
1424
1665
  const nextPeriodEnd = getNextPeriodEnd(now, plan.interval ?? "monthly");
1425
1666
  await ctx.context.adapter.update({
@@ -1428,7 +1669,7 @@ const chargeRecurringSubscription = (options) => {
1428
1669
  periodStart: now,
1429
1670
  periodEnd: nextPeriodEnd,
1430
1671
  updatedAt: now,
1431
- paystackTransactionReference: chargeData.reference
1672
+ paystackTransactionReference: chargeData?.reference ?? dataRaw?.reference
1432
1673
  },
1433
1674
  where: [{
1434
1675
  field: "id",
@@ -1446,7 +1687,6 @@ const chargeRecurringSubscription = (options) => {
1446
1687
  }, { status: 400 });
1447
1688
  });
1448
1689
  };
1449
-
1450
1690
  //#endregion
1451
1691
  //#region src/schema.ts
1452
1692
  const transactions = { paystackTransaction: { fields: {
@@ -1568,6 +1808,10 @@ const subscriptions = { subscription: { fields: {
1568
1808
  seats: {
1569
1809
  type: "number",
1570
1810
  required: false
1811
+ },
1812
+ pendingPlan: {
1813
+ type: "string",
1814
+ required: false
1571
1815
  }
1572
1816
  } } };
1573
1817
  const user = { user: { fields: { paystackCustomerCode: {
@@ -1705,45 +1949,9 @@ const getSchema = (options) => {
1705
1949
  }
1706
1950
  return mergeSchema(baseSchema, options.schema);
1707
1951
  };
1708
-
1709
- //#endregion
1710
- //#region src/limits.ts
1711
- const getOrganizationSubscription = async (ctx, organizationId) => {
1712
- return await ctx.context.adapter.findOne({
1713
- model: "subscription",
1714
- where: [{
1715
- field: "referenceId",
1716
- value: organizationId
1717
- }]
1718
- });
1719
- };
1720
- const checkSeatLimit = async (ctx, organizationId, seatsToAdd = 1) => {
1721
- const subscription = await getOrganizationSubscription(ctx, organizationId);
1722
- if (subscription?.seats === void 0 || subscription.seats === null) return true;
1723
- const members = await ctx.context.adapter.findMany({
1724
- model: "member",
1725
- where: [{
1726
- field: "organizationId",
1727
- value: organizationId
1728
- }]
1729
- });
1730
- if (members.length + seatsToAdd > subscription.seats) throw new APIError("FORBIDDEN", { message: `Organization member limit reached. Used: ${members.length}, Max: ${subscription.seats}` });
1731
- return true;
1732
- };
1733
- const checkTeamLimit = async (ctx, organizationId, maxTeams) => {
1734
- if ((await ctx.context.adapter.findMany({
1735
- model: "team",
1736
- where: [{
1737
- field: "organizationId",
1738
- value: organizationId
1739
- }]
1740
- })).length >= maxTeams) throw new APIError("FORBIDDEN", { message: `Organization team limit reached. Max teams: ${maxTeams}` });
1741
- return true;
1742
- };
1743
-
1744
1952
  //#endregion
1745
1953
  //#region src/index.ts
1746
- const INTERNAL_ERROR_CODES = defineErrorCodes({ ...PAYSTACK_ERROR_CODES });
1954
+ const INTERNAL_ERROR_CODES = defineErrorCodes({ ...Object.fromEntries(Object.entries(PAYSTACK_ERROR_CODES).map(([key, value]) => [key, typeof value === "string" ? value : value.message])) });
1747
1955
  const paystack = (options) => {
1748
1956
  const routeOptions = options;
1749
1957
  return {
@@ -1774,14 +1982,14 @@ const paystack = (options) => {
1774
1982
  return { options: {
1775
1983
  databaseHooks: {
1776
1984
  user: { create: { async after(user, hookCtx) {
1777
- if (hookCtx === void 0 || hookCtx === null || options.createCustomerOnSignUp !== true) return;
1985
+ if (!hookCtx || options.createCustomerOnSignUp !== true || user.email === null || user.email === void 0 || user.email === "") return;
1778
1986
  const sdkRes = unwrapSdkResult(await getPaystackOps(options.paystackClient).customerCreate({
1779
1987
  email: user.email,
1780
1988
  first_name: user.name ?? void 0,
1781
1989
  metadata: { userId: user.id }
1782
1990
  }));
1783
1991
  const customerCode = sdkRes?.customer_code ?? (sdkRes?.data)?.customer_code;
1784
- if (customerCode === void 0 || customerCode === null) return;
1992
+ if (customerCode === "" || customerCode === null || customerCode === void 0) return;
1785
1993
  await ctx.adapter.update({
1786
1994
  model: "user",
1787
1995
  where: [{
@@ -1806,7 +2014,7 @@ const paystack = (options) => {
1806
2014
  value: "owner"
1807
2015
  }]
1808
2016
  });
1809
- if (ownerMember !== null && ownerMember !== void 0) targetEmail = (await ctx.adapter.findOne({
2017
+ if (ownerMember) targetEmail = (await ctx.adapter.findOne({
1810
2018
  model: "user",
1811
2019
  where: [{
1812
2020
  field: "id",
@@ -1822,7 +2030,7 @@ const paystack = (options) => {
1822
2030
  }, extraCreateParams);
1823
2031
  const sdkRes = unwrapSdkResult(await getPaystackOps(options.paystackClient).customerCreate(params));
1824
2032
  const customerCode = sdkRes?.customer_code ?? (sdkRes?.data)?.customer_code;
1825
- if (customerCode === void 0 || customerCode === null) return;
2033
+ if (customerCode === "" || customerCode === null || customerCode === void 0 || sdkRes === null || sdkRes === void 0) return;
1826
2034
  await ctx.internalAdapter.updateOrganization(org.id, { paystackCustomerCode: customerCode });
1827
2035
  await options.organization?.onCustomerCreate?.({
1828
2036
  paystackCustomer: sdkRes,
@@ -1836,17 +2044,37 @@ const paystack = (options) => {
1836
2044
  }
1837
2045
  } } } : void 0
1838
2046
  },
1839
- member: { create: { before: async (member, ctx) => {
1840
- if (options.subscription?.enabled === true && member.organizationId && ctx !== null && ctx !== void 0) await checkSeatLimit(ctx, member.organizationId);
1841
- } } },
1842
- invitation: { create: { before: async (invitation, ctx) => {
1843
- if (options.subscription?.enabled === true && invitation.organizationId && ctx !== null && ctx !== void 0) await checkSeatLimit(ctx, invitation.organizationId);
1844
- } } },
2047
+ member: {
2048
+ create: {
2049
+ before: async (member, ctx) => {
2050
+ if (options.subscription?.enabled === true && member.organizationId && ctx !== null && ctx !== void 0) await checkSeatLimit(ctx, member.organizationId);
2051
+ },
2052
+ after: async (member, ctx) => {
2053
+ if (options.subscription?.enabled === true && typeof member?.organizationId === "string" && ctx) await syncSubscriptionSeats(ctx, member.organizationId, routeOptions);
2054
+ }
2055
+ },
2056
+ delete: { after: async (member, ctx) => {
2057
+ if (options.subscription?.enabled === true && typeof member?.organizationId === "string" && ctx) await syncSubscriptionSeats(ctx, member.organizationId, routeOptions);
2058
+ } }
2059
+ },
2060
+ invitation: {
2061
+ create: {
2062
+ before: async (invitation, ctx) => {
2063
+ if (options.subscription?.enabled === true && invitation.organizationId && ctx !== null && ctx !== void 0) await checkSeatLimit(ctx, invitation.organizationId);
2064
+ },
2065
+ after: async (invitation, ctx) => {
2066
+ if (options.subscription?.enabled === true && typeof invitation?.organizationId === "string" && ctx) await syncSubscriptionSeats(ctx, invitation.organizationId, routeOptions);
2067
+ }
2068
+ },
2069
+ delete: { after: async (invitation, ctx) => {
2070
+ if (options.subscription?.enabled === true && typeof invitation?.organizationId === "string" && ctx) await syncSubscriptionSeats(ctx, invitation.organizationId, routeOptions);
2071
+ } }
2072
+ },
1845
2073
  team: { create: { before: async (team, ctx) => {
1846
- if (options.subscription?.enabled === true && team.organizationId && ctx !== null && ctx !== void 0) {
2074
+ if (options.subscription?.enabled === true && team.organizationId && ctx) {
1847
2075
  const subscription = await getOrganizationSubscription(ctx, team.organizationId);
1848
- if (subscription !== null && subscription !== void 0) {
1849
- const maxTeams = ((await getPlanByName(options, subscription.plan))?.limits)?.teams;
2076
+ if (subscription) {
2077
+ const maxTeams = ((await getPlanByName(routeOptions, subscription.plan))?.limits)?.teams;
1850
2078
  if (typeof maxTeams === "number") await checkTeamLimit(ctx, team.organizationId, maxTeams);
1851
2079
  }
1852
2080
  }
@@ -1856,7 +2084,7 @@ const paystack = (options) => {
1856
2084
  $ERROR_CODES: INTERNAL_ERROR_CODES
1857
2085
  };
1858
2086
  };
1859
-
1860
2087
  //#endregion
1861
2088
  export { paystack };
2089
+
1862
2090
  //# sourceMappingURL=index.mjs.map