@alexasomba/better-auth-paystack 1.0.4 → 1.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 +44 -1
- package/dist/client.d.mts +5 -1
- package/dist/client.d.mts.map +1 -1
- package/dist/client.mjs +3 -0
- package/dist/client.mjs.map +1 -1
- package/dist/index.d.mts +38 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +560 -338
- package/dist/index.mjs.map +1 -1
- package/dist/{types-Dlv_nSLg.d.mts → types-BVSLYZGY.d.mts} +77 -26
- package/dist/types-BVSLYZGY.d.mts.map +1 -0
- package/package.json +8 -8
- package/dist/types-Dlv_nSLg.d.mts.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -6,6 +6,104 @@ import { APIError, getSessionFromCtx, originCheck, sessionMiddleware } from "bet
|
|
|
6
6
|
import * as z from "zod/v4";
|
|
7
7
|
import { mergeSchema } from "better-auth/db";
|
|
8
8
|
|
|
9
|
+
//#region src/paystack-sdk.ts
|
|
10
|
+
function isOpenApiFetchResponse(value) {
|
|
11
|
+
return value !== null && value !== void 0 && typeof value === "object" && ("data" in value || "error" in value || "response" in value);
|
|
12
|
+
}
|
|
13
|
+
function unwrapSdkResult(result) {
|
|
14
|
+
if (isOpenApiFetchResponse(result)) {
|
|
15
|
+
if (result.error !== void 0 && result.error !== null) throw new Error(typeof result.error === "string" ? result.error : JSON.stringify(result.error));
|
|
16
|
+
return result.data ?? result;
|
|
17
|
+
}
|
|
18
|
+
if (result !== null && result !== void 0 && typeof result === "object" && "data" in result) {
|
|
19
|
+
const data = result.data;
|
|
20
|
+
if (data !== null && typeof data === "object" && "data" in data) return data.data;
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
function getPaystackOps(paystackClient) {
|
|
26
|
+
return {
|
|
27
|
+
customerCreate: (params) => {
|
|
28
|
+
if (paystackClient?.customer_create !== void 0) return paystackClient.customer_create({ body: params });
|
|
29
|
+
return paystackClient?.customer?.create?.(params);
|
|
30
|
+
},
|
|
31
|
+
customerUpdate: (code, params) => {
|
|
32
|
+
if (paystackClient?.customer_update !== void 0) return paystackClient.customer_update({
|
|
33
|
+
params: { path: { code } },
|
|
34
|
+
body: params
|
|
35
|
+
});
|
|
36
|
+
return paystackClient?.customer?.update?.(code, params);
|
|
37
|
+
},
|
|
38
|
+
transactionInitialize: (body) => {
|
|
39
|
+
if (paystackClient?.transaction_initialize !== void 0) return paystackClient.transaction_initialize({ body });
|
|
40
|
+
return paystackClient?.transaction?.initialize?.(body);
|
|
41
|
+
},
|
|
42
|
+
transactionVerify: (reference) => {
|
|
43
|
+
if (paystackClient?.transaction_verify !== void 0) return paystackClient.transaction_verify({ params: { path: { reference } } });
|
|
44
|
+
return paystackClient?.transaction?.verify?.(reference);
|
|
45
|
+
},
|
|
46
|
+
subscriptionCreate: (body) => {
|
|
47
|
+
if (paystackClient?.subscription_create !== void 0) return paystackClient.subscription_create({ body });
|
|
48
|
+
return paystackClient?.subscription?.create?.(body);
|
|
49
|
+
},
|
|
50
|
+
subscriptionDisable: (body) => {
|
|
51
|
+
if (paystackClient?.subscription_disable !== void 0) return paystackClient.subscription_disable({ body });
|
|
52
|
+
return paystackClient?.subscription?.disable?.(body);
|
|
53
|
+
},
|
|
54
|
+
subscriptionEnable: (body) => {
|
|
55
|
+
if (paystackClient?.subscription_enable !== void 0) return paystackClient.subscription_enable({ body });
|
|
56
|
+
return paystackClient?.subscription?.enable?.(body);
|
|
57
|
+
},
|
|
58
|
+
subscriptionFetch: async (idOrCode) => {
|
|
59
|
+
if (paystackClient?.subscription_fetch !== void 0) try {
|
|
60
|
+
return await paystackClient.subscription_fetch({ params: { path: { code: idOrCode } } });
|
|
61
|
+
} catch {
|
|
62
|
+
const compatFetch = paystackClient.subscription_fetch;
|
|
63
|
+
return compatFetch({ params: { path: { id_or_code: idOrCode } } });
|
|
64
|
+
}
|
|
65
|
+
return paystackClient?.subscription?.fetch?.(idOrCode);
|
|
66
|
+
},
|
|
67
|
+
subscriptionManageLink: (code) => {
|
|
68
|
+
if (paystackClient?.subscription_manageLink !== void 0) return paystackClient.subscription_manageLink({ params: { path: { code } } });
|
|
69
|
+
if (paystackClient?.subscription_manage_link !== void 0) return paystackClient.subscription_manage_link({ params: { path: { code } } });
|
|
70
|
+
return paystackClient?.subscription?.manage?.link?.(code);
|
|
71
|
+
},
|
|
72
|
+
subscriptionManageEmail: (code, email) => {
|
|
73
|
+
if (paystackClient?.subscription_manageEmail !== void 0) return paystackClient.subscription_manageEmail({ params: { path: { code } } });
|
|
74
|
+
return paystackClient?.subscription?.manage?.email?.(code, email);
|
|
75
|
+
},
|
|
76
|
+
transactionChargeAuthorization: (body) => {
|
|
77
|
+
if (paystackClient?.transaction_chargeAuthorization !== void 0) return paystackClient.transaction_chargeAuthorization({ body });
|
|
78
|
+
return paystackClient?.transaction?.chargeAuthorization?.(body);
|
|
79
|
+
},
|
|
80
|
+
productList: () => {
|
|
81
|
+
if (paystackClient?.product_list !== void 0) return paystackClient.product_list();
|
|
82
|
+
return paystackClient?.product?.list?.();
|
|
83
|
+
},
|
|
84
|
+
productFetch: (idOrCode) => {
|
|
85
|
+
if (paystackClient?.product_fetch !== void 0) return paystackClient.product_fetch({ params: { path: { id_or_code: idOrCode } } });
|
|
86
|
+
return paystackClient?.product?.fetch?.(idOrCode);
|
|
87
|
+
},
|
|
88
|
+
productCreate: (params) => {
|
|
89
|
+
if (paystackClient?.product_create !== void 0) return paystackClient.product_create({ body: params });
|
|
90
|
+
return paystackClient?.product?.create?.(params);
|
|
91
|
+
},
|
|
92
|
+
productUpdate: (idOrCode, params) => {
|
|
93
|
+
if (paystackClient?.product_update !== void 0) return paystackClient.product_update({
|
|
94
|
+
params: { path: { id_or_code: idOrCode } },
|
|
95
|
+
body: params
|
|
96
|
+
});
|
|
97
|
+
return paystackClient?.product?.update?.(idOrCode, params);
|
|
98
|
+
},
|
|
99
|
+
productDelete: (idOrCode) => {
|
|
100
|
+
if (paystackClient?.product_delete !== void 0) return paystackClient.product_delete({ params: { path: { id_or_code: idOrCode } } });
|
|
101
|
+
return paystackClient?.product?.delete?.(idOrCode);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
//#endregion
|
|
9
107
|
//#region src/utils.ts
|
|
10
108
|
async function getPlans(subscriptionOptions) {
|
|
11
109
|
if (subscriptionOptions?.enabled === true) return typeof subscriptionOptions.plans === "function" ? subscriptionOptions.plans() : subscriptionOptions.plans;
|
|
@@ -20,7 +118,7 @@ async function getProducts(productOptions) {
|
|
|
20
118
|
return [];
|
|
21
119
|
}
|
|
22
120
|
async function getProductByName(options, name) {
|
|
23
|
-
return await getProducts(options.products).then((products) => products?.find((product) => product.name.toLowerCase() === name.toLowerCase()));
|
|
121
|
+
return await getProducts(options.products).then((products) => products?.find((product) => product.name.toLowerCase() === name.toLowerCase()) ?? null);
|
|
24
122
|
}
|
|
25
123
|
function getNextPeriodEnd(startDate, interval) {
|
|
26
124
|
const date = new Date(startDate);
|
|
@@ -62,6 +160,62 @@ function validateMinAmount(amount, currency) {
|
|
|
62
160
|
}[currency.toUpperCase()];
|
|
63
161
|
return min !== void 0 ? amount >= min : true;
|
|
64
162
|
}
|
|
163
|
+
async function syncProductQuantityFromPaystack(ctx, productName, paystackClient) {
|
|
164
|
+
let localProduct = await ctx.context.adapter.findOne({
|
|
165
|
+
model: "paystackProduct",
|
|
166
|
+
where: [{
|
|
167
|
+
field: "name",
|
|
168
|
+
value: productName
|
|
169
|
+
}]
|
|
170
|
+
});
|
|
171
|
+
localProduct ??= await ctx.context.adapter.findOne({
|
|
172
|
+
model: "paystackProduct",
|
|
173
|
+
where: [{
|
|
174
|
+
field: "slug",
|
|
175
|
+
value: productName.toLowerCase().replace(/\s+/g, "-")
|
|
176
|
+
}]
|
|
177
|
+
});
|
|
178
|
+
if (!localProduct?.paystackId) {
|
|
179
|
+
if (localProduct && localProduct.unlimited !== true && localProduct.quantity !== void 0 && localProduct.quantity > 0) await ctx.context.adapter.update({
|
|
180
|
+
model: "paystackProduct",
|
|
181
|
+
update: {
|
|
182
|
+
quantity: localProduct.quantity - 1,
|
|
183
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
184
|
+
},
|
|
185
|
+
where: [{
|
|
186
|
+
field: "id",
|
|
187
|
+
value: localProduct.id
|
|
188
|
+
}]
|
|
189
|
+
});
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
const remoteQuantity = unwrapSdkResult(await getPaystackOps(paystackClient).productFetch(localProduct.paystackId))?.quantity;
|
|
194
|
+
if (remoteQuantity !== void 0) await ctx.context.adapter.update({
|
|
195
|
+
model: "paystackProduct",
|
|
196
|
+
update: {
|
|
197
|
+
quantity: remoteQuantity,
|
|
198
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
199
|
+
},
|
|
200
|
+
where: [{
|
|
201
|
+
field: "id",
|
|
202
|
+
value: localProduct.id
|
|
203
|
+
}]
|
|
204
|
+
});
|
|
205
|
+
} catch {
|
|
206
|
+
if (localProduct.unlimited !== true && localProduct.quantity !== void 0 && localProduct.quantity > 0) await ctx.context.adapter.update({
|
|
207
|
+
model: "paystackProduct",
|
|
208
|
+
update: {
|
|
209
|
+
quantity: localProduct.quantity - 1,
|
|
210
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
211
|
+
},
|
|
212
|
+
where: [{
|
|
213
|
+
field: "id",
|
|
214
|
+
value: localProduct.id
|
|
215
|
+
}]
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
65
219
|
|
|
66
220
|
//#endregion
|
|
67
221
|
//#region src/middleware.ts
|
|
@@ -102,96 +256,6 @@ const referenceMiddleware = (options, action) => createAuthMiddleware(async (ctx
|
|
|
102
256
|
throw new APIError("BAD_REQUEST", { message: "Passing referenceId isn't allowed without subscription.authorizeReference or valid organization membership." });
|
|
103
257
|
});
|
|
104
258
|
|
|
105
|
-
//#endregion
|
|
106
|
-
//#region src/paystack-sdk.ts
|
|
107
|
-
function isOpenApiFetchResponse(value) {
|
|
108
|
-
return value !== null && value !== void 0 && typeof value === "object" && ("data" in value || "error" in value || "response" in value);
|
|
109
|
-
}
|
|
110
|
-
function unwrapSdkResult(result) {
|
|
111
|
-
if (isOpenApiFetchResponse(result)) {
|
|
112
|
-
if (result.error !== void 0 && result.error !== null) throw new Error(typeof result.error === "string" ? result.error : JSON.stringify(result.error));
|
|
113
|
-
return result.data;
|
|
114
|
-
}
|
|
115
|
-
if (result !== null && result !== void 0 && typeof result === "object" && "data" in result) return result.data ?? result;
|
|
116
|
-
return result;
|
|
117
|
-
}
|
|
118
|
-
const normalizeMetadata = (value) => {
|
|
119
|
-
if (value === void 0 || value === null || value === "") return void 0;
|
|
120
|
-
return typeof value === "string" ? value : JSON.stringify(value);
|
|
121
|
-
};
|
|
122
|
-
const normalizeMetadataBody = (body) => {
|
|
123
|
-
const { metadata, ...rest } = body;
|
|
124
|
-
const normalized = normalizeMetadata(metadata);
|
|
125
|
-
if (normalized === void 0) return rest;
|
|
126
|
-
return {
|
|
127
|
-
...rest,
|
|
128
|
-
metadata: normalized
|
|
129
|
-
};
|
|
130
|
-
};
|
|
131
|
-
function getPaystackOps(paystackClient) {
|
|
132
|
-
return {
|
|
133
|
-
customerCreate: (params) => {
|
|
134
|
-
if (paystackClient?.customer_create !== void 0) {
|
|
135
|
-
const body = normalizeMetadataBody(params);
|
|
136
|
-
return paystackClient.customer_create({ body });
|
|
137
|
-
}
|
|
138
|
-
return paystackClient?.customer?.create?.(params);
|
|
139
|
-
},
|
|
140
|
-
customerUpdate: (code, params) => {
|
|
141
|
-
if (paystackClient?.customer_update !== void 0) {
|
|
142
|
-
const body = normalizeMetadataBody(params);
|
|
143
|
-
return paystackClient.customer_update({
|
|
144
|
-
params: { path: { code } },
|
|
145
|
-
body
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
return paystackClient?.customer?.update?.(code, params);
|
|
149
|
-
},
|
|
150
|
-
transactionInitialize: (body) => {
|
|
151
|
-
if (paystackClient?.transaction_initialize !== void 0) return paystackClient.transaction_initialize({ body });
|
|
152
|
-
return paystackClient?.transaction?.initialize?.(body);
|
|
153
|
-
},
|
|
154
|
-
transactionVerify: (reference) => {
|
|
155
|
-
if (paystackClient?.transaction_verify !== void 0) return paystackClient.transaction_verify({ params: { path: { reference } } });
|
|
156
|
-
return paystackClient?.transaction?.verify?.(reference);
|
|
157
|
-
},
|
|
158
|
-
subscriptionCreate: (body) => {
|
|
159
|
-
if (paystackClient?.subscription_create !== void 0) return paystackClient.subscription_create({ body });
|
|
160
|
-
return paystackClient?.subscription?.create?.(body);
|
|
161
|
-
},
|
|
162
|
-
subscriptionDisable: (body) => {
|
|
163
|
-
if (paystackClient?.subscription_disable !== void 0) return paystackClient.subscription_disable({ body });
|
|
164
|
-
return paystackClient?.subscription?.disable?.(body);
|
|
165
|
-
},
|
|
166
|
-
subscriptionEnable: (body) => {
|
|
167
|
-
if (paystackClient?.subscription_enable !== void 0) return paystackClient.subscription_enable({ body });
|
|
168
|
-
return paystackClient?.subscription?.enable?.(body);
|
|
169
|
-
},
|
|
170
|
-
subscriptionFetch: async (idOrCode) => {
|
|
171
|
-
if (paystackClient?.subscription_fetch !== void 0) try {
|
|
172
|
-
return await paystackClient.subscription_fetch({ params: { path: { code: idOrCode } } });
|
|
173
|
-
} catch {
|
|
174
|
-
const compatFetch = paystackClient.subscription_fetch;
|
|
175
|
-
return compatFetch({ params: { path: { id_or_code: idOrCode } } });
|
|
176
|
-
}
|
|
177
|
-
return paystackClient?.subscription?.fetch?.(idOrCode);
|
|
178
|
-
},
|
|
179
|
-
subscriptionManageLink: (code) => {
|
|
180
|
-
if (paystackClient?.subscription_manageLink !== void 0) return paystackClient.subscription_manageLink({ params: { path: { code } } });
|
|
181
|
-
if (paystackClient?.subscription_manage_link !== void 0) return paystackClient.subscription_manage_link({ params: { path: { code } } });
|
|
182
|
-
return paystackClient?.subscription?.manage?.link?.(code);
|
|
183
|
-
},
|
|
184
|
-
subscriptionManageEmail: (code, email) => {
|
|
185
|
-
if (paystackClient?.subscription_manageEmail !== void 0) return paystackClient.subscription_manageEmail({ params: { path: { code } } });
|
|
186
|
-
return paystackClient?.subscription?.manage?.email?.(code, email);
|
|
187
|
-
},
|
|
188
|
-
transactionChargeAuthorization: (body) => {
|
|
189
|
-
if (paystackClient?.transaction_chargeAuthorization !== void 0) return paystackClient.transaction_chargeAuthorization({ body });
|
|
190
|
-
return paystackClient?.transaction?.chargeAuthorization?.(body);
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
259
|
//#endregion
|
|
196
260
|
//#region src/routes.ts
|
|
197
261
|
const PAYSTACK_ERROR_CODES = defineErrorCodes({
|
|
@@ -244,14 +308,14 @@ const paystackWebhook = (options) => {
|
|
|
244
308
|
status: 401
|
|
245
309
|
});
|
|
246
310
|
const event = JSON.parse(payload);
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
311
|
+
const eventName = event.event;
|
|
312
|
+
const data = event.data;
|
|
313
|
+
if (eventName === "charge.success") {
|
|
314
|
+
const reference = data?.reference;
|
|
315
|
+
const paystackId = data?.id !== void 0 && data?.id !== null ? String(data.id) : void 0;
|
|
316
|
+
if (reference !== void 0 && reference !== null && reference !== "") {
|
|
317
|
+
try {
|
|
318
|
+
await ctx.context.adapter.update({
|
|
255
319
|
model: "paystackTransaction",
|
|
256
320
|
update: {
|
|
257
321
|
status: "success",
|
|
@@ -263,134 +327,150 @@ const paystackWebhook = (options) => {
|
|
|
263
327
|
value: reference
|
|
264
328
|
}]
|
|
265
329
|
});
|
|
330
|
+
} catch (e) {
|
|
331
|
+
ctx.context.logger.warn("Failed to update transaction status for charge.success", e);
|
|
266
332
|
}
|
|
267
|
-
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
value: reference
|
|
279
|
-
}]
|
|
280
|
-
});
|
|
281
|
-
} catch (e) {
|
|
282
|
-
ctx.context.logger.warn("Failed to update transaction status for charge.failure", e);
|
|
283
|
-
}
|
|
333
|
+
try {
|
|
334
|
+
const transaction = await ctx.context.adapter.findOne({
|
|
335
|
+
model: "paystackTransaction",
|
|
336
|
+
where: [{
|
|
337
|
+
field: "reference",
|
|
338
|
+
value: reference
|
|
339
|
+
}]
|
|
340
|
+
});
|
|
341
|
+
if (transaction?.product) await syncProductQuantityFromPaystack(ctx, transaction.product, options.paystackClient);
|
|
342
|
+
} catch (e) {
|
|
343
|
+
ctx.context.logger.warn("Failed to sync product quantity", e);
|
|
284
344
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (eventName === "charge.failure") {
|
|
348
|
+
const reference = data?.reference;
|
|
349
|
+
if (reference !== void 0 && reference !== null && reference !== "") try {
|
|
350
|
+
await ctx.context.adapter.update({
|
|
351
|
+
model: "paystackTransaction",
|
|
352
|
+
update: {
|
|
353
|
+
status: "failed",
|
|
354
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
355
|
+
},
|
|
356
|
+
where: [{
|
|
357
|
+
field: "reference",
|
|
358
|
+
value: reference
|
|
359
|
+
}]
|
|
360
|
+
});
|
|
361
|
+
} catch (e) {
|
|
362
|
+
ctx.context.logger.warn("Failed to update transaction status for charge.failure", e);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (options.subscription?.enabled === true) try {
|
|
366
|
+
if (eventName === "subscription.create") {
|
|
367
|
+
const subscriptionCode = data?.subscription_code ?? data?.subscription?.subscription_code ?? data?.code;
|
|
368
|
+
const customerCode = data?.customer?.customer_code ?? data?.customer_code ?? data?.customer?.code;
|
|
369
|
+
const planCode = data?.plan?.plan_code ?? data?.plan_code ?? data?.plan;
|
|
370
|
+
let metadata = data?.metadata;
|
|
371
|
+
if (typeof metadata === "string") try {
|
|
372
|
+
metadata = JSON.parse(metadata);
|
|
373
|
+
} catch {}
|
|
374
|
+
const referenceIdFromMetadata = typeof metadata === "object" && metadata !== null ? metadata.referenceId : void 0;
|
|
375
|
+
let planNameFromMetadata = typeof metadata === "object" && metadata !== null ? metadata.plan : void 0;
|
|
376
|
+
if (typeof planNameFromMetadata === "string") planNameFromMetadata = planNameFromMetadata.toLowerCase();
|
|
377
|
+
const plans = await getPlans(options.subscription);
|
|
378
|
+
const planFromCode = planCode !== void 0 && planCode !== null && planCode !== "" ? plans.find((p) => p.planCode !== void 0 && p.planCode !== null && p.planCode === planCode) : void 0;
|
|
379
|
+
const planPart = planFromCode?.name ?? planNameFromMetadata;
|
|
380
|
+
const planName = planPart !== void 0 && planPart !== null && planPart !== "" ? planPart.toLowerCase() : void 0;
|
|
381
|
+
if (subscriptionCode !== void 0 && subscriptionCode !== null && subscriptionCode !== "") {
|
|
382
|
+
const where = [];
|
|
383
|
+
if (referenceIdFromMetadata !== void 0 && referenceIdFromMetadata !== null && referenceIdFromMetadata !== "") where.push({
|
|
384
|
+
field: "referenceId",
|
|
385
|
+
value: referenceIdFromMetadata
|
|
386
|
+
});
|
|
387
|
+
else if (customerCode !== void 0 && customerCode !== null && customerCode !== "") where.push({
|
|
388
|
+
field: "paystackCustomerCode",
|
|
389
|
+
value: customerCode
|
|
390
|
+
});
|
|
391
|
+
if (planName !== void 0 && planName !== null && planName !== "") where.push({
|
|
392
|
+
field: "plan",
|
|
393
|
+
value: planName
|
|
394
|
+
});
|
|
395
|
+
if (where.length > 0) {
|
|
396
|
+
const matches = await ctx.context.adapter.findMany({
|
|
397
|
+
model: "subscription",
|
|
398
|
+
where
|
|
313
399
|
});
|
|
314
|
-
|
|
315
|
-
|
|
400
|
+
const subscription = matches !== void 0 && matches !== null ? matches[0] : void 0;
|
|
401
|
+
if (subscription !== void 0 && subscription !== null) {
|
|
402
|
+
await ctx.context.adapter.update({
|
|
316
403
|
model: "subscription",
|
|
317
|
-
|
|
404
|
+
update: {
|
|
405
|
+
paystackSubscriptionCode: subscriptionCode,
|
|
406
|
+
status: "active",
|
|
407
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
408
|
+
periodEnd: data?.next_payment_date !== void 0 && data?.next_payment_date !== null && data?.next_payment_date !== "" ? new Date(data.next_payment_date) : void 0
|
|
409
|
+
},
|
|
410
|
+
where: [{
|
|
411
|
+
field: "id",
|
|
412
|
+
value: subscription.id
|
|
413
|
+
}]
|
|
318
414
|
});
|
|
319
|
-
const
|
|
320
|
-
if (
|
|
321
|
-
await
|
|
322
|
-
|
|
323
|
-
|
|
415
|
+
const plan = planFromCode ?? (planName !== void 0 && planName !== null && planName !== "" ? await getPlanByName(options, planName) : void 0);
|
|
416
|
+
if (plan !== void 0 && plan !== null) {
|
|
417
|
+
await options.subscription.onSubscriptionComplete?.({
|
|
418
|
+
event,
|
|
419
|
+
subscription: {
|
|
420
|
+
...subscription,
|
|
324
421
|
paystackSubscriptionCode: subscriptionCode,
|
|
325
|
-
status: "active"
|
|
326
|
-
updatedAt: /* @__PURE__ */ new Date(),
|
|
327
|
-
periodEnd: data?.next_payment_date !== void 0 && data?.next_payment_date !== null && data?.next_payment_date !== "" ? new Date(data.next_payment_date) : void 0
|
|
422
|
+
status: "active"
|
|
328
423
|
},
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
paystackSubscriptionCode: subscriptionCode,
|
|
341
|
-
status: "active"
|
|
342
|
-
},
|
|
343
|
-
plan
|
|
344
|
-
}, ctx);
|
|
345
|
-
await options.subscription.onSubscriptionCreated?.({
|
|
346
|
-
event,
|
|
347
|
-
subscription: {
|
|
348
|
-
...subscription,
|
|
349
|
-
paystackSubscriptionCode: subscriptionCode,
|
|
350
|
-
status: "active"
|
|
351
|
-
},
|
|
352
|
-
plan
|
|
353
|
-
}, ctx);
|
|
354
|
-
}
|
|
424
|
+
plan
|
|
425
|
+
}, ctx);
|
|
426
|
+
await options.subscription.onSubscriptionCreated?.({
|
|
427
|
+
event,
|
|
428
|
+
subscription: {
|
|
429
|
+
...subscription,
|
|
430
|
+
paystackSubscriptionCode: subscriptionCode,
|
|
431
|
+
status: "active"
|
|
432
|
+
},
|
|
433
|
+
plan
|
|
434
|
+
}, ctx);
|
|
355
435
|
}
|
|
356
436
|
}
|
|
357
437
|
}
|
|
358
438
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
}
|
|
439
|
+
}
|
|
440
|
+
if (eventName === "subscription.disable" || eventName === "subscription.not_renew") {
|
|
441
|
+
const subscriptionCode = data?.subscription_code ?? data?.subscription?.subscription_code ?? data?.code;
|
|
442
|
+
if (subscriptionCode !== void 0 && subscriptionCode !== null && subscriptionCode !== "") {
|
|
443
|
+
const existing = await ctx.context.adapter.findOne({
|
|
444
|
+
model: "subscription",
|
|
445
|
+
where: [{
|
|
446
|
+
field: "paystackSubscriptionCode",
|
|
447
|
+
value: subscriptionCode
|
|
448
|
+
}]
|
|
449
|
+
});
|
|
450
|
+
let newStatus = "canceled";
|
|
451
|
+
if (existing?.cancelAtPeriodEnd === true && existing.periodEnd !== void 0 && existing.periodEnd !== null && new Date(existing.periodEnd) > /* @__PURE__ */ new Date()) newStatus = "active";
|
|
452
|
+
await ctx.context.adapter.update({
|
|
453
|
+
model: "subscription",
|
|
454
|
+
update: {
|
|
455
|
+
status: newStatus,
|
|
456
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
457
|
+
},
|
|
458
|
+
where: [{
|
|
459
|
+
field: "paystackSubscriptionCode",
|
|
460
|
+
value: subscriptionCode
|
|
461
|
+
}]
|
|
462
|
+
});
|
|
463
|
+
if (existing) await options.subscription.onSubscriptionCancel?.({
|
|
464
|
+
event,
|
|
465
|
+
subscription: {
|
|
466
|
+
...existing,
|
|
467
|
+
status: "canceled"
|
|
468
|
+
}
|
|
469
|
+
}, ctx);
|
|
390
470
|
}
|
|
391
|
-
} catch (_e) {
|
|
392
|
-
ctx.context.logger.error("Failed to sync Paystack webhook event", _e);
|
|
393
471
|
}
|
|
472
|
+
} catch (_e) {
|
|
473
|
+
ctx.context.logger.error("Failed to sync Paystack webhook event", _e);
|
|
394
474
|
}
|
|
395
475
|
await options.onEvent?.(event);
|
|
396
476
|
return ctx.json({ received: true });
|
|
@@ -449,14 +529,14 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
|
|
|
449
529
|
let product;
|
|
450
530
|
if (planName !== void 0 && planName !== null && planName !== "") {
|
|
451
531
|
if (subscriptionOptions?.enabled !== true) throw new APIError("BAD_REQUEST", { message: "Subscriptions are not enabled." });
|
|
452
|
-
plan = await getPlanByName(options, planName);
|
|
532
|
+
plan = await getPlanByName(options, planName) ?? void 0;
|
|
453
533
|
if (!plan) throw new APIError("BAD_REQUEST", {
|
|
454
534
|
code: "SUBSCRIPTION_PLAN_NOT_FOUND",
|
|
455
535
|
message: PAYSTACK_ERROR_CODES.SUBSCRIPTION_PLAN_NOT_FOUND,
|
|
456
536
|
status: 400
|
|
457
537
|
});
|
|
458
538
|
} else if (productName !== void 0 && productName !== null && productName !== "") {
|
|
459
|
-
if (typeof productName === "string") product = await getProductByName(options, productName);
|
|
539
|
+
if (typeof productName === "string") product = await getProductByName(options, productName) ?? void 0;
|
|
460
540
|
if (!product) throw new APIError("BAD_REQUEST", {
|
|
461
541
|
message: `Product '${productName}' not found.`,
|
|
462
542
|
status: 400
|
|
@@ -465,7 +545,7 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
|
|
|
465
545
|
message: "Either 'plan', 'product', or 'amount' is required to initialize a transaction.",
|
|
466
546
|
status: 400
|
|
467
547
|
});
|
|
468
|
-
const amount = bodyAmount ?? product?.
|
|
548
|
+
const amount = bodyAmount ?? product?.price;
|
|
469
549
|
const finalCurrency = currency ?? product?.currency ?? plan?.currency ?? "NGN";
|
|
470
550
|
let url;
|
|
471
551
|
let reference;
|
|
@@ -580,6 +660,7 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
|
|
|
580
660
|
currency: plan?.currency ?? currency ?? "NGN",
|
|
581
661
|
status: "pending",
|
|
582
662
|
plan: plan?.name.toLowerCase(),
|
|
663
|
+
product: product?.name.toLowerCase(),
|
|
583
664
|
metadata: extraMetadata !== void 0 && extraMetadata !== null ? JSON.stringify(extraMetadata) : void 0,
|
|
584
665
|
createdAt: /* @__PURE__ */ new Date(),
|
|
585
666
|
updatedAt: /* @__PURE__ */ new Date()
|
|
@@ -651,13 +732,12 @@ const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
|
|
|
651
732
|
message: error?.message ?? PAYSTACK_ERROR_CODES.FAILED_TO_VERIFY_TRANSACTION
|
|
652
733
|
});
|
|
653
734
|
}
|
|
654
|
-
|
|
655
|
-
if (data !== null && data !== void 0 && typeof data === "object" && "status" in data && "data" in data) data = data.data;
|
|
735
|
+
const data = unwrapSdkResult(verifyRes);
|
|
656
736
|
const status = data?.status;
|
|
657
737
|
const reference = data?.reference ?? ctx.body.reference;
|
|
658
738
|
const paystackId = data?.id !== void 0 && data?.id !== null ? String(data.id) : void 0;
|
|
659
739
|
const authorizationCode = (data?.authorization)?.authorization_code;
|
|
660
|
-
if (status === "success")
|
|
740
|
+
if (status === "success") {
|
|
661
741
|
const session = await getSessionFromCtx(ctx);
|
|
662
742
|
const referenceId = (await ctx.context.adapter.findOne({
|
|
663
743
|
model: "paystackTransaction",
|
|
@@ -690,107 +770,116 @@ const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
|
|
|
690
770
|
}
|
|
691
771
|
if (!authorized) throw new APIError("UNAUTHORIZED");
|
|
692
772
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
plan: planConfig.planCode,
|
|
750
|
-
authorization: authorizationCode,
|
|
751
|
-
start_date: trialEnd
|
|
752
|
-
}));
|
|
753
|
-
paystackSubscriptionCode = (subData?.data ?? subData)?.subscription_code;
|
|
773
|
+
try {
|
|
774
|
+
await ctx.context.adapter.update({
|
|
775
|
+
model: "paystackTransaction",
|
|
776
|
+
update: {
|
|
777
|
+
status: "success",
|
|
778
|
+
paystackId,
|
|
779
|
+
...data?.amount !== void 0 && data?.amount !== null ? { amount: data.amount } : {},
|
|
780
|
+
...data?.currency !== void 0 && data?.currency !== null ? { currency: data.currency } : {},
|
|
781
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
782
|
+
},
|
|
783
|
+
where: [{
|
|
784
|
+
field: "reference",
|
|
785
|
+
value: reference
|
|
786
|
+
}]
|
|
787
|
+
});
|
|
788
|
+
const customer = data?.customer;
|
|
789
|
+
const paystackCustomerCodeFromPaystack = customer !== void 0 && customer !== null && typeof customer === "object" ? customer.customer_code : void 0;
|
|
790
|
+
if (paystackCustomerCodeFromPaystack !== void 0 && paystackCustomerCodeFromPaystack !== null && paystackCustomerCodeFromPaystack !== "" && referenceId !== void 0 && referenceId !== null && referenceId !== "") if ((options.organization?.enabled === true && (referenceId.startsWith("org_") || await ctx.context.adapter.findOne({
|
|
791
|
+
model: "organization",
|
|
792
|
+
where: [{
|
|
793
|
+
field: "id",
|
|
794
|
+
value: referenceId
|
|
795
|
+
}]
|
|
796
|
+
}) !== null)) === true) await ctx.context.adapter.update({
|
|
797
|
+
model: "organization",
|
|
798
|
+
update: { paystackCustomerCode: paystackCustomerCodeFromPaystack },
|
|
799
|
+
where: [{
|
|
800
|
+
field: "id",
|
|
801
|
+
value: referenceId
|
|
802
|
+
}]
|
|
803
|
+
});
|
|
804
|
+
else await ctx.context.adapter.update({
|
|
805
|
+
model: "user",
|
|
806
|
+
update: { paystackCustomerCode: paystackCustomerCodeFromPaystack },
|
|
807
|
+
where: [{
|
|
808
|
+
field: "id",
|
|
809
|
+
value: referenceId
|
|
810
|
+
}]
|
|
811
|
+
});
|
|
812
|
+
const transaction = await ctx.context.adapter.findOne({
|
|
813
|
+
model: "paystackTransaction",
|
|
814
|
+
where: [{
|
|
815
|
+
field: "reference",
|
|
816
|
+
value: reference
|
|
817
|
+
}]
|
|
818
|
+
});
|
|
819
|
+
if (transaction?.product) await syncProductQuantityFromPaystack(ctx, transaction.product, options.paystackClient);
|
|
820
|
+
let isTrial = false;
|
|
821
|
+
let trialEnd;
|
|
822
|
+
let targetPlan;
|
|
823
|
+
if (data?.metadata !== void 0 && data?.metadata !== null) {
|
|
824
|
+
const metaRaw = data.metadata;
|
|
825
|
+
const meta = typeof metaRaw === "string" ? JSON.parse(metaRaw) : metaRaw;
|
|
826
|
+
isTrial = meta.isTrial === true || meta.isTrial === "true";
|
|
827
|
+
trialEnd = meta.trialEnd;
|
|
828
|
+
targetPlan = meta.plan;
|
|
754
829
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
830
|
+
let paystackSubscriptionCode;
|
|
831
|
+
if (isTrial === true && targetPlan !== void 0 && targetPlan !== null && targetPlan !== "" && trialEnd !== void 0 && trialEnd !== null && trialEnd !== "") {
|
|
832
|
+
const email = (data?.customer)?.email;
|
|
833
|
+
const planConfig = (await getPlans(subscriptionOptions)).find((p) => p.name.toLowerCase() === targetPlan?.toLowerCase());
|
|
834
|
+
if (planConfig !== void 0 && (planConfig.planCode === void 0 || planConfig.planCode === null || planConfig.planCode === "")) paystackSubscriptionCode = `LOC_${reference}`;
|
|
835
|
+
if (authorizationCode !== void 0 && authorizationCode !== null && authorizationCode !== "" && email !== void 0 && email !== null && email !== "" && planConfig?.planCode !== void 0 && planConfig?.planCode !== null && planConfig?.planCode !== "") {
|
|
836
|
+
const subData = unwrapSdkResult(await paystack.subscriptionCreate({
|
|
837
|
+
customer: email,
|
|
838
|
+
plan: planConfig.planCode,
|
|
839
|
+
authorization: authorizationCode,
|
|
840
|
+
start_date: trialEnd
|
|
841
|
+
}));
|
|
842
|
+
paystackSubscriptionCode = (subData?.data ?? subData)?.subscription_code;
|
|
843
|
+
}
|
|
844
|
+
} else if (isTrial !== true) {
|
|
845
|
+
const planCodeFromPaystack = (data?.plan)?.plan_code;
|
|
846
|
+
if (planCodeFromPaystack === void 0 || planCodeFromPaystack === null || planCodeFromPaystack === "") paystackSubscriptionCode = `LOC_${reference}`;
|
|
847
|
+
else paystackSubscriptionCode = (data?.subscription)?.subscription_code;
|
|
848
|
+
}
|
|
849
|
+
const updatedSubscription = await ctx.context.adapter.update({
|
|
850
|
+
model: "subscription",
|
|
851
|
+
update: {
|
|
852
|
+
status: isTrial === true ? "trialing" : "active",
|
|
853
|
+
periodStart: /* @__PURE__ */ new Date(),
|
|
854
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
855
|
+
...isTrial === true && trialEnd !== void 0 && trialEnd !== null && trialEnd !== "" ? {
|
|
856
|
+
trialStart: /* @__PURE__ */ new Date(),
|
|
857
|
+
trialEnd: new Date(trialEnd),
|
|
858
|
+
periodEnd: new Date(trialEnd)
|
|
859
|
+
} : {},
|
|
860
|
+
...paystackSubscriptionCode !== void 0 && paystackSubscriptionCode !== null && paystackSubscriptionCode !== "" ? { paystackSubscriptionCode } : {},
|
|
861
|
+
...authorizationCode !== void 0 && authorizationCode !== null && authorizationCode !== "" ? { paystackAuthorizationCode: authorizationCode } : {}
|
|
862
|
+
},
|
|
863
|
+
where: [{
|
|
864
|
+
field: "paystackTransactionReference",
|
|
865
|
+
value: reference
|
|
866
|
+
}, ...referenceId !== void 0 && referenceId !== null && referenceId !== "" ? [{
|
|
867
|
+
field: "referenceId",
|
|
868
|
+
value: referenceId
|
|
869
|
+
}] : []]
|
|
870
|
+
});
|
|
871
|
+
if (updatedSubscription && subscriptionOptions?.enabled === true && "onSubscriptionComplete" in subscriptionOptions && typeof subscriptionOptions.onSubscriptionComplete === "function") {
|
|
872
|
+
const plan = (await getPlans(subscriptionOptions)).find((p) => p.name.toLowerCase() === updatedSubscription.plan.toLowerCase());
|
|
873
|
+
if (plan) await subscriptionOptions.onSubscriptionComplete({
|
|
874
|
+
event: data,
|
|
875
|
+
subscription: updatedSubscription,
|
|
876
|
+
plan
|
|
877
|
+
}, ctx);
|
|
878
|
+
}
|
|
879
|
+
} catch (e) {
|
|
880
|
+
ctx.context.logger.error("Failed to update transaction/subscription after verification", e);
|
|
789
881
|
}
|
|
790
|
-
}
|
|
791
|
-
ctx.context.logger.error("Failed to update transaction/subscription after verification", e);
|
|
792
|
-
}
|
|
793
|
-
else if (status === "failed" || status === "abandoned") try {
|
|
882
|
+
} else if (status === "failed" || status === "abandoned") try {
|
|
794
883
|
await ctx.context.adapter.update({
|
|
795
884
|
model: "paystackTransaction",
|
|
796
885
|
update: {
|
|
@@ -1049,6 +1138,69 @@ const getSubscriptionManageLink = (options) => {
|
|
|
1049
1138
|
}
|
|
1050
1139
|
});
|
|
1051
1140
|
};
|
|
1141
|
+
const syncProducts = (options) => {
|
|
1142
|
+
return createAuthEndpoint("/paystack/sync-products", {
|
|
1143
|
+
method: "POST",
|
|
1144
|
+
metadata: { ...HIDE_METADATA },
|
|
1145
|
+
disableBody: true,
|
|
1146
|
+
use: [sessionMiddleware]
|
|
1147
|
+
}, async (ctx) => {
|
|
1148
|
+
console.error("DEBUG: syncProducts endpoint hit!");
|
|
1149
|
+
const paystack = getPaystackOps(options.paystackClient);
|
|
1150
|
+
try {
|
|
1151
|
+
const res = unwrapSdkResult(await paystack.productList());
|
|
1152
|
+
const productsData = res !== null && typeof res === "object" && "status" in res && "data" in res ? res.data : res?.data ?? res;
|
|
1153
|
+
if (!Array.isArray(productsData)) return ctx.json({
|
|
1154
|
+
status: "success",
|
|
1155
|
+
count: 0
|
|
1156
|
+
});
|
|
1157
|
+
for (const product of productsData) {
|
|
1158
|
+
const paystackId = String(product.id);
|
|
1159
|
+
const existing = await ctx.context.adapter.findOne({
|
|
1160
|
+
model: "paystackProduct",
|
|
1161
|
+
where: [{
|
|
1162
|
+
field: "paystackId",
|
|
1163
|
+
value: paystackId
|
|
1164
|
+
}]
|
|
1165
|
+
});
|
|
1166
|
+
const productData = {
|
|
1167
|
+
name: product.name,
|
|
1168
|
+
description: product.description,
|
|
1169
|
+
price: product.price,
|
|
1170
|
+
currency: product.currency,
|
|
1171
|
+
quantity: product.quantity,
|
|
1172
|
+
unlimited: product.unlimited,
|
|
1173
|
+
paystackId,
|
|
1174
|
+
slug: product.slug ?? product.name.toLowerCase().replace(/\s+/g, "-"),
|
|
1175
|
+
metadata: product.metadata ? JSON.stringify(product.metadata) : void 0,
|
|
1176
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1177
|
+
};
|
|
1178
|
+
if (existing) await ctx.context.adapter.update({
|
|
1179
|
+
model: "paystackProduct",
|
|
1180
|
+
update: productData,
|
|
1181
|
+
where: [{
|
|
1182
|
+
field: "id",
|
|
1183
|
+
value: existing.id
|
|
1184
|
+
}]
|
|
1185
|
+
});
|
|
1186
|
+
else await ctx.context.adapter.create({
|
|
1187
|
+
model: "paystackProduct",
|
|
1188
|
+
data: {
|
|
1189
|
+
...productData,
|
|
1190
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
1191
|
+
}
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
return ctx.json({
|
|
1195
|
+
status: "success",
|
|
1196
|
+
count: productsData.length
|
|
1197
|
+
});
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
ctx.context.logger.error("Failed to sync products", error);
|
|
1200
|
+
throw new APIError("BAD_REQUEST", { message: error?.message ?? "Failed to sync products" });
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
};
|
|
1052
1204
|
const getConfig = (options) => {
|
|
1053
1205
|
return createAuthEndpoint("/paystack/get-config", {
|
|
1054
1206
|
method: "GET",
|
|
@@ -1115,7 +1267,11 @@ const chargeRecurringSubscription = (options) => {
|
|
|
1115
1267
|
}
|
|
1116
1268
|
}
|
|
1117
1269
|
if (email === void 0 || email === null || email === "") throw new APIError("NOT_FOUND", { message: "User email not found" });
|
|
1118
|
-
|
|
1270
|
+
const finalCurrency = plan.currency ?? "NGN";
|
|
1271
|
+
if (!validateMinAmount(amount, finalCurrency)) throw new APIError("BAD_REQUEST", {
|
|
1272
|
+
message: `Amount ${amount} is less than the minimum required for ${finalCurrency}.`,
|
|
1273
|
+
status: 400
|
|
1274
|
+
});
|
|
1119
1275
|
const data = unwrapSdkResult(await getPaystackOps(options.paystackClient).transactionChargeAuthorization({
|
|
1120
1276
|
email,
|
|
1121
1277
|
amount,
|
|
@@ -1161,7 +1317,8 @@ const chargeRecurringSubscription = (options) => {
|
|
|
1161
1317
|
const transactions = { paystackTransaction: { fields: {
|
|
1162
1318
|
reference: {
|
|
1163
1319
|
type: "string",
|
|
1164
|
-
required: true
|
|
1320
|
+
required: true,
|
|
1321
|
+
unique: true
|
|
1165
1322
|
},
|
|
1166
1323
|
paystackId: {
|
|
1167
1324
|
type: "string",
|
|
@@ -1169,11 +1326,13 @@ const transactions = { paystackTransaction: { fields: {
|
|
|
1169
1326
|
},
|
|
1170
1327
|
referenceId: {
|
|
1171
1328
|
type: "string",
|
|
1172
|
-
required: true
|
|
1329
|
+
required: true,
|
|
1330
|
+
index: true
|
|
1173
1331
|
},
|
|
1174
1332
|
userId: {
|
|
1175
1333
|
type: "string",
|
|
1176
|
-
required: true
|
|
1334
|
+
required: true,
|
|
1335
|
+
index: true
|
|
1177
1336
|
},
|
|
1178
1337
|
amount: {
|
|
1179
1338
|
type: "number",
|
|
@@ -1191,6 +1350,10 @@ const transactions = { paystackTransaction: { fields: {
|
|
|
1191
1350
|
type: "string",
|
|
1192
1351
|
required: false
|
|
1193
1352
|
},
|
|
1353
|
+
product: {
|
|
1354
|
+
type: "string",
|
|
1355
|
+
required: false
|
|
1356
|
+
},
|
|
1194
1357
|
metadata: {
|
|
1195
1358
|
type: "string",
|
|
1196
1359
|
required: false
|
|
@@ -1207,23 +1370,28 @@ const transactions = { paystackTransaction: { fields: {
|
|
|
1207
1370
|
const subscriptions = { subscription: { fields: {
|
|
1208
1371
|
plan: {
|
|
1209
1372
|
type: "string",
|
|
1210
|
-
required: true
|
|
1373
|
+
required: true,
|
|
1374
|
+
index: true
|
|
1211
1375
|
},
|
|
1212
1376
|
referenceId: {
|
|
1213
1377
|
type: "string",
|
|
1214
|
-
required: true
|
|
1378
|
+
required: true,
|
|
1379
|
+
index: true
|
|
1215
1380
|
},
|
|
1216
1381
|
paystackCustomerCode: {
|
|
1217
1382
|
type: "string",
|
|
1218
|
-
required: false
|
|
1383
|
+
required: false,
|
|
1384
|
+
index: true
|
|
1219
1385
|
},
|
|
1220
1386
|
paystackSubscriptionCode: {
|
|
1221
1387
|
type: "string",
|
|
1222
|
-
required: false
|
|
1388
|
+
required: false,
|
|
1389
|
+
unique: true
|
|
1223
1390
|
},
|
|
1224
1391
|
paystackTransactionReference: {
|
|
1225
1392
|
type: "string",
|
|
1226
|
-
required: false
|
|
1393
|
+
required: false,
|
|
1394
|
+
index: true
|
|
1227
1395
|
},
|
|
1228
1396
|
paystackAuthorizationCode: {
|
|
1229
1397
|
type: "string",
|
|
@@ -1269,28 +1437,82 @@ const subscriptions = { subscription: { fields: {
|
|
|
1269
1437
|
} } };
|
|
1270
1438
|
const user = { user: { fields: { paystackCustomerCode: {
|
|
1271
1439
|
type: "string",
|
|
1272
|
-
required: false
|
|
1440
|
+
required: false,
|
|
1441
|
+
index: true
|
|
1273
1442
|
} } } };
|
|
1274
1443
|
const organization = { organization: { fields: {
|
|
1275
1444
|
paystackCustomerCode: {
|
|
1276
1445
|
type: "string",
|
|
1277
|
-
required: false
|
|
1446
|
+
required: false,
|
|
1447
|
+
index: true
|
|
1278
1448
|
},
|
|
1279
1449
|
email: {
|
|
1280
1450
|
type: "string",
|
|
1281
1451
|
required: false
|
|
1282
1452
|
}
|
|
1283
1453
|
} } };
|
|
1454
|
+
const products = { paystackProduct: { fields: {
|
|
1455
|
+
name: {
|
|
1456
|
+
type: "string",
|
|
1457
|
+
required: true
|
|
1458
|
+
},
|
|
1459
|
+
description: {
|
|
1460
|
+
type: "string",
|
|
1461
|
+
required: false
|
|
1462
|
+
},
|
|
1463
|
+
price: {
|
|
1464
|
+
type: "number",
|
|
1465
|
+
required: true
|
|
1466
|
+
},
|
|
1467
|
+
currency: {
|
|
1468
|
+
type: "string",
|
|
1469
|
+
required: true
|
|
1470
|
+
},
|
|
1471
|
+
quantity: {
|
|
1472
|
+
type: "number",
|
|
1473
|
+
required: false,
|
|
1474
|
+
defaultValue: 0
|
|
1475
|
+
},
|
|
1476
|
+
unlimited: {
|
|
1477
|
+
type: "boolean",
|
|
1478
|
+
required: false,
|
|
1479
|
+
defaultValue: true
|
|
1480
|
+
},
|
|
1481
|
+
paystackId: {
|
|
1482
|
+
type: "string",
|
|
1483
|
+
required: false,
|
|
1484
|
+
unique: true
|
|
1485
|
+
},
|
|
1486
|
+
slug: {
|
|
1487
|
+
type: "string",
|
|
1488
|
+
required: true,
|
|
1489
|
+
unique: true
|
|
1490
|
+
},
|
|
1491
|
+
metadata: {
|
|
1492
|
+
type: "string",
|
|
1493
|
+
required: false
|
|
1494
|
+
},
|
|
1495
|
+
createdAt: {
|
|
1496
|
+
type: "date",
|
|
1497
|
+
required: true
|
|
1498
|
+
},
|
|
1499
|
+
updatedAt: {
|
|
1500
|
+
type: "date",
|
|
1501
|
+
required: true
|
|
1502
|
+
}
|
|
1503
|
+
} } };
|
|
1284
1504
|
const getSchema = (options) => {
|
|
1285
1505
|
let baseSchema;
|
|
1286
1506
|
if (options.subscription?.enabled === true) baseSchema = {
|
|
1287
1507
|
...subscriptions,
|
|
1288
1508
|
...transactions,
|
|
1289
|
-
...user
|
|
1509
|
+
...user,
|
|
1510
|
+
...products
|
|
1290
1511
|
};
|
|
1291
1512
|
else baseSchema = {
|
|
1292
1513
|
...user,
|
|
1293
|
-
...transactions
|
|
1514
|
+
...transactions,
|
|
1515
|
+
...products
|
|
1294
1516
|
};
|
|
1295
1517
|
if (options.organization?.enabled === true) baseSchema = {
|
|
1296
1518
|
...baseSchema,
|
|
@@ -1358,7 +1580,8 @@ const paystack = (options) => {
|
|
|
1358
1580
|
upgradeSubscription: upgradeSubscription(options),
|
|
1359
1581
|
cancelSubscription: cancelSubscription(options),
|
|
1360
1582
|
restoreSubscription: restoreSubscription(options),
|
|
1361
|
-
chargeRecurringSubscription: chargeRecurringSubscription(options)
|
|
1583
|
+
chargeRecurringSubscription: chargeRecurringSubscription(options),
|
|
1584
|
+
syncProducts: syncProducts(options)
|
|
1362
1585
|
},
|
|
1363
1586
|
schema: getSchema(options),
|
|
1364
1587
|
init: (ctx) => {
|
|
@@ -1366,12 +1589,12 @@ const paystack = (options) => {
|
|
|
1366
1589
|
databaseHooks: {
|
|
1367
1590
|
user: { create: { async after(user, hookCtx) {
|
|
1368
1591
|
if (hookCtx === void 0 || hookCtx === null || options.createCustomerOnSignUp !== true) return;
|
|
1369
|
-
const
|
|
1592
|
+
const sdkRes = unwrapSdkResult(await getPaystackOps(options.paystackClient).customerCreate({
|
|
1370
1593
|
email: user.email,
|
|
1371
1594
|
first_name: user.name ?? void 0,
|
|
1372
1595
|
metadata: { userId: user.id }
|
|
1373
1596
|
}));
|
|
1374
|
-
const customerCode =
|
|
1597
|
+
const customerCode = sdkRes?.customer_code ?? (sdkRes?.data)?.customer_code;
|
|
1375
1598
|
if (customerCode === void 0 || customerCode === null) return;
|
|
1376
1599
|
await ctx.adapter.update({
|
|
1377
1600
|
model: "user",
|
|
@@ -1412,12 +1635,11 @@ const paystack = (options) => {
|
|
|
1412
1635
|
metadata: { organizationId: org.id }
|
|
1413
1636
|
}, extraCreateParams);
|
|
1414
1637
|
const sdkRes = unwrapSdkResult(await getPaystackOps(options.paystackClient).customerCreate(params));
|
|
1415
|
-
const
|
|
1416
|
-
const customerCode = paystackCustomer?.customer_code;
|
|
1638
|
+
const customerCode = sdkRes?.customer_code ?? (sdkRes?.data)?.customer_code;
|
|
1417
1639
|
if (customerCode === void 0 || customerCode === null) return;
|
|
1418
1640
|
await ctx.internalAdapter.updateOrganization(org.id, { paystackCustomerCode: customerCode });
|
|
1419
1641
|
await options.organization?.onCustomerCreate?.({
|
|
1420
|
-
paystackCustomer,
|
|
1642
|
+
paystackCustomer: sdkRes,
|
|
1421
1643
|
organization: {
|
|
1422
1644
|
...org,
|
|
1423
1645
|
paystackCustomerCode: customerCode
|