@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/README.md +156 -47
- package/dist/client.d.mts +74 -222
- package/dist/client.d.mts.map +1 -1
- package/dist/client.mjs +35 -54
- package/dist/client.mjs.map +1 -1
- package/dist/index-Dwbeddkr.d.mts +711 -0
- package/dist/index-Dwbeddkr.d.mts.map +1 -0
- package/dist/index.d.mts +2 -623
- package/dist/index.mjs +638 -704
- package/dist/index.mjs.map +1 -1
- package/package.json +65 -74
- package/dist/index.d.mts.map +0 -1
- package/dist/types-BOpjdQrr.d.mts +0 -395
- package/dist/types-BOpjdQrr.d.mts.map +0 -1
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
|
|
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
|
-
|
|
11
|
-
|
|
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 (
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
37
|
+
return current;
|
|
24
38
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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 (
|
|
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
|
|
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
|
|
194
|
-
if (localProduct && localProduct.unlimited !== true && localProduct.quantity
|
|
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
|
|
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
|
|
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
|
|
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 ===
|
|
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
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
|
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 ===
|
|
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(
|
|
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 (
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
463
|
-
const subscriptionCode =
|
|
464
|
-
const customerCode =
|
|
465
|
-
const planCode =
|
|
466
|
-
let 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
|
|
471
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
538
|
-
const subscriptionCode =
|
|
539
|
-
if (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 =
|
|
549
|
-
const periodEnd = nextPaymentDate ? new Date(nextPaymentDate) : existing?.periodEnd ? new Date(existing.periodEnd) : void 0;
|
|
550
|
-
if (periodEnd && periodEnd >
|
|
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
|
|
575
|
-
const subscriptionCode =
|
|
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
|
|
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 = "/
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 &&
|
|
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 (
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
767
|
-
if (
|
|
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
|
|
724
|
+
let targetEmail = email ?? user.email;
|
|
781
725
|
let paystackCustomerCode = user.paystackCustomerCode;
|
|
782
|
-
if (options.organization?.enabled === true && referenceId !== void 0 && referenceId !== null && referenceId !==
|
|
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
|
-
|
|
792
|
-
if (
|
|
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
|
|
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
|
|
821
|
-
product: product
|
|
822
|
-
isTrial:
|
|
823
|
-
trialEnd: trialEnd
|
|
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 (
|
|
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 !==
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
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
|
-
|
|
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 ??
|
|
925
|
-
initBody.amount = Math.max(Math.round(finalAmount),
|
|
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
|
|
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
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
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
|
|
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
|
|
954
|
-
product: product
|
|
955
|
-
metadata: extraMetadata !== void 0 && extraMetadata
|
|
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
|
|
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
|
|
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
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
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 &&
|
|
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
|
|
996
|
-
const upgradeSubscription = (options
|
|
997
|
-
const
|
|
998
|
-
|
|
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
|
|
974
|
+
let data;
|
|
1017
975
|
try {
|
|
1018
|
-
|
|
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
|
|
981
|
+
message: error instanceof Error ? error.message : PAYSTACK_ERROR_CODES.FAILED_TO_VERIFY_TRANSACTION.message
|
|
1024
982
|
});
|
|
1025
983
|
}
|
|
1026
|
-
|
|
1027
|
-
const status = data
|
|
1028
|
-
const reference = data
|
|
1029
|
-
const
|
|
1030
|
-
const
|
|
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
|
|
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
|
-
})
|
|
1040
|
-
|
|
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
|
-
|
|
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 !==
|
|
1020
|
+
if (member !== void 0 && member !== null) authorized = true;
|
|
1061
1021
|
}
|
|
1062
|
-
if (
|
|
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
|
-
|
|
1071
|
-
|
|
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
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
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
|
|
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
|
|
1115
|
-
const
|
|
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
|
|
1123
|
-
const 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 &&
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
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 =
|
|
1100
|
+
else paystackSubscriptionCode = data.subscription?.subscription_code ?? void 0;
|
|
1139
1101
|
}
|
|
1140
|
-
const
|
|
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
|
|
1113
|
+
status: isTrial ? "trialing" : "active",
|
|
1154
1114
|
periodStart: /* @__PURE__ */ new Date(),
|
|
1155
1115
|
updatedAt: /* @__PURE__ */ new Date(),
|
|
1156
|
-
...isTrial
|
|
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
|
|
1162
|
-
...authorizationCode !== void 0 && authorizationCode !== null
|
|
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
|
|
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(
|
|
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 (
|
|
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
|
|
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 = "/
|
|
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 (
|
|
1242
|
-
const
|
|
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) =>
|
|
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
|
-
|
|
1263
|
-
|
|
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
|
|
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 = "/
|
|
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
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
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
|
|
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
|
|
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
|
|
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 = "/
|
|
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
|
|
1383
|
-
|
|
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
|
|
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
|
|
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
|
|
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 = "/
|
|
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
|
|
1433
|
-
|
|
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
|
|
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
|
|
1448
|
-
return createAuthEndpoint(
|
|
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
|
|
1520
|
-
return createAuthEndpoint(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1992
|
-
|
|
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
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
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
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
organization
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
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 &&
|
|
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 &&
|
|
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 &&
|
|
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 &&
|
|
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
|
|
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(
|
|
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
|