@alexasomba/better-auth-paystack 1.0.4 → 1.1.2
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 +15 -1
- package/dist/client.d.mts.map +1 -1
- package/dist/client.mjs +18 -0
- package/dist/client.mjs.map +1 -1
- package/dist/index.d.mts +172 -26
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +770 -362
- package/dist/index.mjs.map +1 -1
- package/dist/{types-Dlv_nSLg.d.mts → types-CMXvth6C.d.mts} +99 -28
- package/dist/types-CMXvth6C.d.mts.map +1 -0
- package/package.json +13 -13
- package/dist/types-Dlv_nSLg.d.mts.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -6,6 +6,108 @@ 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
|
+
planList: () => {
|
|
104
|
+
if (paystackClient?.plan_list !== void 0) return paystackClient.plan_list();
|
|
105
|
+
return paystackClient?.plan?.list?.();
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
//#endregion
|
|
9
111
|
//#region src/utils.ts
|
|
10
112
|
async function getPlans(subscriptionOptions) {
|
|
11
113
|
if (subscriptionOptions?.enabled === true) return typeof subscriptionOptions.plans === "function" ? subscriptionOptions.plans() : subscriptionOptions.plans;
|
|
@@ -20,7 +122,7 @@ async function getProducts(productOptions) {
|
|
|
20
122
|
return [];
|
|
21
123
|
}
|
|
22
124
|
async function getProductByName(options, name) {
|
|
23
|
-
return await getProducts(options.products).then((products) => products?.find((product) => product.name.toLowerCase() === name.toLowerCase()));
|
|
125
|
+
return await getProducts(options.products).then((products) => products?.find((product) => product.name.toLowerCase() === name.toLowerCase()) ?? null);
|
|
24
126
|
}
|
|
25
127
|
function getNextPeriodEnd(startDate, interval) {
|
|
26
128
|
const date = new Date(startDate);
|
|
@@ -62,6 +164,62 @@ function validateMinAmount(amount, currency) {
|
|
|
62
164
|
}[currency.toUpperCase()];
|
|
63
165
|
return min !== void 0 ? amount >= min : true;
|
|
64
166
|
}
|
|
167
|
+
async function syncProductQuantityFromPaystack(ctx, productName, paystackClient) {
|
|
168
|
+
let localProduct = await ctx.context.adapter.findOne({
|
|
169
|
+
model: "paystackProduct",
|
|
170
|
+
where: [{
|
|
171
|
+
field: "name",
|
|
172
|
+
value: productName
|
|
173
|
+
}]
|
|
174
|
+
});
|
|
175
|
+
localProduct ??= await ctx.context.adapter.findOne({
|
|
176
|
+
model: "paystackProduct",
|
|
177
|
+
where: [{
|
|
178
|
+
field: "slug",
|
|
179
|
+
value: productName.toLowerCase().replace(/\s+/g, "-")
|
|
180
|
+
}]
|
|
181
|
+
});
|
|
182
|
+
if (localProduct?.paystackId === void 0 || localProduct?.paystackId === null || localProduct?.paystackId === "") {
|
|
183
|
+
if (localProduct && localProduct.unlimited !== true && localProduct.quantity !== void 0 && localProduct.quantity > 0) await ctx.context.adapter.update({
|
|
184
|
+
model: "paystackProduct",
|
|
185
|
+
update: {
|
|
186
|
+
quantity: localProduct.quantity - 1,
|
|
187
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
188
|
+
},
|
|
189
|
+
where: [{
|
|
190
|
+
field: "id",
|
|
191
|
+
value: localProduct.id
|
|
192
|
+
}]
|
|
193
|
+
});
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const remoteQuantity = unwrapSdkResult(await getPaystackOps(paystackClient).productFetch(localProduct.paystackId))?.quantity;
|
|
198
|
+
if (remoteQuantity !== void 0) await ctx.context.adapter.update({
|
|
199
|
+
model: "paystackProduct",
|
|
200
|
+
update: {
|
|
201
|
+
quantity: remoteQuantity,
|
|
202
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
203
|
+
},
|
|
204
|
+
where: [{
|
|
205
|
+
field: "id",
|
|
206
|
+
value: localProduct.id
|
|
207
|
+
}]
|
|
208
|
+
});
|
|
209
|
+
} catch {
|
|
210
|
+
if (localProduct.unlimited !== true && localProduct.quantity !== void 0 && localProduct.quantity > 0) await ctx.context.adapter.update({
|
|
211
|
+
model: "paystackProduct",
|
|
212
|
+
update: {
|
|
213
|
+
quantity: localProduct.quantity - 1,
|
|
214
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
215
|
+
},
|
|
216
|
+
where: [{
|
|
217
|
+
field: "id",
|
|
218
|
+
value: localProduct.id
|
|
219
|
+
}]
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
65
223
|
|
|
66
224
|
//#endregion
|
|
67
225
|
//#region src/middleware.ts
|
|
@@ -102,96 +260,6 @@ const referenceMiddleware = (options, action) => createAuthMiddleware(async (ctx
|
|
|
102
260
|
throw new APIError("BAD_REQUEST", { message: "Passing referenceId isn't allowed without subscription.authorizeReference or valid organization membership." });
|
|
103
261
|
});
|
|
104
262
|
|
|
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
263
|
//#endregion
|
|
196
264
|
//#region src/routes.ts
|
|
197
265
|
const PAYSTACK_ERROR_CODES = defineErrorCodes({
|
|
@@ -244,14 +312,14 @@ const paystackWebhook = (options) => {
|
|
|
244
312
|
status: 401
|
|
245
313
|
});
|
|
246
314
|
const event = JSON.parse(payload);
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
315
|
+
const eventName = event.event;
|
|
316
|
+
const data = event.data;
|
|
317
|
+
if (eventName === "charge.success") {
|
|
318
|
+
const reference = data?.reference;
|
|
319
|
+
const paystackId = data?.id !== void 0 && data?.id !== null ? String(data.id) : void 0;
|
|
320
|
+
if (reference !== void 0 && reference !== null && reference !== "") {
|
|
321
|
+
try {
|
|
322
|
+
await ctx.context.adapter.update({
|
|
255
323
|
model: "paystackTransaction",
|
|
256
324
|
update: {
|
|
257
325
|
status: "success",
|
|
@@ -263,134 +331,156 @@ const paystackWebhook = (options) => {
|
|
|
263
331
|
value: reference
|
|
264
332
|
}]
|
|
265
333
|
});
|
|
334
|
+
} catch (e) {
|
|
335
|
+
ctx.context.logger.warn("Failed to update transaction status for charge.success", e);
|
|
266
336
|
}
|
|
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
|
-
}
|
|
337
|
+
try {
|
|
338
|
+
const transaction = await ctx.context.adapter.findOne({
|
|
339
|
+
model: "paystackTransaction",
|
|
340
|
+
where: [{
|
|
341
|
+
field: "reference",
|
|
342
|
+
value: reference
|
|
343
|
+
}]
|
|
344
|
+
});
|
|
345
|
+
if (transaction?.product) await syncProductQuantityFromPaystack(ctx, transaction.product, options.paystackClient);
|
|
346
|
+
} catch (e) {
|
|
347
|
+
ctx.context.logger.warn("Failed to sync product quantity", e);
|
|
284
348
|
}
|
|
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
|
-
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (eventName === "charge.failure") {
|
|
352
|
+
const reference = data?.reference;
|
|
353
|
+
if (reference !== void 0 && reference !== null && reference !== "") try {
|
|
354
|
+
await ctx.context.adapter.update({
|
|
355
|
+
model: "paystackTransaction",
|
|
356
|
+
update: {
|
|
357
|
+
status: "failed",
|
|
358
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
359
|
+
},
|
|
360
|
+
where: [{
|
|
361
|
+
field: "reference",
|
|
362
|
+
value: reference
|
|
363
|
+
}]
|
|
364
|
+
});
|
|
365
|
+
} catch (e) {
|
|
366
|
+
ctx.context.logger.warn("Failed to update transaction status for charge.failure", e);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (options.subscription?.enabled === true) try {
|
|
370
|
+
if (eventName === "subscription.create") {
|
|
371
|
+
const payloadData = data;
|
|
372
|
+
const subscriptionCode = payloadData?.subscription_code ?? payloadData?.subscription?.subscription_code ?? payloadData?.code;
|
|
373
|
+
const customerCode = payloadData?.customer?.customer_code ?? payloadData?.customer_code ?? payloadData?.customer?.code;
|
|
374
|
+
const planCode = payloadData?.plan?.plan_code ?? payloadData?.plan_code ?? payloadData?.plan;
|
|
375
|
+
let metadata = payloadData?.metadata;
|
|
376
|
+
if (typeof metadata === "string") try {
|
|
377
|
+
metadata = JSON.parse(metadata);
|
|
378
|
+
} catch {}
|
|
379
|
+
const referenceIdFromMetadata = typeof metadata === "object" && metadata !== null ? metadata.referenceId : void 0;
|
|
380
|
+
let planNameFromMetadata = typeof metadata === "object" && metadata !== null ? metadata.plan : void 0;
|
|
381
|
+
if (typeof planNameFromMetadata === "string") planNameFromMetadata = planNameFromMetadata.toLowerCase();
|
|
382
|
+
const plans = await getPlans(options.subscription);
|
|
383
|
+
const planFromCode = planCode !== void 0 && planCode !== null && planCode !== "" ? plans.find((p) => p.planCode !== void 0 && p.planCode !== null && p.planCode === planCode) : void 0;
|
|
384
|
+
const planPart = planFromCode?.name ?? planNameFromMetadata;
|
|
385
|
+
const planName = planPart !== void 0 && planPart !== null && planPart !== "" ? planPart.toLowerCase() : void 0;
|
|
386
|
+
if (subscriptionCode !== void 0 && subscriptionCode !== null && subscriptionCode !== "") {
|
|
387
|
+
const where = [];
|
|
388
|
+
if (referenceIdFromMetadata !== void 0 && referenceIdFromMetadata !== null && referenceIdFromMetadata !== "") where.push({
|
|
389
|
+
field: "referenceId",
|
|
390
|
+
value: referenceIdFromMetadata
|
|
391
|
+
});
|
|
392
|
+
else if (customerCode !== void 0 && customerCode !== null && customerCode !== "") where.push({
|
|
393
|
+
field: "paystackCustomerCode",
|
|
394
|
+
value: customerCode
|
|
395
|
+
});
|
|
396
|
+
if (planName !== void 0 && planName !== null && planName !== "") where.push({
|
|
397
|
+
field: "plan",
|
|
398
|
+
value: planName
|
|
399
|
+
});
|
|
400
|
+
if (where.length > 0) {
|
|
401
|
+
const matches = await ctx.context.adapter.findMany({
|
|
402
|
+
model: "subscription",
|
|
403
|
+
where
|
|
313
404
|
});
|
|
314
|
-
|
|
315
|
-
|
|
405
|
+
const subscription = matches !== void 0 && matches !== null ? matches[0] : void 0;
|
|
406
|
+
if (subscription !== void 0 && subscription !== null) {
|
|
407
|
+
await ctx.context.adapter.update({
|
|
316
408
|
model: "subscription",
|
|
317
|
-
|
|
409
|
+
update: {
|
|
410
|
+
paystackSubscriptionCode: subscriptionCode,
|
|
411
|
+
status: "active",
|
|
412
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
413
|
+
periodEnd: payloadData?.next_payment_date !== void 0 && payloadData?.next_payment_date !== null && payloadData?.next_payment_date !== "" ? new Date(payloadData.next_payment_date) : void 0
|
|
414
|
+
},
|
|
415
|
+
where: [{
|
|
416
|
+
field: "id",
|
|
417
|
+
value: subscription.id
|
|
418
|
+
}]
|
|
318
419
|
});
|
|
319
|
-
const
|
|
320
|
-
if (
|
|
321
|
-
await
|
|
322
|
-
|
|
323
|
-
|
|
420
|
+
const plan = planFromCode ?? (planName !== void 0 && planName !== null && planName !== "" ? await getPlanByName(options, planName) : void 0);
|
|
421
|
+
if (plan !== void 0 && plan !== null) {
|
|
422
|
+
await options.subscription.onSubscriptionComplete?.({
|
|
423
|
+
event,
|
|
424
|
+
subscription: {
|
|
425
|
+
...subscription,
|
|
324
426
|
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
|
|
427
|
+
status: "active"
|
|
328
428
|
},
|
|
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
|
-
}
|
|
429
|
+
plan
|
|
430
|
+
}, ctx);
|
|
431
|
+
await options.subscription.onSubscriptionCreated?.({
|
|
432
|
+
event,
|
|
433
|
+
subscription: {
|
|
434
|
+
...subscription,
|
|
435
|
+
paystackSubscriptionCode: subscriptionCode,
|
|
436
|
+
status: "active"
|
|
437
|
+
},
|
|
438
|
+
plan
|
|
439
|
+
}, ctx);
|
|
355
440
|
}
|
|
356
441
|
}
|
|
357
442
|
}
|
|
358
443
|
}
|
|
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
|
-
|
|
444
|
+
}
|
|
445
|
+
if (eventName === "subscription.disable" || eventName === "subscription.not_renew") {
|
|
446
|
+
const payloadData = data;
|
|
447
|
+
const subscriptionCode = payloadData?.subscription_code ?? payloadData?.subscription?.subscription_code ?? payloadData?.code;
|
|
448
|
+
if (subscriptionCode !== void 0 && subscriptionCode !== null && subscriptionCode !== "") {
|
|
449
|
+
const existing = await ctx.context.adapter.findOne({
|
|
450
|
+
model: "subscription",
|
|
451
|
+
where: [{
|
|
452
|
+
field: "paystackSubscriptionCode",
|
|
453
|
+
value: subscriptionCode
|
|
454
|
+
}]
|
|
455
|
+
});
|
|
456
|
+
let newStatus = "canceled";
|
|
457
|
+
const nextPaymentDate = data?.next_payment_date;
|
|
458
|
+
const periodEnd = nextPaymentDate ? new Date(nextPaymentDate) : existing?.periodEnd ? new Date(existing.periodEnd) : void 0;
|
|
459
|
+
if (periodEnd && periodEnd > /* @__PURE__ */ new Date()) newStatus = "active";
|
|
460
|
+
await ctx.context.adapter.update({
|
|
461
|
+
model: "subscription",
|
|
462
|
+
update: {
|
|
463
|
+
status: newStatus,
|
|
464
|
+
cancelAtPeriodEnd: true,
|
|
465
|
+
...periodEnd ? { periodEnd } : {},
|
|
466
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
467
|
+
},
|
|
468
|
+
where: [{
|
|
469
|
+
field: "paystackSubscriptionCode",
|
|
470
|
+
value: subscriptionCode
|
|
471
|
+
}]
|
|
472
|
+
});
|
|
473
|
+
if (existing) await options.subscription.onSubscriptionCancel?.({
|
|
474
|
+
event,
|
|
475
|
+
subscription: {
|
|
476
|
+
...existing,
|
|
477
|
+
status: "canceled"
|
|
478
|
+
}
|
|
479
|
+
}, ctx);
|
|
390
480
|
}
|
|
391
|
-
} catch (_e) {
|
|
392
|
-
ctx.context.logger.error("Failed to sync Paystack webhook event", _e);
|
|
393
481
|
}
|
|
482
|
+
} catch (_e) {
|
|
483
|
+
ctx.context.logger.error("Failed to sync Paystack webhook event", _e);
|
|
394
484
|
}
|
|
395
485
|
await options.onEvent?.(event);
|
|
396
486
|
return ctx.json({ received: true });
|
|
@@ -449,14 +539,40 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
|
|
|
449
539
|
let product;
|
|
450
540
|
if (planName !== void 0 && planName !== null && planName !== "") {
|
|
451
541
|
if (subscriptionOptions?.enabled !== true) throw new APIError("BAD_REQUEST", { message: "Subscriptions are not enabled." });
|
|
452
|
-
plan = await getPlanByName(options, planName);
|
|
542
|
+
plan = await getPlanByName(options, planName) ?? void 0;
|
|
543
|
+
if (!plan) {
|
|
544
|
+
const nativePlan = await ctx.context.adapter.findOne({
|
|
545
|
+
model: "paystackPlan",
|
|
546
|
+
where: [{
|
|
547
|
+
field: "name",
|
|
548
|
+
value: planName
|
|
549
|
+
}]
|
|
550
|
+
});
|
|
551
|
+
if (nativePlan) plan = nativePlan;
|
|
552
|
+
else plan = await ctx.context.adapter.findOne({
|
|
553
|
+
model: "paystackPlan",
|
|
554
|
+
where: [{
|
|
555
|
+
field: "planCode",
|
|
556
|
+
value: planName
|
|
557
|
+
}]
|
|
558
|
+
}) ?? void 0;
|
|
559
|
+
}
|
|
453
560
|
if (!plan) throw new APIError("BAD_REQUEST", {
|
|
454
561
|
code: "SUBSCRIPTION_PLAN_NOT_FOUND",
|
|
455
562
|
message: PAYSTACK_ERROR_CODES.SUBSCRIPTION_PLAN_NOT_FOUND,
|
|
456
563
|
status: 400
|
|
457
564
|
});
|
|
458
565
|
} else if (productName !== void 0 && productName !== null && productName !== "") {
|
|
459
|
-
if (typeof productName === "string")
|
|
566
|
+
if (typeof productName === "string") {
|
|
567
|
+
product ??= await getProductByName(options, productName) ?? void 0;
|
|
568
|
+
product ??= await ctx.context.adapter.findOne({
|
|
569
|
+
model: "paystackProduct",
|
|
570
|
+
where: [{
|
|
571
|
+
field: "name",
|
|
572
|
+
value: productName
|
|
573
|
+
}]
|
|
574
|
+
}) ?? void 0;
|
|
575
|
+
}
|
|
460
576
|
if (!product) throw new APIError("BAD_REQUEST", {
|
|
461
577
|
message: `Product '${productName}' not found.`,
|
|
462
578
|
status: 400
|
|
@@ -465,7 +581,7 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
|
|
|
465
581
|
message: "Either 'plan', 'product', or 'amount' is required to initialize a transaction.",
|
|
466
582
|
status: 400
|
|
467
583
|
});
|
|
468
|
-
const amount = bodyAmount ?? product?.
|
|
584
|
+
const amount = bodyAmount ?? product?.price;
|
|
469
585
|
const finalCurrency = currency ?? product?.currency ?? plan?.currency ?? "NGN";
|
|
470
586
|
let url;
|
|
471
587
|
let reference;
|
|
@@ -580,6 +696,7 @@ const initializeTransaction = (options, path = "/paystack/initialize-transaction
|
|
|
580
696
|
currency: plan?.currency ?? currency ?? "NGN",
|
|
581
697
|
status: "pending",
|
|
582
698
|
plan: plan?.name.toLowerCase(),
|
|
699
|
+
product: product?.name.toLowerCase(),
|
|
583
700
|
metadata: extraMetadata !== void 0 && extraMetadata !== null ? JSON.stringify(extraMetadata) : void 0,
|
|
584
701
|
createdAt: /* @__PURE__ */ new Date(),
|
|
585
702
|
updatedAt: /* @__PURE__ */ new Date()
|
|
@@ -651,13 +768,12 @@ const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
|
|
|
651
768
|
message: error?.message ?? PAYSTACK_ERROR_CODES.FAILED_TO_VERIFY_TRANSACTION
|
|
652
769
|
});
|
|
653
770
|
}
|
|
654
|
-
|
|
655
|
-
if (data !== null && data !== void 0 && typeof data === "object" && "status" in data && "data" in data) data = data.data;
|
|
771
|
+
const data = unwrapSdkResult(verifyRes);
|
|
656
772
|
const status = data?.status;
|
|
657
773
|
const reference = data?.reference ?? ctx.body.reference;
|
|
658
774
|
const paystackId = data?.id !== void 0 && data?.id !== null ? String(data.id) : void 0;
|
|
659
775
|
const authorizationCode = (data?.authorization)?.authorization_code;
|
|
660
|
-
if (status === "success")
|
|
776
|
+
if (status === "success") {
|
|
661
777
|
const session = await getSessionFromCtx(ctx);
|
|
662
778
|
const referenceId = (await ctx.context.adapter.findOne({
|
|
663
779
|
model: "paystackTransaction",
|
|
@@ -690,107 +806,123 @@ const verifyTransaction = (options, path = "/paystack/verify-transaction") => {
|
|
|
690
806
|
}
|
|
691
807
|
if (!authorized) throw new APIError("UNAUTHORIZED");
|
|
692
808
|
}
|
|
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;
|
|
809
|
+
try {
|
|
810
|
+
await ctx.context.adapter.update({
|
|
811
|
+
model: "paystackTransaction",
|
|
812
|
+
update: {
|
|
813
|
+
status: "success",
|
|
814
|
+
paystackId,
|
|
815
|
+
...data?.amount !== void 0 && data?.amount !== null ? { amount: data.amount } : {},
|
|
816
|
+
...data?.currency !== void 0 && data?.currency !== null ? { currency: data.currency } : {},
|
|
817
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
818
|
+
},
|
|
819
|
+
where: [{
|
|
820
|
+
field: "reference",
|
|
821
|
+
value: reference
|
|
822
|
+
}]
|
|
823
|
+
});
|
|
824
|
+
const customer = data?.customer;
|
|
825
|
+
const paystackCustomerCodeFromPaystack = customer !== void 0 && customer !== null && typeof customer === "object" ? customer.customer_code : void 0;
|
|
826
|
+
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({
|
|
827
|
+
model: "organization",
|
|
828
|
+
where: [{
|
|
829
|
+
field: "id",
|
|
830
|
+
value: referenceId
|
|
831
|
+
}]
|
|
832
|
+
}) !== null)) === true) await ctx.context.adapter.update({
|
|
833
|
+
model: "organization",
|
|
834
|
+
update: { paystackCustomerCode: paystackCustomerCodeFromPaystack },
|
|
835
|
+
where: [{
|
|
836
|
+
field: "id",
|
|
837
|
+
value: referenceId
|
|
838
|
+
}]
|
|
839
|
+
});
|
|
840
|
+
else await ctx.context.adapter.update({
|
|
841
|
+
model: "user",
|
|
842
|
+
update: { paystackCustomerCode: paystackCustomerCodeFromPaystack },
|
|
843
|
+
where: [{
|
|
844
|
+
field: "id",
|
|
845
|
+
value: referenceId
|
|
846
|
+
}]
|
|
847
|
+
});
|
|
848
|
+
const transaction = await ctx.context.adapter.findOne({
|
|
849
|
+
model: "paystackTransaction",
|
|
850
|
+
where: [{
|
|
851
|
+
field: "reference",
|
|
852
|
+
value: reference
|
|
853
|
+
}]
|
|
854
|
+
});
|
|
855
|
+
if (transaction?.product) await syncProductQuantityFromPaystack(ctx, transaction.product, options.paystackClient);
|
|
856
|
+
let isTrial = false;
|
|
857
|
+
let trialEnd;
|
|
858
|
+
let targetPlan;
|
|
859
|
+
if (data?.metadata !== void 0 && data?.metadata !== null) {
|
|
860
|
+
const metaRaw = data.metadata;
|
|
861
|
+
const meta = typeof metaRaw === "string" ? JSON.parse(metaRaw) : metaRaw;
|
|
862
|
+
isTrial = meta.isTrial === true || meta.isTrial === "true";
|
|
863
|
+
trialEnd = meta.trialEnd;
|
|
864
|
+
targetPlan = meta.plan;
|
|
754
865
|
}
|
|
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
|
-
if (
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
866
|
+
let paystackSubscriptionCode;
|
|
867
|
+
if (isTrial === true && targetPlan !== void 0 && targetPlan !== null && targetPlan !== "" && trialEnd !== void 0 && trialEnd !== null && trialEnd !== "") {
|
|
868
|
+
const email = (data?.customer)?.email;
|
|
869
|
+
const planConfig = (await getPlans(subscriptionOptions)).find((p) => p.name.toLowerCase() === targetPlan?.toLowerCase());
|
|
870
|
+
if (planConfig !== void 0 && (planConfig.planCode === void 0 || planConfig.planCode === null || planConfig.planCode === "")) paystackSubscriptionCode = `LOC_${reference}`;
|
|
871
|
+
if (authorizationCode !== void 0 && authorizationCode !== null && authorizationCode !== "" && email !== void 0 && email !== null && email !== "" && planConfig?.planCode !== void 0 && planConfig?.planCode !== null && planConfig?.planCode !== "") {
|
|
872
|
+
const subData = unwrapSdkResult(await paystack.subscriptionCreate({
|
|
873
|
+
customer: email,
|
|
874
|
+
plan: planConfig.planCode,
|
|
875
|
+
authorization: authorizationCode,
|
|
876
|
+
start_date: trialEnd
|
|
877
|
+
}));
|
|
878
|
+
paystackSubscriptionCode = (subData?.data ?? subData)?.subscription_code;
|
|
879
|
+
}
|
|
880
|
+
} else if (isTrial !== true) {
|
|
881
|
+
const planCodeFromPaystack = (data?.plan)?.plan_code;
|
|
882
|
+
if (planCodeFromPaystack === void 0 || planCodeFromPaystack === null || planCodeFromPaystack === "") paystackSubscriptionCode = `LOC_${reference}`;
|
|
883
|
+
else paystackSubscriptionCode = (data?.subscription)?.subscription_code;
|
|
884
|
+
}
|
|
885
|
+
const existingSubs = await ctx.context.adapter.findMany({
|
|
886
|
+
model: "subscription",
|
|
887
|
+
where: [{
|
|
888
|
+
field: "paystackTransactionReference",
|
|
889
|
+
value: reference
|
|
890
|
+
}]
|
|
891
|
+
});
|
|
892
|
+
let targetSub;
|
|
893
|
+
if (existingSubs && existingSubs.length > 0) targetSub = existingSubs.find((s) => !(referenceId !== void 0 && referenceId !== null && referenceId !== "") || s.referenceId === referenceId);
|
|
894
|
+
let updatedSubscription = null;
|
|
895
|
+
if (targetSub !== void 0 && targetSub !== null) updatedSubscription = await ctx.context.adapter.update({
|
|
896
|
+
model: "subscription",
|
|
897
|
+
update: {
|
|
898
|
+
status: isTrial === true ? "trialing" : "active",
|
|
899
|
+
periodStart: /* @__PURE__ */ new Date(),
|
|
900
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
901
|
+
...isTrial === true && trialEnd !== void 0 && trialEnd !== null && trialEnd !== "" ? {
|
|
902
|
+
trialStart: /* @__PURE__ */ new Date(),
|
|
903
|
+
trialEnd: new Date(trialEnd),
|
|
904
|
+
periodEnd: new Date(trialEnd)
|
|
905
|
+
} : {},
|
|
906
|
+
...paystackSubscriptionCode !== void 0 && paystackSubscriptionCode !== null && paystackSubscriptionCode !== "" ? { paystackSubscriptionCode } : {},
|
|
907
|
+
...authorizationCode !== void 0 && authorizationCode !== null && authorizationCode !== "" ? { paystackAuthorizationCode: authorizationCode } : {}
|
|
908
|
+
},
|
|
909
|
+
where: [{
|
|
910
|
+
field: "id",
|
|
911
|
+
value: targetSub.id
|
|
912
|
+
}]
|
|
913
|
+
});
|
|
914
|
+
if (updatedSubscription && subscriptionOptions?.enabled === true && "onSubscriptionComplete" in subscriptionOptions && typeof subscriptionOptions.onSubscriptionComplete === "function") {
|
|
915
|
+
const plan = (await getPlans(subscriptionOptions)).find((p) => p.name.toLowerCase() === updatedSubscription.plan.toLowerCase());
|
|
916
|
+
if (plan) await subscriptionOptions.onSubscriptionComplete({
|
|
917
|
+
event: data,
|
|
918
|
+
subscription: updatedSubscription,
|
|
919
|
+
plan
|
|
920
|
+
}, ctx);
|
|
921
|
+
}
|
|
922
|
+
} catch (e) {
|
|
923
|
+
ctx.context.logger.error("Failed to update transaction/subscription after verification", e);
|
|
789
924
|
}
|
|
790
|
-
}
|
|
791
|
-
ctx.context.logger.error("Failed to update transaction/subscription after verification", e);
|
|
792
|
-
}
|
|
793
|
-
else if (status === "failed" || status === "abandoned") try {
|
|
925
|
+
} else if (status === "failed" || status === "abandoned") try {
|
|
794
926
|
await ctx.context.adapter.update({
|
|
795
927
|
model: "paystackTransaction",
|
|
796
928
|
update: {
|
|
@@ -1026,27 +1158,182 @@ const enablePaystackSubscription = (options, path = "/paystack/enable-subscripti
|
|
|
1026
1158
|
}
|
|
1027
1159
|
});
|
|
1028
1160
|
};
|
|
1029
|
-
const getSubscriptionManageLink = (options) => {
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
] : [sessionMiddleware, originCheck]
|
|
1038
|
-
}, async (ctx) => {
|
|
1161
|
+
const getSubscriptionManageLink = (options, path = "/paystack/get-subscription-manage-link") => {
|
|
1162
|
+
const manageLinkQuerySchema = z.object({ subscriptionCode: z.string() });
|
|
1163
|
+
const useMiddlewares = options.subscription?.enabled === true ? [
|
|
1164
|
+
sessionMiddleware,
|
|
1165
|
+
originCheck,
|
|
1166
|
+
referenceMiddleware(options, "get-subscription-manage-link")
|
|
1167
|
+
] : [sessionMiddleware, originCheck];
|
|
1168
|
+
const handler = async (ctx) => {
|
|
1039
1169
|
const { subscriptionCode } = ctx.query;
|
|
1170
|
+
if (subscriptionCode.startsWith("LOC_") || subscriptionCode.startsWith("sub_local_")) return ctx.json({
|
|
1171
|
+
link: null,
|
|
1172
|
+
message: "Local subscriptions cannot be managed on Paystack"
|
|
1173
|
+
});
|
|
1040
1174
|
const paystack = getPaystackOps(options.paystackClient);
|
|
1041
1175
|
try {
|
|
1042
1176
|
const res = unwrapSdkResult(await paystack.subscriptionManageLink(subscriptionCode));
|
|
1043
|
-
const data = res !== null && res !== void 0 && "status" in res && "data" in res ? res.data : res?.data !== void 0 ? res.data : res;
|
|
1177
|
+
const data = res !== null && res !== void 0 && typeof res === "object" && "status" in res && "data" in res ? res.data : res?.data !== void 0 ? res.data : res;
|
|
1044
1178
|
const link = typeof data === "string" ? data : data?.link;
|
|
1045
1179
|
return ctx.json({ link });
|
|
1046
1180
|
} catch (error) {
|
|
1047
1181
|
ctx.context.logger.error("Failed to get subscription manage link", error);
|
|
1048
1182
|
throw new APIError("BAD_REQUEST", { message: error?.message ?? "Failed to get subscription manage link" });
|
|
1049
1183
|
}
|
|
1184
|
+
};
|
|
1185
|
+
return createAuthEndpoint(path, {
|
|
1186
|
+
method: "GET",
|
|
1187
|
+
query: manageLinkQuerySchema,
|
|
1188
|
+
use: useMiddlewares
|
|
1189
|
+
}, handler);
|
|
1190
|
+
};
|
|
1191
|
+
const syncProducts = (options) => {
|
|
1192
|
+
return createAuthEndpoint("/paystack/sync-products", {
|
|
1193
|
+
method: "POST",
|
|
1194
|
+
metadata: { ...HIDE_METADATA },
|
|
1195
|
+
disableBody: true,
|
|
1196
|
+
use: [sessionMiddleware]
|
|
1197
|
+
}, async (ctx) => {
|
|
1198
|
+
console.error("DEBUG: syncProducts endpoint hit!");
|
|
1199
|
+
const paystack = getPaystackOps(options.paystackClient);
|
|
1200
|
+
try {
|
|
1201
|
+
const res = unwrapSdkResult(await paystack.productList());
|
|
1202
|
+
const productsData = res !== null && typeof res === "object" && "status" in res && "data" in res ? res.data : res?.data ?? res;
|
|
1203
|
+
if (!Array.isArray(productsData)) return ctx.json({
|
|
1204
|
+
status: "success",
|
|
1205
|
+
count: 0
|
|
1206
|
+
});
|
|
1207
|
+
for (const product of productsData) {
|
|
1208
|
+
const paystackId = String(product.id);
|
|
1209
|
+
const existing = await ctx.context.adapter.findOne({
|
|
1210
|
+
model: "paystackProduct",
|
|
1211
|
+
where: [{
|
|
1212
|
+
field: "paystackId",
|
|
1213
|
+
value: paystackId
|
|
1214
|
+
}]
|
|
1215
|
+
});
|
|
1216
|
+
const productData = {
|
|
1217
|
+
name: product.name,
|
|
1218
|
+
description: product.description,
|
|
1219
|
+
price: product.price,
|
|
1220
|
+
currency: product.currency,
|
|
1221
|
+
quantity: product.quantity,
|
|
1222
|
+
unlimited: product.unlimited,
|
|
1223
|
+
paystackId,
|
|
1224
|
+
slug: product.slug ?? product.name.toLowerCase().replace(/\s+/g, "-"),
|
|
1225
|
+
metadata: product.metadata ? JSON.stringify(product.metadata) : void 0,
|
|
1226
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1227
|
+
};
|
|
1228
|
+
if (existing) await ctx.context.adapter.update({
|
|
1229
|
+
model: "paystackProduct",
|
|
1230
|
+
update: productData,
|
|
1231
|
+
where: [{
|
|
1232
|
+
field: "id",
|
|
1233
|
+
value: existing.id
|
|
1234
|
+
}]
|
|
1235
|
+
});
|
|
1236
|
+
else await ctx.context.adapter.create({
|
|
1237
|
+
model: "paystackProduct",
|
|
1238
|
+
data: {
|
|
1239
|
+
...productData,
|
|
1240
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
1241
|
+
}
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
return ctx.json({
|
|
1245
|
+
status: "success",
|
|
1246
|
+
count: productsData.length
|
|
1247
|
+
});
|
|
1248
|
+
} catch (error) {
|
|
1249
|
+
ctx.context.logger.error("Failed to sync products", error);
|
|
1250
|
+
throw new APIError("BAD_REQUEST", { message: error?.message ?? "Failed to sync products" });
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
};
|
|
1254
|
+
const listProducts = (_options) => {
|
|
1255
|
+
return createAuthEndpoint("/paystack/list-products", {
|
|
1256
|
+
method: "GET",
|
|
1257
|
+
metadata: { openapi: { operationId: "listPaystackProducts" } }
|
|
1258
|
+
}, async (ctx) => {
|
|
1259
|
+
const sorted = (await ctx.context.adapter.findMany({ model: "paystackProduct" })).sort((a, b) => a.name.localeCompare(b.name));
|
|
1260
|
+
return ctx.json({ products: sorted });
|
|
1261
|
+
});
|
|
1262
|
+
};
|
|
1263
|
+
const syncPlans = (options) => {
|
|
1264
|
+
return createAuthEndpoint("/paystack/sync-plans", {
|
|
1265
|
+
method: "POST",
|
|
1266
|
+
metadata: { ...HIDE_METADATA },
|
|
1267
|
+
disableBody: true,
|
|
1268
|
+
use: [sessionMiddleware]
|
|
1269
|
+
}, async (ctx) => {
|
|
1270
|
+
const paystack = getPaystackOps(options.paystackClient);
|
|
1271
|
+
try {
|
|
1272
|
+
const res = unwrapSdkResult(await paystack.planList());
|
|
1273
|
+
const plansData = res !== null && typeof res === "object" && "status" in res && "data" in res ? res.data : res?.data ?? res;
|
|
1274
|
+
if (!Array.isArray(plansData)) return ctx.json({
|
|
1275
|
+
status: "success",
|
|
1276
|
+
count: 0
|
|
1277
|
+
});
|
|
1278
|
+
for (const plan of plansData) {
|
|
1279
|
+
const paystackId = String(plan.id);
|
|
1280
|
+
const existing = await ctx.context.adapter.findOne({
|
|
1281
|
+
model: "paystackPlan",
|
|
1282
|
+
where: [{
|
|
1283
|
+
field: "paystackId",
|
|
1284
|
+
value: paystackId
|
|
1285
|
+
}]
|
|
1286
|
+
});
|
|
1287
|
+
const planData = {
|
|
1288
|
+
name: plan.name,
|
|
1289
|
+
description: plan.description,
|
|
1290
|
+
amount: plan.amount,
|
|
1291
|
+
currency: plan.currency,
|
|
1292
|
+
interval: plan.interval,
|
|
1293
|
+
planCode: plan.plan_code,
|
|
1294
|
+
paystackId,
|
|
1295
|
+
metadata: plan.metadata ? JSON.stringify(plan.metadata) : void 0,
|
|
1296
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1297
|
+
};
|
|
1298
|
+
if (existing) await ctx.context.adapter.update({
|
|
1299
|
+
model: "paystackPlan",
|
|
1300
|
+
update: planData,
|
|
1301
|
+
where: [{
|
|
1302
|
+
field: "id",
|
|
1303
|
+
value: existing.id
|
|
1304
|
+
}]
|
|
1305
|
+
});
|
|
1306
|
+
else await ctx.context.adapter.create({
|
|
1307
|
+
model: "paystackPlan",
|
|
1308
|
+
data: {
|
|
1309
|
+
...planData,
|
|
1310
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
1311
|
+
}
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
return ctx.json({
|
|
1315
|
+
status: "success",
|
|
1316
|
+
count: plansData.length
|
|
1317
|
+
});
|
|
1318
|
+
} catch (error) {
|
|
1319
|
+
ctx.context.logger.error("Failed to sync plans", error);
|
|
1320
|
+
throw new APIError("BAD_REQUEST", { message: error?.message ?? "Failed to sync plans" });
|
|
1321
|
+
}
|
|
1322
|
+
});
|
|
1323
|
+
};
|
|
1324
|
+
const listPlans = (_options) => {
|
|
1325
|
+
return createAuthEndpoint("/paystack/list-plans", {
|
|
1326
|
+
method: "GET",
|
|
1327
|
+
metadata: { ...HIDE_METADATA },
|
|
1328
|
+
use: [sessionMiddleware]
|
|
1329
|
+
}, async (ctx) => {
|
|
1330
|
+
try {
|
|
1331
|
+
const plans = await ctx.context.adapter.findMany({ model: "paystackPlan" });
|
|
1332
|
+
return ctx.json({ plans });
|
|
1333
|
+
} catch (error) {
|
|
1334
|
+
ctx.context.logger.error("Failed to list plans", error);
|
|
1335
|
+
throw new APIError("BAD_REQUEST", { message: error?.message ?? "Failed to list plans" });
|
|
1336
|
+
}
|
|
1050
1337
|
});
|
|
1051
1338
|
};
|
|
1052
1339
|
const getConfig = (options) => {
|
|
@@ -1115,7 +1402,11 @@ const chargeRecurringSubscription = (options) => {
|
|
|
1115
1402
|
}
|
|
1116
1403
|
}
|
|
1117
1404
|
if (email === void 0 || email === null || email === "") throw new APIError("NOT_FOUND", { message: "User email not found" });
|
|
1118
|
-
|
|
1405
|
+
const finalCurrency = plan.currency ?? "NGN";
|
|
1406
|
+
if (!validateMinAmount(amount, finalCurrency)) throw new APIError("BAD_REQUEST", {
|
|
1407
|
+
message: `Amount ${amount} is less than the minimum required for ${finalCurrency}.`,
|
|
1408
|
+
status: 400
|
|
1409
|
+
});
|
|
1119
1410
|
const data = unwrapSdkResult(await getPaystackOps(options.paystackClient).transactionChargeAuthorization({
|
|
1120
1411
|
email,
|
|
1121
1412
|
amount,
|
|
@@ -1161,7 +1452,8 @@ const chargeRecurringSubscription = (options) => {
|
|
|
1161
1452
|
const transactions = { paystackTransaction: { fields: {
|
|
1162
1453
|
reference: {
|
|
1163
1454
|
type: "string",
|
|
1164
|
-
required: true
|
|
1455
|
+
required: true,
|
|
1456
|
+
unique: true
|
|
1165
1457
|
},
|
|
1166
1458
|
paystackId: {
|
|
1167
1459
|
type: "string",
|
|
@@ -1169,11 +1461,13 @@ const transactions = { paystackTransaction: { fields: {
|
|
|
1169
1461
|
},
|
|
1170
1462
|
referenceId: {
|
|
1171
1463
|
type: "string",
|
|
1172
|
-
required: true
|
|
1464
|
+
required: true,
|
|
1465
|
+
index: true
|
|
1173
1466
|
},
|
|
1174
1467
|
userId: {
|
|
1175
1468
|
type: "string",
|
|
1176
|
-
required: true
|
|
1469
|
+
required: true,
|
|
1470
|
+
index: true
|
|
1177
1471
|
},
|
|
1178
1472
|
amount: {
|
|
1179
1473
|
type: "number",
|
|
@@ -1191,6 +1485,10 @@ const transactions = { paystackTransaction: { fields: {
|
|
|
1191
1485
|
type: "string",
|
|
1192
1486
|
required: false
|
|
1193
1487
|
},
|
|
1488
|
+
product: {
|
|
1489
|
+
type: "string",
|
|
1490
|
+
required: false
|
|
1491
|
+
},
|
|
1194
1492
|
metadata: {
|
|
1195
1493
|
type: "string",
|
|
1196
1494
|
required: false
|
|
@@ -1207,23 +1505,28 @@ const transactions = { paystackTransaction: { fields: {
|
|
|
1207
1505
|
const subscriptions = { subscription: { fields: {
|
|
1208
1506
|
plan: {
|
|
1209
1507
|
type: "string",
|
|
1210
|
-
required: true
|
|
1508
|
+
required: true,
|
|
1509
|
+
index: true
|
|
1211
1510
|
},
|
|
1212
1511
|
referenceId: {
|
|
1213
1512
|
type: "string",
|
|
1214
|
-
required: true
|
|
1513
|
+
required: true,
|
|
1514
|
+
index: true
|
|
1215
1515
|
},
|
|
1216
1516
|
paystackCustomerCode: {
|
|
1217
1517
|
type: "string",
|
|
1218
|
-
required: false
|
|
1518
|
+
required: false,
|
|
1519
|
+
index: true
|
|
1219
1520
|
},
|
|
1220
1521
|
paystackSubscriptionCode: {
|
|
1221
1522
|
type: "string",
|
|
1222
|
-
required: false
|
|
1523
|
+
required: false,
|
|
1524
|
+
unique: true
|
|
1223
1525
|
},
|
|
1224
1526
|
paystackTransactionReference: {
|
|
1225
1527
|
type: "string",
|
|
1226
|
-
required: false
|
|
1528
|
+
required: false,
|
|
1529
|
+
index: true
|
|
1227
1530
|
},
|
|
1228
1531
|
paystackAuthorizationCode: {
|
|
1229
1532
|
type: "string",
|
|
@@ -1269,28 +1572,128 @@ const subscriptions = { subscription: { fields: {
|
|
|
1269
1572
|
} } };
|
|
1270
1573
|
const user = { user: { fields: { paystackCustomerCode: {
|
|
1271
1574
|
type: "string",
|
|
1272
|
-
required: false
|
|
1575
|
+
required: false,
|
|
1576
|
+
index: true
|
|
1273
1577
|
} } } };
|
|
1274
1578
|
const organization = { organization: { fields: {
|
|
1275
1579
|
paystackCustomerCode: {
|
|
1276
1580
|
type: "string",
|
|
1277
|
-
required: false
|
|
1581
|
+
required: false,
|
|
1582
|
+
index: true
|
|
1278
1583
|
},
|
|
1279
1584
|
email: {
|
|
1280
1585
|
type: "string",
|
|
1281
1586
|
required: false
|
|
1282
1587
|
}
|
|
1283
1588
|
} } };
|
|
1589
|
+
const products = { paystackProduct: { fields: {
|
|
1590
|
+
name: {
|
|
1591
|
+
type: "string",
|
|
1592
|
+
required: true
|
|
1593
|
+
},
|
|
1594
|
+
description: {
|
|
1595
|
+
type: "string",
|
|
1596
|
+
required: false
|
|
1597
|
+
},
|
|
1598
|
+
price: {
|
|
1599
|
+
type: "number",
|
|
1600
|
+
required: true
|
|
1601
|
+
},
|
|
1602
|
+
currency: {
|
|
1603
|
+
type: "string",
|
|
1604
|
+
required: true
|
|
1605
|
+
},
|
|
1606
|
+
quantity: {
|
|
1607
|
+
type: "number",
|
|
1608
|
+
required: false,
|
|
1609
|
+
defaultValue: 0
|
|
1610
|
+
},
|
|
1611
|
+
unlimited: {
|
|
1612
|
+
type: "boolean",
|
|
1613
|
+
required: false,
|
|
1614
|
+
defaultValue: true
|
|
1615
|
+
},
|
|
1616
|
+
paystackId: {
|
|
1617
|
+
type: "string",
|
|
1618
|
+
required: false,
|
|
1619
|
+
unique: true
|
|
1620
|
+
},
|
|
1621
|
+
slug: {
|
|
1622
|
+
type: "string",
|
|
1623
|
+
required: true,
|
|
1624
|
+
unique: true
|
|
1625
|
+
},
|
|
1626
|
+
metadata: {
|
|
1627
|
+
type: "string",
|
|
1628
|
+
required: false
|
|
1629
|
+
},
|
|
1630
|
+
createdAt: {
|
|
1631
|
+
type: "date",
|
|
1632
|
+
required: true
|
|
1633
|
+
},
|
|
1634
|
+
updatedAt: {
|
|
1635
|
+
type: "date",
|
|
1636
|
+
required: true
|
|
1637
|
+
}
|
|
1638
|
+
} } };
|
|
1639
|
+
const plans = { paystackPlan: { fields: {
|
|
1640
|
+
name: {
|
|
1641
|
+
type: "string",
|
|
1642
|
+
required: true
|
|
1643
|
+
},
|
|
1644
|
+
description: {
|
|
1645
|
+
type: "string",
|
|
1646
|
+
required: false
|
|
1647
|
+
},
|
|
1648
|
+
amount: {
|
|
1649
|
+
type: "number",
|
|
1650
|
+
required: true
|
|
1651
|
+
},
|
|
1652
|
+
currency: {
|
|
1653
|
+
type: "string",
|
|
1654
|
+
required: true
|
|
1655
|
+
},
|
|
1656
|
+
interval: {
|
|
1657
|
+
type: "string",
|
|
1658
|
+
required: true
|
|
1659
|
+
},
|
|
1660
|
+
planCode: {
|
|
1661
|
+
type: "string",
|
|
1662
|
+
required: true,
|
|
1663
|
+
unique: true
|
|
1664
|
+
},
|
|
1665
|
+
paystackId: {
|
|
1666
|
+
type: "string",
|
|
1667
|
+
required: true,
|
|
1668
|
+
unique: true
|
|
1669
|
+
},
|
|
1670
|
+
metadata: {
|
|
1671
|
+
type: "string",
|
|
1672
|
+
required: false
|
|
1673
|
+
},
|
|
1674
|
+
createdAt: {
|
|
1675
|
+
type: "date",
|
|
1676
|
+
required: true
|
|
1677
|
+
},
|
|
1678
|
+
updatedAt: {
|
|
1679
|
+
type: "date",
|
|
1680
|
+
required: true
|
|
1681
|
+
}
|
|
1682
|
+
} } };
|
|
1284
1683
|
const getSchema = (options) => {
|
|
1285
1684
|
let baseSchema;
|
|
1286
1685
|
if (options.subscription?.enabled === true) baseSchema = {
|
|
1287
1686
|
...subscriptions,
|
|
1288
1687
|
...transactions,
|
|
1289
|
-
...user
|
|
1688
|
+
...user,
|
|
1689
|
+
...products,
|
|
1690
|
+
...plans
|
|
1290
1691
|
};
|
|
1291
1692
|
else baseSchema = {
|
|
1292
1693
|
...user,
|
|
1293
|
-
...transactions
|
|
1694
|
+
...transactions,
|
|
1695
|
+
...products,
|
|
1696
|
+
...plans
|
|
1294
1697
|
};
|
|
1295
1698
|
if (options.organization?.enabled === true) baseSchema = {
|
|
1296
1699
|
...baseSchema,
|
|
@@ -1342,23 +1745,29 @@ const checkTeamLimit = async (ctx, organizationId, maxTeams) => {
|
|
|
1342
1745
|
//#region src/index.ts
|
|
1343
1746
|
const INTERNAL_ERROR_CODES = defineErrorCodes({ ...PAYSTACK_ERROR_CODES });
|
|
1344
1747
|
const paystack = (options) => {
|
|
1748
|
+
const routeOptions = options;
|
|
1345
1749
|
return {
|
|
1346
1750
|
id: "paystack",
|
|
1347
1751
|
endpoints: {
|
|
1348
|
-
initializeTransaction: initializeTransaction(
|
|
1349
|
-
verifyTransaction: verifyTransaction(
|
|
1350
|
-
listSubscriptions: listSubscriptions(
|
|
1351
|
-
paystackWebhook: paystackWebhook(
|
|
1352
|
-
listTransactions: listTransactions(
|
|
1353
|
-
getConfig: getConfig(
|
|
1354
|
-
disableSubscription: disablePaystackSubscription(
|
|
1355
|
-
enableSubscription: enablePaystackSubscription(
|
|
1356
|
-
getSubscriptionManageLink: getSubscriptionManageLink(
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1752
|
+
initializeTransaction: initializeTransaction(routeOptions),
|
|
1753
|
+
verifyTransaction: verifyTransaction(routeOptions),
|
|
1754
|
+
listSubscriptions: listSubscriptions(routeOptions),
|
|
1755
|
+
paystackWebhook: paystackWebhook(routeOptions),
|
|
1756
|
+
listTransactions: listTransactions(routeOptions),
|
|
1757
|
+
getConfig: getConfig(routeOptions),
|
|
1758
|
+
disableSubscription: disablePaystackSubscription(routeOptions),
|
|
1759
|
+
enableSubscription: enablePaystackSubscription(routeOptions),
|
|
1760
|
+
getSubscriptionManageLink: getSubscriptionManageLink(routeOptions),
|
|
1761
|
+
subscriptionManageLink: getSubscriptionManageLink(routeOptions, "/paystack/subscription/manage-link"),
|
|
1762
|
+
createSubscription: createSubscription(routeOptions),
|
|
1763
|
+
upgradeSubscription: upgradeSubscription(routeOptions),
|
|
1764
|
+
cancelSubscription: cancelSubscription(routeOptions),
|
|
1765
|
+
restoreSubscription: restoreSubscription(routeOptions),
|
|
1766
|
+
chargeRecurringSubscription: chargeRecurringSubscription(routeOptions),
|
|
1767
|
+
syncProducts: syncProducts(routeOptions),
|
|
1768
|
+
listProducts: listProducts(routeOptions),
|
|
1769
|
+
syncPlans: syncPlans(routeOptions),
|
|
1770
|
+
listPlans: listPlans(routeOptions)
|
|
1362
1771
|
},
|
|
1363
1772
|
schema: getSchema(options),
|
|
1364
1773
|
init: (ctx) => {
|
|
@@ -1366,12 +1775,12 @@ const paystack = (options) => {
|
|
|
1366
1775
|
databaseHooks: {
|
|
1367
1776
|
user: { create: { async after(user, hookCtx) {
|
|
1368
1777
|
if (hookCtx === void 0 || hookCtx === null || options.createCustomerOnSignUp !== true) return;
|
|
1369
|
-
const
|
|
1778
|
+
const sdkRes = unwrapSdkResult(await getPaystackOps(options.paystackClient).customerCreate({
|
|
1370
1779
|
email: user.email,
|
|
1371
1780
|
first_name: user.name ?? void 0,
|
|
1372
1781
|
metadata: { userId: user.id }
|
|
1373
1782
|
}));
|
|
1374
|
-
const customerCode =
|
|
1783
|
+
const customerCode = sdkRes?.customer_code ?? (sdkRes?.data)?.customer_code;
|
|
1375
1784
|
if (customerCode === void 0 || customerCode === null) return;
|
|
1376
1785
|
await ctx.adapter.update({
|
|
1377
1786
|
model: "user",
|
|
@@ -1412,12 +1821,11 @@ const paystack = (options) => {
|
|
|
1412
1821
|
metadata: { organizationId: org.id }
|
|
1413
1822
|
}, extraCreateParams);
|
|
1414
1823
|
const sdkRes = unwrapSdkResult(await getPaystackOps(options.paystackClient).customerCreate(params));
|
|
1415
|
-
const
|
|
1416
|
-
const customerCode = paystackCustomer?.customer_code;
|
|
1824
|
+
const customerCode = sdkRes?.customer_code ?? (sdkRes?.data)?.customer_code;
|
|
1417
1825
|
if (customerCode === void 0 || customerCode === null) return;
|
|
1418
1826
|
await ctx.internalAdapter.updateOrganization(org.id, { paystackCustomerCode: customerCode });
|
|
1419
1827
|
await options.organization?.onCustomerCreate?.({
|
|
1420
|
-
paystackCustomer,
|
|
1828
|
+
paystackCustomer: sdkRes,
|
|
1421
1829
|
organization: {
|
|
1422
1830
|
...org,
|
|
1423
1831
|
paystackCustomerCode: customerCode
|