@alexasomba/better-auth-paystack 2.1.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,8 +1,7 @@
1
- import { defineErrorCodes } from "@better-auth/core/utils/error-codes";
1
+ import { t as PACKAGE_VERSION } from "./version-C_50YiuM.mjs";
2
+ import { HIDE_METADATA, defineErrorCodes, logger } from "better-auth";
2
3
  import { defu } from "defu";
3
- import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api";
4
- import { HIDE_METADATA, logger } from "better-auth";
5
- import { APIError, getSessionFromCtx, originCheck, sessionMiddleware } from "better-auth/api";
4
+ import { APIError, createAuthEndpoint, createAuthMiddleware, getSessionFromCtx, originCheck, sessionMiddleware } from "better-auth/api";
6
5
  import { z } from "zod";
7
6
  import { PaystackResponse } from "@alexasomba/paystack-node";
8
7
  import { mergeSchema } from "better-auth/db";
@@ -45,6 +44,30 @@ function getPaystackOps(client) {
45
44
  }
46
45
  //#endregion
47
46
  //#region src/utils.ts
47
+ function getPlanSeatAmount(plan) {
48
+ if (plan.seatAmount !== void 0) {
49
+ if (typeof plan.seatAmount === "number" && Number.isFinite(plan.seatAmount)) return plan.seatAmount;
50
+ throw new Error(`Invalid seatAmount for plan '${plan.name}'. Expected a finite number.`);
51
+ }
52
+ if (plan.seatPriceId === void 0 || plan.seatPriceId === null || plan.seatPriceId === "") return;
53
+ const parsed = typeof plan.seatPriceId === "string" ? Number(plan.seatPriceId) : plan.seatPriceId;
54
+ if (typeof parsed === "number" && Number.isFinite(parsed)) return parsed;
55
+ throw new Error(`Invalid seatPriceId for plan '${plan.name}'. Expected a numeric amount in the smallest currency unit.`);
56
+ }
57
+ function calculatePlanAmount(plan, quantity) {
58
+ return (plan.amount ?? 0) + quantity * (getPlanSeatAmount(plan) ?? 0);
59
+ }
60
+ function isLocalSubscriptionCode(subscriptionCode) {
61
+ return typeof subscriptionCode === "string" && (subscriptionCode.startsWith("LOC_") || subscriptionCode.startsWith("sub_local_"));
62
+ }
63
+ function isLocallyManagedSubscription(subscription) {
64
+ if (isLocalSubscriptionCode(subscription.paystackSubscriptionCode)) return true;
65
+ if (typeof subscription.paystackSubscriptionCode === "string" && subscription.paystackSubscriptionCode !== "") return false;
66
+ return subscription.paystackPlanCode === void 0 || subscription.paystackPlanCode === null || subscription.paystackPlanCode === "";
67
+ }
68
+ function assertLocallyManagedSubscription(subscription, action) {
69
+ if (!isLocallyManagedSubscription(subscription)) throw new Error(`Paystack-managed subscriptions do not support ${action}. Use local billing for seat-based or prorated subscription changes.`);
70
+ }
48
71
  async function getPlans(subscriptionOptions) {
49
72
  if (subscriptionOptions?.enabled === true) return typeof subscriptionOptions.plans === "function" ? subscriptionOptions.plans() : subscriptionOptions.plans;
50
73
  throw new Error("Subscriptions are not enabled in the Paystack options.");
@@ -135,7 +158,9 @@ async function syncProductQuantityFromPaystack(ctx, productName, paystackClient)
135
158
  return;
136
159
  }
137
160
  try {
138
- const remoteQuantity = unwrapSdkResult(await paystackClient.product?.fetch(localProduct.paystackId))?.quantity;
161
+ const paystackProductId = Number(localProduct.paystackId);
162
+ if (!Number.isFinite(paystackProductId)) return;
163
+ const remoteQuantity = unwrapSdkResult(await paystackClient.product?.fetch(paystackProductId))?.quantity;
139
164
  if (remoteQuantity !== void 0 && localProduct.id !== void 0) await ctx.context.adapter.update({
140
165
  model: "paystackProduct",
141
166
  update: {
@@ -175,7 +200,7 @@ async function syncSubscriptionSeats(ctx, organizationId, options) {
175
200
  if (subscription === null || subscription === void 0) return;
176
201
  const plan = await getPlanByName(options, subscription.plan);
177
202
  if (plan === null || plan === void 0) return;
178
- if (plan.seatAmount === void 0) return;
203
+ if (getPlanSeatAmount(plan) === void 0) return;
179
204
  const quantity = (await adapter.findMany({
180
205
  model: "member",
181
206
  where: [{
@@ -183,12 +208,8 @@ async function syncSubscriptionSeats(ctx, organizationId, options) {
183
208
  value: organizationId
184
209
  }]
185
210
  })).length;
186
- let totalAmount = plan.amount ?? 0;
187
- if (plan.seatAmount !== void 0 && plan.seatAmount !== null && typeof plan.seatAmount === "number") totalAmount += quantity * plan.seatAmount;
188
211
  try {
189
- const client = options.paystackClient;
190
- if (client === void 0 || client === null) return;
191
- unwrapSdkResult(await client.subscription?.update(subscription.paystackSubscriptionCode, { body: { amount: totalAmount } }));
212
+ assertLocallyManagedSubscription(subscription, "automatic seat sync");
192
213
  await adapter.update({
193
214
  model: "subscription",
194
215
  where: [{
@@ -202,26 +223,39 @@ async function syncSubscriptionSeats(ctx, organizationId, options) {
202
223
  });
203
224
  } catch (e) {
204
225
  const log = ctx.context.logger;
205
- if (log !== void 0 && log !== null) log.error("Failed to sync subscription seats with Paystack", e);
226
+ if (log !== void 0 && log !== null) log.error("Failed to sync subscription seats", e);
206
227
  }
207
228
  }
208
229
  //#endregion
209
230
  //#region src/middleware.ts
231
+ const BILLING_ORG_ROLES = new Set(["owner", "admin"]);
232
+ function hasBillingRole(role) {
233
+ if (Array.isArray(role)) return role.some((value) => hasBillingRole(value));
234
+ if (typeof role !== "string") return false;
235
+ return role.split(",").map((value) => value.trim()).some((value) => BILLING_ORG_ROLES.has(value));
236
+ }
210
237
  const referenceMiddleware = (options, action) => createAuthMiddleware(async (ctx) => {
211
238
  const session = ctx.context.session;
212
239
  if (session === null || session === void 0) throw new APIError("UNAUTHORIZED");
213
240
  const body = ctx.body ?? {};
214
241
  const query = ctx.query ?? {};
215
- const referenceId = body.referenceId ?? query.referenceId ?? session.user.id;
242
+ const requestQueryReferenceId = typeof ctx.request?.url === "string" ? new URL(ctx.request.url).searchParams.get("referenceId") ?? void 0 : void 0;
243
+ const referenceId = body.referenceId ?? query.referenceId ?? requestQueryReferenceId ?? session.user.id;
216
244
  const subscriptionOptions = options.subscription;
217
- if (referenceId === session.user.id) return { referenceId };
245
+ if (referenceId === session.user.id) return { context: {
246
+ ...ctx.context,
247
+ referenceId
248
+ } };
218
249
  if (subscriptionOptions?.enabled === true && "authorizeReference" in subscriptionOptions && typeof subscriptionOptions.authorizeReference === "function") {
219
250
  if (await subscriptionOptions.authorizeReference({
220
251
  user: session.user,
221
252
  session: session.session,
222
253
  referenceId,
223
254
  action
224
- }, ctx) === true) return { referenceId };
255
+ }, ctx) === true) return { context: {
256
+ ...ctx.context,
257
+ referenceId
258
+ } };
225
259
  throw new APIError("UNAUTHORIZED");
226
260
  }
227
261
  if (options.organization?.enabled === true) {
@@ -235,13 +269,13 @@ const referenceMiddleware = (options, action) => createAuthMiddleware(async (ctx
235
269
  value: referenceId
236
270
  }]
237
271
  });
238
- if (member !== null && member !== void 0) {
239
- logger.debug("DEBUG MIDDLEWARE MEMBER FOUND:", member);
240
- return { referenceId };
241
- }
272
+ if (member !== null && member !== void 0 && hasBillingRole(member.role)) return { context: {
273
+ ...ctx.context,
274
+ referenceId
275
+ } };
242
276
  }
243
- 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.`);
244
- throw new APIError("BAD_REQUEST", { message: "Passing referenceId isn't allowed without subscription.authorizeReference or valid organization membership." });
277
+ logger.error(`Passing referenceId into a subscription action isn't allowed unless subscription.authorizeReference allows it or the session user is an organization owner/admin.`);
278
+ throw new APIError("BAD_REQUEST", { message: "Passing referenceId isn't allowed without subscription.authorizeReference or organization owner/admin membership." });
245
279
  });
246
280
  //#endregion
247
281
  //#region src/limits.ts
@@ -288,8 +322,43 @@ const PAYSTACK_ERROR_CODES = defineErrorCodes({
288
322
  FAILED_TO_VERIFY_TRANSACTION: "Failed to verify transaction",
289
323
  FAILED_TO_DISABLE_SUBSCRIPTION: "Failed to disable subscription",
290
324
  FAILED_TO_ENABLE_SUBSCRIPTION: "Failed to enable subscription",
291
- EMAIL_VERIFICATION_REQUIRED: "Email verification is required before you can subscribe to a plan"
325
+ EMAIL_VERIFICATION_REQUIRED: "Email verification is required before you can subscribe to a plan",
326
+ SUBSCRIPTION_PAYMENT_CHANNEL_NOT_ALLOWED: "This subscription only supports specific payment channels"
292
327
  });
328
+ function getAllowedSubscriptionChannels(options) {
329
+ const channels = options.subscription?.allowedPaymentChannels;
330
+ return Array.isArray(channels) && channels.length > 0 ? channels : void 0;
331
+ }
332
+ function isAllowedSubscriptionChannel(channel, allowedChannels) {
333
+ if (allowedChannels === void 0) return true;
334
+ return channel !== void 0 && channel !== null && allowedChannels.includes(channel);
335
+ }
336
+ async function assertReferenceAccess(ctx, options, data) {
337
+ if (data.referenceId === data.user.id) return;
338
+ if (options.subscription?.enabled === true && typeof options.subscription.authorizeReference === "function") {
339
+ if (await options.subscription.authorizeReference({
340
+ user: data.user,
341
+ session: data.session,
342
+ referenceId: data.referenceId,
343
+ action: data.action
344
+ }, ctx) === true) return;
345
+ throw new APIError("UNAUTHORIZED");
346
+ }
347
+ if (options.organization?.enabled === true) {
348
+ const member = await ctx.context.adapter.findOne({
349
+ model: "member",
350
+ where: [{
351
+ field: "userId",
352
+ value: data.user.id
353
+ }, {
354
+ field: "organizationId",
355
+ value: data.referenceId
356
+ }]
357
+ });
358
+ if (member !== null && member !== void 0 && hasBillingRole(member.role)) return;
359
+ }
360
+ throw new APIError("UNAUTHORIZED");
361
+ }
293
362
  async function hmacSha512Hex(secret, message) {
294
363
  const encoder = new TextEncoder();
295
364
  const keyData = encoder.encode(secret);
@@ -338,7 +407,7 @@ const paystackWebhook = (options, path = "/webhook") => {
338
407
  message: "Missing x-paystack-signature header",
339
408
  status: 401
340
409
  });
341
- if (await hmacSha512Hex(options.webhook?.secret ?? options.secretKey, payload) !== signature) throw new APIError("UNAUTHORIZED", {
410
+ if (await hmacSha512Hex(options.webhook?.secret ?? options.paystackWebhookSecret ?? options.secretKey, payload) !== signature) throw new APIError("UNAUTHORIZED", {
342
411
  message: "Invalid Paystack webhook signature",
343
412
  status: 401
344
413
  });
@@ -504,7 +573,7 @@ const paystackWebhook = (options, path = "/webhook") => {
504
573
  value: subscriptionCode
505
574
  }]
506
575
  });
507
- if (existing) await options.subscription.onSubscriptionCancel?.({
576
+ if (existing !== null && existing !== void 0) await options.subscription.onSubscriptionCancel?.({
508
577
  event,
509
578
  subscription: {
510
579
  ...existing,
@@ -575,7 +644,7 @@ const initializeTransaction = (options, path = "/initialize-transaction") => {
575
644
  if (callbackURL !== void 0 && callbackURL !== null && callbackURL !== "") {
576
645
  const checkTrusted = () => {
577
646
  try {
578
- if (callbackURL.startsWith("/")) return true;
647
+ if (callbackURL?.startsWith("/") === true) return true;
579
648
  const baseUrl = ctx.context?.baseURL ?? ctx.request?.url ?? "";
580
649
  if (baseUrl === "") return false;
581
650
  const baseOrigin = new URL(baseUrl).origin;
@@ -690,39 +759,44 @@ const initializeTransaction = (options, path = "/initialize-transaction") => {
690
759
  });
691
760
  }
692
761
  }
693
- if (plan !== void 0 && (plan.seatAmount !== void 0 || plan.seatPriceId !== void 0)) {
694
- const members = await ctx.context.adapter.findMany({
695
- model: "member",
696
- where: [{
697
- field: "organizationId",
698
- value: referenceId
699
- }]
700
- });
701
- const seatCount = members.length > 0 ? members.length : 1;
702
- const quantityToUse = quantity ?? seatCount;
703
- amount = (plan.amount ?? 0) + quantityToUse * (plan.seatAmount ?? plan.seatPriceId ?? 0);
762
+ if (plan !== void 0) try {
763
+ if (getPlanSeatAmount(plan) !== void 0) {
764
+ const members = await ctx.context.adapter.findMany({
765
+ model: "member",
766
+ where: [{
767
+ field: "organizationId",
768
+ value: referenceId
769
+ }]
770
+ });
771
+ const seatCount = members.length > 0 ? members.length : 1;
772
+ amount = calculatePlanAmount(plan, quantity ?? seatCount);
773
+ }
774
+ } catch (error) {
775
+ throw new APIError("BAD_REQUEST", { message: error instanceof Error ? error.message : "Invalid seat configuration for plan." });
704
776
  }
705
777
  let url;
706
778
  let reference;
707
779
  let accessCode;
708
780
  let trialStart;
709
781
  let trialEnd;
710
- if (plan?.freeTrial?.days !== void 0 && plan.freeTrial.days > 0) {
711
- if ((await ctx.context.adapter.findMany({
712
- model: "subscription",
713
- where: [{
714
- field: "referenceId",
715
- value: referenceId
716
- }]
717
- }))?.some((sub) => sub.trialStart !== void 0 && sub.trialStart !== null || sub.trialEnd !== void 0 && sub.trialEnd !== null || sub.status === "trialing") === false) {
718
- trialStart = /* @__PURE__ */ new Date();
719
- trialEnd = /* @__PURE__ */ new Date();
720
- trialEnd.setDate(trialEnd.getDate() + plan.freeTrial.days);
721
- }
722
- }
782
+ const requestedTrialDays = plan?.freeTrial?.days !== void 0 && plan.freeTrial.days > 0 ? plan.freeTrial.days : 0;
783
+ const trialRequested = requestedTrialDays > 0;
784
+ let trialGranted = false;
785
+ let trialDeniedReason;
786
+ if (trialRequested) if ((await ctx.context.adapter.findMany({
787
+ model: "subscription",
788
+ where: [{
789
+ field: "referenceId",
790
+ value: referenceId
791
+ }]
792
+ }))?.some((sub) => sub.trialStart !== void 0 && sub.trialStart !== null || sub.trialEnd !== void 0 && sub.trialEnd !== null || sub.status === "trialing") === false) {
793
+ trialStart = /* @__PURE__ */ new Date();
794
+ trialEnd = /* @__PURE__ */ new Date();
795
+ trialEnd.setDate(trialEnd.getDate() + requestedTrialDays);
796
+ trialGranted = true;
797
+ } else trialDeniedReason = "already_used";
723
798
  try {
724
799
  let targetEmail = email ?? user.email;
725
- let paystackCustomerCode = user.paystackCustomerCode;
726
800
  if (options.organization?.enabled === true && referenceId !== void 0 && referenceId !== null && referenceId !== user.id) {
727
801
  const org = await ctx.context.adapter.findOne({
728
802
  model: "organization",
@@ -732,8 +806,6 @@ const initializeTransaction = (options, path = "/initialize-transaction") => {
732
806
  }]
733
807
  });
734
808
  if (org !== void 0 && org !== null) {
735
- const paystackOrg = org;
736
- if (paystackOrg.paystackCustomerCode !== void 0 && paystackOrg.paystackCustomerCode !== null && paystackOrg.paystackCustomerCode !== "") paystackCustomerCode = paystackOrg.paystackCustomerCode;
737
809
  const orgWithEmail = org;
738
810
  if (orgWithEmail.email !== void 0 && orgWithEmail.email !== null && orgWithEmail.email !== "") targetEmail = orgWithEmail.email;
739
811
  else {
@@ -760,14 +832,18 @@ const initializeTransaction = (options, path = "/initialize-transaction") => {
760
832
  }
761
833
  }
762
834
  }
835
+ const allowedSubscriptionChannels = plan ? getAllowedSubscriptionChannels(options) : void 0;
763
836
  const metadata = JSON.stringify({
764
837
  referenceId,
765
838
  userId: user.id,
766
839
  plan: plan !== void 0 ? plan.name.toLowerCase() : void 0,
767
840
  product: product !== void 0 ? product.name.toLowerCase() : void 0,
841
+ ...extraMetadata,
768
842
  isTrial: trialStart !== void 0,
769
- trialEnd: trialEnd !== void 0 ? trialEnd.toISOString() : void 0,
770
- ...extraMetadata
843
+ trialRequested,
844
+ trialGranted,
845
+ trialDeniedReason,
846
+ trialEnd: trialEnd !== void 0 ? trialEnd.toISOString() : void 0
771
847
  });
772
848
  const initBody = {
773
849
  email: targetEmail,
@@ -776,13 +852,10 @@ const initializeTransaction = (options, path = "/initialize-transaction") => {
776
852
  currency: finalCurrency,
777
853
  quantity
778
854
  };
779
- if (paystackCustomerCode !== void 0 && paystackCustomerCode !== null && paystackCustomerCode !== "") try {
780
- const ops = getPaystackOps(options.paystackClient);
781
- if (ops !== void 0 && ops !== null && initBody.email !== "") await ops.customer?.update(paystackCustomerCode, { body: { email: initBody.email } });
782
- } catch (_e) {}
855
+ if (allowedSubscriptionChannels !== void 0) initBody.channels = allowedSubscriptionChannels;
783
856
  if (plan !== void 0 && prorateAndCharge === true) {
784
857
  const existingSub = await getOrganizationSubscription(ctx, referenceId);
785
- if (existingSub?.status === "active" && existingSub.paystackAuthorizationCode !== void 0 && existingSub.paystackAuthorizationCode !== null && existingSub.paystackAuthorizationCode !== "" && existingSub.paystackSubscriptionCode !== void 0 && existingSub.paystackSubscriptionCode !== null && existingSub.paystackSubscriptionCode !== "") {
858
+ if (existingSub?.status === "active" && existingSub.paystackSubscriptionCode !== void 0 && existingSub.paystackSubscriptionCode !== null && existingSub.paystackSubscriptionCode !== "") {
786
859
  if (existingSub.periodEnd !== void 0 && existingSub.periodEnd !== null && existingSub.periodStart !== void 0 && existingSub.periodStart !== null) {
787
860
  const now = /* @__PURE__ */ new Date();
788
861
  const periodEndLocal = new Date(existingSub.periodEnd);
@@ -800,51 +873,109 @@ const initializeTransaction = (options, path = "/initialize-transaction") => {
800
873
  }) ?? void 0;
801
874
  if (oldPlan !== void 0 && oldPlan !== null) {
802
875
  const oldSeatCount = existingSub.seats;
803
- oldAmount = (oldPlan.amount ?? 0) + oldSeatCount * (oldPlan.seatAmount ?? oldPlan.seatPriceId ?? 0);
876
+ oldAmount = calculatePlanAmount(oldPlan, oldSeatCount);
804
877
  }
805
878
  }
806
879
  let membersCount = 1;
807
- if (plan.seatAmount !== void 0 || plan.seatPriceId !== void 0) {
808
- const members = await ctx.context.adapter.findMany({
809
- model: "member",
810
- where: [{
811
- field: "organizationId",
812
- value: referenceId
813
- }]
814
- });
815
- membersCount = members.length > 0 ? members.length : 1;
880
+ let newSeatCount = quantity ?? existingSub.seats ?? membersCount;
881
+ let newAmount;
882
+ try {
883
+ assertLocallyManagedSubscription(existingSub, "plan or seat changes");
884
+ if (getPlanSeatAmount(plan) !== void 0) {
885
+ const members = await ctx.context.adapter.findMany({
886
+ model: "member",
887
+ where: [{
888
+ field: "organizationId",
889
+ value: referenceId
890
+ }]
891
+ });
892
+ membersCount = members.length > 0 ? members.length : 1;
893
+ }
894
+ newSeatCount = quantity ?? existingSub.seats ?? membersCount;
895
+ newAmount = calculatePlanAmount(plan, newSeatCount);
896
+ } catch (error) {
897
+ throw new APIError("BAD_REQUEST", { message: error instanceof Error ? error.message : "Invalid seat configuration for plan." });
816
898
  }
817
- const newSeatCount = quantity ?? existingSub.seats ?? membersCount;
818
- const newAmount = (plan.amount ?? 0) + newSeatCount * (plan.seatAmount ?? plan.seatPriceId ?? 0);
819
899
  const costDifference = newAmount - oldAmount;
900
+ const prorationMetadata = {
901
+ type: "proration",
902
+ subscriptionId: existingSub.id,
903
+ referenceId,
904
+ newPlan: plan.name.toLowerCase(),
905
+ oldPlan: existingSub.plan,
906
+ newSeatCount,
907
+ remainingDays
908
+ };
909
+ let completedProrationReference;
820
910
  if (costDifference > 0 && remainingDays > 0) {
821
911
  const proratedAmount = Math.round(costDifference / totalDays * remainingDays);
822
- if (proratedAmount >= 5e3) {
823
- const ops = getPaystackOps(options.paystackClient);
824
- if (ops === void 0 || ops === null) {
825
- ctx.context.logger.error("Paystack client not configured for proration charge");
826
- return;
827
- }
828
- if (unwrapSdkResult(await ops.transaction?.chargeAuthorization({ body: {
912
+ if (proratedAmount < 5e3) throw new APIError("BAD_REQUEST", {
913
+ message: "Prorated upgrade amount is below Paystack's minimum charge. Schedule the change for period end instead.",
914
+ status: 400
915
+ });
916
+ const ops = getPaystackOps(options.paystackClient);
917
+ if (ops === void 0 || ops === null) {
918
+ ctx.context.logger.error("Paystack client not configured for proration charge");
919
+ return;
920
+ }
921
+ if (existingSub.paystackAuthorizationCode !== void 0 && existingSub.paystackAuthorizationCode !== null && existingSub.paystackAuthorizationCode !== "") {
922
+ const sdkRes = unwrapSdkResult(await ops.transaction?.chargeAuthorization({ body: {
829
923
  email: targetEmail,
830
924
  amount: proratedAmount,
831
925
  authorization_code: existingSub.paystackAuthorizationCode,
832
926
  reference: `upg_${existingSub.id}_${Date.now()}_${Math.random().toString(36).substring(7)}`,
833
- metadata: {
834
- type: "proration",
927
+ metadata: JSON.stringify(prorationMetadata)
928
+ } }));
929
+ if (sdkRes?.status !== "success") throw new APIError("BAD_REQUEST", { message: "Failed to process prorated charge via saved authorization." });
930
+ await ctx.context.adapter.create({
931
+ model: "paystackTransaction",
932
+ data: {
933
+ reference: sdkRes.reference ?? "",
934
+ paystackId: sdkRes.id !== void 0 && sdkRes.id !== null ? String(sdkRes.id) : void 0,
835
935
  referenceId,
836
- newPlan: plan.name,
837
- oldPlan: existingSub.plan,
838
- remainingDays
936
+ userId: user.id,
937
+ amount: sdkRes.amount ?? proratedAmount,
938
+ currency: sdkRes.currency ?? finalCurrency,
939
+ status: "success",
940
+ plan: plan.name.toLowerCase(),
941
+ metadata: JSON.stringify(prorationMetadata),
942
+ createdAt: /* @__PURE__ */ new Date(),
943
+ updatedAt: /* @__PURE__ */ new Date()
839
944
  }
840
- } }))?.status !== "success") throw new APIError("BAD_REQUEST", { message: "Failed to process prorated charge via saved authorization." });
945
+ });
946
+ completedProrationReference = sdkRes.reference ?? void 0;
947
+ } else {
948
+ const initRes = unwrapSdkResult(await ops.transaction?.initialize({ body: {
949
+ email: targetEmail,
950
+ amount: proratedAmount,
951
+ currency: finalCurrency,
952
+ callback_url: callbackURL ?? void 0,
953
+ metadata: JSON.stringify(prorationMetadata),
954
+ ...allowedSubscriptionChannels !== void 0 ? { channels: allowedSubscriptionChannels } : {}
955
+ } }));
956
+ await ctx.context.adapter.create({
957
+ model: "paystackTransaction",
958
+ data: {
959
+ reference: initRes?.reference ?? "",
960
+ referenceId,
961
+ userId: user.id,
962
+ amount: proratedAmount,
963
+ currency: finalCurrency,
964
+ status: "pending",
965
+ plan: plan.name.toLowerCase(),
966
+ metadata: JSON.stringify(prorationMetadata),
967
+ createdAt: /* @__PURE__ */ new Date(),
968
+ updatedAt: /* @__PURE__ */ new Date()
969
+ }
970
+ });
971
+ return ctx.json({
972
+ url: initRes?.authorization_url,
973
+ reference: initRes?.reference,
974
+ accessCode: initRes?.access_code,
975
+ redirect: true
976
+ });
841
977
  }
842
978
  }
843
- const ops = getPaystackOps(options.paystackClient);
844
- if (ops !== void 0 && ops !== null) await ops.subscription?.update(existingSub.paystackSubscriptionCode, { body: {
845
- amount: newAmount,
846
- plan: plan.planCode
847
- } });
848
979
  await ctx.context.adapter.update({
849
980
  model: "subscription",
850
981
  where: [{
@@ -854,6 +985,7 @@ const initializeTransaction = (options, path = "/initialize-transaction") => {
854
985
  update: {
855
986
  plan: plan.name,
856
987
  seats: newSeatCount,
988
+ ...completedProrationReference !== void 0 ? { paystackTransactionReference: completedProrationReference } : {},
857
989
  updatedAt: /* @__PURE__ */ new Date()
858
990
  }
859
991
  });
@@ -987,6 +1119,7 @@ const verifyTransaction = (options, path = "/verify-transaction") => {
987
1119
  const paystackIdRaw = data.id;
988
1120
  const paystackId = paystackIdRaw !== void 0 && paystackIdRaw !== null ? String(paystackIdRaw) : void 0;
989
1121
  const authorizationCode = data.authorization?.authorization_code;
1122
+ const allowedSubscriptionChannels = getAllowedSubscriptionChannels(options);
990
1123
  if (status === "success") {
991
1124
  const session = await getSessionFromCtx(ctx);
992
1125
  const txRecord = await ctx.context.adapter.findOne({
@@ -997,6 +1130,26 @@ const verifyTransaction = (options, path = "/verify-transaction") => {
997
1130
  }]
998
1131
  });
999
1132
  const referenceId = txRecord !== void 0 && txRecord !== null && txRecord.referenceId !== void 0 && txRecord.referenceId !== null && txRecord.referenceId !== "" ? txRecord.referenceId : session !== void 0 && session !== null ? session.user.id : void 0;
1133
+ if ((txRecord?.plan !== void 0 && txRecord.plan !== null && txRecord.plan !== "" || Boolean(data.plan)) && isAllowedSubscriptionChannel(data.channel ?? void 0, allowedSubscriptionChannels) === false) {
1134
+ await ctx.context.adapter.update({
1135
+ model: "paystackTransaction",
1136
+ update: {
1137
+ status: "failed",
1138
+ paystackId,
1139
+ amount: data.amount,
1140
+ currency: data.currency,
1141
+ updatedAt: /* @__PURE__ */ new Date()
1142
+ },
1143
+ where: [{
1144
+ field: "reference",
1145
+ value: reference
1146
+ }]
1147
+ });
1148
+ throw new APIError("BAD_REQUEST", {
1149
+ code: "SUBSCRIPTION_PAYMENT_CHANNEL_NOT_ALLOWED",
1150
+ message: `This subscription requires one of: ${allowedSubscriptionChannels?.join(", ") ?? "allowed channels"}.`
1151
+ });
1152
+ }
1000
1153
  if (session !== void 0 && session !== null && referenceId !== void 0 && referenceId !== null && referenceId !== "" && referenceId !== session.user.id) {
1001
1154
  const authRef = subscriptionOptions?.authorizeReference;
1002
1155
  let authorized = false;
@@ -1077,11 +1230,36 @@ const verifyTransaction = (options, path = "/verify-transaction") => {
1077
1230
  let isTrial = false;
1078
1231
  let trialEnd;
1079
1232
  let targetPlan;
1233
+ let metadataObj = {};
1080
1234
  if (data.metadata !== void 0 && data.metadata !== null && data.metadata !== "") {
1081
- const meta = typeof data.metadata === "string" ? JSON.parse(data.metadata) : data.metadata;
1082
- isTrial = meta.isTrial === true || meta.isTrial === "true";
1083
- trialEnd = meta.trialEnd;
1084
- targetPlan = meta.plan;
1235
+ metadataObj = typeof data.metadata === "string" ? JSON.parse(data.metadata) : data.metadata;
1236
+ isTrial = metadataObj.isTrial === true || metadataObj.isTrial === "true";
1237
+ trialEnd = metadataObj.trialEnd;
1238
+ targetPlan = metadataObj.plan;
1239
+ }
1240
+ if (metadataObj.type === "proration") {
1241
+ const subscriptionId = metadataObj.subscriptionId;
1242
+ const newPlan = metadataObj.newPlan;
1243
+ const newSeatCount = metadataObj.newSeatCount;
1244
+ if (subscriptionId !== void 0 && subscriptionId !== "" && newPlan !== void 0 && newPlan !== "") await ctx.context.adapter.update({
1245
+ model: "subscription",
1246
+ update: {
1247
+ plan: newPlan,
1248
+ ...typeof newSeatCount === "number" ? { seats: newSeatCount } : {},
1249
+ paystackTransactionReference: reference,
1250
+ ...authorizationCode !== void 0 && authorizationCode !== null ? { paystackAuthorizationCode: authorizationCode } : {},
1251
+ updatedAt: /* @__PURE__ */ new Date()
1252
+ },
1253
+ where: [{
1254
+ field: "id",
1255
+ value: subscriptionId
1256
+ }]
1257
+ });
1258
+ return ctx.json({
1259
+ status,
1260
+ reference,
1261
+ data
1262
+ });
1085
1263
  }
1086
1264
  let paystackSubscriptionCode;
1087
1265
  if (isTrial && targetPlan !== void 0 && trialEnd !== void 0) {
@@ -1161,8 +1339,15 @@ const listSubscriptions = (options, path = "/list-subscriptions") => {
1161
1339
  const session = await getSessionFromCtx(ctx);
1162
1340
  if (session === void 0 || session === null) throw new APIError("UNAUTHORIZED");
1163
1341
  const referenceIdPart = ctx.context.referenceId;
1164
- const queryRefId = ctx.query?.referenceId;
1165
- const referenceId = referenceIdPart ?? queryRefId ?? session.user.id;
1342
+ const queryRefId = ctx.query?.referenceId ?? (typeof ctx.request?.url === "string" ? new URL(ctx.request.url).searchParams.get("referenceId") ?? void 0 : void 0);
1343
+ const userId = session.user.id;
1344
+ if (queryRefId !== void 0 && queryRefId !== userId && referenceIdPart !== queryRefId) await assertReferenceAccess(ctx, options, {
1345
+ user: session.user,
1346
+ session: session.session,
1347
+ referenceId: queryRefId,
1348
+ action: "list-subscriptions"
1349
+ });
1350
+ const referenceId = queryRefId ?? referenceIdPart ?? userId;
1166
1351
  const res = await ctx.context.adapter.findMany({
1167
1352
  model: "subscription",
1168
1353
  where: [{
@@ -1186,8 +1371,15 @@ const listTransactions = (options, path = "/list-transactions") => {
1186
1371
  const session = await getSessionFromCtx(ctx);
1187
1372
  if (session === void 0 || session === null) throw new APIError("UNAUTHORIZED");
1188
1373
  const referenceIdPart = ctx.context.referenceId;
1189
- const queryRefId = ctx.query?.referenceId;
1190
- const referenceId = referenceIdPart ?? queryRefId ?? session.user.id;
1374
+ const queryRefId = ctx.query?.referenceId ?? (typeof ctx.request?.url === "string" ? new URL(ctx.request.url).searchParams.get("referenceId") ?? void 0 : void 0);
1375
+ const userId = session.user.id;
1376
+ if (queryRefId !== void 0 && queryRefId !== userId && referenceIdPart !== queryRefId) await assertReferenceAccess(ctx, options, {
1377
+ user: session.user,
1378
+ session: session.session,
1379
+ referenceId: queryRefId,
1380
+ action: "list-transactions"
1381
+ });
1382
+ const referenceId = queryRefId ?? referenceIdPart ?? userId;
1191
1383
  const sorted = (await ctx.context.adapter.findMany({
1192
1384
  model: "paystackTransaction",
1193
1385
  where: [{
@@ -1238,7 +1430,7 @@ const disablePaystackSubscription = (options, path = "/disable-subscription") =>
1238
1430
  const { subscriptionCode, atPeriodEnd } = ctx.body;
1239
1431
  const paystack = getPaystackOps(options.paystackClient);
1240
1432
  try {
1241
- if (subscriptionCode.startsWith("LOC_") || subscriptionCode.startsWith("sub_local_")) {
1433
+ if (isLocalSubscriptionCode(subscriptionCode)) {
1242
1434
  const sub = await ctx.context.adapter.findOne({
1243
1435
  model: "subscription",
1244
1436
  where: [{
@@ -1246,7 +1438,7 @@ const disablePaystackSubscription = (options, path = "/disable-subscription") =>
1246
1438
  value: subscriptionCode
1247
1439
  }]
1248
1440
  });
1249
- if (sub) {
1441
+ if (sub !== null && sub !== void 0) {
1250
1442
  await ctx.context.adapter.update({
1251
1443
  model: "subscription",
1252
1444
  update: {
@@ -1370,7 +1562,7 @@ const getSubscriptionManageLink = (options, path = "/subscription-manage-link")
1370
1562
  ] : [sessionMiddleware, originCheck];
1371
1563
  const handler = async (ctx) => {
1372
1564
  const { subscriptionCode } = ctx.query;
1373
- if (subscriptionCode.startsWith("LOC_") || subscriptionCode.startsWith("sub_local_")) return ctx.json({
1565
+ if (isLocalSubscriptionCode(subscriptionCode)) return ctx.json({
1374
1566
  link: null,
1375
1567
  message: "Local subscriptions cannot be managed on Paystack"
1376
1568
  });
@@ -1426,8 +1618,6 @@ const getConfig = (options, path = "/get-config") => {
1426
1618
  });
1427
1619
  });
1428
1620
  };
1429
- //#endregion
1430
- //#region src/schema.ts
1431
1621
  const transactions = { paystackTransaction: { fields: {
1432
1622
  reference: {
1433
1623
  type: "string",
@@ -1663,8 +1853,17 @@ const plans = { paystackPlan: { fields: {
1663
1853
  required: true
1664
1854
  }
1665
1855
  } } };
1856
+ ({
1857
+ ...subscriptions,
1858
+ ...transactions,
1859
+ ...user,
1860
+ ...organization,
1861
+ ...products,
1862
+ ...plans
1863
+ });
1666
1864
  const getSchema = (options) => {
1667
1865
  let baseSchema;
1866
+ const optionSchema = options.schema;
1668
1867
  if (options.subscription?.enabled === true) baseSchema = {
1669
1868
  ...subscriptions,
1670
1869
  ...transactions,
@@ -1683,10 +1882,10 @@ const getSchema = (options) => {
1683
1882
  ...organization
1684
1883
  };
1685
1884
  if (options.schema !== void 0 && options.subscription?.enabled !== true && "subscription" in options.schema) {
1686
- const { subscription: _subscription, ...restSchema } = options.schema;
1885
+ const { subscription: _subscription, ...restSchema } = optionSchema ?? {};
1687
1886
  return mergeSchema(baseSchema, restSchema);
1688
1887
  }
1689
- return mergeSchema(baseSchema, options.schema);
1888
+ return mergeSchema(baseSchema, optionSchema);
1690
1889
  };
1691
1890
  //#endregion
1692
1891
  //#region src/operations.ts
@@ -1813,6 +2012,7 @@ async function chargeSubscriptionRenewal(ctx, options, input) {
1813
2012
  const amount = bodyAmount ?? plan.amount;
1814
2013
  if (amount === void 0 || amount === null) throw new APIError("BAD_REQUEST", { message: "Plan amount is not defined" });
1815
2014
  let email;
2015
+ let billingUserId = subscription.userId;
1816
2016
  const referenceId = subscription.referenceId;
1817
2017
  if (referenceId !== void 0 && referenceId !== null && referenceId !== "") {
1818
2018
  const user = await ctx.context.adapter.findOne({
@@ -1822,8 +2022,10 @@ async function chargeSubscriptionRenewal(ctx, options, input) {
1822
2022
  value: referenceId
1823
2023
  }]
1824
2024
  });
1825
- if (user !== void 0 && user !== null) email = user.email;
1826
- else if (options.organization?.enabled === true) {
2025
+ if (user !== void 0 && user !== null) {
2026
+ email = user.email;
2027
+ billingUserId = user.id;
2028
+ } else if (options.organization?.enabled === true) {
1827
2029
  const ownerMember = await ctx.context.adapter.findOne({
1828
2030
  model: "member",
1829
2031
  where: [{
@@ -1834,13 +2036,17 @@ async function chargeSubscriptionRenewal(ctx, options, input) {
1834
2036
  value: "owner"
1835
2037
  }]
1836
2038
  });
1837
- if (ownerMember !== void 0 && ownerMember !== null) email = (await ctx.context.adapter.findOne({
1838
- model: "user",
1839
- where: [{
1840
- field: "id",
1841
- value: ownerMember.userId
1842
- }]
1843
- }))?.email;
2039
+ if (ownerMember !== void 0 && ownerMember !== null) {
2040
+ const ownerUser = await ctx.context.adapter.findOne({
2041
+ model: "user",
2042
+ where: [{
2043
+ field: "id",
2044
+ value: ownerMember.userId
2045
+ }]
2046
+ });
2047
+ email = ownerUser?.email;
2048
+ billingUserId = ownerUser?.id ?? ownerMember.userId;
2049
+ }
1844
2050
  }
1845
2051
  }
1846
2052
  if (email === void 0 || email === null || email === "") throw new APIError("NOT_FOUND", { message: "User email not found" });
@@ -1854,14 +2060,34 @@ async function chargeSubscriptionRenewal(ctx, options, input) {
1854
2060
  amount,
1855
2061
  authorization_code: subscription.paystackAuthorizationCode,
1856
2062
  reference: `rec_${subscription.id}_${Date.now()}`,
1857
- metadata: {
2063
+ metadata: JSON.stringify({
1858
2064
  subscriptionId,
1859
2065
  referenceId
1860
- }
2066
+ })
1861
2067
  } }));
1862
2068
  if (chargeData?.status === "success" && chargeData.reference !== void 0) {
1863
2069
  const now = /* @__PURE__ */ new Date();
1864
2070
  const nextPeriodEnd = getNextPeriodEnd(now, plan.interval ?? "monthly");
2071
+ await ctx.context.adapter.create({
2072
+ model: "paystackTransaction",
2073
+ data: {
2074
+ reference: chargeData.reference,
2075
+ paystackId: chargeData.id !== void 0 && chargeData.id !== null ? String(chargeData.id) : void 0,
2076
+ referenceId,
2077
+ userId: billingUserId,
2078
+ amount: chargeData.amount,
2079
+ currency: chargeData.currency,
2080
+ status: "success",
2081
+ plan: plan.name.toLowerCase(),
2082
+ metadata: JSON.stringify({
2083
+ type: "renewal",
2084
+ subscriptionId,
2085
+ referenceId
2086
+ }),
2087
+ createdAt: now,
2088
+ updatedAt: now
2089
+ }
2090
+ });
1865
2091
  await ctx.context.adapter.update({
1866
2092
  model: "subscription",
1867
2093
  update: {
@@ -1888,10 +2114,17 @@ async function chargeSubscriptionRenewal(ctx, options, input) {
1888
2114
  //#endregion
1889
2115
  //#region src/index.ts
1890
2116
  const INTERNAL_ERROR_CODES = defineErrorCodes(Object.fromEntries(Object.entries(PAYSTACK_ERROR_CODES).map(([key, value]) => [key, typeof value === "string" ? value : value.message])));
1891
- const paystack = (options) => {
1892
- const routeOptions = options;
2117
+ const createPaystackPlugin = (options) => {
2118
+ const routeOptions = {
2119
+ ...options,
2120
+ webhook: {
2121
+ ...options.webhook,
2122
+ secret: options.webhook?.secret ?? options.paystackWebhookSecret
2123
+ }
2124
+ };
1893
2125
  return {
1894
2126
  id: "paystack",
2127
+ version: PACKAGE_VERSION,
1895
2128
  endpoints: {
1896
2129
  initializeTransaction: initializeTransaction(routeOptions, "/paystack/initialize-transaction"),
1897
2130
  verifyTransaction: verifyTransaction(routeOptions, "/paystack/verify-transaction"),
@@ -1911,90 +2144,97 @@ const paystack = (options) => {
1911
2144
  listPlans: listPlans(routeOptions, "/paystack/list-plans")
1912
2145
  },
1913
2146
  schema: getSchema(options),
1914
- init: (ctx) => {
1915
- return { options: {
1916
- databaseHooks: {
1917
- user: { create: { async after(user, hookCtx) {
1918
- if (!hookCtx || options.createCustomerOnSignUp !== true || user.email === null || user.email === void 0 || user.email === "") return;
1919
- try {
1920
- const paystackOps = getPaystackOps(options.paystackClient);
1921
- if (!paystackOps) return;
1922
- const sdkRes = unwrapSdkResult(await paystackOps.customer?.create({ body: {
1923
- email: user.email,
1924
- first_name: user.name ?? void 0,
1925
- metadata: { userId: user.id }
1926
- } }) ?? await Promise.reject(/* @__PURE__ */ new Error("Paystack client missing customer ops")));
1927
- const customerCode = sdkRes?.customer_code;
1928
- if (customerCode !== void 0 && customerCode !== null && customerCode !== "") {
1929
- await ctx.adapter.update({
1930
- model: "user",
1931
- where: [{
1932
- field: "id",
1933
- value: user.id
1934
- }],
1935
- update: { paystackCustomerCode: customerCode }
1936
- });
1937
- if (typeof options.onCustomerCreate === "function") await options.onCustomerCreate({
1938
- paystackCustomer: sdkRes,
1939
- user: {
1940
- ...user,
1941
- paystackCustomerCode: customerCode
1942
- }
1943
- }, hookCtx);
1944
- }
1945
- } catch (error) {
1946
- ctx.logger.error("Failed to create Paystack customer for user", error);
2147
+ init: ((ctx) => {
2148
+ const organizationPluginAvailable = ctx.hasPlugin("organization");
2149
+ if (options.organization?.enabled === true && !organizationPluginAvailable) ctx.logger.error("Paystack organization billing is enabled, but the Better Auth organization plugin was not found. Organization billing hooks will be skipped.");
2150
+ return { options: { databaseHooks: {
2151
+ user: { create: { async after(user, hookCtx) {
2152
+ if (!hookCtx || options.createCustomerOnSignUp !== true || user.email === null || user.email === void 0 || user.email === "") return;
2153
+ try {
2154
+ const paystackOps = getPaystackOps(options.paystackClient);
2155
+ if (!paystackOps) return;
2156
+ const sdkRes = unwrapSdkResult(await paystackOps.customer?.create({ body: {
2157
+ email: user.email,
2158
+ first_name: user.name ?? void 0,
2159
+ metadata: JSON.stringify({ userId: user.id })
2160
+ } }) ?? await Promise.reject(/* @__PURE__ */ new Error("Paystack client missing customer ops")));
2161
+ const customerCode = sdkRes?.customer_code;
2162
+ if (customerCode !== void 0 && customerCode !== null && customerCode !== "") {
2163
+ await ctx.adapter.update({
2164
+ model: "user",
2165
+ where: [{
2166
+ field: "id",
2167
+ value: user.id
2168
+ }],
2169
+ update: { paystackCustomerCode: customerCode }
2170
+ });
2171
+ if (typeof options.onCustomerCreate === "function") await options.onCustomerCreate({
2172
+ paystackCustomer: sdkRes,
2173
+ user: {
2174
+ ...user,
2175
+ paystackCustomerCode: customerCode
2176
+ }
2177
+ }, hookCtx);
1947
2178
  }
1948
- } } },
1949
- organization: options.organization?.enabled === true ? { create: { async after(org, hookCtx) {
1950
- try {
1951
- const extraCreateParams = typeof options.organization?.getCustomerCreateParams === "function" ? await options.organization.getCustomerCreateParams(org, hookCtx) : {};
1952
- let targetEmail = org.email;
1953
- if (targetEmail === void 0 || targetEmail === null) {
1954
- const ownerMember = await ctx.adapter.findOne({
1955
- model: "member",
1956
- where: [{
1957
- field: "organizationId",
1958
- value: org.id
1959
- }, {
1960
- field: "role",
1961
- value: "owner"
1962
- }]
1963
- });
1964
- if (ownerMember !== null && ownerMember !== void 0) targetEmail = (await ctx.adapter.findOne({
1965
- model: "user",
1966
- where: [{
1967
- field: "id",
1968
- value: ownerMember.userId
1969
- }]
1970
- }))?.email;
1971
- }
1972
- if (targetEmail === void 0 || targetEmail === null) return;
1973
- const params = defu({
1974
- email: targetEmail,
1975
- first_name: org.name,
1976
- metadata: { organizationId: org.id }
1977
- }, extraCreateParams);
1978
- const paystackOps = getPaystackOps(options.paystackClient);
1979
- if (!paystackOps) return;
1980
- const sdkRes = unwrapSdkResult(await paystackOps.customer?.create({ body: params }) ?? await Promise.reject(/* @__PURE__ */ new Error("Paystack client missing customer ops")));
1981
- const customerCode = sdkRes?.customer_code;
1982
- if (customerCode !== void 0 && customerCode !== null && customerCode !== "" && sdkRes !== void 0 && sdkRes !== null) {
1983
- await ctx.internalAdapter.updateOrganization(org.id, { paystackCustomerCode: customerCode });
1984
- if (typeof options.organization?.onCustomerCreate === "function") await options.organization.onCustomerCreate({
1985
- paystackCustomer: sdkRes,
1986
- organization: {
1987
- ...org,
1988
- paystackCustomerCode: customerCode
1989
- }
1990
- }, hookCtx);
1991
- }
1992
- } catch (error) {
1993
- ctx.logger.error("Failed to create Paystack customer for organization", error);
2179
+ } catch (error) {
2180
+ ctx.logger.error("Failed to create Paystack customer for user", error);
2181
+ }
2182
+ } } },
2183
+ organization: options.organization?.enabled === true && organizationPluginAvailable ? { create: { async after(org, hookCtx) {
2184
+ try {
2185
+ const extraCreateParams = typeof options.organization?.getCustomerCreateParams === "function" ? await options.organization.getCustomerCreateParams(org, hookCtx) : {};
2186
+ let targetEmail = org.email;
2187
+ if (targetEmail === void 0 || targetEmail === null) {
2188
+ const ownerMember = await ctx.adapter.findOne({
2189
+ model: "member",
2190
+ where: [{
2191
+ field: "organizationId",
2192
+ value: org.id
2193
+ }, {
2194
+ field: "role",
2195
+ value: "owner"
2196
+ }]
2197
+ });
2198
+ if (ownerMember !== null && ownerMember !== void 0) targetEmail = (await ctx.adapter.findOne({
2199
+ model: "user",
2200
+ where: [{
2201
+ field: "id",
2202
+ value: ownerMember.userId
2203
+ }]
2204
+ }))?.email;
1994
2205
  }
1995
- } } } : void 0
1996
- },
1997
- member: {
2206
+ if (targetEmail === void 0 || targetEmail === null) return;
2207
+ const params = defu({
2208
+ email: targetEmail,
2209
+ first_name: org.name,
2210
+ metadata: JSON.stringify({ organizationId: org.id })
2211
+ }, extraCreateParams);
2212
+ const paystackOps = getPaystackOps(options.paystackClient);
2213
+ if (!paystackOps) return;
2214
+ const sdkRes = unwrapSdkResult(await paystackOps.customer?.create({ body: params }) ?? await Promise.reject(/* @__PURE__ */ new Error("Paystack client missing customer ops")));
2215
+ const customerCode = sdkRes?.customer_code;
2216
+ if (customerCode !== void 0 && customerCode !== null && customerCode !== "" && sdkRes !== void 0 && sdkRes !== null) {
2217
+ await ctx.adapter.update({
2218
+ model: "organization",
2219
+ where: [{
2220
+ field: "id",
2221
+ value: org.id
2222
+ }],
2223
+ update: { paystackCustomerCode: customerCode }
2224
+ });
2225
+ if (typeof options.organization?.onCustomerCreate === "function") await options.organization.onCustomerCreate({
2226
+ paystackCustomer: sdkRes,
2227
+ organization: {
2228
+ ...org,
2229
+ paystackCustomerCode: customerCode
2230
+ }
2231
+ }, hookCtx);
2232
+ }
2233
+ } catch (error) {
2234
+ ctx.logger.error("Failed to create Paystack customer for organization", error);
2235
+ }
2236
+ } } } : void 0,
2237
+ member: organizationPluginAvailable ? {
1998
2238
  create: {
1999
2239
  before: async (member, ctx) => {
2000
2240
  if (options.subscription?.enabled === true && member.organizationId && ctx !== null && ctx !== void 0) await checkSeatLimit(ctx, member.organizationId);
@@ -2006,8 +2246,8 @@ const paystack = (options) => {
2006
2246
  delete: { after: async (member, ctx) => {
2007
2247
  if (options.subscription?.enabled === true && typeof member?.organizationId === "string" && ctx) await syncSubscriptionSeats(ctx, member.organizationId, routeOptions);
2008
2248
  } }
2009
- },
2010
- invitation: {
2249
+ } : void 0,
2250
+ invitation: organizationPluginAvailable ? {
2011
2251
  create: {
2012
2252
  before: async (invitation, ctx) => {
2013
2253
  if (options.subscription?.enabled === true && invitation.organizationId && ctx !== null && ctx !== void 0) await checkSeatLimit(ctx, invitation.organizationId);
@@ -2019,8 +2259,8 @@ const paystack = (options) => {
2019
2259
  delete: { after: async (invitation, ctx) => {
2020
2260
  if (options.subscription?.enabled === true && typeof invitation?.organizationId === "string" && ctx) await syncSubscriptionSeats(ctx, invitation.organizationId, routeOptions);
2021
2261
  } }
2022
- },
2023
- team: { create: { before: async (team, ctx) => {
2262
+ } : void 0,
2263
+ team: organizationPluginAvailable ? { create: { before: async (team, ctx) => {
2024
2264
  if (options.subscription?.enabled === true && team.organizationId && ctx) {
2025
2265
  const subscription = await getOrganizationSubscription(ctx, team.organizationId);
2026
2266
  if (subscription !== null && subscription !== void 0) {
@@ -2028,13 +2268,14 @@ const paystack = (options) => {
2028
2268
  if (typeof maxTeams === "number") await checkTeamLimit(ctx, team.organizationId, maxTeams);
2029
2269
  }
2030
2270
  }
2031
- } } }
2032
- } };
2033
- },
2271
+ } } } : void 0
2272
+ } } };
2273
+ }),
2034
2274
  $ERROR_CODES: INTERNAL_ERROR_CODES,
2035
2275
  options
2036
2276
  };
2037
2277
  };
2278
+ const paystack = createPaystackPlugin;
2038
2279
  //#endregion
2039
2280
  export { chargeSubscriptionRenewal, paystack, syncPaystackPlans, syncPaystackProducts };
2040
2281