@alexasomba/better-auth-paystack 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,296 +1,11 @@
1
+ import { defineErrorCodes } from "@better-auth/core/utils";
1
2
  import { defu } from "defu";
2
- import { createEndpoint, createMiddleware } from "better-call";
3
+ import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api";
3
4
  import { HIDE_METADATA, logger } from "better-auth";
4
5
  import { APIError, getSessionFromCtx, originCheck, sessionMiddleware } from "better-auth/api";
5
6
  import * as z from "zod/v4";
6
7
  import { mergeSchema } from "better-auth/db";
7
8
 
8
- //#region node_modules/.pnpm/@better-auth+core@1.4.7_@better-auth+utils@0.3.0_@better-fetch+fetch@1.1.21_better-call_d24665956e428978271bbb947eda0893/node_modules/@better-auth/core/dist/env-DbssmzoK.mjs
9
- const _envShim = Object.create(null);
10
- const _getEnv = (useShim) => globalThis.process?.env || globalThis.Deno?.env.toObject() || globalThis.__env__ || (useShim ? _envShim : globalThis);
11
- const env = new Proxy(_envShim, {
12
- get(_, prop) {
13
- return _getEnv()[prop] ?? _envShim[prop];
14
- },
15
- has(_, prop) {
16
- return prop in _getEnv() || prop in _envShim;
17
- },
18
- set(_, prop, value) {
19
- const env$1 = _getEnv(true);
20
- env$1[prop] = value;
21
- return true;
22
- },
23
- deleteProperty(_, prop) {
24
- if (!prop) return false;
25
- const env$1 = _getEnv(true);
26
- delete env$1[prop];
27
- return true;
28
- },
29
- ownKeys() {
30
- const env$1 = _getEnv(true);
31
- return Object.keys(env$1);
32
- }
33
- });
34
- const nodeENV = typeof process !== "undefined" && process.env && process.env.NODE_ENV || "";
35
- /**
36
- * Get environment variable with fallback
37
- */
38
- function getEnvVar(key, fallback) {
39
- if (typeof process !== "undefined" && process.env) return process.env[key] ?? fallback;
40
- if (typeof Deno !== "undefined") return Deno.env.get(key) ?? fallback;
41
- if (typeof Bun !== "undefined") return Bun.env[key] ?? fallback;
42
- return fallback;
43
- }
44
- /**
45
- * Common environment variables used in Better Auth
46
- */
47
- const ENV = Object.freeze({
48
- get BETTER_AUTH_SECRET() {
49
- return getEnvVar("BETTER_AUTH_SECRET");
50
- },
51
- get AUTH_SECRET() {
52
- return getEnvVar("AUTH_SECRET");
53
- },
54
- get BETTER_AUTH_TELEMETRY() {
55
- return getEnvVar("BETTER_AUTH_TELEMETRY");
56
- },
57
- get BETTER_AUTH_TELEMETRY_ID() {
58
- return getEnvVar("BETTER_AUTH_TELEMETRY_ID");
59
- },
60
- get NODE_ENV() {
61
- return getEnvVar("NODE_ENV", "development");
62
- },
63
- get PACKAGE_VERSION() {
64
- return getEnvVar("PACKAGE_VERSION", "0.0.0");
65
- },
66
- get BETTER_AUTH_TELEMETRY_ENDPOINT() {
67
- return getEnvVar("BETTER_AUTH_TELEMETRY_ENDPOINT", "https://telemetry.better-auth.com/v1/track");
68
- }
69
- });
70
- const COLORS_2 = 1;
71
- const COLORS_16 = 4;
72
- const COLORS_256 = 8;
73
- const COLORS_16m = 24;
74
- const TERM_ENVS = {
75
- eterm: COLORS_16,
76
- cons25: COLORS_16,
77
- console: COLORS_16,
78
- cygwin: COLORS_16,
79
- dtterm: COLORS_16,
80
- gnome: COLORS_16,
81
- hurd: COLORS_16,
82
- jfbterm: COLORS_16,
83
- konsole: COLORS_16,
84
- kterm: COLORS_16,
85
- mlterm: COLORS_16,
86
- mosh: COLORS_16m,
87
- putty: COLORS_16,
88
- st: COLORS_16,
89
- "rxvt-unicode-24bit": COLORS_16m,
90
- terminator: COLORS_16m,
91
- "xterm-kitty": COLORS_16m
92
- };
93
- const CI_ENVS_MAP = new Map(Object.entries({
94
- APPVEYOR: COLORS_256,
95
- BUILDKITE: COLORS_256,
96
- CIRCLECI: COLORS_16m,
97
- DRONE: COLORS_256,
98
- GITEA_ACTIONS: COLORS_16m,
99
- GITHUB_ACTIONS: COLORS_16m,
100
- GITLAB_CI: COLORS_256,
101
- TRAVIS: COLORS_256
102
- }));
103
- const TERM_ENVS_REG_EXP = [
104
- /ansi/,
105
- /color/,
106
- /linux/,
107
- /direct/,
108
- /^con[0-9]*x[0-9]/,
109
- /^rxvt/,
110
- /^screen/,
111
- /^xterm/,
112
- /^vt100/,
113
- /^vt220/
114
- ];
115
- function getColorDepth() {
116
- if (getEnvVar("FORCE_COLOR") !== void 0) switch (getEnvVar("FORCE_COLOR")) {
117
- case "":
118
- case "1":
119
- case "true": return COLORS_16;
120
- case "2": return COLORS_256;
121
- case "3": return COLORS_16m;
122
- default: return COLORS_2;
123
- }
124
- if (getEnvVar("NODE_DISABLE_COLORS") !== void 0 && getEnvVar("NODE_DISABLE_COLORS") !== "" || getEnvVar("NO_COLOR") !== void 0 && getEnvVar("NO_COLOR") !== "" || getEnvVar("TERM") === "dumb") return COLORS_2;
125
- if (getEnvVar("TMUX")) return COLORS_16m;
126
- if ("TF_BUILD" in env && "AGENT_NAME" in env) return COLORS_16;
127
- if ("CI" in env) {
128
- for (const { 0: envName, 1: colors } of CI_ENVS_MAP) if (envName in env) return colors;
129
- if (getEnvVar("CI_NAME") === "codeship") return COLORS_256;
130
- return COLORS_2;
131
- }
132
- if ("TEAMCITY_VERSION" in env) return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.exec(getEnvVar("TEAMCITY_VERSION")) !== null ? COLORS_16 : COLORS_2;
133
- switch (getEnvVar("TERM_PROGRAM")) {
134
- case "iTerm.app":
135
- if (!getEnvVar("TERM_PROGRAM_VERSION") || /^[0-2]\./.exec(getEnvVar("TERM_PROGRAM_VERSION")) !== null) return COLORS_256;
136
- return COLORS_16m;
137
- case "HyperTerm":
138
- case "MacTerm": return COLORS_16m;
139
- case "Apple_Terminal": return COLORS_256;
140
- }
141
- if (getEnvVar("COLORTERM") === "truecolor" || getEnvVar("COLORTERM") === "24bit") return COLORS_16m;
142
- if (getEnvVar("TERM")) {
143
- if (/truecolor/.exec(getEnvVar("TERM")) !== null) return COLORS_16m;
144
- if (/^xterm-256/.exec(getEnvVar("TERM")) !== null) return COLORS_256;
145
- const termEnv = getEnvVar("TERM").toLowerCase();
146
- if (TERM_ENVS[termEnv]) return TERM_ENVS[termEnv];
147
- if (TERM_ENVS_REG_EXP.some((term) => term.exec(termEnv) !== null)) return COLORS_16;
148
- }
149
- if (getEnvVar("COLORTERM")) return COLORS_16;
150
- return COLORS_2;
151
- }
152
- const TTY_COLORS = {
153
- reset: "\x1B[0m",
154
- bright: "\x1B[1m",
155
- dim: "\x1B[2m",
156
- undim: "\x1B[22m",
157
- underscore: "\x1B[4m",
158
- blink: "\x1B[5m",
159
- reverse: "\x1B[7m",
160
- hidden: "\x1B[8m",
161
- fg: {
162
- black: "\x1B[30m",
163
- red: "\x1B[31m",
164
- green: "\x1B[32m",
165
- yellow: "\x1B[33m",
166
- blue: "\x1B[34m",
167
- magenta: "\x1B[35m",
168
- cyan: "\x1B[36m",
169
- white: "\x1B[37m"
170
- },
171
- bg: {
172
- black: "\x1B[40m",
173
- red: "\x1B[41m",
174
- green: "\x1B[42m",
175
- yellow: "\x1B[43m",
176
- blue: "\x1B[44m",
177
- magenta: "\x1B[45m",
178
- cyan: "\x1B[46m",
179
- white: "\x1B[47m"
180
- }
181
- };
182
- const levels = [
183
- "debug",
184
- "info",
185
- "success",
186
- "warn",
187
- "error"
188
- ];
189
- function shouldPublishLog(currentLogLevel, logLevel) {
190
- return levels.indexOf(logLevel) >= levels.indexOf(currentLogLevel);
191
- }
192
- const levelColors = {
193
- info: TTY_COLORS.fg.blue,
194
- success: TTY_COLORS.fg.green,
195
- warn: TTY_COLORS.fg.yellow,
196
- error: TTY_COLORS.fg.red,
197
- debug: TTY_COLORS.fg.magenta
198
- };
199
- const formatMessage = (level, message, colorsEnabled) => {
200
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
201
- if (colorsEnabled) return `${TTY_COLORS.dim}${timestamp}${TTY_COLORS.reset} ${levelColors[level]}${level.toUpperCase()}${TTY_COLORS.reset} ${TTY_COLORS.bright}[Better Auth]:${TTY_COLORS.reset} ${message}`;
202
- return `${timestamp} ${level.toUpperCase()} [Better Auth]: ${message}`;
203
- };
204
- const createLogger = (options) => {
205
- const enabled = options?.disabled !== true;
206
- const logLevel = options?.level ?? "error";
207
- const colorsEnabled = options?.disableColors !== void 0 ? !options.disableColors : getColorDepth() !== 1;
208
- const LogFunc = (level, message, args = []) => {
209
- if (!enabled || !shouldPublishLog(logLevel, level)) return;
210
- const formattedMessage = formatMessage(level, message, colorsEnabled);
211
- if (!options || typeof options.log !== "function") {
212
- if (level === "error") console.error(formattedMessage, ...args);
213
- else if (level === "warn") console.warn(formattedMessage, ...args);
214
- else console.log(formattedMessage, ...args);
215
- return;
216
- }
217
- options.log(level === "success" ? "info" : level, message, ...args);
218
- };
219
- return {
220
- ...Object.fromEntries(levels.map((level) => [level, (...[message, ...args]) => LogFunc(level, message, args)])),
221
- get level() {
222
- return logLevel;
223
- }
224
- };
225
- };
226
- const logger$1 = createLogger();
227
-
228
- //#endregion
229
- //#region node_modules/.pnpm/@better-auth+core@1.4.7_@better-auth+utils@0.3.0_@better-fetch+fetch@1.1.21_better-call_d24665956e428978271bbb947eda0893/node_modules/@better-auth/core/dist/utils-NloIXYE0.mjs
230
- function defineErrorCodes(codes) {
231
- return codes;
232
- }
233
-
234
- //#endregion
235
- //#region node_modules/.pnpm/@better-auth+core@1.4.7_@better-auth+utils@0.3.0_@better-fetch+fetch@1.1.21_better-call_d24665956e428978271bbb947eda0893/node_modules/@better-auth/core/dist/async_hooks/index.mjs
236
- const AsyncLocalStoragePromise = import(
237
- /* @vite-ignore */
238
- /* webpackIgnore: true */
239
- "node:async_hooks"
240
- ).then((mod) => mod.AsyncLocalStorage).catch((err) => {
241
- if ("AsyncLocalStorage" in globalThis) return globalThis.AsyncLocalStorage;
242
- if (typeof window !== "undefined") return null;
243
- console.warn("[better-auth] Warning: AsyncLocalStorage is not available in this environment. Some features may not work as expected.");
244
- console.warn("[better-auth] Please read more about this warning at https://better-auth.com/docs/installation#mount-handler");
245
- console.warn("[better-auth] If you are using Cloudflare Workers, please see: https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag");
246
- throw err;
247
- });
248
- async function getAsyncLocalStorage() {
249
- const mod = await AsyncLocalStoragePromise;
250
- if (mod === null) throw new Error("getAsyncLocalStorage is only available in server code");
251
- else return mod;
252
- }
253
-
254
- //#endregion
255
- //#region node_modules/.pnpm/@better-auth+core@1.4.7_@better-auth+utils@0.3.0_@better-fetch+fetch@1.1.21_better-call_d24665956e428978271bbb947eda0893/node_modules/@better-auth/core/dist/context-DblZrIwO.mjs
256
- let currentContextAsyncStorage = null;
257
- const ensureAsyncStorage$2 = async () => {
258
- if (!currentContextAsyncStorage) currentContextAsyncStorage = new (await (getAsyncLocalStorage()))();
259
- return currentContextAsyncStorage;
260
- };
261
- async function runWithEndpointContext(context, fn) {
262
- return (await ensureAsyncStorage$2()).run(context, fn);
263
- }
264
-
265
- //#endregion
266
- //#region node_modules/.pnpm/@better-auth+core@1.4.7_@better-auth+utils@0.3.0_@better-fetch+fetch@1.1.21_better-call_d24665956e428978271bbb947eda0893/node_modules/@better-auth/core/dist/api/index.mjs
267
- const optionsMiddleware = createMiddleware(async () => {
268
- /**
269
- * This will be passed on the instance of
270
- * the context. Used to infer the type
271
- * here.
272
- */
273
- return {};
274
- });
275
- const createAuthMiddleware = createMiddleware.create({ use: [optionsMiddleware, createMiddleware(async () => {
276
- return {};
277
- })] });
278
- const use = [optionsMiddleware];
279
- function createAuthEndpoint(pathOrOptions, handlerOrOptions, handlerOrNever) {
280
- const path = typeof pathOrOptions === "string" ? pathOrOptions : void 0;
281
- const options = typeof handlerOrOptions === "object" ? handlerOrOptions : pathOrOptions;
282
- const handler = typeof handlerOrOptions === "function" ? handlerOrOptions : handlerOrNever;
283
- if (path) return createEndpoint(path, {
284
- ...options,
285
- use: [...options?.use || [], ...use]
286
- }, async (ctx) => runWithEndpointContext(ctx, () => handler(ctx)));
287
- return createEndpoint({
288
- ...options,
289
- use: [...options?.use || [], ...use]
290
- }, async (ctx) => runWithEndpointContext(ctx, () => handler(ctx)));
291
- }
292
-
293
- //#endregion
294
9
  //#region src/utils.ts
295
10
  async function getPlans(subscriptionOptions) {
296
11
  if (subscriptionOptions?.enabled) return typeof subscriptionOptions.plans === "function" ? await subscriptionOptions.plans() : subscriptionOptions.plans;
@@ -299,6 +14,13 @@ async function getPlans(subscriptionOptions) {
299
14
  async function getPlanByName(options, name) {
300
15
  return await getPlans(options.subscription).then((plans) => plans?.find((plan) => plan.name.toLowerCase() === name.toLowerCase()));
301
16
  }
17
+ async function getProducts(productOptions) {
18
+ if (productOptions?.products) return typeof productOptions.products === "function" ? await productOptions.products() : productOptions.products;
19
+ return [];
20
+ }
21
+ async function getProductByName(options, name) {
22
+ return await getProducts(options.products).then((products) => products?.find((product) => product.name.toLowerCase() === name.toLowerCase()));
23
+ }
302
24
 
303
25
  //#endregion
304
26
  //#region src/middleware.ts
@@ -423,6 +145,22 @@ const paystackWebhook = (options) => {
423
145
  const eventName = String(event?.event ?? "");
424
146
  const data = event?.data;
425
147
  try {
148
+ if (eventName === "charge.success") {
149
+ const reference = data?.reference;
150
+ const paystackId = data?.id ? String(data.id) : void 0;
151
+ if (reference) await ctx.context.adapter.update({
152
+ model: "paystackTransaction",
153
+ update: {
154
+ status: "success",
155
+ paystackId,
156
+ updatedAt: /* @__PURE__ */ new Date()
157
+ },
158
+ where: [{
159
+ field: "reference",
160
+ value: reference
161
+ }]
162
+ });
163
+ }
426
164
  if (eventName === "subscription.create") {
427
165
  const subscriptionCode = data?.subscription_code ?? data?.subscription?.subscription_code ?? data?.code;
428
166
  const customerCode = data?.customer?.customer_code ?? data?.customer_code ?? data?.customer?.code;
@@ -506,7 +244,12 @@ const paystackWebhook = (options) => {
506
244
  });
507
245
  };
508
246
  const initializeTransactionBodySchema = z.object({
509
- plan: z.string(),
247
+ plan: z.string().optional(),
248
+ product: z.string().optional(),
249
+ amount: z.number().optional(),
250
+ currency: z.string().optional(),
251
+ email: z.string().optional(),
252
+ metadata: z.record(z.string(), z.any()).optional(),
510
253
  referenceId: z.string().optional(),
511
254
  callbackURL: z.string().optional()
512
255
  });
@@ -521,60 +264,78 @@ const initializeTransaction = (options) => {
521
264
  referenceMiddleware(subscriptionOptions, "initialize-transaction")
522
265
  ] : [sessionMiddleware, originCheck]
523
266
  }, async (ctx) => {
524
- const paystack$1 = getPaystackOps(options.paystackClient);
525
- if (!subscriptionOptions?.enabled) throw new APIError("BAD_REQUEST", { message: "Subscriptions are not enabled in the Paystack options." });
526
- if (ctx.body.callbackURL) {
527
- const isTrustedOriginFn = ctx.context?.isTrustedOrigin;
528
- if (!(isTrustedOriginFn ? isTrustedOriginFn(ctx.body.callbackURL, { allowRelativePaths: true }) : (() => {
267
+ const paystack = getPaystackOps(options.paystackClient);
268
+ const { plan: planName, product: productName, amount: bodyAmount, currency, email, metadata: extraMetadata, callbackURL } = ctx.body;
269
+ if (callbackURL) {
270
+ const checkTrusted = () => {
529
271
  try {
530
- if (ctx.body.callbackURL.startsWith("/")) return true;
272
+ if (!callbackURL) return false;
273
+ if (callbackURL.startsWith("/")) return true;
531
274
  const baseUrl = ctx.context?.baseURL ?? ctx.request?.url ?? "";
532
275
  if (!baseUrl) return false;
533
276
  const baseOrigin = new URL(baseUrl).origin;
534
- return new URL(ctx.body.callbackURL).origin === baseOrigin;
277
+ return new URL(callbackURL).origin === baseOrigin;
535
278
  } catch {
536
279
  return false;
537
280
  }
538
- })())) throw new APIError("FORBIDDEN", {
281
+ };
282
+ if (!checkTrusted()) throw new APIError("FORBIDDEN", {
539
283
  message: "callbackURL is not a trusted origin.",
540
284
  status: 403
541
285
  });
542
286
  }
543
287
  const session = await getSessionFromCtx(ctx);
544
288
  if (!session) throw new APIError("UNAUTHORIZED");
545
- const user$1 = session.user;
546
- const referenceIdFromCtx = ctx.context.referenceId;
547
- const referenceId = ctx.body.referenceId || referenceIdFromCtx || session.user.id;
548
- if (subscriptionOptions.requireEmailVerification && !user$1.emailVerified) throw new APIError("BAD_REQUEST", {
289
+ const user = session.user;
290
+ if (subscriptionOptions?.enabled && subscriptionOptions.requireEmailVerification && !user.emailVerified) throw new APIError("BAD_REQUEST", {
549
291
  code: "EMAIL_VERIFICATION_REQUIRED",
550
292
  message: PAYSTACK_ERROR_CODES.EMAIL_VERIFICATION_REQUIRED
551
293
  });
552
- const plan = await getPlanByName(options, ctx.body.plan);
553
- if (!plan) throw new APIError("BAD_REQUEST", {
554
- code: "SUBSCRIPTION_PLAN_NOT_FOUND",
555
- message: PAYSTACK_ERROR_CODES.SUBSCRIPTION_PLAN_NOT_FOUND
556
- });
557
- if (!plan.planCode && !plan.amount) throw new APIError("BAD_REQUEST", { message: "Paystack transaction initialization requires either plan.planCode (Paystack plan code) or plan.amount (smallest unit)." });
294
+ let plan;
295
+ let product;
296
+ if (planName) {
297
+ if (!subscriptionOptions?.enabled) throw new APIError("BAD_REQUEST", { message: "Subscriptions are not enabled." });
298
+ plan = await getPlanByName(options, planName);
299
+ if (!plan) throw new APIError("BAD_REQUEST", {
300
+ code: "SUBSCRIPTION_PLAN_NOT_FOUND",
301
+ message: PAYSTACK_ERROR_CODES.SUBSCRIPTION_PLAN_NOT_FOUND
302
+ });
303
+ } else if (productName) {
304
+ product = await getProductByName(options, productName);
305
+ if (!product) throw new APIError("BAD_REQUEST", { message: `Product '${productName}' not found.` });
306
+ } else if (!bodyAmount) throw new APIError("BAD_REQUEST", { message: "Either 'plan', 'product', or 'amount' is required to initialize a transaction." });
307
+ const amount = bodyAmount || product?.amount;
308
+ const finalCurrency = currency || product?.currency || plan?.currency || "NGN";
309
+ const referenceIdFromCtx = ctx.context.referenceId;
310
+ const referenceId = ctx.body.referenceId || referenceIdFromCtx || session.user.id;
558
311
  let url;
559
312
  let reference;
560
313
  let accessCode;
561
314
  try {
562
315
  const metadata = JSON.stringify({
563
316
  referenceId,
564
- userId: user$1.id,
565
- plan: plan.name.toLowerCase()
317
+ userId: user.id,
318
+ plan: plan?.name.toLowerCase(),
319
+ product: product?.name.toLowerCase(),
320
+ ...extraMetadata
566
321
  });
567
322
  const initBody = {
568
- email: user$1.email,
569
- callback_url: ctx.body.callbackURL,
570
- currency: plan.currency,
571
- plan: plan.planCode,
572
- invoice_limit: plan.invoiceLimit,
573
- metadata
323
+ email: email || user.email,
324
+ callback_url: callbackURL,
325
+ metadata,
326
+ currency: finalCurrency
574
327
  };
575
- if (!plan.planCode && plan.amount) initBody.amount = String(plan.amount);
576
- const initRes = unwrapSdkResult(await paystack$1.transactionInitialize(initBody));
577
- const data = initRes && typeof initRes === "object" && "status" in initRes && "data" in initRes ? initRes.data : initRes?.data ?? initRes;
328
+ if (plan) {
329
+ initBody.plan = plan.planCode;
330
+ initBody.invoice_limit = plan.invoiceLimit;
331
+ if (!plan.planCode && plan.amount) initBody.amount = plan.amount;
332
+ } else {
333
+ if (!amount) throw new Error("Amount is required for one-time payments");
334
+ initBody.amount = amount;
335
+ }
336
+ const initRes = unwrapSdkResult(await paystack.transactionInitialize(initBody));
337
+ let data = initRes && typeof initRes === "object" && "status" in initRes && "data" in initRes ? initRes.data : initRes?.data ?? initRes;
338
+ if (data && typeof data === "object" && "status" in data && "data" in data) data = data.data;
578
339
  url = data?.authorization_url;
579
340
  reference = data?.reference;
580
341
  accessCode = data?.access_code;
@@ -585,8 +346,23 @@ const initializeTransaction = (options) => {
585
346
  message: error?.message || PAYSTACK_ERROR_CODES.FAILED_TO_INITIALIZE_TRANSACTION
586
347
  });
587
348
  }
588
- const paystackCustomerCode = user$1.paystackCustomerCode;
349
+ const paystackCustomerCode = user.paystackCustomerCode;
589
350
  await ctx.context.adapter.create({
351
+ model: "paystackTransaction",
352
+ data: {
353
+ reference,
354
+ referenceId,
355
+ userId: user.id,
356
+ amount: plan?.amount || amount,
357
+ currency: plan?.currency || currency || "NGN",
358
+ status: "pending",
359
+ plan: plan?.name.toLowerCase(),
360
+ metadata: extraMetadata ? JSON.stringify(extraMetadata) : void 0,
361
+ createdAt: /* @__PURE__ */ new Date(),
362
+ updatedAt: /* @__PURE__ */ new Date()
363
+ }
364
+ });
365
+ if (plan) await ctx.context.adapter.create({
590
366
  model: "subscription",
591
367
  data: {
592
368
  plan: plan.name.toLowerCase(),
@@ -616,10 +392,10 @@ const verifyTransaction = (options) => {
616
392
  referenceMiddleware(subscriptionOptions, "verify-transaction")
617
393
  ] : [sessionMiddleware, originCheck]
618
394
  }, async (ctx) => {
619
- const paystack$1 = getPaystackOps(options.paystackClient);
395
+ const paystack = getPaystackOps(options.paystackClient);
620
396
  let verifyRes;
621
397
  try {
622
- verifyRes = unwrapSdkResult(await paystack$1.transactionVerify(ctx.body.reference));
398
+ verifyRes = unwrapSdkResult(await paystack.transactionVerify(ctx.body.reference));
623
399
  } catch (error) {
624
400
  ctx.context.logger.error("Failed to verify Paystack transaction", error);
625
401
  throw new APIError("BAD_REQUEST", {
@@ -627,12 +403,26 @@ const verifyTransaction = (options) => {
627
403
  message: error?.message || PAYSTACK_ERROR_CODES.FAILED_TO_VERIFY_TRANSACTION
628
404
  });
629
405
  }
630
- const data = verifyRes && typeof verifyRes === "object" && "status" in verifyRes && "data" in verifyRes ? verifyRes.data : verifyRes?.data ?? verifyRes;
406
+ let data = verifyRes && typeof verifyRes === "object" && "status" in verifyRes && "data" in verifyRes ? verifyRes.data : verifyRes?.data ?? verifyRes;
407
+ if (data && typeof data === "object" && "status" in data && "data" in data) data = data.data;
631
408
  const status = data?.status;
632
409
  const reference = data?.reference ?? ctx.body.reference;
410
+ const paystackId = data?.id ? String(data.id) : void 0;
633
411
  if (status === "success") try {
634
412
  const session = await getSessionFromCtx(ctx);
635
413
  const referenceId = ctx.context.referenceId ?? (session?.user)?.id;
414
+ await ctx.context.adapter.update({
415
+ model: "paystackTransaction",
416
+ update: {
417
+ status: "success",
418
+ paystackId,
419
+ updatedAt: /* @__PURE__ */ new Date()
420
+ },
421
+ where: [{
422
+ field: "reference",
423
+ value: reference
424
+ }]
425
+ });
636
426
  await ctx.context.adapter.update({
637
427
  model: "subscription",
638
428
  update: {
@@ -649,7 +439,22 @@ const verifyTransaction = (options) => {
649
439
  }] : []]
650
440
  });
651
441
  } catch (e) {
652
- ctx.context.logger.error("Failed to update subscription after transaction verification", e);
442
+ ctx.context.logger.error("Failed to update transaction/subscription after verification", e);
443
+ }
444
+ else if (status === "failed" || status === "abandoned") try {
445
+ await ctx.context.adapter.update({
446
+ model: "paystackTransaction",
447
+ update: {
448
+ status,
449
+ updatedAt: /* @__PURE__ */ new Date()
450
+ },
451
+ where: [{
452
+ field: "reference",
453
+ value: reference
454
+ }]
455
+ });
456
+ } catch (e) {
457
+ ctx.context.logger.error("Failed to update transaction status", e);
653
458
  }
654
459
  return ctx.json({
655
460
  status,
@@ -684,6 +489,31 @@ const listSubscriptions = (options) => {
684
489
  return ctx.json({ subscriptions: res });
685
490
  });
686
491
  };
492
+ const listTransactions = (options) => {
493
+ const listQuerySchema = z.object({ referenceId: z.string().optional() });
494
+ const subscriptionOptions = options.subscription;
495
+ return createAuthEndpoint("/paystack/transaction/list", {
496
+ method: "GET",
497
+ query: listQuerySchema,
498
+ use: subscriptionOptions?.enabled ? [
499
+ sessionMiddleware,
500
+ originCheck,
501
+ referenceMiddleware(subscriptionOptions, "list-transactions")
502
+ ] : [sessionMiddleware, originCheck]
503
+ }, async (ctx) => {
504
+ const session = await getSessionFromCtx(ctx);
505
+ if (!session) throw new APIError("UNAUTHORIZED");
506
+ const referenceId = ctx.context.referenceId ?? ctx.query?.referenceId ?? session.user.id;
507
+ const sorted = (await ctx.context.adapter.findMany({
508
+ model: "paystackTransaction",
509
+ where: [{
510
+ field: "referenceId",
511
+ value: referenceId
512
+ }]
513
+ })).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
514
+ return ctx.json({ transactions: sorted });
515
+ });
516
+ };
687
517
  const enableDisableBodySchema = z.object({
688
518
  referenceId: z.string().optional(),
689
519
  subscriptionCode: z.string(),
@@ -720,20 +550,20 @@ const disablePaystackSubscription = (options) => {
720
550
  ] : [sessionMiddleware, originCheck]
721
551
  }, async (ctx) => {
722
552
  const { subscriptionCode } = ctx.body;
723
- const paystack$1 = getPaystackOps(options.paystackClient);
553
+ const paystack = getPaystackOps(options.paystackClient);
724
554
  try {
725
555
  let emailToken = ctx.body.emailToken;
726
556
  if (!emailToken) try {
727
- const fetchRes = unwrapSdkResult(await paystack$1.subscriptionFetch(subscriptionCode));
557
+ const fetchRes = unwrapSdkResult(await paystack.subscriptionFetch(subscriptionCode));
728
558
  emailToken = (fetchRes && typeof fetchRes === "object" && "status" in fetchRes && "data" in fetchRes ? fetchRes.data : fetchRes?.data ?? fetchRes)?.email_token;
729
559
  } catch {}
730
560
  if (!emailToken) try {
731
- const linkRes = unwrapSdkResult(await paystack$1.subscriptionManageLink(subscriptionCode));
561
+ const linkRes = unwrapSdkResult(await paystack.subscriptionManageLink(subscriptionCode));
732
562
  const link = (linkRes && typeof linkRes === "object" && "status" in linkRes && "data" in linkRes ? linkRes.data : linkRes?.data ?? linkRes)?.link;
733
563
  if (typeof link === "string") emailToken = tryGetEmailTokenFromSubscriptionManageLink(link);
734
564
  } catch {}
735
565
  if (!emailToken) throw new APIError("BAD_REQUEST", { message: "Missing emailToken. Provide it explicitly or ensure your server can fetch it from Paystack using the subscription code." });
736
- const result = unwrapSdkResult(await paystack$1.subscriptionDisable({
566
+ const result = unwrapSdkResult(await paystack.subscriptionDisable({
737
567
  code: subscriptionCode,
738
568
  token: emailToken
739
569
  }));
@@ -759,20 +589,20 @@ const enablePaystackSubscription = (options) => {
759
589
  ] : [sessionMiddleware, originCheck]
760
590
  }, async (ctx) => {
761
591
  const { subscriptionCode } = ctx.body;
762
- const paystack$1 = getPaystackOps(options.paystackClient);
592
+ const paystack = getPaystackOps(options.paystackClient);
763
593
  try {
764
594
  let emailToken = ctx.body.emailToken;
765
595
  if (!emailToken) try {
766
- const fetchRes = unwrapSdkResult(await paystack$1.subscriptionFetch(subscriptionCode));
596
+ const fetchRes = unwrapSdkResult(await paystack.subscriptionFetch(subscriptionCode));
767
597
  emailToken = (fetchRes && typeof fetchRes === "object" && "status" in fetchRes && "data" in fetchRes ? fetchRes.data : fetchRes?.data ?? fetchRes)?.email_token;
768
598
  } catch {}
769
599
  if (!emailToken) try {
770
- const linkRes = unwrapSdkResult(await paystack$1.subscriptionManageLink(subscriptionCode));
600
+ const linkRes = unwrapSdkResult(await paystack.subscriptionManageLink(subscriptionCode));
771
601
  const link = (linkRes && typeof linkRes === "object" && "status" in linkRes && "data" in linkRes ? linkRes.data : linkRes?.data ?? linkRes)?.link;
772
602
  if (typeof link === "string") emailToken = tryGetEmailTokenFromSubscriptionManageLink(link);
773
603
  } catch {}
774
604
  if (!emailToken) throw new APIError("BAD_REQUEST", { message: "Missing emailToken. Provide it explicitly or ensure your server can fetch it from Paystack using the subscription code." });
775
- const result = unwrapSdkResult(await paystack$1.subscriptionEnable({
605
+ const result = unwrapSdkResult(await paystack.subscriptionEnable({
776
606
  code: subscriptionCode,
777
607
  token: emailToken
778
608
  }));
@@ -786,9 +616,91 @@ const enablePaystackSubscription = (options) => {
786
616
  }
787
617
  });
788
618
  };
619
+ const subscriptionCodeSchema = z.object({ subscriptionCode: z.string() });
620
+ const getSubscriptionManageLink = (options) => {
621
+ const subscriptionOptions = options.subscription;
622
+ return createAuthEndpoint("/paystack/subscription/manage-link", {
623
+ method: "GET",
624
+ query: subscriptionCodeSchema,
625
+ use: subscriptionOptions?.enabled ? [
626
+ sessionMiddleware,
627
+ originCheck,
628
+ referenceMiddleware(subscriptionOptions, "get-subscription-manage-link")
629
+ ] : [sessionMiddleware, originCheck]
630
+ }, async (ctx) => {
631
+ const { subscriptionCode } = ctx.query;
632
+ const paystack = getPaystackOps(options.paystackClient);
633
+ try {
634
+ const linkRes = unwrapSdkResult(await paystack.subscriptionManageLink(subscriptionCode));
635
+ const data = linkRes && typeof linkRes === "object" && "status" in linkRes && "data" in linkRes ? linkRes.data : linkRes?.data ?? linkRes;
636
+ return ctx.json({ link: data?.link });
637
+ } catch (error) {
638
+ ctx.context.logger.error("Failed to get Paystack subscription manage link", error);
639
+ throw new APIError("BAD_REQUEST", { message: error?.message || "Failed to fetch subscription management link" });
640
+ }
641
+ });
642
+ };
643
+ const getConfig = (options) => {
644
+ return createAuthEndpoint("/paystack/get-config", {
645
+ method: "GET",
646
+ metadata: { openapi: { operationId: "getPaystackConfig" } }
647
+ }, async (ctx) => {
648
+ const [plans, products] = await Promise.all([options.subscription?.enabled ? getPlans(options.subscription) : Promise.resolve([]), getProducts(options.products)]);
649
+ return ctx.json({
650
+ plans,
651
+ products
652
+ });
653
+ });
654
+ };
789
655
 
790
656
  //#endregion
791
657
  //#region src/schema.ts
658
+ const transactions = { paystackTransaction: { fields: {
659
+ reference: {
660
+ type: "string",
661
+ required: true
662
+ },
663
+ paystackId: {
664
+ type: "string",
665
+ required: false
666
+ },
667
+ referenceId: {
668
+ type: "string",
669
+ required: true
670
+ },
671
+ userId: {
672
+ type: "string",
673
+ required: true
674
+ },
675
+ amount: {
676
+ type: "number",
677
+ required: true
678
+ },
679
+ currency: {
680
+ type: "string",
681
+ required: true
682
+ },
683
+ status: {
684
+ type: "string",
685
+ required: true
686
+ },
687
+ plan: {
688
+ type: "string",
689
+ required: false
690
+ },
691
+ metadata: {
692
+ type: "string",
693
+ required: false
694
+ },
695
+ createdAt: {
696
+ type: "date",
697
+ required: true
698
+ },
699
+ updatedAt: {
700
+ type: "date",
701
+ required: true
702
+ }
703
+ } } };
792
704
  const subscriptions = { subscription: { fields: {
793
705
  plan: {
794
706
  type: "string",
@@ -852,9 +764,13 @@ const getSchema = (options) => {
852
764
  let baseSchema;
853
765
  if (options.subscription?.enabled) baseSchema = {
854
766
  ...subscriptions,
767
+ ...transactions,
855
768
  ...user
856
769
  };
857
- else baseSchema = { ...user };
770
+ else baseSchema = {
771
+ ...user,
772
+ ...transactions
773
+ };
858
774
  if (options.schema && !options.subscription?.enabled && "subscription" in options.schema) {
859
775
  const { subscription: _subscription, ...restSchema } = options.schema;
860
776
  return mergeSchema(baseSchema, restSchema);
@@ -866,40 +782,41 @@ const getSchema = (options) => {
866
782
  //#region src/index.ts
867
783
  const INTERNAL_ERROR_CODES = defineErrorCodes({ ...PAYSTACK_ERROR_CODES });
868
784
  const paystack = (options) => {
869
- const baseEndpoints = { paystackWebhook: paystackWebhook(options) };
870
- const subscriptionEnabledEndpoints = {
871
- ...baseEndpoints,
872
- initializeTransaction: initializeTransaction(options),
873
- verifyTransaction: verifyTransaction(options),
874
- listSubscriptions: listSubscriptions(options),
875
- disablePaystackSubscription: disablePaystackSubscription(options),
876
- enablePaystackSubscription: enablePaystackSubscription(options)
877
- };
878
785
  return {
879
786
  id: "paystack",
880
- endpoints: options.subscription?.enabled ? subscriptionEnabledEndpoints : baseEndpoints,
787
+ endpoints: {
788
+ paystackWebhook: paystackWebhook(options),
789
+ listTransactions: listTransactions(options),
790
+ getConfig: getConfig(options),
791
+ initializeTransaction: initializeTransaction(options),
792
+ verifyTransaction: verifyTransaction(options),
793
+ listSubscriptions: listSubscriptions(options),
794
+ disablePaystackSubscription: disablePaystackSubscription(options),
795
+ enablePaystackSubscription: enablePaystackSubscription(options),
796
+ getSubscriptionManageLink: getSubscriptionManageLink(options)
797
+ },
881
798
  init(ctx) {
882
- return { options: { databaseHooks: { user: { create: { async after(user$1, hookCtx) {
799
+ return { options: { databaseHooks: { user: { create: { async after(user, hookCtx) {
883
800
  if (!hookCtx || !options.createCustomerOnSignUp) return;
884
801
  try {
885
- const firstName = user$1.name?.split(" ")[0];
886
- const lastName = user$1.name?.split(" ").slice(1).join(" ") || void 0;
887
- const extraCreateParams = options.getCustomerCreateParams ? await options.getCustomerCreateParams(user$1, hookCtx) : {};
802
+ const firstName = user.name?.split(" ")[0];
803
+ const lastName = user.name?.split(" ").slice(1).join(" ") || void 0;
804
+ const extraCreateParams = options.getCustomerCreateParams ? await options.getCustomerCreateParams(user, hookCtx) : {};
888
805
  const params = defu({
889
- email: user$1.email,
806
+ email: user.email,
890
807
  first_name: firstName,
891
808
  last_name: lastName,
892
- metadata: { userId: user$1.id }
809
+ metadata: { userId: user.id }
893
810
  }, extraCreateParams);
894
811
  const res = unwrapSdkResult(await getPaystackOps(options.paystackClient).customerCreate(params));
895
812
  const paystackCustomer = res && typeof res === "object" && "status" in res && "data" in res ? res.data : res?.data ?? res;
896
813
  const customerCode = paystackCustomer?.customer_code;
897
814
  if (!customerCode) return;
898
- await hookCtx.context.internalAdapter.updateUser(user$1.id, { paystackCustomerCode: customerCode });
815
+ await hookCtx.context.internalAdapter.updateUser(user.id, { paystackCustomerCode: customerCode });
899
816
  await options.onCustomerCreate?.({
900
817
  paystackCustomer,
901
818
  user: {
902
- ...user$1,
819
+ ...user,
903
820
  paystackCustomerCode: customerCode
904
821
  }
905
822
  }, hookCtx);