@alexasomba/better-auth-paystack 1.2.0 → 2.1.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
@@ -3,121 +3,46 @@ 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
- import * as z from "zod/v4";
6
+ import { z } from "zod";
7
+ import { PaystackResponse } from "@alexasomba/paystack-node";
7
8
  import { mergeSchema } from "better-auth/db";
8
-
9
9
  //#region src/paystack-sdk.ts
10
- function isOpenApiFetchResponse(value) {
11
- return value !== null && value !== void 0 && typeof value === "object" && ("data" in value || "error" in value || "response" in value);
10
+ /**
11
+ * Interface for checking if a result is a PaystackResponse from the SDK v1.9.1+
12
+ */
13
+ function IsPaystackResponse(value) {
14
+ return value instanceof PaystackResponse;
12
15
  }
16
+ /**
17
+ * Unwraps a Paystack SDK result, extracting the data or throwing an APIError if the request failed.
18
+ * Leverages the native .unwrap() method in SDK v1.9.1+ if available.
19
+ */
13
20
  function unwrapSdkResult(result) {
14
- if (isOpenApiFetchResponse(result)) {
15
- if (result.error !== void 0 && result.error !== null) throw new Error(typeof result.error === "string" ? result.error : JSON.stringify(result.error));
16
- return result.data ?? result;
21
+ if (IsPaystackResponse(result)) try {
22
+ return result.unwrap();
23
+ } catch (e) {
24
+ throw new APIError("BAD_REQUEST", { message: e?.message ?? "Paystack API error" });
17
25
  }
18
- if (result !== null && result !== void 0 && typeof result === "object" && "data" in result) {
19
- const data = result.data;
20
- if (data !== null && typeof data === "object" && "data" in data) return data.data;
21
- return data;
26
+ let current = result;
27
+ while (current !== null && current !== void 0 && typeof current === "object") {
28
+ const body = current;
29
+ if (body.status === false) throw new APIError("BAD_REQUEST", { message: body.message ?? "Paystack API error" });
30
+ if ("authorization_url" in body || "reference" in body || "customer_code" in body) break;
31
+ if ("data" in body && body.data !== void 0 && body.data !== null && typeof body.data === "object") {
32
+ current = body.data;
33
+ continue;
34
+ }
35
+ break;
22
36
  }
23
- return result;
37
+ return current;
24
38
  }
25
- function getPaystackOps(paystackClient) {
26
- return {
27
- customerCreate: (params) => {
28
- if (paystackClient?.customer_create !== void 0) return paystackClient.customer_create({ body: params });
29
- return paystackClient?.customer?.create?.(params);
30
- },
31
- customerUpdate: (code, params) => {
32
- if (paystackClient?.customer_update !== void 0) return paystackClient.customer_update({
33
- params: { path: { code } },
34
- body: params
35
- });
36
- return paystackClient?.customer?.update?.(code, params);
37
- },
38
- transactionInitialize: (body) => {
39
- if (paystackClient?.transaction_initialize !== void 0) return paystackClient.transaction_initialize({ body });
40
- return paystackClient?.transaction?.initialize?.(body);
41
- },
42
- transactionVerify: (reference) => {
43
- if (paystackClient?.transaction_verify !== void 0) return paystackClient.transaction_verify({ params: { path: { reference } } });
44
- return paystackClient?.transaction?.verify?.(reference);
45
- },
46
- subscriptionCreate: (body) => {
47
- if (paystackClient?.subscription_create !== void 0) return paystackClient.subscription_create({ body });
48
- return paystackClient?.subscription?.create?.(body);
49
- },
50
- subscriptionDisable: (body) => {
51
- if (paystackClient?.subscription_disable !== void 0) return paystackClient.subscription_disable({ body });
52
- return paystackClient?.subscription?.disable?.(body);
53
- },
54
- subscriptionEnable: (body) => {
55
- if (paystackClient?.subscription_enable !== void 0) return paystackClient.subscription_enable({ body });
56
- return paystackClient?.subscription?.enable?.(body);
57
- },
58
- subscriptionFetch: async (idOrCode) => {
59
- if (paystackClient?.subscription_fetch !== void 0) try {
60
- return await paystackClient.subscription_fetch({ params: { path: { code: idOrCode } } });
61
- } catch {
62
- const compatFetch = paystackClient.subscription_fetch;
63
- return compatFetch({ params: { path: { id_or_code: idOrCode } } });
64
- }
65
- return paystackClient?.subscription?.fetch?.(idOrCode);
66
- },
67
- subscriptionManageLink: (code) => {
68
- if (paystackClient?.subscription_manageLink !== void 0) return paystackClient.subscription_manageLink({ params: { path: { code } } });
69
- if (paystackClient?.subscription_manage_link !== void 0) return paystackClient.subscription_manage_link({ params: { path: { code } } });
70
- return paystackClient?.subscription?.manage?.link?.(code);
71
- },
72
- subscriptionManageEmail: (code, email) => {
73
- if (paystackClient?.subscription_manageEmail !== void 0) return paystackClient.subscription_manageEmail({ params: { path: { code } } });
74
- return paystackClient?.subscription?.manage?.email?.(code, email);
75
- },
76
- subscriptionUpdate: (params) => {
77
- if (paystackClient?.subscription_update !== void 0) return paystackClient.subscription_update({
78
- params: { path: { code: params.code } },
79
- body: {
80
- plan: params.plan,
81
- authorization: params.authorization,
82
- amount: params.amount
83
- }
84
- });
85
- return paystackClient?.subscription?.update?.(params.code, params);
86
- },
87
- transactionChargeAuthorization: (body) => {
88
- if (paystackClient?.transaction_chargeAuthorization !== void 0) return paystackClient.transaction_chargeAuthorization({ body });
89
- return paystackClient?.transaction?.chargeAuthorization?.(body);
90
- },
91
- productList: () => {
92
- if (paystackClient?.product_list !== void 0) return paystackClient.product_list();
93
- return paystackClient?.product?.list?.();
94
- },
95
- productFetch: (idOrCode) => {
96
- if (paystackClient?.product_fetch !== void 0) return paystackClient.product_fetch({ params: { path: { id_or_code: idOrCode } } });
97
- return paystackClient?.product?.fetch?.(idOrCode);
98
- },
99
- productCreate: (params) => {
100
- if (paystackClient?.product_create !== void 0) return paystackClient.product_create({ body: params });
101
- return paystackClient?.product?.create?.(params);
102
- },
103
- productUpdate: (idOrCode, params) => {
104
- if (paystackClient?.product_update !== void 0) return paystackClient.product_update({
105
- params: { path: { id_or_code: idOrCode } },
106
- body: params
107
- });
108
- return paystackClient?.product?.update?.(idOrCode, params);
109
- },
110
- productDelete: (idOrCode) => {
111
- if (paystackClient?.product_delete !== void 0) return paystackClient.product_delete({ params: { path: { id_or_code: idOrCode } } });
112
- return paystackClient?.product?.delete?.(idOrCode);
113
- },
114
- planList: () => {
115
- if (paystackClient?.plan_list !== void 0) return paystackClient.plan_list();
116
- return paystackClient?.plan?.list?.();
117
- }
118
- };
39
+ /**
40
+ * Returns the operations object from a Paystack client.
41
+ * For v1.9.1+, the client itself uses the grouped structure.
42
+ */
43
+ function getPaystackOps(client) {
44
+ return client;
119
45
  }
120
-
121
46
  //#endregion
122
47
  //#region src/utils.ts
123
48
  async function getPlans(subscriptionOptions) {
@@ -125,7 +50,12 @@ async function getPlans(subscriptionOptions) {
125
50
  throw new Error("Subscriptions are not enabled in the Paystack options.");
126
51
  }
127
52
  async function getPlanByName(options, name) {
128
- if (options.subscription?.enabled === true) return (await getPlans(options.subscription)).find((plan) => plan.name.toLowerCase() === name.toLowerCase()) ?? null;
53
+ if (typeof name !== "string" || name.trim() === "") return null;
54
+ if (options.subscription?.enabled === true) {
55
+ const plans = await getPlans(options.subscription);
56
+ const normalizedName = name.toLowerCase();
57
+ return plans.find((plan) => typeof plan.name === "string" && plan.name.toLowerCase() === normalizedName) ?? null;
58
+ }
129
59
  return null;
130
60
  }
131
61
  async function getProducts(productOptions) {
@@ -133,7 +63,7 @@ async function getProducts(productOptions) {
133
63
  return [];
134
64
  }
135
65
  async function getProductByName(options, name) {
136
- return await getProducts(options.products).then((products) => products?.find((product) => product.name.toLowerCase() === name.toLowerCase()) ?? null);
66
+ return await getProducts(options.products).then((products) => products !== void 0 && products !== null ? products.find((product) => product.name.toLowerCase() === name.toLowerCase()) ?? null : null);
137
67
  }
138
68
  function getNextPeriodEnd(startDate, interval) {
139
69
  const date = new Date(startDate);
@@ -190,8 +120,8 @@ async function syncProductQuantityFromPaystack(ctx, productName, paystackClient)
190
120
  value: productName.toLowerCase().replace(/\s+/g, "-")
191
121
  }]
192
122
  });
193
- if (localProduct?.paystackId === void 0 || localProduct?.paystackId === null || localProduct?.paystackId === "") {
194
- if (localProduct && localProduct.unlimited !== true && localProduct.quantity !== void 0 && localProduct.quantity > 0) await ctx.context.adapter.update({
123
+ if (localProduct?.paystackId === void 0 || localProduct.paystackId === null || localProduct.paystackId === "") {
124
+ if (localProduct?.id !== void 0 && localProduct.unlimited !== true && typeof localProduct.quantity === "number" && localProduct.quantity > 0) await ctx.context.adapter.update({
195
125
  model: "paystackProduct",
196
126
  update: {
197
127
  quantity: localProduct.quantity - 1,
@@ -205,8 +135,8 @@ async function syncProductQuantityFromPaystack(ctx, productName, paystackClient)
205
135
  return;
206
136
  }
207
137
  try {
208
- const remoteQuantity = unwrapSdkResult(await getPaystackOps(paystackClient).productFetch(localProduct.paystackId))?.quantity;
209
- if (remoteQuantity !== void 0) await ctx.context.adapter.update({
138
+ const remoteQuantity = unwrapSdkResult(await paystackClient.product?.fetch(localProduct.paystackId))?.quantity;
139
+ if (remoteQuantity !== void 0 && localProduct.id !== void 0) await ctx.context.adapter.update({
210
140
  model: "paystackProduct",
211
141
  update: {
212
142
  quantity: remoteQuantity,
@@ -218,7 +148,7 @@ async function syncProductQuantityFromPaystack(ctx, productName, paystackClient)
218
148
  }]
219
149
  });
220
150
  } catch {
221
- if (localProduct.unlimited !== true && localProduct.quantity !== void 0 && localProduct.quantity > 0) await ctx.context.adapter.update({
151
+ if (localProduct?.id !== void 0 && localProduct.unlimited !== true && typeof localProduct.quantity === "number" && localProduct.quantity > 0) await ctx.context.adapter.update({
222
152
  model: "paystackProduct",
223
153
  update: {
224
154
  quantity: localProduct.quantity - 1,
@@ -233,7 +163,7 @@ async function syncProductQuantityFromPaystack(ctx, productName, paystackClient)
233
163
  }
234
164
  async function syncSubscriptionSeats(ctx, organizationId, options) {
235
165
  if (options.subscription?.enabled !== true) return;
236
- const adapter = ctx.context?.adapter ?? ctx.adapter;
166
+ const adapter = ctx.context.adapter;
237
167
  const subscription = await adapter.findOne({
238
168
  model: "subscription",
239
169
  where: [{
@@ -241,10 +171,11 @@ async function syncSubscriptionSeats(ctx, organizationId, options) {
241
171
  value: organizationId
242
172
  }]
243
173
  });
244
- if (subscription === null || subscription.paystackSubscriptionCode === void 0 || subscription.paystackSubscriptionCode === null) return;
174
+ if (subscription?.paystackSubscriptionCode === void 0 || subscription.paystackSubscriptionCode === null || subscription.paystackSubscriptionCode === "") return;
175
+ if (subscription === null || subscription === void 0) return;
245
176
  const plan = await getPlanByName(options, subscription.plan);
246
- if (plan === null) return;
247
- if (plan.seatAmount === void 0 && plan.seatPlanCode === void 0) return;
177
+ if (plan === null || plan === void 0) return;
178
+ if (plan.seatAmount === void 0) return;
248
179
  const quantity = (await adapter.findMany({
249
180
  model: "member",
250
181
  where: [{
@@ -253,13 +184,11 @@ async function syncSubscriptionSeats(ctx, organizationId, options) {
253
184
  }]
254
185
  })).length;
255
186
  let totalAmount = plan.amount ?? 0;
256
- if (plan.seatAmount !== void 0 && plan.seatAmount !== null) totalAmount += quantity * plan.seatAmount;
257
- const ops = getPaystackOps(options.paystackClient);
187
+ if (plan.seatAmount !== void 0 && plan.seatAmount !== null && typeof plan.seatAmount === "number") totalAmount += quantity * plan.seatAmount;
258
188
  try {
259
- await ops.subscriptionUpdate({
260
- code: subscription.paystackSubscriptionCode,
261
- amount: totalAmount
262
- });
189
+ const client = options.paystackClient;
190
+ if (client === void 0 || client === null) return;
191
+ unwrapSdkResult(await client.subscription?.update(subscription.paystackSubscriptionCode, { body: { amount: totalAmount } }));
263
192
  await adapter.update({
264
193
  model: "subscription",
265
194
  where: [{
@@ -272,11 +201,10 @@ async function syncSubscriptionSeats(ctx, organizationId, options) {
272
201
  }
273
202
  });
274
203
  } catch (e) {
275
- const log = ctx.context?.logger ?? ctx.logger;
204
+ const log = ctx.context.logger;
276
205
  if (log !== void 0 && log !== null) log.error("Failed to sync subscription seats with Paystack", e);
277
206
  }
278
207
  }
279
-
280
208
  //#endregion
281
209
  //#region src/middleware.ts
282
210
  const referenceMiddleware = (options, action) => createAuthMiddleware(async (ctx) => {
@@ -287,7 +215,7 @@ const referenceMiddleware = (options, action) => createAuthMiddleware(async (ctx
287
215
  const referenceId = body.referenceId ?? query.referenceId ?? session.user.id;
288
216
  const subscriptionOptions = options.subscription;
289
217
  if (referenceId === session.user.id) return { referenceId };
290
- if (subscriptionOptions?.enabled === true && "authorizeReference" in subscriptionOptions && subscriptionOptions.authorizeReference) {
218
+ if (subscriptionOptions?.enabled === true && "authorizeReference" in subscriptionOptions && typeof subscriptionOptions.authorizeReference === "function") {
291
219
  if (await subscriptionOptions.authorizeReference({
292
220
  user: session.user,
293
221
  session: session.session,
@@ -315,7 +243,6 @@ const referenceMiddleware = (options, action) => createAuthMiddleware(async (ctx
315
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.`);
316
244
  throw new APIError("BAD_REQUEST", { message: "Passing referenceId isn't allowed without subscription.authorizeReference or valid organization membership." });
317
245
  });
318
-
319
246
  //#endregion
320
247
  //#region src/limits.ts
321
248
  const getOrganizationSubscription = async (ctx, organizationId) => {
@@ -329,7 +256,7 @@ const getOrganizationSubscription = async (ctx, organizationId) => {
329
256
  };
330
257
  const checkSeatLimit = async (ctx, organizationId, seatsToAdd = 1) => {
331
258
  const subscription = await getOrganizationSubscription(ctx, organizationId);
332
- if (subscription?.seats === void 0 || subscription.seats === null) return true;
259
+ if (subscription?.seats === null) return true;
333
260
  const members = await ctx.context.adapter.findMany({
334
261
  model: "member",
335
262
  where: [{
@@ -337,6 +264,7 @@ const checkSeatLimit = async (ctx, organizationId, seatsToAdd = 1) => {
337
264
  value: organizationId
338
265
  }]
339
266
  });
267
+ if (!subscription) return true;
340
268
  if (members.length + seatsToAdd > subscription.seats) throw new APIError("FORBIDDEN", { message: `Organization member limit reached. Used: ${members.length}, Max: ${subscription.seats}` });
341
269
  return true;
342
270
  };
@@ -350,7 +278,6 @@ const checkTeamLimit = async (ctx, organizationId, maxTeams) => {
350
278
  })).length >= maxTeams) throw new APIError("FORBIDDEN", { message: `Organization team limit reached. Max teams: ${maxTeams}` });
351
279
  return true;
352
280
  };
353
-
354
281
  //#endregion
355
282
  //#region src/routes.ts
356
283
  const PAYSTACK_ERROR_CODES = defineErrorCodes({
@@ -380,8 +307,8 @@ async function hmacSha512Hex(secret, message) {
380
307
  const { createHmac } = await import("node:crypto");
381
308
  return createHmac("sha512", secret).update(message).digest("hex");
382
309
  }
383
- const paystackWebhook = (options) => {
384
- return createAuthEndpoint("/paystack/webhook", {
310
+ const paystackWebhook = (options, path = "/webhook") => {
311
+ return createAuthEndpoint(path, {
385
312
  method: "POST",
386
313
  metadata: {
387
314
  ...HIDE_METADATA,
@@ -391,14 +318,27 @@ const paystackWebhook = (options) => {
391
318
  disableBody: true
392
319
  }, async (ctx) => {
393
320
  const request = ctx.requestClone ?? ctx.request;
394
- if (!request) throw new APIError("BAD_REQUEST", { message: "Request object is missing from context" });
321
+ if (request === void 0 || request === null) throw new APIError("BAD_REQUEST", { message: "Request object is missing from context" });
395
322
  const payload = await request.text();
396
- const signature = (ctx.headers ?? ctx.request?.headers)?.get("x-paystack-signature");
323
+ const headers = ctx.headers ?? ctx.request?.headers;
324
+ const signature = headers?.get("x-paystack-signature");
325
+ if (options.webhook?.verifyIP === true) {
326
+ const trustedIPs = options.webhook.trustedIPs ?? [
327
+ "52.31.139.75",
328
+ "52.49.173.169",
329
+ "52.214.14.220"
330
+ ];
331
+ const clientIP = headers?.get("x-forwarded-for")?.split(",")[0]?.trim() ?? headers?.get("x-real-ip") ?? ctx.request.ip;
332
+ if (clientIP !== void 0 && clientIP !== null && trustedIPs.includes(clientIP) === false) throw new APIError("UNAUTHORIZED", {
333
+ message: `Forbidden IP: ${clientIP}`,
334
+ status: 401
335
+ });
336
+ }
397
337
  if (signature === void 0 || signature === null || signature === "") throw new APIError("UNAUTHORIZED", {
398
338
  message: "Missing x-paystack-signature header",
399
339
  status: 401
400
340
  });
401
- if (await hmacSha512Hex(options.paystackWebhookSecret, payload) !== signature) throw new APIError("UNAUTHORIZED", {
341
+ if (await hmacSha512Hex(options.webhook?.secret ?? options.secretKey, payload) !== signature) throw new APIError("UNAUTHORIZED", {
402
342
  message: "Invalid Paystack webhook signature",
403
343
  status: 401
404
344
  });
@@ -407,7 +347,8 @@ const paystackWebhook = (options) => {
407
347
  const data = event.data;
408
348
  if (eventName === "charge.success") {
409
349
  const reference = data?.reference;
410
- const paystackId = data?.id !== void 0 && data?.id !== null ? String(data.id) : void 0;
350
+ const paystackIdRaw = data?.id;
351
+ const paystackId = paystackIdRaw !== void 0 && paystackIdRaw !== null ? String(paystackIdRaw) : void 0;
411
352
  if (reference !== void 0 && reference !== null && reference !== "") {
412
353
  try {
413
354
  await ctx.context.adapter.update({
@@ -433,7 +374,9 @@ const paystackWebhook = (options) => {
433
374
  value: reference
434
375
  }]
435
376
  });
436
- if (transaction?.product) await syncProductQuantityFromPaystack(ctx, transaction.product, options.paystackClient);
377
+ if (transaction !== void 0 && transaction !== null && transaction.product !== void 0 && transaction.product !== null && transaction.product !== "") {
378
+ if (options.paystackClient !== void 0 && options.paystackClient !== null) await syncProductQuantityFromPaystack(ctx, transaction.product, options.paystackClient);
379
+ }
437
380
  } catch (e) {
438
381
  ctx.context.logger.warn("Failed to sync product quantity", e);
439
382
  }
@@ -459,19 +402,20 @@ const paystackWebhook = (options) => {
459
402
  }
460
403
  if (options.subscription?.enabled === true) try {
461
404
  if (eventName === "subscription.create") {
462
- const payloadData = data;
463
- const subscriptionCode = payloadData?.subscription_code ?? payloadData?.subscription?.subscription_code ?? payloadData?.code;
464
- const customerCode = payloadData?.customer?.customer_code ?? payloadData?.customer_code ?? payloadData?.customer?.code;
465
- const planCode = payloadData?.plan?.plan_code ?? payloadData?.plan_code ?? payloadData?.plan;
466
- let metadata = payloadData?.metadata;
405
+ const subscriptionData = data;
406
+ const subscriptionCode = subscriptionData.subscription_code ?? "";
407
+ const customerCode = subscriptionData.customer?.customer_code;
408
+ const planCode = subscriptionData.plan?.plan_code;
409
+ let metadata = subscriptionData.metadata;
467
410
  if (typeof metadata === "string") try {
468
411
  metadata = JSON.parse(metadata);
469
412
  } catch {}
470
- const referenceIdFromMetadata = typeof metadata === "object" && metadata !== null ? metadata.referenceId : void 0;
471
- let planNameFromMetadata = typeof metadata === "object" && metadata !== null ? metadata.plan : void 0;
413
+ const metadataObj = metadata !== void 0 && metadata !== null && typeof metadata === "object" ? metadata : {};
414
+ const referenceIdFromMetadata = typeof metadataObj.referenceId === "string" ? metadataObj.referenceId : void 0;
415
+ let planNameFromMetadata = typeof metadataObj.plan === "string" ? metadataObj.plan : void 0;
472
416
  if (typeof planNameFromMetadata === "string") planNameFromMetadata = planNameFromMetadata.toLowerCase();
473
417
  const plans = await getPlans(options.subscription);
474
- const planFromCode = planCode !== void 0 && planCode !== null && planCode !== "" ? plans.find((p) => p.planCode !== void 0 && p.planCode !== null && p.planCode === planCode) : void 0;
418
+ const planFromCode = planCode !== void 0 && planCode !== null && planCode !== "" ? plans.find((p) => p.planCode === planCode) : void 0;
475
419
  const planPart = planFromCode?.name ?? planNameFromMetadata;
476
420
  const planName = planPart !== void 0 && planPart !== null && planPart !== "" ? planPart.toLowerCase() : void 0;
477
421
  if (subscriptionCode !== void 0 && subscriptionCode !== null && subscriptionCode !== "") {
@@ -489,11 +433,10 @@ const paystackWebhook = (options) => {
489
433
  value: planName
490
434
  });
491
435
  if (where.length > 0) {
492
- const matches = await ctx.context.adapter.findMany({
436
+ const subscription = (await ctx.context.adapter.findMany({
493
437
  model: "subscription",
494
438
  where
495
- });
496
- const subscription = matches !== void 0 && matches !== null ? matches[0] : void 0;
439
+ }))?.[0];
497
440
  if (subscription !== void 0 && subscription !== null) {
498
441
  await ctx.context.adapter.update({
499
442
  model: "subscription",
@@ -501,7 +444,7 @@ const paystackWebhook = (options) => {
501
444
  paystackSubscriptionCode: subscriptionCode,
502
445
  status: "active",
503
446
  updatedAt: /* @__PURE__ */ new Date(),
504
- periodEnd: payloadData?.next_payment_date !== void 0 && payloadData?.next_payment_date !== null && payloadData?.next_payment_date !== "" ? new Date(payloadData.next_payment_date) : void 0
447
+ periodEnd: subscriptionData.next_payment_date !== void 0 && subscriptionData.next_payment_date !== null ? new Date(subscriptionData.next_payment_date) : void 0
505
448
  },
506
449
  where: [{
507
450
  field: "id",
@@ -534,9 +477,9 @@ const paystackWebhook = (options) => {
534
477
  }
535
478
  }
536
479
  if (eventName === "subscription.disable" || eventName === "subscription.not_renew") {
537
- const payloadData = data;
538
- const subscriptionCode = payloadData?.subscription_code ?? payloadData?.subscription?.subscription_code ?? payloadData?.code;
539
- if (subscriptionCode !== void 0 && subscriptionCode !== null && subscriptionCode !== "") {
480
+ const subscriptionData = data;
481
+ const subscriptionCode = subscriptionData.subscription_code ?? "";
482
+ if (subscriptionCode !== "") {
540
483
  const existing = await ctx.context.adapter.findOne({
541
484
  model: "subscription",
542
485
  where: [{
@@ -545,9 +488,9 @@ const paystackWebhook = (options) => {
545
488
  }]
546
489
  });
547
490
  let newStatus = "canceled";
548
- const nextPaymentDate = data?.next_payment_date;
549
- const periodEnd = nextPaymentDate ? new Date(nextPaymentDate) : existing?.periodEnd ? new Date(existing.periodEnd) : void 0;
550
- if (periodEnd && periodEnd > /* @__PURE__ */ new Date()) newStatus = "active";
491
+ const nextPaymentDate = subscriptionData.next_payment_date;
492
+ const periodEnd = nextPaymentDate !== void 0 && nextPaymentDate !== null && nextPaymentDate !== "" ? new Date(nextPaymentDate) : existing?.periodEnd !== void 0 && existing.periodEnd !== null ? new Date(existing.periodEnd) : void 0;
493
+ if (periodEnd !== void 0 && periodEnd.getTime() > Date.now()) newStatus = "active";
551
494
  await ctx.context.adapter.update({
552
495
  model: "subscription",
553
496
  update: {
@@ -571,9 +514,9 @@ const paystackWebhook = (options) => {
571
514
  }
572
515
  }
573
516
  if (eventName === "charge.success" || eventName === "invoice.update") {
574
- const payloadData = data;
575
- const subscriptionCode = payloadData?.subscription?.subscription_code ?? payloadData?.subscription_code;
576
- if (subscriptionCode) {
517
+ const subscriptionCodeRaw = (data?.subscription)?.subscription_code ?? data?.subscription_code;
518
+ const subscriptionCode = subscriptionCodeRaw !== void 0 && subscriptionCodeRaw !== null && subscriptionCodeRaw !== "" ? subscriptionCodeRaw : void 0;
519
+ if (subscriptionCode !== void 0) {
577
520
  const existingSub = await ctx.context.adapter.findOne({
578
521
  model: "subscription",
579
522
  where: [{
@@ -581,7 +524,7 @@ const paystackWebhook = (options) => {
581
524
  value: subscriptionCode
582
525
  }]
583
526
  });
584
- if (existingSub?.pendingPlan) await ctx.context.adapter.update({
527
+ if (existingSub !== void 0 && existingSub !== null && existingSub.pendingPlan !== void 0 && existingSub.pendingPlan !== null && existingSub.pendingPlan !== "") await ctx.context.adapter.update({
585
528
  model: "subscription",
586
529
  update: {
587
530
  plan: existingSub.pendingPlan,
@@ -616,7 +559,7 @@ const initializeTransactionBodySchema = z.object({
616
559
  cancelAtPeriodEnd: z.boolean().optional(),
617
560
  prorateAndCharge: z.boolean().optional()
618
561
  });
619
- const initializeTransaction = (options, path = "/paystack/initialize-transaction") => {
562
+ const initializeTransaction = (options, path = "/initialize-transaction") => {
620
563
  const subscriptionOptions = options.subscription;
621
564
  return createAuthEndpoint(path, {
622
565
  method: "POST",
@@ -632,25 +575,24 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
632
575
  if (callbackURL !== void 0 && callbackURL !== null && callbackURL !== "") {
633
576
  const checkTrusted = () => {
634
577
  try {
635
- if (!callbackURL) return false;
636
578
  if (callbackURL.startsWith("/")) return true;
637
579
  const baseUrl = ctx.context?.baseURL ?? ctx.request?.url ?? "";
638
- if (!baseUrl) return false;
580
+ if (baseUrl === "") return false;
639
581
  const baseOrigin = new URL(baseUrl).origin;
640
582
  return new URL(callbackURL).origin === baseOrigin;
641
583
  } catch {
642
584
  return false;
643
585
  }
644
586
  };
645
- if (!checkTrusted()) throw new APIError("FORBIDDEN", {
587
+ if (checkTrusted() === false) throw new APIError("FORBIDDEN", {
646
588
  message: "callbackURL is not a trusted origin.",
647
589
  status: 403
648
590
  });
649
591
  }
650
592
  const session = await getSessionFromCtx(ctx);
651
- if (!session) throw new APIError("UNAUTHORIZED");
593
+ if (session === void 0 || session === null) throw new APIError("UNAUTHORIZED");
652
594
  const user = session.user;
653
- if (subscriptionOptions?.enabled === true && subscriptionOptions.requireEmailVerification === true && !user.emailVerified) throw new APIError("BAD_REQUEST", {
595
+ if (subscriptionOptions?.enabled === true && subscriptionOptions.requireEmailVerification === true && user.emailVerified !== true) throw new APIError("BAD_REQUEST", {
654
596
  code: "EMAIL_VERIFICATION_REQUIRED",
655
597
  message: PAYSTACK_ERROR_CODES.EMAIL_VERIFICATION_REQUIRED.message
656
598
  });
@@ -659,7 +601,7 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
659
601
  if (planName !== void 0 && planName !== null && planName !== "") {
660
602
  if (subscriptionOptions?.enabled !== true) throw new APIError("BAD_REQUEST", { message: "Subscriptions are not enabled." });
661
603
  plan = await getPlanByName(options, planName) ?? void 0;
662
- if (!plan) {
604
+ if (plan === void 0 || plan === null) try {
663
605
  const nativePlan = await ctx.context.adapter.findOne({
664
606
  model: "paystackPlan",
665
607
  where: [{
@@ -667,7 +609,7 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
667
609
  value: planName
668
610
  }]
669
611
  });
670
- if (nativePlan) plan = nativePlan;
612
+ if (nativePlan !== void 0 && nativePlan !== null) plan = nativePlan;
671
613
  else plan = await ctx.context.adapter.findOne({
672
614
  model: "paystackPlan",
673
615
  where: [{
@@ -675,15 +617,17 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
675
617
  value: planName
676
618
  }]
677
619
  }) ?? void 0;
620
+ } catch {
621
+ plan = void 0;
678
622
  }
679
- if (!plan) throw new APIError("BAD_REQUEST", {
623
+ if (plan === void 0 || plan === null) throw new APIError("BAD_REQUEST", {
680
624
  code: "SUBSCRIPTION_PLAN_NOT_FOUND",
681
625
  message: PAYSTACK_ERROR_CODES.SUBSCRIPTION_PLAN_NOT_FOUND.message,
682
626
  status: 400
683
627
  });
684
628
  } else if (productName !== void 0 && productName !== null && productName !== "") {
685
629
  if (typeof productName === "string") {
686
- product ??= await getProductByName(options, productName) ?? void 0;
630
+ product = await getProductByName(options, productName) ?? void 0;
687
631
  product ??= await ctx.context.adapter.findOne({
688
632
  model: "paystackProduct",
689
633
  where: [{
@@ -692,19 +636,19 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
692
636
  }]
693
637
  }) ?? void 0;
694
638
  }
695
- if (!product) throw new APIError("BAD_REQUEST", {
639
+ if (product === void 0 || product === null) throw new APIError("BAD_REQUEST", {
696
640
  message: `Product '${productName}' not found.`,
697
641
  status: 400
698
642
  });
699
- } else if (bodyAmount === void 0 || bodyAmount === null || bodyAmount === 0) throw new APIError("BAD_REQUEST", {
643
+ } else if (bodyAmount === void 0 || bodyAmount === null) throw new APIError("BAD_REQUEST", {
700
644
  message: "Either 'plan', 'product', or 'amount' is required to initialize a transaction.",
701
645
  status: 400
702
646
  });
703
- let amount = bodyAmount ?? product?.price;
704
- const finalCurrency = currency ?? product?.currency ?? plan?.currency ?? "NGN";
647
+ let amount = bodyAmount ?? product?.price ?? product?.amount;
648
+ const finalCurrency = currency ?? product?.currency ?? product?.currency ?? plan?.currency ?? "NGN";
705
649
  const referenceIdFromCtx = ctx.context.referenceId;
706
- 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;
707
- if (plan && scheduleAtPeriodEnd === true) {
650
+ const referenceId = ctx.body.referenceId ?? referenceIdFromCtx ?? session.user.id;
651
+ if (plan !== void 0 && scheduleAtPeriodEnd === true) {
708
652
  const existingSub = await getOrganizationSubscription(ctx, referenceId);
709
653
  if (existingSub?.status === "active") {
710
654
  await ctx.context.adapter.update({
@@ -746,7 +690,7 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
746
690
  });
747
691
  }
748
692
  }
749
- if (plan && (plan.seatAmount !== void 0 || plan.seatPriceId !== void 0)) {
693
+ if (plan !== void 0 && (plan.seatAmount !== void 0 || plan.seatPriceId !== void 0)) {
750
694
  const members = await ctx.context.adapter.findMany({
751
695
  model: "member",
752
696
  where: [{
@@ -763,23 +707,23 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
763
707
  let accessCode;
764
708
  let trialStart;
765
709
  let trialEnd;
766
- if (plan?.freeTrial?.days !== void 0 && plan.freeTrial.days !== null && plan.freeTrial.days > 0) {
767
- if (!(await ctx.context.adapter.findMany({
710
+ if (plan?.freeTrial?.days !== void 0 && plan.freeTrial.days > 0) {
711
+ if ((await ctx.context.adapter.findMany({
768
712
  model: "subscription",
769
713
  where: [{
770
714
  field: "referenceId",
771
715
  value: referenceId
772
716
  }]
773
- }))?.some((sub) => sub.trialStart !== void 0 && sub.trialStart !== null || sub.trialEnd !== void 0 && sub.trialEnd !== null || sub.status === "trialing")) {
717
+ }))?.some((sub) => sub.trialStart !== void 0 && sub.trialStart !== null || sub.trialEnd !== void 0 && sub.trialEnd !== null || sub.status === "trialing") === false) {
774
718
  trialStart = /* @__PURE__ */ new Date();
775
719
  trialEnd = /* @__PURE__ */ new Date();
776
720
  trialEnd.setDate(trialEnd.getDate() + plan.freeTrial.days);
777
721
  }
778
722
  }
779
723
  try {
780
- let targetEmail = email !== void 0 && email !== null && email !== "" ? email : user.email;
724
+ let targetEmail = email ?? user.email;
781
725
  let paystackCustomerCode = user.paystackCustomerCode;
782
- if (options.organization?.enabled === true && referenceId !== void 0 && referenceId !== null && referenceId !== "" && referenceId !== user.id) {
726
+ if (options.organization?.enabled === true && referenceId !== void 0 && referenceId !== null && referenceId !== user.id) {
783
727
  const org = await ctx.context.adapter.findOne({
784
728
  model: "organization",
785
729
  where: [{
@@ -788,8 +732,10 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
788
732
  }]
789
733
  });
790
734
  if (org !== void 0 && org !== null) {
791
- if (org.paystackCustomerCode !== void 0 && org.paystackCustomerCode !== null && org.paystackCustomerCode !== "") paystackCustomerCode = org.paystackCustomerCode;
792
- if (org.email !== void 0 && org.email !== null && org.email !== "") targetEmail = org.email;
735
+ const paystackOrg = org;
736
+ if (paystackOrg.paystackCustomerCode !== void 0 && paystackOrg.paystackCustomerCode !== null && paystackOrg.paystackCustomerCode !== "") paystackCustomerCode = paystackOrg.paystackCustomerCode;
737
+ const orgWithEmail = org;
738
+ if (orgWithEmail.email !== void 0 && orgWithEmail.email !== null && orgWithEmail.email !== "") targetEmail = orgWithEmail.email;
793
739
  else {
794
740
  const ownerMember = await ctx.context.adapter.findOne({
795
741
  model: "member",
@@ -801,7 +747,7 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
801
747
  value: "owner"
802
748
  }]
803
749
  });
804
- if (ownerMember) {
750
+ if (ownerMember !== void 0 && ownerMember !== null) {
805
751
  const ownerUser = await ctx.context.adapter.findOne({
806
752
  model: "user",
807
753
  where: [{
@@ -809,7 +755,7 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
809
755
  value: ownerMember.userId
810
756
  }]
811
757
  });
812
- if (ownerUser?.email !== void 0 && ownerUser?.email !== null && ownerUser?.email !== "") targetEmail = ownerUser.email;
758
+ if (ownerUser !== void 0 && ownerUser !== null && ownerUser.email !== void 0 && ownerUser.email !== null && ownerUser.email !== "") targetEmail = ownerUser.email;
813
759
  }
814
760
  }
815
761
  }
@@ -817,103 +763,109 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
817
763
  const metadata = JSON.stringify({
818
764
  referenceId,
819
765
  userId: user.id,
820
- plan: plan?.name.toLowerCase(),
821
- product: product?.name.toLowerCase(),
822
- isTrial: !!trialStart,
823
- trialEnd: trialEnd?.toISOString(),
766
+ plan: plan !== void 0 ? plan.name.toLowerCase() : void 0,
767
+ product: product !== void 0 ? product.name.toLowerCase() : void 0,
768
+ isTrial: trialStart !== void 0,
769
+ trialEnd: trialEnd !== void 0 ? trialEnd.toISOString() : void 0,
824
770
  ...extraMetadata
825
771
  });
826
772
  const initBody = {
827
773
  email: targetEmail,
828
- callback_url: callbackURL,
774
+ callback_url: callbackURL ?? void 0,
829
775
  metadata,
830
776
  currency: finalCurrency,
831
777
  quantity
832
778
  };
833
779
  if (paystackCustomerCode !== void 0 && paystackCustomerCode !== null && paystackCustomerCode !== "") try {
834
780
  const ops = getPaystackOps(options.paystackClient);
835
- if (initBody.email !== void 0 && initBody.email !== null && initBody.email !== "") await ops.customerUpdate(paystackCustomerCode, { email: initBody.email });
781
+ if (ops !== void 0 && ops !== null && initBody.email !== "") await ops.customer?.update(paystackCustomerCode, { body: { email: initBody.email } });
836
782
  } catch (_e) {}
837
- if (plan && prorateAndCharge === true) {
783
+ if (plan !== void 0 && prorateAndCharge === true) {
838
784
  const existingSub = await getOrganizationSubscription(ctx, referenceId);
839
- if (existingSub?.status === "active" && existingSub.paystackAuthorizationCode !== null && existingSub.paystackAuthorizationCode !== void 0 && existingSub.paystackSubscriptionCode !== null && existingSub.paystackSubscriptionCode !== void 0) {
840
- const now = /* @__PURE__ */ new Date();
841
- const periodEndLocal = existingSub.periodEnd ? new Date(existingSub.periodEnd) : new Date(now.getTime() + 720 * 60 * 60 * 1e3);
842
- const periodStartLocal = existingSub.periodStart ? new Date(existingSub.periodStart) : now;
843
- const totalDays = Math.max(1, Math.ceil((periodEndLocal.getTime() - periodStartLocal.getTime()) / (1e3 * 60 * 60 * 24)));
844
- const remainingDays = Math.max(0, Math.ceil((periodEndLocal.getTime() - now.getTime()) / (1e3 * 60 * 60 * 24)));
845
- let oldAmount = 0;
846
- if (existingSub.plan) {
847
- const oldPlan = await getPlanByName(options, existingSub.plan) ?? await ctx.context.adapter.findOne({
848
- model: "paystackPlan",
849
- where: [{
850
- field: "name",
851
- value: existingSub.plan
852
- }]
853
- });
854
- if (oldPlan) {
855
- const oldSeatCount = existingSub.seats ?? 1;
856
- oldAmount = (oldPlan.amount ?? 0) + oldSeatCount * (oldPlan.seatAmount ?? oldPlan.seatPriceId ?? 0);
785
+ if (existingSub?.status === "active" && existingSub.paystackAuthorizationCode !== void 0 && existingSub.paystackAuthorizationCode !== null && existingSub.paystackAuthorizationCode !== "" && existingSub.paystackSubscriptionCode !== void 0 && existingSub.paystackSubscriptionCode !== null && existingSub.paystackSubscriptionCode !== "") {
786
+ if (existingSub.periodEnd !== void 0 && existingSub.periodEnd !== null && existingSub.periodStart !== void 0 && existingSub.periodStart !== null) {
787
+ const now = /* @__PURE__ */ new Date();
788
+ const periodEndLocal = new Date(existingSub.periodEnd);
789
+ const periodStartLocal = new Date(existingSub.periodStart);
790
+ const totalDays = Math.max(1, Math.ceil((periodEndLocal.getTime() - periodStartLocal.getTime()) / (1e3 * 60 * 60 * 24)));
791
+ const remainingDays = Math.max(0, Math.ceil((periodEndLocal.getTime() - now.getTime()) / (1e3 * 60 * 60 * 24)));
792
+ let oldAmount = 0;
793
+ if (existingSub.plan !== "") {
794
+ const oldPlan = await getPlanByName(options, existingSub.plan) ?? await ctx.context.adapter.findOne({
795
+ model: "paystackPlan",
796
+ where: [{
797
+ field: "name",
798
+ value: existingSub.plan
799
+ }]
800
+ }) ?? void 0;
801
+ if (oldPlan !== void 0 && oldPlan !== null) {
802
+ const oldSeatCount = existingSub.seats;
803
+ oldAmount = (oldPlan.amount ?? 0) + oldSeatCount * (oldPlan.seatAmount ?? oldPlan.seatPriceId ?? 0);
804
+ }
857
805
  }
858
- }
859
- let membersCount = 1;
860
- if (plan.seatAmount !== void 0 || plan.seatPriceId !== void 0) {
861
- const members = await ctx.context.adapter.findMany({
862
- model: "member",
863
- where: [{
864
- field: "organizationId",
865
- value: referenceId
866
- }]
867
- });
868
- membersCount = members.length > 0 ? members.length : 1;
869
- }
870
- const newSeatCount = quantity ?? existingSub.seats ?? membersCount;
871
- const newAmount = (plan.amount ?? 0) + newSeatCount * (plan.seatAmount ?? plan.seatPriceId ?? 0);
872
- const costDifference = newAmount - oldAmount;
873
- if (costDifference > 0 && remainingDays > 0) {
874
- const proratedAmount = Math.round(costDifference / totalDays * remainingDays);
875
- if (proratedAmount >= 5e3) {
876
- const chargeData = unwrapSdkResult(await getPaystackOps(options.paystackClient).transactionChargeAuthorization({
877
- email: targetEmail,
878
- amount: proratedAmount,
879
- authorization_code: existingSub.paystackAuthorizationCode,
880
- reference: `prorate_${Date.now()}_${Math.random().toString(36).substring(7)}`,
881
- metadata: {
882
- type: "proration",
883
- referenceId,
884
- newPlan: plan.name,
885
- oldPlan: existingSub.plan,
886
- remainingDays
806
+ 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;
816
+ }
817
+ const newSeatCount = quantity ?? existingSub.seats ?? membersCount;
818
+ const newAmount = (plan.amount ?? 0) + newSeatCount * (plan.seatAmount ?? plan.seatPriceId ?? 0);
819
+ const costDifference = newAmount - oldAmount;
820
+ if (costDifference > 0 && remainingDays > 0) {
821
+ 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;
887
827
  }
888
- }));
889
- if ((chargeData?.data?.status ?? chargeData?.status) !== "success") throw new APIError("BAD_REQUEST", { message: "Failed to process prorated charge via saved authorization." });
828
+ if (unwrapSdkResult(await ops.transaction?.chargeAuthorization({ body: {
829
+ email: targetEmail,
830
+ amount: proratedAmount,
831
+ authorization_code: existingSub.paystackAuthorizationCode,
832
+ reference: `upg_${existingSub.id}_${Date.now()}_${Math.random().toString(36).substring(7)}`,
833
+ metadata: {
834
+ type: "proration",
835
+ referenceId,
836
+ newPlan: plan.name,
837
+ oldPlan: existingSub.plan,
838
+ remainingDays
839
+ }
840
+ } }))?.status !== "success") throw new APIError("BAD_REQUEST", { message: "Failed to process prorated charge via saved authorization." });
841
+ }
890
842
  }
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
+ await ctx.context.adapter.update({
849
+ model: "subscription",
850
+ where: [{
851
+ field: "id",
852
+ value: existingSub.id
853
+ }],
854
+ update: {
855
+ plan: plan.name,
856
+ seats: newSeatCount,
857
+ updatedAt: /* @__PURE__ */ new Date()
858
+ }
859
+ });
860
+ return ctx.json({
861
+ status: "success",
862
+ message: "Subscription successfully upgraded with prorated charge.",
863
+ prorated: true
864
+ });
891
865
  }
892
- await getPaystackOps(options.paystackClient).subscriptionUpdate({
893
- code: existingSub.paystackSubscriptionCode,
894
- amount: newAmount,
895
- plan: plan.planCode
896
- });
897
- await ctx.context.adapter.update({
898
- model: "subscription",
899
- where: [{
900
- field: "id",
901
- value: existingSub.id
902
- }],
903
- update: {
904
- plan: plan.name,
905
- seats: newSeatCount,
906
- updatedAt: /* @__PURE__ */ new Date()
907
- }
908
- });
909
- return ctx.json({
910
- status: "success",
911
- message: "Subscription successfully upgraded with prorated charge.",
912
- prorated: true
913
- });
914
866
  }
915
867
  }
916
- if (plan) if (trialStart) initBody.amount = 5e3;
868
+ if (plan !== void 0) if (trialStart !== void 0) initBody.amount = 5e3;
917
869
  else {
918
870
  initBody.plan = plan.planCode;
919
871
  initBody.invoice_limit = plan.invoiceLimit;
@@ -921,43 +873,41 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
921
873
  if (amount !== void 0 && amount !== null) {
922
874
  finalAmount = amount;
923
875
  initBody.quantity = 1;
924
- } else finalAmount = (plan.amount ?? 5e4) * (quantity ?? 1);
925
- initBody.amount = Math.max(Math.round(finalAmount), 5e4);
876
+ } else finalAmount = (plan.amount ?? 0) * (quantity ?? 1);
877
+ initBody.amount = Math.max(Math.round(finalAmount), 5e3);
926
878
  }
927
879
  else {
928
- if (amount === void 0 || amount === null || amount === 0) throw new APIError("BAD_REQUEST", { message: "Amount is required for one-time payments" });
880
+ if (amount === void 0 || amount === null) throw new APIError("BAD_REQUEST", { message: "Amount is required for one-time payments" });
929
881
  initBody.amount = Math.round(amount);
930
882
  }
931
- const initRes = unwrapSdkResult(await paystack.transactionInitialize(initBody));
932
- let data = initRes !== void 0 && initRes !== null && typeof initRes === "object" && "status" in initRes && "data" in initRes ? initRes.data : initRes?.data ?? initRes;
933
- if (data !== void 0 && data !== null && typeof data === "object" && "status" in data && "data" in data) data = data.data;
934
- url = data?.authorization_url;
935
- reference = data?.reference;
936
- accessCode = data?.access_code;
883
+ const sdkRes = unwrapSdkResult(await paystack?.transaction?.initialize({ body: initBody }));
884
+ url = sdkRes?.authorization_url;
885
+ reference = sdkRes?.reference;
886
+ accessCode = sdkRes?.access_code;
937
887
  } catch (error) {
938
888
  ctx.context.logger.error("Failed to initialize Paystack transaction", error);
939
889
  throw new APIError("BAD_REQUEST", {
940
890
  code: "FAILED_TO_INITIALIZE_TRANSACTION",
941
- message: error?.message ?? PAYSTACK_ERROR_CODES.FAILED_TO_INITIALIZE_TRANSACTION.message
891
+ message: error instanceof Error ? error.message : PAYSTACK_ERROR_CODES.FAILED_TO_INITIALIZE_TRANSACTION.message
942
892
  });
943
893
  }
944
894
  await ctx.context.adapter.create({
945
895
  model: "paystackTransaction",
946
896
  data: {
947
- reference,
897
+ reference: reference ?? "",
948
898
  referenceId,
949
899
  userId: user.id,
950
900
  amount: amount ?? 0,
951
901
  currency: plan?.currency ?? currency ?? "NGN",
952
902
  status: "pending",
953
- plan: plan?.name.toLowerCase(),
954
- product: product?.name.toLowerCase(),
955
- metadata: extraMetadata !== void 0 && extraMetadata !== null ? JSON.stringify(extraMetadata) : void 0,
903
+ plan: plan !== void 0 ? plan.name.toLowerCase() : void 0,
904
+ product: product !== void 0 ? product.name.toLowerCase() : void 0,
905
+ metadata: extraMetadata !== void 0 && Object.keys(extraMetadata).length > 0 ? JSON.stringify(extraMetadata) : void 0,
956
906
  createdAt: /* @__PURE__ */ new Date(),
957
907
  updatedAt: /* @__PURE__ */ new Date()
958
908
  }
959
909
  });
960
- if (plan !== void 0 && plan !== null) {
910
+ if (plan !== void 0) {
961
911
  let storedCustomerCode = user.paystackCustomerCode;
962
912
  if (options.organization?.enabled === true && referenceId !== user.id) {
963
913
  const org = await ctx.context.adapter.findOne({
@@ -967,22 +917,34 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
967
917
  value: referenceId
968
918
  }]
969
919
  });
970
- if (org?.paystackCustomerCode !== void 0 && org?.paystackCustomerCode !== null && org.paystackCustomerCode !== "") storedCustomerCode = org.paystackCustomerCode;
920
+ if (org !== void 0 && org !== null) {
921
+ const paystackOrg = org;
922
+ if (paystackOrg.paystackCustomerCode !== void 0 && paystackOrg.paystackCustomerCode !== null && paystackOrg.paystackCustomerCode !== "") storedCustomerCode = paystackOrg.paystackCustomerCode;
923
+ }
971
924
  }
972
925
  const newSubscription = await ctx.context.adapter.create({
973
926
  model: "subscription",
974
927
  data: {
975
928
  plan: plan.name.toLowerCase(),
976
929
  referenceId,
977
- paystackCustomerCode: storedCustomerCode,
978
- paystackTransactionReference: reference,
979
- status: trialStart !== void 0 && trialStart !== null ? "trialing" : "incomplete",
980
- seats: quantity,
930
+ userId: user.id,
931
+ paystackCustomerCode: storedCustomerCode ?? "",
932
+ paystackSubscriptionCode: "",
933
+ paystackPlanCode: plan.planCode,
934
+ paystackAuthorizationCode: "",
935
+ paystackTransactionReference: reference ?? "",
936
+ status: trialStart !== void 0 ? "trialing" : "incomplete",
937
+ seats: quantity ?? 1,
938
+ periodStart: /* @__PURE__ */ new Date(),
939
+ periodEnd: new Date(Date.now() + 720 * 60 * 60 * 1e3),
940
+ cancelAtPeriodEnd: false,
981
941
  trialStart,
982
- trialEnd
942
+ trialEnd,
943
+ createdAt: /* @__PURE__ */ new Date(),
944
+ updatedAt: /* @__PURE__ */ new Date()
983
945
  }
984
946
  });
985
- if (trialStart !== void 0 && trialStart !== null && newSubscription !== null && plan.freeTrial?.onTrialStart !== void 0 && plan.freeTrial?.onTrialStart !== null) await plan.freeTrial.onTrialStart(newSubscription);
947
+ if (trialStart !== void 0 && newSubscription !== void 0 && newSubscription !== null && plan.freeTrial?.onTrialStart !== void 0) await plan.freeTrial.onTrialStart(newSubscription);
986
948
  }
987
949
  return ctx.json({
988
950
  url,
@@ -992,15 +954,11 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
992
954
  });
993
955
  });
994
956
  };
995
- const createSubscription = (options) => initializeTransaction(options, "/paystack/create-subscription");
996
- const upgradeSubscription = (options) => initializeTransaction(options, "/paystack/upgrade-subscription");
997
- const restoreSubscription = (options) => {
998
- return enablePaystackSubscription(options, "/paystack/restore-subscription");
999
- };
1000
- const cancelSubscription = (options) => {
1001
- return disablePaystackSubscription(options, "/paystack/cancel-subscription");
1002
- };
1003
- const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
957
+ const createSubscription = (options, path = "/create-subscription") => initializeTransaction(options, path);
958
+ const upgradeSubscription = (options, path = "/upgrade-subscription") => initializeTransaction(options, path);
959
+ const cancelSubscription = (options, path = "/cancel-subscription") => disablePaystackSubscription(options, path);
960
+ const restoreSubscription = (options, path = "/restore-subscription") => enablePaystackSubscription(options, path);
961
+ const verifyTransaction = (options, path = "/verify-transaction") => {
1004
962
  const verifyBodySchema = z.object({ reference: z.string() });
1005
963
  const subscriptionOptions = options.subscription;
1006
964
  return createAuthEndpoint(path, {
@@ -1013,40 +971,42 @@ const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
1013
971
  ] : [sessionMiddleware, originCheck]
1014
972
  }, async (ctx) => {
1015
973
  const paystack = getPaystackOps(options.paystackClient);
1016
- let verifyRes;
974
+ let data;
1017
975
  try {
1018
- verifyRes = unwrapSdkResult(await paystack.transactionVerify(ctx.body.reference));
976
+ data = unwrapSdkResult(await paystack?.transaction?.verify(ctx.body.reference));
1019
977
  } catch (error) {
1020
978
  ctx.context.logger.error("Failed to verify Paystack transaction", error);
1021
979
  throw new APIError("BAD_REQUEST", {
1022
980
  code: "FAILED_TO_VERIFY_TRANSACTION",
1023
- message: error?.message ?? PAYSTACK_ERROR_CODES.FAILED_TO_VERIFY_TRANSACTION.message
981
+ message: error instanceof Error ? error.message : PAYSTACK_ERROR_CODES.FAILED_TO_VERIFY_TRANSACTION.message
1024
982
  });
1025
983
  }
1026
- const data = unwrapSdkResult(verifyRes);
1027
- const status = data?.status;
1028
- const reference = data?.reference ?? ctx.body.reference;
1029
- const paystackId = data?.id !== void 0 && data?.id !== null ? String(data.id) : void 0;
1030
- const authorizationCode = (data?.authorization)?.authorization_code;
984
+ if (data === void 0 || data === null) throw new APIError("BAD_REQUEST", { message: "Failed to fetch transaction data from Paystack." });
985
+ const status = data.status ?? "failed";
986
+ const reference = data.reference ?? ctx.body.reference;
987
+ const paystackIdRaw = data.id;
988
+ const paystackId = paystackIdRaw !== void 0 && paystackIdRaw !== null ? String(paystackIdRaw) : void 0;
989
+ const authorizationCode = data.authorization?.authorization_code;
1031
990
  if (status === "success") {
1032
991
  const session = await getSessionFromCtx(ctx);
1033
- const referenceId = (await ctx.context.adapter.findOne({
992
+ const txRecord = await ctx.context.adapter.findOne({
1034
993
  model: "paystackTransaction",
1035
994
  where: [{
1036
995
  field: "reference",
1037
996
  value: reference
1038
997
  }]
1039
- }))?.referenceId ?? (session?.user)?.id;
1040
- if (session !== null && session !== void 0 && referenceId !== session.user.id) {
998
+ });
999
+ 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;
1000
+ if (session !== void 0 && session !== null && referenceId !== void 0 && referenceId !== null && referenceId !== "" && referenceId !== session.user.id) {
1041
1001
  const authRef = subscriptionOptions?.authorizeReference;
1042
1002
  let authorized = false;
1043
1003
  if (authRef !== void 0 && authRef !== null) authorized = await authRef({
1044
1004
  user: session.user,
1045
- session,
1005
+ session: session.session,
1046
1006
  referenceId,
1047
1007
  action: "verify-transaction"
1048
1008
  }, ctx);
1049
- else if (options.organization?.enabled === true) {
1009
+ if (authorized === false && options.organization?.enabled === true) {
1050
1010
  const member = await ctx.context.adapter.findOne({
1051
1011
  model: "member",
1052
1012
  where: [{
@@ -1057,9 +1017,9 @@ const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
1057
1017
  value: referenceId
1058
1018
  }]
1059
1019
  });
1060
- if (member !== null && member !== void 0) authorized = true;
1020
+ if (member !== void 0 && member !== null) authorized = true;
1061
1021
  }
1062
- if (!authorized) throw new APIError("UNAUTHORIZED");
1022
+ if (authorized === false) throw new APIError("UNAUTHORIZED");
1063
1023
  }
1064
1024
  try {
1065
1025
  await ctx.context.adapter.update({
@@ -1067,8 +1027,8 @@ const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
1067
1027
  update: {
1068
1028
  status: "success",
1069
1029
  paystackId,
1070
- ...data?.amount !== void 0 && data?.amount !== null ? { amount: data.amount } : {},
1071
- ...data?.currency !== void 0 && data?.currency !== null ? { currency: data.currency } : {},
1030
+ amount: data.amount,
1031
+ currency: data.currency,
1072
1032
  updatedAt: /* @__PURE__ */ new Date()
1073
1033
  },
1074
1034
  where: [{
@@ -1076,30 +1036,36 @@ const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
1076
1036
  value: reference
1077
1037
  }]
1078
1038
  });
1079
- const customer = data?.customer;
1080
- const paystackCustomerCodeFromPaystack = customer !== void 0 && customer !== null && typeof customer === "object" ? customer.customer_code : void 0;
1081
- 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({
1082
- model: "organization",
1083
- where: [{
1084
- field: "id",
1085
- value: referenceId
1086
- }]
1087
- }) !== null)) === 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
- });
1039
+ const paystackCustomerCodeFromPaystack = data.customer?.customer_code;
1040
+ if (paystackCustomerCodeFromPaystack !== void 0 && paystackCustomerCodeFromPaystack !== null && paystackCustomerCodeFromPaystack !== "" && referenceId !== void 0 && referenceId !== null && referenceId !== "") {
1041
+ let isOrg = options.organization?.enabled === true && typeof referenceId === "string" && referenceId.startsWith("org_");
1042
+ if (isOrg === false && options.organization?.enabled === true) {
1043
+ const org = await ctx.context.adapter.findOne({
1044
+ model: "organization",
1045
+ where: [{
1046
+ field: "id",
1047
+ value: referenceId
1048
+ }]
1049
+ });
1050
+ isOrg = org !== void 0 && org !== null;
1051
+ }
1052
+ if (isOrg) await ctx.context.adapter.update({
1053
+ model: "organization",
1054
+ update: { paystackCustomerCode: paystackCustomerCodeFromPaystack },
1055
+ where: [{
1056
+ field: "id",
1057
+ value: referenceId
1058
+ }]
1059
+ });
1060
+ else await ctx.context.adapter.update({
1061
+ model: "user",
1062
+ update: { paystackCustomerCode: paystackCustomerCodeFromPaystack },
1063
+ where: [{
1064
+ field: "id",
1065
+ value: referenceId
1066
+ }]
1067
+ });
1068
+ }
1103
1069
  const transaction = await ctx.context.adapter.findOne({
1104
1070
  model: "paystackTransaction",
1105
1071
  where: [{
@@ -1107,68 +1073,62 @@ const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
1107
1073
  value: reference
1108
1074
  }]
1109
1075
  });
1110
- if (transaction?.product) await syncProductQuantityFromPaystack(ctx, transaction.product, options.paystackClient);
1076
+ if (transaction !== void 0 && transaction !== null && transaction.product !== void 0 && transaction.product !== null && transaction.product !== "" && options.paystackClient !== void 0 && options.paystackClient !== null) await syncProductQuantityFromPaystack(ctx, transaction.product, options.paystackClient);
1111
1077
  let isTrial = false;
1112
1078
  let trialEnd;
1113
1079
  let targetPlan;
1114
- if (data?.metadata !== void 0 && data?.metadata !== null) {
1115
- const metaRaw = data.metadata;
1116
- const meta = typeof metaRaw === "string" ? JSON.parse(metaRaw) : metaRaw;
1080
+ if (data.metadata !== void 0 && data.metadata !== null && data.metadata !== "") {
1081
+ const meta = typeof data.metadata === "string" ? JSON.parse(data.metadata) : data.metadata;
1117
1082
  isTrial = meta.isTrial === true || meta.isTrial === "true";
1118
1083
  trialEnd = meta.trialEnd;
1119
1084
  targetPlan = meta.plan;
1120
1085
  }
1121
1086
  let paystackSubscriptionCode;
1122
- if (isTrial === true && targetPlan !== void 0 && targetPlan !== null && targetPlan !== "" && trialEnd !== void 0 && trialEnd !== null && trialEnd !== "") {
1123
- const email = (data?.customer)?.email;
1087
+ if (isTrial && targetPlan !== void 0 && trialEnd !== void 0) {
1088
+ const email = data.customer?.email;
1124
1089
  const planConfig = (await getPlans(subscriptionOptions)).find((p) => p.name.toLowerCase() === targetPlan?.toLowerCase());
1125
- if (planConfig !== void 0 && (planConfig.planCode === void 0 || planConfig.planCode === null || planConfig.planCode === "")) paystackSubscriptionCode = `LOC_${reference}`;
1126
- if (authorizationCode !== void 0 && authorizationCode !== null && authorizationCode !== "" && email !== void 0 && email !== null && email !== "" && planConfig?.planCode !== void 0 && planConfig?.planCode !== null && planConfig?.planCode !== "") {
1127
- const subData = unwrapSdkResult(await paystack.subscriptionCreate({
1128
- customer: email,
1129
- plan: planConfig.planCode,
1130
- authorization: authorizationCode,
1131
- start_date: trialEnd
1132
- }));
1133
- paystackSubscriptionCode = (subData?.data ?? subData)?.subscription_code;
1134
- }
1135
- } else if (isTrial !== true) {
1136
- const planCodeFromPaystack = (data?.plan)?.plan_code;
1090
+ if (planConfig !== void 0 && planConfig !== null && (planConfig.planCode === void 0 || planConfig.planCode === null || planConfig.planCode === "")) paystackSubscriptionCode = `LOC_${reference}`;
1091
+ if (authorizationCode !== void 0 && authorizationCode !== null && email !== void 0 && email !== null && email !== "" && planConfig?.planCode !== void 0 && planConfig.planCode !== null && planConfig.planCode !== "") paystackSubscriptionCode = unwrapSdkResult(await paystack?.subscription?.create({ body: {
1092
+ customer: email,
1093
+ plan: planConfig.planCode,
1094
+ authorization: authorizationCode,
1095
+ start_date: trialEnd
1096
+ } }))?.subscription_code;
1097
+ } else if (isTrial === false) {
1098
+ const planCodeFromPaystack = data.plan?.plan_code;
1137
1099
  if (planCodeFromPaystack === void 0 || planCodeFromPaystack === null || planCodeFromPaystack === "") paystackSubscriptionCode = `LOC_${reference}`;
1138
- else paystackSubscriptionCode = (data?.subscription)?.subscription_code;
1100
+ else paystackSubscriptionCode = data.subscription?.subscription_code ?? void 0;
1139
1101
  }
1140
- const existingSubs = await ctx.context.adapter.findMany({
1102
+ const targetSub = (await ctx.context.adapter.findMany({
1141
1103
  model: "subscription",
1142
1104
  where: [{
1143
1105
  field: "paystackTransactionReference",
1144
1106
  value: reference
1145
1107
  }]
1146
- });
1147
- let targetSub;
1148
- if (existingSubs && existingSubs.length > 0) targetSub = existingSubs.find((s) => !(referenceId !== void 0 && referenceId !== null && referenceId !== "") || s.referenceId === referenceId);
1108
+ }))?.find((s) => referenceId === void 0 || referenceId === null || referenceId === "" || s.referenceId === referenceId);
1149
1109
  let updatedSubscription = null;
1150
1110
  if (targetSub !== void 0 && targetSub !== null) updatedSubscription = await ctx.context.adapter.update({
1151
1111
  model: "subscription",
1152
1112
  update: {
1153
- status: isTrial === true ? "trialing" : "active",
1113
+ status: isTrial ? "trialing" : "active",
1154
1114
  periodStart: /* @__PURE__ */ new Date(),
1155
1115
  updatedAt: /* @__PURE__ */ new Date(),
1156
- ...isTrial === true && trialEnd !== void 0 && trialEnd !== null && trialEnd !== "" ? {
1116
+ ...isTrial && trialEnd !== void 0 ? {
1157
1117
  trialStart: /* @__PURE__ */ new Date(),
1158
1118
  trialEnd: new Date(trialEnd),
1159
1119
  periodEnd: new Date(trialEnd)
1160
1120
  } : {},
1161
- ...paystackSubscriptionCode !== void 0 && paystackSubscriptionCode !== null && paystackSubscriptionCode !== "" ? { paystackSubscriptionCode } : {},
1162
- ...authorizationCode !== void 0 && authorizationCode !== null && authorizationCode !== "" ? { paystackAuthorizationCode: authorizationCode } : {}
1121
+ ...paystackSubscriptionCode !== void 0 ? { paystackSubscriptionCode } : {},
1122
+ ...authorizationCode !== void 0 && authorizationCode !== null ? { paystackAuthorizationCode: authorizationCode } : {}
1163
1123
  },
1164
1124
  where: [{
1165
1125
  field: "id",
1166
1126
  value: targetSub.id
1167
1127
  }]
1168
1128
  });
1169
- if (updatedSubscription && subscriptionOptions?.enabled === true && "onSubscriptionComplete" in subscriptionOptions && typeof subscriptionOptions.onSubscriptionComplete === "function") {
1129
+ if (updatedSubscription !== void 0 && updatedSubscription !== null && subscriptionOptions?.onSubscriptionComplete !== void 0) {
1170
1130
  const plan = (await getPlans(subscriptionOptions)).find((p) => p.name.toLowerCase() === updatedSubscription.plan.toLowerCase());
1171
- if (plan) await subscriptionOptions.onSubscriptionComplete({
1131
+ if (plan !== void 0) await subscriptionOptions.onSubscriptionComplete({
1172
1132
  event: data,
1173
1133
  subscription: updatedSubscription,
1174
1134
  plan
@@ -1177,20 +1137,6 @@ const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
1177
1137
  } catch (e) {
1178
1138
  ctx.context.logger.error("Failed to update transaction/subscription after verification", e);
1179
1139
  }
1180
- } else if (status === "failed" || status === "abandoned") try {
1181
- await ctx.context.adapter.update({
1182
- model: "paystackTransaction",
1183
- update: {
1184
- status,
1185
- updatedAt: /* @__PURE__ */ new Date()
1186
- },
1187
- where: [{
1188
- field: "reference",
1189
- value: reference
1190
- }]
1191
- });
1192
- } catch (e) {
1193
- ctx.context.logger.error("Failed to update transaction status", e);
1194
1140
  }
1195
1141
  return ctx.json({
1196
1142
  status,
@@ -1199,10 +1145,10 @@ const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
1199
1145
  });
1200
1146
  });
1201
1147
  };
1202
- const listSubscriptions = (options) => {
1148
+ const listSubscriptions = (options, path = "/list-subscriptions") => {
1203
1149
  const listQuerySchema = z.object({ referenceId: z.string().optional() });
1204
1150
  const subscriptionOptions = options.subscription;
1205
- return createAuthEndpoint("/paystack/list-subscriptions", {
1151
+ return createAuthEndpoint(path, {
1206
1152
  method: "GET",
1207
1153
  query: listQuerySchema,
1208
1154
  use: subscriptionOptions?.enabled === true ? [
@@ -1213,10 +1159,10 @@ const listSubscriptions = (options) => {
1213
1159
  }, async (ctx) => {
1214
1160
  if (subscriptionOptions?.enabled !== true) throw new APIError("BAD_REQUEST", { message: "Subscriptions are not enabled in the Paystack options." });
1215
1161
  const session = await getSessionFromCtx(ctx);
1216
- if (!session) throw new APIError("UNAUTHORIZED");
1162
+ if (session === void 0 || session === null) throw new APIError("UNAUTHORIZED");
1217
1163
  const referenceIdPart = ctx.context.referenceId;
1218
1164
  const queryRefId = ctx.query?.referenceId;
1219
- const referenceId = referenceIdPart !== void 0 && referenceIdPart !== null && referenceIdPart !== "" ? referenceIdPart : queryRefId !== void 0 && queryRefId !== null && queryRefId !== "" ? queryRefId : session.user.id;
1165
+ const referenceId = referenceIdPart ?? queryRefId ?? session.user.id;
1220
1166
  const res = await ctx.context.adapter.findMany({
1221
1167
  model: "subscription",
1222
1168
  where: [{
@@ -1227,7 +1173,7 @@ const listSubscriptions = (options) => {
1227
1173
  return ctx.json({ subscriptions: res });
1228
1174
  });
1229
1175
  };
1230
- const listTransactions = (options, path = "/paystack/list-transactions") => {
1176
+ const listTransactions = (options, path = "/list-transactions") => {
1231
1177
  return createAuthEndpoint(path, {
1232
1178
  method: "GET",
1233
1179
  query: z.object({ referenceId: z.string().optional() }),
@@ -1238,15 +1184,17 @@ const listTransactions = (options, path = "/paystack/list-transactions") => {
1238
1184
  ] : [sessionMiddleware, originCheck]
1239
1185
  }, async (ctx) => {
1240
1186
  const session = await getSessionFromCtx(ctx);
1241
- if (!session) throw new APIError("UNAUTHORIZED");
1242
- const referenceId = ctx.context.referenceId ?? ctx.query?.referenceId ?? session.user.id;
1187
+ if (session === void 0 || session === null) throw new APIError("UNAUTHORIZED");
1188
+ const referenceIdPart = ctx.context.referenceId;
1189
+ const queryRefId = ctx.query?.referenceId;
1190
+ const referenceId = referenceIdPart ?? queryRefId ?? session.user.id;
1243
1191
  const sorted = (await ctx.context.adapter.findMany({
1244
1192
  model: "paystackTransaction",
1245
1193
  where: [{
1246
1194
  field: "referenceId",
1247
1195
  value: referenceId
1248
1196
  }]
1249
- })).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
1197
+ })).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
1250
1198
  return ctx.json({ transactions: sorted });
1251
1199
  });
1252
1200
  };
@@ -1259,8 +1207,10 @@ const enableDisableBodySchema = z.object({
1259
1207
  function decodeBase64UrlToString(value) {
1260
1208
  const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
1261
1209
  const padded = normalized + "===".slice((normalized.length + 3) % 4);
1262
- if (typeof globalThis.atob === "function") return globalThis.atob(padded);
1263
- return Buffer.from(padded, "base64").toString("utf8");
1210
+ const binaryString = atob(padded);
1211
+ const bytes = new Uint8Array(binaryString.length);
1212
+ for (let i = 0; i < binaryString.length; i++) bytes[i] = binaryString.charCodeAt(i);
1213
+ return new TextDecoder().decode(bytes);
1264
1214
  }
1265
1215
  function tryGetEmailTokenFromSubscriptionManageLink(link) {
1266
1216
  try {
@@ -1270,12 +1220,12 @@ function tryGetEmailTokenFromSubscriptionManageLink(link) {
1270
1220
  if (parts.length < 2) return void 0;
1271
1221
  const payloadJson = decodeBase64UrlToString(parts[1]);
1272
1222
  const payload = JSON.parse(payloadJson);
1273
- return typeof payload?.email_token === "string" ? payload.email_token : void 0;
1223
+ return typeof payload.email_token === "string" ? payload.email_token : void 0;
1274
1224
  } catch {
1275
1225
  return;
1276
1226
  }
1277
1227
  }
1278
- const disablePaystackSubscription = (options, path = "/paystack/disable-subscription") => {
1228
+ const disablePaystackSubscription = (options, path = "/disable-subscription") => {
1279
1229
  return createAuthEndpoint(path, {
1280
1230
  method: "POST",
1281
1231
  body: enableDisableBodySchema,
@@ -1288,7 +1238,7 @@ const disablePaystackSubscription = (options, path = "/paystack/disable-subscrip
1288
1238
  const { subscriptionCode, atPeriodEnd } = ctx.body;
1289
1239
  const paystack = getPaystackOps(options.paystackClient);
1290
1240
  try {
1291
- if (subscriptionCode.startsWith("LOC_")) {
1241
+ if (subscriptionCode.startsWith("LOC_") || subscriptionCode.startsWith("sub_local_")) {
1292
1242
  const sub = await ctx.context.adapter.findOne({
1293
1243
  model: "subscription",
1294
1244
  where: [{
@@ -1316,22 +1266,21 @@ const disablePaystackSubscription = (options, path = "/paystack/disable-subscrip
1316
1266
  let emailToken = ctx.body.emailToken;
1317
1267
  let nextPaymentDate;
1318
1268
  try {
1319
- const fetchRes = unwrapSdkResult(await paystack.subscriptionFetch(subscriptionCode));
1320
- 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;
1321
- if (emailToken === void 0 || emailToken === null || emailToken === "") emailToken = data?.email_token;
1322
- nextPaymentDate = data?.next_payment_date;
1269
+ const fetchRes = unwrapSdkResult(await paystack?.subscription?.fetch(subscriptionCode));
1270
+ if (fetchRes !== void 0 && fetchRes !== null) {
1271
+ emailToken ??= fetchRes.email_token ?? void 0;
1272
+ nextPaymentDate = fetchRes.next_payment_date ?? void 0;
1273
+ }
1323
1274
  } catch {}
1324
1275
  if (emailToken === void 0 || emailToken === null || emailToken === "") try {
1325
- const linkRes = unwrapSdkResult(await paystack.subscriptionManageLink(subscriptionCode));
1326
- 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;
1327
- const link = typeof data === "string" ? data : data?.link;
1276
+ const link = unwrapSdkResult(await paystack?.subscription?.manageLink(subscriptionCode))?.link;
1328
1277
  if (link !== void 0 && link !== null && link !== "") emailToken = tryGetEmailTokenFromSubscriptionManageLink(link);
1329
1278
  } catch {}
1330
1279
  if (emailToken === void 0 || emailToken === null || emailToken === "") throw new Error("Could not retrieve email_token for subscription disable.");
1331
- await paystack.subscriptionDisable({
1280
+ await paystack?.subscription?.disable({ body: {
1332
1281
  code: subscriptionCode,
1333
1282
  token: emailToken
1334
- });
1283
+ } });
1335
1284
  const periodEnd = nextPaymentDate !== void 0 && nextPaymentDate !== null && nextPaymentDate !== "" ? new Date(nextPaymentDate) : void 0;
1336
1285
  const sub = await ctx.context.adapter.findOne({
1337
1286
  model: "subscription",
@@ -1340,7 +1289,7 @@ const disablePaystackSubscription = (options, path = "/paystack/disable-subscrip
1340
1289
  value: subscriptionCode
1341
1290
  }]
1342
1291
  });
1343
- if (sub) await ctx.context.adapter.update({
1292
+ if (sub !== void 0 && sub !== null) await ctx.context.adapter.update({
1344
1293
  model: "subscription",
1345
1294
  update: {
1346
1295
  status: atPeriodEnd === false ? "canceled" : "active",
@@ -1359,12 +1308,12 @@ const disablePaystackSubscription = (options, path = "/paystack/disable-subscrip
1359
1308
  ctx.context.logger.error("Failed to disable subscription", error);
1360
1309
  throw new APIError("BAD_REQUEST", {
1361
1310
  code: "FAILED_TO_DISABLE_SUBSCRIPTION",
1362
- message: error?.message ?? PAYSTACK_ERROR_CODES.FAILED_TO_DISABLE_SUBSCRIPTION.message
1311
+ message: error instanceof Error ? error.message : PAYSTACK_ERROR_CODES.FAILED_TO_DISABLE_SUBSCRIPTION.message
1363
1312
  });
1364
1313
  }
1365
1314
  });
1366
1315
  };
1367
- const enablePaystackSubscription = (options, path = "/paystack/enable-subscription") => {
1316
+ const enablePaystackSubscription = (options, path = "/enable-subscription") => {
1368
1317
  return createAuthEndpoint(path, {
1369
1318
  method: "POST",
1370
1319
  body: enableDisableBodySchema,
@@ -1379,20 +1328,18 @@ const enablePaystackSubscription = (options, path = "/paystack/enable-subscripti
1379
1328
  try {
1380
1329
  let emailToken = ctx.body.emailToken;
1381
1330
  if (emailToken === void 0 || emailToken === null || emailToken === "") try {
1382
- const fetchRes = unwrapSdkResult(await paystack.subscriptionFetch(subscriptionCode));
1383
- 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;
1331
+ const fetchRes = unwrapSdkResult(await paystack?.subscription?.fetch(subscriptionCode));
1332
+ if (fetchRes !== void 0 && fetchRes !== null) emailToken = fetchRes.email_token ?? void 0;
1384
1333
  } catch {}
1385
1334
  if (emailToken === void 0 || emailToken === null || emailToken === "") try {
1386
- const linkRes = unwrapSdkResult(await paystack.subscriptionManageLink(subscriptionCode));
1387
- const data = linkRes !== null && linkRes !== void 0 && "status" in linkRes && "data" in linkRes ? linkRes.data : linkRes?.data !== void 0 ? linkRes.data : linkRes;
1388
- const link = typeof data === "string" ? data : data?.link;
1335
+ const link = unwrapSdkResult(await paystack?.subscription?.manageLink(subscriptionCode))?.link;
1389
1336
  if (link !== void 0 && link !== null && link !== "") emailToken = tryGetEmailTokenFromSubscriptionManageLink(link);
1390
1337
  } catch {}
1391
1338
  if (emailToken === void 0 || emailToken === null || emailToken === "") throw new APIError("BAD_REQUEST", { message: "Could not retrieve email_token for subscription enable." });
1392
- await paystack.subscriptionEnable({
1339
+ await paystack?.subscription?.enable({ body: {
1393
1340
  code: subscriptionCode,
1394
1341
  token: emailToken
1395
- });
1342
+ } });
1396
1343
  await ctx.context.adapter.update({
1397
1344
  model: "subscription",
1398
1345
  update: {
@@ -1409,12 +1356,12 @@ const enablePaystackSubscription = (options, path = "/paystack/enable-subscripti
1409
1356
  ctx.context.logger.error("Failed to enable subscription", error);
1410
1357
  throw new APIError("BAD_REQUEST", {
1411
1358
  code: "FAILED_TO_ENABLE_SUBSCRIPTION",
1412
- message: error?.message ?? PAYSTACK_ERROR_CODES.FAILED_TO_ENABLE_SUBSCRIPTION.message
1359
+ message: error instanceof Error ? error.message : PAYSTACK_ERROR_CODES.FAILED_TO_ENABLE_SUBSCRIPTION.message
1413
1360
  });
1414
1361
  }
1415
1362
  });
1416
1363
  };
1417
- const getSubscriptionManageLink = (options, path = "/paystack/get-subscription-manage-link") => {
1364
+ const getSubscriptionManageLink = (options, path = "/subscription-manage-link") => {
1418
1365
  const manageLinkQuerySchema = z.object({ subscriptionCode: z.string() });
1419
1366
  const useMiddlewares = options.subscription?.enabled === true ? [
1420
1367
  sessionMiddleware,
@@ -1429,13 +1376,11 @@ const getSubscriptionManageLink = (options, path = "/paystack/get-subscription-m
1429
1376
  });
1430
1377
  const paystack = getPaystackOps(options.paystackClient);
1431
1378
  try {
1432
- const res = unwrapSdkResult(await paystack.subscriptionManageLink(subscriptionCode));
1433
- 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;
1434
- const link = typeof data === "string" ? data : data?.link;
1435
- return ctx.json({ link });
1379
+ const res = unwrapSdkResult(await paystack?.subscription?.manageLink(subscriptionCode));
1380
+ return ctx.json({ link: res?.link || null });
1436
1381
  } catch (error) {
1437
1382
  ctx.context.logger.error("Failed to get subscription manage link", error);
1438
- throw new APIError("BAD_REQUEST", { message: error?.message ?? "Failed to get subscription manage link" });
1383
+ throw new APIError("BAD_REQUEST", { message: error instanceof Error ? error.message : "Failed to get subscription manage link" });
1439
1384
  }
1440
1385
  };
1441
1386
  return createAuthEndpoint(path, {
@@ -1444,71 +1389,8 @@ const getSubscriptionManageLink = (options, path = "/paystack/get-subscription-m
1444
1389
  use: useMiddlewares
1445
1390
  }, handler);
1446
1391
  };
1447
- const syncProducts = (options) => {
1448
- return createAuthEndpoint("/paystack/sync-products", {
1449
- method: "POST",
1450
- metadata: { ...HIDE_METADATA },
1451
- disableBody: true,
1452
- use: [sessionMiddleware]
1453
- }, async (ctx) => {
1454
- console.error("DEBUG: syncProducts endpoint hit!");
1455
- const paystack = getPaystackOps(options.paystackClient);
1456
- try {
1457
- const res = unwrapSdkResult(await paystack.productList());
1458
- const productsData = res !== null && typeof res === "object" && "status" in res && "data" in res ? res.data : res?.data ?? res;
1459
- if (!Array.isArray(productsData)) return ctx.json({
1460
- status: "success",
1461
- count: 0
1462
- });
1463
- for (const product of productsData) {
1464
- const paystackId = String(product.id);
1465
- const existing = await ctx.context.adapter.findOne({
1466
- model: "paystackProduct",
1467
- where: [{
1468
- field: "paystackId",
1469
- value: paystackId
1470
- }]
1471
- });
1472
- const productData = {
1473
- name: product.name,
1474
- description: product.description,
1475
- price: product.price,
1476
- currency: product.currency,
1477
- quantity: product.quantity,
1478
- unlimited: product.unlimited,
1479
- paystackId,
1480
- slug: product.slug ?? product.name.toLowerCase().replace(/\s+/g, "-"),
1481
- metadata: product.metadata ? JSON.stringify(product.metadata) : void 0,
1482
- updatedAt: /* @__PURE__ */ new Date()
1483
- };
1484
- if (existing) await ctx.context.adapter.update({
1485
- model: "paystackProduct",
1486
- update: productData,
1487
- where: [{
1488
- field: "id",
1489
- value: existing.id
1490
- }]
1491
- });
1492
- else await ctx.context.adapter.create({
1493
- model: "paystackProduct",
1494
- data: {
1495
- ...productData,
1496
- createdAt: /* @__PURE__ */ new Date()
1497
- }
1498
- });
1499
- }
1500
- return ctx.json({
1501
- status: "success",
1502
- count: productsData.length
1503
- });
1504
- } catch (error) {
1505
- ctx.context.logger.error("Failed to sync products", error);
1506
- throw new APIError("BAD_REQUEST", { message: error?.message ?? "Failed to sync products" });
1507
- }
1508
- });
1509
- };
1510
- const listProducts = (_options) => {
1511
- return createAuthEndpoint("/paystack/list-products", {
1392
+ const listProducts = (_options, path = "/list-products") => {
1393
+ return createAuthEndpoint(path, {
1512
1394
  method: "GET",
1513
1395
  metadata: { openapi: { operationId: "listPaystackProducts" } }
1514
1396
  }, async (ctx) => {
@@ -1516,69 +1398,8 @@ const listProducts = (_options) => {
1516
1398
  return ctx.json({ products: sorted });
1517
1399
  });
1518
1400
  };
1519
- const syncPlans = (options) => {
1520
- return createAuthEndpoint("/paystack/sync-plans", {
1521
- method: "POST",
1522
- metadata: { ...HIDE_METADATA },
1523
- disableBody: true,
1524
- use: [sessionMiddleware]
1525
- }, async (ctx) => {
1526
- const paystack = getPaystackOps(options.paystackClient);
1527
- try {
1528
- const res = unwrapSdkResult(await paystack.planList());
1529
- const plansData = res !== null && typeof res === "object" && "status" in res && "data" in res ? res.data : res?.data ?? res;
1530
- if (!Array.isArray(plansData)) return ctx.json({
1531
- status: "success",
1532
- count: 0
1533
- });
1534
- for (const plan of plansData) {
1535
- const paystackId = String(plan.id);
1536
- const existing = await ctx.context.adapter.findOne({
1537
- model: "paystackPlan",
1538
- where: [{
1539
- field: "paystackId",
1540
- value: paystackId
1541
- }]
1542
- });
1543
- const planData = {
1544
- name: plan.name,
1545
- description: plan.description,
1546
- amount: plan.amount,
1547
- currency: plan.currency,
1548
- interval: plan.interval,
1549
- planCode: plan.plan_code,
1550
- paystackId,
1551
- metadata: plan.metadata ? JSON.stringify(plan.metadata) : void 0,
1552
- updatedAt: /* @__PURE__ */ new Date()
1553
- };
1554
- if (existing) await ctx.context.adapter.update({
1555
- model: "paystackPlan",
1556
- update: planData,
1557
- where: [{
1558
- field: "id",
1559
- value: existing.id
1560
- }]
1561
- });
1562
- else await ctx.context.adapter.create({
1563
- model: "paystackPlan",
1564
- data: {
1565
- ...planData,
1566
- createdAt: /* @__PURE__ */ new Date()
1567
- }
1568
- });
1569
- }
1570
- return ctx.json({
1571
- status: "success",
1572
- count: plansData.length
1573
- });
1574
- } catch (error) {
1575
- ctx.context.logger.error("Failed to sync plans", error);
1576
- throw new APIError("BAD_REQUEST", { message: error?.message ?? "Failed to sync plans" });
1577
- }
1578
- });
1579
- };
1580
- const listPlans = (_options) => {
1581
- return createAuthEndpoint("/paystack/list-plans", {
1401
+ const listPlans = (_options, path = "/list-plans") => {
1402
+ return createAuthEndpoint(path, {
1582
1403
  method: "GET",
1583
1404
  metadata: { ...HIDE_METADATA },
1584
1405
  use: [sessionMiddleware]
@@ -1588,12 +1409,12 @@ const listPlans = (_options) => {
1588
1409
  return ctx.json({ plans });
1589
1410
  } catch (error) {
1590
1411
  ctx.context.logger.error("Failed to list plans", error);
1591
- throw new APIError("BAD_REQUEST", { message: error?.message ?? "Failed to list plans" });
1412
+ throw new APIError("BAD_REQUEST", { message: error instanceof Error ? error.message : "Failed to list plans" });
1592
1413
  }
1593
1414
  });
1594
1415
  };
1595
- const getConfig = (options) => {
1596
- return createAuthEndpoint("/paystack/get-config", {
1416
+ const getConfig = (options, path = "/get-config") => {
1417
+ return createAuthEndpoint(path, {
1597
1418
  method: "GET",
1598
1419
  metadata: { openapi: { operationId: "getPaystackConfig" } }
1599
1420
  }, async (ctx) => {
@@ -1605,104 +1426,6 @@ const getConfig = (options) => {
1605
1426
  });
1606
1427
  });
1607
1428
  };
1608
- const chargeRecurringSubscription = (options) => {
1609
- return createAuthEndpoint("/paystack/charge-recurring", {
1610
- method: "POST",
1611
- body: z.object({
1612
- subscriptionId: z.string(),
1613
- amount: z.number().optional()
1614
- })
1615
- }, async (ctx) => {
1616
- const { subscriptionId, amount: bodyAmount } = ctx.body;
1617
- const subscription = await ctx.context.adapter.findOne({
1618
- model: "subscription",
1619
- where: [{
1620
- field: "id",
1621
- value: subscriptionId
1622
- }]
1623
- });
1624
- if (subscription === null || subscription === void 0) throw new APIError("NOT_FOUND", { message: "Subscription not found" });
1625
- if (subscription.paystackAuthorizationCode === void 0 || subscription.paystackAuthorizationCode === null || subscription.paystackAuthorizationCode === "") throw new APIError("BAD_REQUEST", { message: "No authorization code found for this subscription" });
1626
- const plan = (await getPlans(options.subscription)).find((p) => p.name.toLowerCase() === subscription.plan.toLowerCase());
1627
- if (plan === void 0 || plan === null) throw new APIError("NOT_FOUND", { message: "Plan not found" });
1628
- const amount = bodyAmount ?? plan.amount;
1629
- if (amount === void 0 || amount === null) throw new APIError("BAD_REQUEST", { message: "Plan amount is not defined" });
1630
- let email;
1631
- if (subscription.referenceId !== void 0 && subscription.referenceId !== null && subscription.referenceId !== "") {
1632
- const user = await ctx.context.adapter.findOne({
1633
- model: "user",
1634
- where: [{
1635
- field: "id",
1636
- value: subscription.referenceId
1637
- }]
1638
- });
1639
- if (user !== void 0 && user !== null) email = user.email;
1640
- else if (options.organization?.enabled === true) {
1641
- const ownerMember = await ctx.context.adapter.findOne({
1642
- model: "member",
1643
- where: [{
1644
- field: "organizationId",
1645
- value: subscription.referenceId
1646
- }, {
1647
- field: "role",
1648
- value: "owner"
1649
- }]
1650
- });
1651
- if (ownerMember !== void 0 && ownerMember !== null) email = (await ctx.context.adapter.findOne({
1652
- model: "user",
1653
- where: [{
1654
- field: "id",
1655
- value: ownerMember.userId
1656
- }]
1657
- }))?.email;
1658
- }
1659
- }
1660
- if (email === void 0 || email === null || email === "") throw new APIError("NOT_FOUND", { message: "User email not found" });
1661
- const finalCurrency = plan.currency ?? "NGN";
1662
- if (!validateMinAmount(amount, finalCurrency)) throw new APIError("BAD_REQUEST", {
1663
- message: `Amount ${amount} is less than the minimum required for ${finalCurrency}.`,
1664
- status: 400
1665
- });
1666
- const data = unwrapSdkResult(await getPaystackOps(options.paystackClient).transactionChargeAuthorization({
1667
- email,
1668
- amount,
1669
- authorization_code: subscription.paystackAuthorizationCode,
1670
- currency: plan.currency,
1671
- metadata: {
1672
- subscriptionId,
1673
- referenceId: subscription.referenceId,
1674
- plan: plan.name
1675
- }
1676
- }));
1677
- const chargeData = data?.data ?? data;
1678
- if (chargeData?.status === "success") {
1679
- const now = /* @__PURE__ */ new Date();
1680
- const nextPeriodEnd = getNextPeriodEnd(now, plan.interval ?? "monthly");
1681
- await ctx.context.adapter.update({
1682
- model: "subscription",
1683
- update: {
1684
- periodStart: now,
1685
- periodEnd: nextPeriodEnd,
1686
- updatedAt: now,
1687
- paystackTransactionReference: chargeData.reference
1688
- },
1689
- where: [{
1690
- field: "id",
1691
- value: subscription.id
1692
- }]
1693
- });
1694
- return ctx.json({
1695
- status: "success",
1696
- data: chargeData
1697
- });
1698
- }
1699
- return ctx.json({
1700
- status: "failed",
1701
- data: chargeData
1702
- }, { status: 400 });
1703
- });
1704
- };
1705
-
1706
1429
  //#endregion
1707
1430
  //#region src/schema.ts
1708
1431
  const transactions = { paystackTransaction: { fields: {
@@ -1965,60 +1688,267 @@ const getSchema = (options) => {
1965
1688
  }
1966
1689
  return mergeSchema(baseSchema, options.schema);
1967
1690
  };
1968
-
1691
+ //#endregion
1692
+ //#region src/operations.ts
1693
+ async function syncPaystackProducts(ctx, options) {
1694
+ const paystack = getPaystackOps(options.paystackClient);
1695
+ try {
1696
+ const productsData = unwrapSdkResult(await paystack?.product?.list({}));
1697
+ if (!Array.isArray(productsData)) return {
1698
+ status: "success",
1699
+ count: 0
1700
+ };
1701
+ for (const product of productsData) {
1702
+ const paystackId = String(product.id);
1703
+ const existing = await ctx.context.adapter.findOne({
1704
+ model: "paystackProduct",
1705
+ where: [{
1706
+ field: "paystackId",
1707
+ value: paystackId
1708
+ }]
1709
+ });
1710
+ const productFields = {
1711
+ name: product.name ?? "",
1712
+ description: product.description ?? "",
1713
+ price: product.price ?? 0,
1714
+ currency: product.currency ?? "",
1715
+ quantity: product.quantity ?? 0,
1716
+ unlimited: product.unlimited !== void 0 && product.unlimited !== null && product.unlimited !== false,
1717
+ paystackId,
1718
+ slug: product.slug ?? product.name?.toLowerCase().replace(/\s+/g, "-") ?? "",
1719
+ metadata: product.metadata !== void 0 && product.metadata !== null ? JSON.stringify(product.metadata) : void 0,
1720
+ updatedAt: /* @__PURE__ */ new Date()
1721
+ };
1722
+ if (existing !== void 0 && existing !== null) await ctx.context.adapter.update({
1723
+ model: "paystackProduct",
1724
+ update: productFields,
1725
+ where: [{
1726
+ field: "id",
1727
+ value: String(existing.id)
1728
+ }]
1729
+ });
1730
+ else await ctx.context.adapter.create({
1731
+ model: "paystackProduct",
1732
+ data: {
1733
+ ...productFields,
1734
+ createdAt: /* @__PURE__ */ new Date()
1735
+ }
1736
+ });
1737
+ }
1738
+ return {
1739
+ status: "success",
1740
+ count: productsData.length
1741
+ };
1742
+ } catch (error) {
1743
+ ctx.context.logger.error("Failed to sync products", error);
1744
+ throw new APIError("BAD_REQUEST", { message: error instanceof Error ? error.message : "Failed to sync products" });
1745
+ }
1746
+ }
1747
+ async function syncPaystackPlans(ctx, options) {
1748
+ const paystack = getPaystackOps(options.paystackClient);
1749
+ try {
1750
+ const plansData = unwrapSdkResult(await paystack?.plan?.list());
1751
+ if (!Array.isArray(plansData)) return {
1752
+ status: "success",
1753
+ count: 0
1754
+ };
1755
+ for (const plan of plansData) {
1756
+ const paystackId = String(plan.id);
1757
+ const existing = await ctx.context.adapter.findOne({
1758
+ model: "paystackPlan",
1759
+ where: [{
1760
+ field: "paystackId",
1761
+ value: paystackId
1762
+ }]
1763
+ });
1764
+ const planData = {
1765
+ name: plan.name ?? "",
1766
+ description: plan.description ?? "",
1767
+ amount: plan.amount ?? 0,
1768
+ currency: plan.currency ?? "",
1769
+ interval: plan.interval ?? "",
1770
+ planCode: plan.plan_code ?? "",
1771
+ paystackId,
1772
+ metadata: plan.metadata !== void 0 && plan.metadata !== null ? JSON.stringify(plan.metadata) : void 0,
1773
+ updatedAt: /* @__PURE__ */ new Date()
1774
+ };
1775
+ if (existing !== void 0 && existing !== null) await ctx.context.adapter.update({
1776
+ model: "paystackPlan",
1777
+ update: planData,
1778
+ where: [{
1779
+ field: "id",
1780
+ value: existing.id
1781
+ }]
1782
+ });
1783
+ else await ctx.context.adapter.create({
1784
+ model: "paystackPlan",
1785
+ data: {
1786
+ ...planData,
1787
+ createdAt: /* @__PURE__ */ new Date()
1788
+ }
1789
+ });
1790
+ }
1791
+ return {
1792
+ status: "success",
1793
+ count: plansData.length
1794
+ };
1795
+ } catch (error) {
1796
+ ctx.context.logger.error("Failed to sync plans", error);
1797
+ throw new APIError("BAD_REQUEST", { message: error instanceof Error ? error.message : "Failed to sync plans" });
1798
+ }
1799
+ }
1800
+ async function chargeSubscriptionRenewal(ctx, options, input) {
1801
+ const { subscriptionId, amount: bodyAmount } = input;
1802
+ const subscription = await ctx.context.adapter.findOne({
1803
+ model: "subscription",
1804
+ where: [{
1805
+ field: "id",
1806
+ value: subscriptionId
1807
+ }]
1808
+ });
1809
+ if (subscription === void 0 || subscription === null) throw new APIError("NOT_FOUND", { message: "Subscription not found" });
1810
+ if (subscription.paystackAuthorizationCode === void 0 || subscription.paystackAuthorizationCode === null || subscription.paystackAuthorizationCode === "") throw new APIError("BAD_REQUEST", { message: "No authorization code found for this subscription" });
1811
+ const plan = (await getPlans(options.subscription)).find((candidate) => candidate.name.toLowerCase() === subscription.plan.toLowerCase());
1812
+ if (plan === void 0 || plan === null) throw new APIError("NOT_FOUND", { message: "Plan not found" });
1813
+ const amount = bodyAmount ?? plan.amount;
1814
+ if (amount === void 0 || amount === null) throw new APIError("BAD_REQUEST", { message: "Plan amount is not defined" });
1815
+ let email;
1816
+ const referenceId = subscription.referenceId;
1817
+ if (referenceId !== void 0 && referenceId !== null && referenceId !== "") {
1818
+ const user = await ctx.context.adapter.findOne({
1819
+ model: "user",
1820
+ where: [{
1821
+ field: "id",
1822
+ value: referenceId
1823
+ }]
1824
+ });
1825
+ if (user !== void 0 && user !== null) email = user.email;
1826
+ else if (options.organization?.enabled === true) {
1827
+ const ownerMember = await ctx.context.adapter.findOne({
1828
+ model: "member",
1829
+ where: [{
1830
+ field: "organizationId",
1831
+ value: referenceId
1832
+ }, {
1833
+ field: "role",
1834
+ value: "owner"
1835
+ }]
1836
+ });
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;
1844
+ }
1845
+ }
1846
+ if (email === void 0 || email === null || email === "") throw new APIError("NOT_FOUND", { message: "User email not found" });
1847
+ const finalCurrency = plan.currency ?? "NGN";
1848
+ if (!validateMinAmount(amount, finalCurrency)) throw new APIError("BAD_REQUEST", {
1849
+ message: `Amount ${amount} is less than the minimum required for ${finalCurrency}.`,
1850
+ status: 400
1851
+ });
1852
+ const chargeData = unwrapSdkResult(await getPaystackOps(options.paystackClient)?.transaction?.chargeAuthorization({ body: {
1853
+ email,
1854
+ amount,
1855
+ authorization_code: subscription.paystackAuthorizationCode,
1856
+ reference: `rec_${subscription.id}_${Date.now()}`,
1857
+ metadata: {
1858
+ subscriptionId,
1859
+ referenceId
1860
+ }
1861
+ } }));
1862
+ if (chargeData?.status === "success" && chargeData.reference !== void 0) {
1863
+ const now = /* @__PURE__ */ new Date();
1864
+ const nextPeriodEnd = getNextPeriodEnd(now, plan.interval ?? "monthly");
1865
+ await ctx.context.adapter.update({
1866
+ model: "subscription",
1867
+ update: {
1868
+ periodStart: now,
1869
+ periodEnd: nextPeriodEnd,
1870
+ updatedAt: now,
1871
+ paystackTransactionReference: chargeData.reference
1872
+ },
1873
+ where: [{
1874
+ field: "id",
1875
+ value: subscription.id
1876
+ }]
1877
+ });
1878
+ return {
1879
+ status: "success",
1880
+ data: chargeData
1881
+ };
1882
+ }
1883
+ return {
1884
+ status: "failed",
1885
+ data: chargeData
1886
+ };
1887
+ }
1969
1888
  //#endregion
1970
1889
  //#region src/index.ts
1971
- const INTERNAL_ERROR_CODES = defineErrorCodes({ ...Object.fromEntries(Object.entries(PAYSTACK_ERROR_CODES).map(([key, value]) => [key, typeof value === "string" ? value : value.message])) });
1890
+ const INTERNAL_ERROR_CODES = defineErrorCodes(Object.fromEntries(Object.entries(PAYSTACK_ERROR_CODES).map(([key, value]) => [key, typeof value === "string" ? value : value.message])));
1972
1891
  const paystack = (options) => {
1973
1892
  const routeOptions = options;
1974
1893
  return {
1975
1894
  id: "paystack",
1976
1895
  endpoints: {
1977
- initializeTransaction: initializeTransaction(routeOptions),
1978
- verifyTransaction: verifyTransaction(routeOptions),
1979
- listSubscriptions: listSubscriptions(routeOptions),
1980
- paystackWebhook: paystackWebhook(routeOptions),
1981
- listTransactions: listTransactions(routeOptions),
1982
- getConfig: getConfig(routeOptions),
1983
- disableSubscription: disablePaystackSubscription(routeOptions),
1984
- enableSubscription: enablePaystackSubscription(routeOptions),
1985
- getSubscriptionManageLink: getSubscriptionManageLink(routeOptions),
1896
+ initializeTransaction: initializeTransaction(routeOptions, "/paystack/initialize-transaction"),
1897
+ verifyTransaction: verifyTransaction(routeOptions, "/paystack/verify-transaction"),
1898
+ listSubscriptions: listSubscriptions(routeOptions, "/paystack/list-subscriptions"),
1899
+ paystackWebhook: paystackWebhook(routeOptions, "/paystack/webhook"),
1900
+ listTransactions: listTransactions(routeOptions, "/paystack/list-transactions"),
1901
+ getConfig: getConfig(routeOptions, "/paystack/config"),
1902
+ disableSubscription: disablePaystackSubscription(routeOptions, "/paystack/disable-subscription"),
1903
+ enableSubscription: enablePaystackSubscription(routeOptions, "/paystack/enable-subscription"),
1904
+ getSubscriptionManageLink: getSubscriptionManageLink(routeOptions, "/paystack/subscription-manage-link"),
1986
1905
  subscriptionManageLink: getSubscriptionManageLink(routeOptions, "/paystack/subscription/manage-link"),
1987
- createSubscription: createSubscription(routeOptions),
1988
- upgradeSubscription: upgradeSubscription(routeOptions),
1989
- cancelSubscription: cancelSubscription(routeOptions),
1990
- restoreSubscription: restoreSubscription(routeOptions),
1991
- chargeRecurringSubscription: chargeRecurringSubscription(routeOptions),
1992
- syncProducts: syncProducts(routeOptions),
1993
- listProducts: listProducts(routeOptions),
1994
- syncPlans: syncPlans(routeOptions),
1995
- listPlans: listPlans(routeOptions)
1906
+ createSubscription: createSubscription(routeOptions, "/paystack/create-subscription"),
1907
+ upgradeSubscription: upgradeSubscription(routeOptions, "/paystack/upgrade-subscription"),
1908
+ cancelSubscription: cancelSubscription(routeOptions, "/paystack/cancel-subscription"),
1909
+ restoreSubscription: restoreSubscription(routeOptions, "/paystack/restore-subscription"),
1910
+ listProducts: listProducts(routeOptions, "/paystack/list-products"),
1911
+ listPlans: listPlans(routeOptions, "/paystack/list-plans")
1996
1912
  },
1997
1913
  schema: getSchema(options),
1998
1914
  init: (ctx) => {
1999
1915
  return { options: {
2000
1916
  databaseHooks: {
2001
1917
  user: { create: { async after(user, hookCtx) {
2002
- if (hookCtx === void 0 || hookCtx === null || options.createCustomerOnSignUp !== true) return;
2003
- const sdkRes = unwrapSdkResult(await getPaystackOps(options.paystackClient).customerCreate({
2004
- email: user.email,
2005
- first_name: user.name ?? void 0,
2006
- metadata: { userId: user.id }
2007
- }));
2008
- const customerCode = sdkRes?.customer_code ?? (sdkRes?.data)?.customer_code;
2009
- if (customerCode === void 0 || customerCode === null) return;
2010
- await ctx.adapter.update({
2011
- model: "user",
2012
- where: [{
2013
- field: "id",
2014
- value: user.id
2015
- }],
2016
- update: { paystackCustomerCode: customerCode }
2017
- });
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);
1947
+ }
2018
1948
  } } },
2019
1949
  organization: options.organization?.enabled === true ? { create: { async after(org, hookCtx) {
2020
1950
  try {
2021
- const extraCreateParams = options.organization?.getCustomerCreateParams ? await options.organization.getCustomerCreateParams(org, hookCtx) : {};
1951
+ const extraCreateParams = typeof options.organization?.getCustomerCreateParams === "function" ? await options.organization.getCustomerCreateParams(org, hookCtx) : {};
2022
1952
  let targetEmail = org.email;
2023
1953
  if (targetEmail === void 0 || targetEmail === null) {
2024
1954
  const ownerMember = await ctx.adapter.findOne({
@@ -2045,17 +1975,20 @@ const paystack = (options) => {
2045
1975
  first_name: org.name,
2046
1976
  metadata: { organizationId: org.id }
2047
1977
  }, extraCreateParams);
2048
- const sdkRes = unwrapSdkResult(await getPaystackOps(options.paystackClient).customerCreate(params));
2049
- const customerCode = sdkRes?.customer_code ?? (sdkRes?.data)?.customer_code;
2050
- if (customerCode === void 0 || customerCode === null) return;
2051
- await ctx.internalAdapter.updateOrganization(org.id, { paystackCustomerCode: customerCode });
2052
- await options.organization?.onCustomerCreate?.({
2053
- paystackCustomer: sdkRes,
2054
- organization: {
2055
- ...org,
2056
- paystackCustomerCode: customerCode
2057
- }
2058
- }, hookCtx);
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
+ }
2059
1992
  } catch (error) {
2060
1993
  ctx.logger.error("Failed to create Paystack customer for organization", error);
2061
1994
  }
@@ -2067,11 +2000,11 @@ const paystack = (options) => {
2067
2000
  if (options.subscription?.enabled === true && member.organizationId && ctx !== null && ctx !== void 0) await checkSeatLimit(ctx, member.organizationId);
2068
2001
  },
2069
2002
  after: async (member, ctx) => {
2070
- if (options.subscription?.enabled === true && member?.organizationId !== void 0 && member?.organizationId !== null && ctx !== void 0 && ctx !== null) await syncSubscriptionSeats(ctx, member.organizationId, options);
2003
+ if (options.subscription?.enabled === true && typeof member?.organizationId === "string" && ctx) await syncSubscriptionSeats(ctx, member.organizationId, routeOptions);
2071
2004
  }
2072
2005
  },
2073
2006
  delete: { after: async (member, ctx) => {
2074
- if (options.subscription?.enabled === true && member?.organizationId !== void 0 && member?.organizationId !== null && ctx !== void 0 && ctx !== null) await syncSubscriptionSeats(ctx, member.organizationId, options);
2007
+ if (options.subscription?.enabled === true && typeof member?.organizationId === "string" && ctx) await syncSubscriptionSeats(ctx, member.organizationId, routeOptions);
2075
2008
  } }
2076
2009
  },
2077
2010
  invitation: {
@@ -2080,28 +2013,29 @@ const paystack = (options) => {
2080
2013
  if (options.subscription?.enabled === true && invitation.organizationId && ctx !== null && ctx !== void 0) await checkSeatLimit(ctx, invitation.organizationId);
2081
2014
  },
2082
2015
  after: async (invitation, ctx) => {
2083
- if (options.subscription?.enabled === true && invitation?.organizationId !== void 0 && invitation?.organizationId !== null && ctx !== void 0 && ctx !== null) await syncSubscriptionSeats(ctx, invitation.organizationId, options);
2016
+ if (options.subscription?.enabled === true && typeof invitation?.organizationId === "string" && ctx) await syncSubscriptionSeats(ctx, invitation.organizationId, routeOptions);
2084
2017
  }
2085
2018
  },
2086
2019
  delete: { after: async (invitation, ctx) => {
2087
- if (options.subscription?.enabled === true && invitation?.organizationId !== void 0 && invitation?.organizationId !== null && ctx !== void 0 && ctx !== null) await syncSubscriptionSeats(ctx, invitation.organizationId, options);
2020
+ if (options.subscription?.enabled === true && typeof invitation?.organizationId === "string" && ctx) await syncSubscriptionSeats(ctx, invitation.organizationId, routeOptions);
2088
2021
  } }
2089
2022
  },
2090
2023
  team: { create: { before: async (team, ctx) => {
2091
- if (options.subscription?.enabled === true && team.organizationId && ctx !== null && ctx !== void 0) {
2024
+ if (options.subscription?.enabled === true && team.organizationId && ctx) {
2092
2025
  const subscription = await getOrganizationSubscription(ctx, team.organizationId);
2093
2026
  if (subscription !== null && subscription !== void 0) {
2094
- const maxTeams = ((await getPlanByName(options, subscription.plan))?.limits)?.teams;
2027
+ const maxTeams = ((await getPlanByName(routeOptions, subscription.plan))?.limits)?.teams;
2095
2028
  if (typeof maxTeams === "number") await checkTeamLimit(ctx, team.organizationId, maxTeams);
2096
2029
  }
2097
2030
  }
2098
2031
  } } }
2099
2032
  } };
2100
2033
  },
2101
- $ERROR_CODES: INTERNAL_ERROR_CODES
2034
+ $ERROR_CODES: INTERNAL_ERROR_CODES,
2035
+ options
2102
2036
  };
2103
2037
  };
2104
-
2105
2038
  //#endregion
2106
- export { paystack };
2039
+ export { chargeSubscriptionRenewal, paystack, syncPaystackPlans, syncPaystackProducts };
2040
+
2107
2041
  //# sourceMappingURL=index.mjs.map