@paykit-sdk/monnify 1.2.0 → 1.3.1

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 ADDED
@@ -0,0 +1,149 @@
1
+ # @paykit-sdk/monnify
2
+
3
+ Monnify provider for PayKit.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @paykit-sdk/monnify
9
+ # or
10
+ pnpm add @paykit-sdk/monnify
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```typescript
16
+ import { createEndpointHandlers, PayKit } from '@paykit-sdk/core';
17
+ import { monnify } from '@paykit-sdk/monnify';
18
+
19
+ export const paykit = new PayKit(monnify());
20
+ export const endpoints = createEndpointHandlers(paykit);
21
+ ```
22
+
23
+ Or with direct config:
24
+
25
+ ```typescript
26
+ import { PayKit } from '@paykit-sdk/core';
27
+ import { createMonnify } from '@paykit-sdk/monnify';
28
+
29
+ export const paykit = new PayKit(
30
+ createMonnify({
31
+ apiKey: 'MK_TEST_...',
32
+ secretKey: 'your-secret-key',
33
+ isSandbox: true,
34
+ }),
35
+ );
36
+ ```
37
+
38
+ ## Environment Variables
39
+
40
+ ```bash
41
+ MONNIFY_API_KEY=MK_TEST_...
42
+ MONNIFY_SECRET_KEY=your-secret-key
43
+ MONNIFY_SANDBOX=true
44
+ ```
45
+
46
+ ## Next.js API Route
47
+
48
+ ```typescript
49
+ // app/api/paykit/[...endpoint]/route.ts
50
+ import { endpoints } from '@/lib/paykit';
51
+ import { EndpointPath } from '@paykit-sdk/core';
52
+ import { NextRequest, NextResponse } from 'next/server';
53
+
54
+ export async function POST(
55
+ request: NextRequest,
56
+ { params }: { params: Promise<{ endpoint: string[] }> },
57
+ ) {
58
+ const { endpoint: endpointArray } = await params;
59
+ const endpoint = ('/' + endpointArray.join('/')) as EndpointPath;
60
+ const handler = endpoints[endpoint];
61
+
62
+ if (!handler) {
63
+ return NextResponse.json(
64
+ { message: 'Not found' },
65
+ { status: 404 },
66
+ );
67
+ }
68
+
69
+ const { args } = await request.json();
70
+ const result = await handler(...args);
71
+ return NextResponse.json({ result });
72
+ }
73
+ ```
74
+
75
+ ## Webhooks
76
+
77
+ Monnify sends signed POST requests with an HMAC-SHA512 signature.
78
+
79
+ ```typescript
80
+ // app/api/paykit/webhooks/route.ts
81
+ import { paykit } from '@/lib/paykit';
82
+ import { NextRequest, NextResponse } from 'next/server';
83
+
84
+ export async function POST(request: NextRequest) {
85
+ const body = await request.text();
86
+
87
+ const webhook = paykit.webhooks
88
+ .setup({ webhookSecret: process.env.MONNIFY_SECRET_KEY! })
89
+ .on('payment.created', async event => {
90
+ /* SUCCESSFUL_TRANSACTION or SUCCESSFUL_TRANSACTION_OFFLINE */
91
+ })
92
+ .on('payment.updated', async event => {
93
+ /* SETTLEMENT */
94
+ })
95
+ .on('payment.failed', async event => {
96
+ /* REJECTED_PAYMENT */
97
+ });
98
+
99
+ await webhook.handle({
100
+ body,
101
+ headersAsObject: Object.fromEntries(request.headers),
102
+ fullUrl: request.url,
103
+ });
104
+
105
+ return NextResponse.json({ received: true });
106
+ }
107
+ ```
108
+
109
+ Monnify event mappings:
110
+
111
+ | Monnify event | PayKit event emitted |
112
+ | -------------------------------- | -------------------- |
113
+ | `SUCCESSFUL_TRANSACTION` | `payment.created` |
114
+ | `SUCCESSFUL_TRANSACTION_OFFLINE` | `payment.created` |
115
+ | `REJECTED_PAYMENT` | `payment.failed` |
116
+ | `SETTLEMENT` | `payment.updated` |
117
+
118
+ ## Checkout
119
+
120
+ Monnify requires an email customer and amount/currency in `provider_metadata`:
121
+
122
+ ```typescript
123
+ const checkout = await paykit.checkouts.create({
124
+ customer: { email: 'user@example.com' },
125
+ item_id: 'prod_123',
126
+ session_type: 'one_time',
127
+ quantity: 1,
128
+ success_url: 'https://example.com/success',
129
+ provider_metadata: {
130
+ amount: 5000,
131
+ currency: 'NGN',
132
+ },
133
+ });
134
+
135
+ // Redirect user to checkout.payment_url
136
+ ```
137
+
138
+ ## Documentation
139
+
140
+ Full docs at [docs.usepaykit.com/providers/monnify](https://docs.usepaykit.com/providers/monnify).
141
+
142
+ ## Support
143
+
144
+ - [Monnify Documentation](https://developers.monnify.com)
145
+ - [PayKit Issues](https://github.com/usepaykit/paykit-sdk/issues)
146
+
147
+ ## License
148
+
149
+ ISC
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var core = require('@paykit-sdk/core');
4
+ var crypto$1 = require('crypto');
4
5
  var jsSha512 = require('js-sha512');
5
6
 
6
7
  // src/index.ts
@@ -210,6 +211,48 @@ var MonnifyProvider = class extends core.AbstractPayKitProvider {
210
211
  }
211
212
  return altResponse.value.responseBody;
212
213
  }
214
+ /**
215
+ * Monnify only has one way to move money: a hosted redirect
216
+ * transaction. createCheckout and createPayment both boil down to
217
+ * this same call - only the input validation and the response
218
+ * mapper differ between the two.
219
+ */
220
+ async initializeTransaction(params) {
221
+ const paymentReference = crypto.randomUUID();
222
+ const body = {
223
+ amount: params.amount,
224
+ paymentReference,
225
+ paymentDescription: params.description,
226
+ currencyCode: params.currency,
227
+ redirectUrl: params.redirectUrl,
228
+ paymentMethods: ["CARD", "ACCOUNT_TRANSFER"],
229
+ metadata: params.metadata,
230
+ customerEmail: params.email
231
+ };
232
+ const response = await this._client.post(
233
+ "/v1/merchant/transactions/init-transaction",
234
+ {
235
+ body: JSON.stringify(body),
236
+ headers: await this.tokenManager.getAuthHeaders()
237
+ }
238
+ );
239
+ const responseBody = this.ensureResponse(
240
+ response,
241
+ "initializeTransaction"
242
+ );
243
+ const transactionReference = responseBody.transactionReference;
244
+ const checkoutUrl = responseBody.checkoutUrl;
245
+ const transactionResponse = await this._client.get(
246
+ `/v2/merchant/transactions/query?paymentReference=${paymentReference}`,
247
+ { headers: await this.tokenManager.getAuthHeaders() }
248
+ );
249
+ const transactionData = this.ensureResponse(
250
+ transactionResponse,
251
+ "initializeTransaction",
252
+ "Failed to retrieve transaction details"
253
+ );
254
+ return { ...transactionData, checkoutUrl, transactionReference };
255
+ }
213
256
  createCheckout = async (params) => {
214
257
  const data = this.validateSchema(
215
258
  core.createCheckoutSchema,
@@ -232,50 +275,21 @@ var MonnifyProvider = class extends core.AbstractPayKitProvider {
232
275
  data.provider_metadata ?? { currency: "NGN" },
233
276
  "The following fields must be present in the provider_metadata of createCheckout: {keys}"
234
277
  );
235
- const paymentReference = crypto.randomUUID();
236
- const body = {
278
+ const transactionData = await this.initializeTransaction({
279
+ email: data.customer.email,
237
280
  amount,
238
- paymentReference,
239
- paymentDescription: `Checkout for ${data.item_id} x ${data.quantity} item${data.quantity > 1 ? "s" : ""}`,
240
- currencyCode: currency,
281
+ currency,
241
282
  redirectUrl: data.success_url,
242
- paymentMethods: ["CARD", "ACCOUNT_TRANSFER"],
283
+ description: `Checkout for ${data.item_id} x ${data.quantity} item${data.quantity > 1 ? "s" : ""}`,
243
284
  metadata: {
244
285
  ...params.metadata,
245
286
  [core.PAYKIT_METADATA_KEY]: JSON.stringify({
246
287
  item: data.item_id,
247
288
  qty: data.quantity
248
289
  })
249
- },
250
- customerEmail: data.customer.email
251
- };
252
- const response = await this._client.post(
253
- "/v1/merchant/transactions/init-transaction",
254
- {
255
- body: JSON.stringify(body),
256
- headers: await this.tokenManager.getAuthHeaders()
257
290
  }
258
- );
259
- const responseBody = this.ensureResponse(
260
- response,
261
- "createCheckout"
262
- );
263
- const transactionReference = responseBody.transactionReference;
264
- const checkoutUrl = responseBody.checkoutUrl;
265
- const checkoutResponse = await this._client.get(
266
- `/v2/merchant/transactions/query?paymentReference=${paymentReference}`,
267
- { headers: await this.tokenManager.getAuthHeaders() }
268
- );
269
- const checkoutData = this.ensureResponse(
270
- checkoutResponse,
271
- "createCheckout",
272
- "Failed to retrieve checkout details"
273
- );
274
- return Checkout$inboundSchema({
275
- ...checkoutData,
276
- checkoutUrl,
277
- transactionReference
278
291
  });
292
+ return Checkout$inboundSchema(transactionData);
279
293
  };
280
294
  retrieveCheckout = async (id) => {
281
295
  const transactionData = await this.queryTransaction(
@@ -394,14 +408,39 @@ var MonnifyProvider = class extends core.AbstractPayKitProvider {
394
408
  );
395
409
  };
396
410
  createPayment = async (params) => {
397
- throw new core.ProviderNotSupportedError(
398
- "createPayment",
399
- "Moniepoint",
400
- {
401
- reason: "Moniepoint doesn't support creating payments",
402
- alternative: "Use the createPayment method instead"
403
- }
411
+ const data = this.validateSchema(
412
+ core.createPaymentSchema,
413
+ params,
414
+ "createPayment"
404
415
  );
416
+ if (!core.isEmailCustomer(data.customer)) {
417
+ throw new core.InvalidTypeError(
418
+ "customer",
419
+ "object (customer) with email",
420
+ "string (customer ID)",
421
+ {
422
+ provider: this.providerName,
423
+ method: "createPayment"
424
+ }
425
+ );
426
+ }
427
+ const { success_url } = core.validateRequiredKeys(
428
+ ["success_url"],
429
+ data.provider_metadata ?? {},
430
+ "The following fields must be present in the provider_metadata of createPayment: {keys}"
431
+ );
432
+ const transactionData = await this.initializeTransaction({
433
+ email: data.customer.email,
434
+ amount: data.amount.toString(),
435
+ currency: data.currency,
436
+ redirectUrl: success_url,
437
+ description: `Payment for ${data.item_id}`,
438
+ metadata: {
439
+ ...data.metadata,
440
+ [core.PAYKIT_METADATA_KEY]: JSON.stringify({ item: data.item_id })
441
+ }
442
+ });
443
+ return Payment$inboundSchema(transactionData);
405
444
  };
406
445
  retrievePayment = async (id) => {
407
446
  try {
@@ -503,11 +542,10 @@ var MonnifyProvider = class extends core.AbstractPayKitProvider {
503
542
  provider: this.providerName
504
543
  });
505
544
  }
506
- const computedHash = jsSha512.sha512.hmac(
507
- webhookSecret,
508
- JSON.stringify(body)
509
- );
510
- if (computedHash !== receivedHash)
545
+ const computedHash = jsSha512.sha512.hmac(webhookSecret, body);
546
+ const computedBuf = Buffer.from(computedHash, "hex");
547
+ const receivedBuf = Buffer.from(receivedHash, "hex");
548
+ if (computedBuf.length !== receivedBuf.length || !crypto$1.timingSafeEqual(computedBuf, receivedBuf))
511
549
  throw new core.WebhookError("Invalid Monnify signature", {
512
550
  provider: this.providerName
513
551
  });
@@ -527,35 +565,31 @@ var MonnifyProvider = class extends core.AbstractPayKitProvider {
527
565
  );
528
566
  }
529
567
  const { eventType, eventData } = parsedBody;
568
+ const created = Math.floor(Date.now() / 1e3);
569
+ const results = [];
570
+ results.push({
571
+ id: `monnify:${eventType}:${crypto.randomUUID()}`,
572
+ type: `monnify.${eventType}`,
573
+ created,
574
+ data: eventData,
575
+ is_raw: true
576
+ });
530
577
  const eventMapper = monnifyToPaykitEventMap[eventType];
531
- if (!eventMapper) {
532
- throw new core.WebhookError("Unknown Monnify event type", {
533
- provider: this.providerName
578
+ const standardType = typeof eventMapper === "function" ? eventMapper(eventData) : eventMapper;
579
+ if (standardType) {
580
+ results.push({
581
+ id: `paykit:${eventType}:${crypto.randomUUID()}`,
582
+ type: standardType,
583
+ created,
584
+ data: eventData
585
+ // todo: add mapper for event data
534
586
  });
587
+ } else if (this.opts.debug) {
588
+ console.info(
589
+ `[Monnify] No standard mapping for event: ${eventType}. Available as raw event.`
590
+ );
535
591
  }
536
- if (typeof eventMapper === "function") {
537
- const event = eventMapper(eventData);
538
- return [
539
- {
540
- type: event,
541
- created: (/* @__PURE__ */ new Date()).getTime(),
542
- id: `paykit:webhook:${Math.random().toString(36).substring(2, 15)}`,
543
- data: eventData
544
- // todo: add mapper for event data
545
- }
546
- ];
547
- } else {
548
- const event = eventMapper;
549
- return [
550
- {
551
- type: event,
552
- created: (/* @__PURE__ */ new Date()).getTime(),
553
- id: `paykit:webhook:${Math.random().toString(36).substring(2, 15)}`,
554
- data: eventData
555
- // todo: add mapper for event data
556
- }
557
- ];
558
- }
592
+ return results;
559
593
  };
560
594
  };
561
595
 
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
- import { schema, Schema, validateRequiredKeys, AbstractPayKitProvider, HTTPClient, OAuth2TokenManager, ValidationError, OperationFailedError, createCheckoutSchema, isEmailCustomer, InvalidTypeError, PAYKIT_METADATA_KEY, ProviderNotSupportedError, createRefundSchema, WebhookError, parseJSON, omitInternalMetadata } from '@paykit-sdk/core';
1
+ import { schema, Schema, validateRequiredKeys, AbstractPayKitProvider, HTTPClient, OAuth2TokenManager, ValidationError, OperationFailedError, createCheckoutSchema, isEmailCustomer, InvalidTypeError, PAYKIT_METADATA_KEY, ProviderNotSupportedError, createPaymentSchema, createRefundSchema, WebhookError, parseJSON, omitInternalMetadata } from '@paykit-sdk/core';
2
+ import { timingSafeEqual } from 'crypto';
2
3
  import { sha512 } from 'js-sha512';
3
4
 
4
5
  // src/index.ts
@@ -208,6 +209,48 @@ var MonnifyProvider = class extends AbstractPayKitProvider {
208
209
  }
209
210
  return altResponse.value.responseBody;
210
211
  }
212
+ /**
213
+ * Monnify only has one way to move money: a hosted redirect
214
+ * transaction. createCheckout and createPayment both boil down to
215
+ * this same call - only the input validation and the response
216
+ * mapper differ between the two.
217
+ */
218
+ async initializeTransaction(params) {
219
+ const paymentReference = crypto.randomUUID();
220
+ const body = {
221
+ amount: params.amount,
222
+ paymentReference,
223
+ paymentDescription: params.description,
224
+ currencyCode: params.currency,
225
+ redirectUrl: params.redirectUrl,
226
+ paymentMethods: ["CARD", "ACCOUNT_TRANSFER"],
227
+ metadata: params.metadata,
228
+ customerEmail: params.email
229
+ };
230
+ const response = await this._client.post(
231
+ "/v1/merchant/transactions/init-transaction",
232
+ {
233
+ body: JSON.stringify(body),
234
+ headers: await this.tokenManager.getAuthHeaders()
235
+ }
236
+ );
237
+ const responseBody = this.ensureResponse(
238
+ response,
239
+ "initializeTransaction"
240
+ );
241
+ const transactionReference = responseBody.transactionReference;
242
+ const checkoutUrl = responseBody.checkoutUrl;
243
+ const transactionResponse = await this._client.get(
244
+ `/v2/merchant/transactions/query?paymentReference=${paymentReference}`,
245
+ { headers: await this.tokenManager.getAuthHeaders() }
246
+ );
247
+ const transactionData = this.ensureResponse(
248
+ transactionResponse,
249
+ "initializeTransaction",
250
+ "Failed to retrieve transaction details"
251
+ );
252
+ return { ...transactionData, checkoutUrl, transactionReference };
253
+ }
211
254
  createCheckout = async (params) => {
212
255
  const data = this.validateSchema(
213
256
  createCheckoutSchema,
@@ -230,50 +273,21 @@ var MonnifyProvider = class extends AbstractPayKitProvider {
230
273
  data.provider_metadata ?? { currency: "NGN" },
231
274
  "The following fields must be present in the provider_metadata of createCheckout: {keys}"
232
275
  );
233
- const paymentReference = crypto.randomUUID();
234
- const body = {
276
+ const transactionData = await this.initializeTransaction({
277
+ email: data.customer.email,
235
278
  amount,
236
- paymentReference,
237
- paymentDescription: `Checkout for ${data.item_id} x ${data.quantity} item${data.quantity > 1 ? "s" : ""}`,
238
- currencyCode: currency,
279
+ currency,
239
280
  redirectUrl: data.success_url,
240
- paymentMethods: ["CARD", "ACCOUNT_TRANSFER"],
281
+ description: `Checkout for ${data.item_id} x ${data.quantity} item${data.quantity > 1 ? "s" : ""}`,
241
282
  metadata: {
242
283
  ...params.metadata,
243
284
  [PAYKIT_METADATA_KEY]: JSON.stringify({
244
285
  item: data.item_id,
245
286
  qty: data.quantity
246
287
  })
247
- },
248
- customerEmail: data.customer.email
249
- };
250
- const response = await this._client.post(
251
- "/v1/merchant/transactions/init-transaction",
252
- {
253
- body: JSON.stringify(body),
254
- headers: await this.tokenManager.getAuthHeaders()
255
288
  }
256
- );
257
- const responseBody = this.ensureResponse(
258
- response,
259
- "createCheckout"
260
- );
261
- const transactionReference = responseBody.transactionReference;
262
- const checkoutUrl = responseBody.checkoutUrl;
263
- const checkoutResponse = await this._client.get(
264
- `/v2/merchant/transactions/query?paymentReference=${paymentReference}`,
265
- { headers: await this.tokenManager.getAuthHeaders() }
266
- );
267
- const checkoutData = this.ensureResponse(
268
- checkoutResponse,
269
- "createCheckout",
270
- "Failed to retrieve checkout details"
271
- );
272
- return Checkout$inboundSchema({
273
- ...checkoutData,
274
- checkoutUrl,
275
- transactionReference
276
289
  });
290
+ return Checkout$inboundSchema(transactionData);
277
291
  };
278
292
  retrieveCheckout = async (id) => {
279
293
  const transactionData = await this.queryTransaction(
@@ -392,14 +406,39 @@ var MonnifyProvider = class extends AbstractPayKitProvider {
392
406
  );
393
407
  };
394
408
  createPayment = async (params) => {
395
- throw new ProviderNotSupportedError(
396
- "createPayment",
397
- "Moniepoint",
398
- {
399
- reason: "Moniepoint doesn't support creating payments",
400
- alternative: "Use the createPayment method instead"
401
- }
409
+ const data = this.validateSchema(
410
+ createPaymentSchema,
411
+ params,
412
+ "createPayment"
402
413
  );
414
+ if (!isEmailCustomer(data.customer)) {
415
+ throw new InvalidTypeError(
416
+ "customer",
417
+ "object (customer) with email",
418
+ "string (customer ID)",
419
+ {
420
+ provider: this.providerName,
421
+ method: "createPayment"
422
+ }
423
+ );
424
+ }
425
+ const { success_url } = validateRequiredKeys(
426
+ ["success_url"],
427
+ data.provider_metadata ?? {},
428
+ "The following fields must be present in the provider_metadata of createPayment: {keys}"
429
+ );
430
+ const transactionData = await this.initializeTransaction({
431
+ email: data.customer.email,
432
+ amount: data.amount.toString(),
433
+ currency: data.currency,
434
+ redirectUrl: success_url,
435
+ description: `Payment for ${data.item_id}`,
436
+ metadata: {
437
+ ...data.metadata,
438
+ [PAYKIT_METADATA_KEY]: JSON.stringify({ item: data.item_id })
439
+ }
440
+ });
441
+ return Payment$inboundSchema(transactionData);
403
442
  };
404
443
  retrievePayment = async (id) => {
405
444
  try {
@@ -501,11 +540,10 @@ var MonnifyProvider = class extends AbstractPayKitProvider {
501
540
  provider: this.providerName
502
541
  });
503
542
  }
504
- const computedHash = sha512.hmac(
505
- webhookSecret,
506
- JSON.stringify(body)
507
- );
508
- if (computedHash !== receivedHash)
543
+ const computedHash = sha512.hmac(webhookSecret, body);
544
+ const computedBuf = Buffer.from(computedHash, "hex");
545
+ const receivedBuf = Buffer.from(receivedHash, "hex");
546
+ if (computedBuf.length !== receivedBuf.length || !timingSafeEqual(computedBuf, receivedBuf))
509
547
  throw new WebhookError("Invalid Monnify signature", {
510
548
  provider: this.providerName
511
549
  });
@@ -525,35 +563,31 @@ var MonnifyProvider = class extends AbstractPayKitProvider {
525
563
  );
526
564
  }
527
565
  const { eventType, eventData } = parsedBody;
566
+ const created = Math.floor(Date.now() / 1e3);
567
+ const results = [];
568
+ results.push({
569
+ id: `monnify:${eventType}:${crypto.randomUUID()}`,
570
+ type: `monnify.${eventType}`,
571
+ created,
572
+ data: eventData,
573
+ is_raw: true
574
+ });
528
575
  const eventMapper = monnifyToPaykitEventMap[eventType];
529
- if (!eventMapper) {
530
- throw new WebhookError("Unknown Monnify event type", {
531
- provider: this.providerName
576
+ const standardType = typeof eventMapper === "function" ? eventMapper(eventData) : eventMapper;
577
+ if (standardType) {
578
+ results.push({
579
+ id: `paykit:${eventType}:${crypto.randomUUID()}`,
580
+ type: standardType,
581
+ created,
582
+ data: eventData
583
+ // todo: add mapper for event data
532
584
  });
585
+ } else if (this.opts.debug) {
586
+ console.info(
587
+ `[Monnify] No standard mapping for event: ${eventType}. Available as raw event.`
588
+ );
533
589
  }
534
- if (typeof eventMapper === "function") {
535
- const event = eventMapper(eventData);
536
- return [
537
- {
538
- type: event,
539
- created: (/* @__PURE__ */ new Date()).getTime(),
540
- id: `paykit:webhook:${Math.random().toString(36).substring(2, 15)}`,
541
- data: eventData
542
- // todo: add mapper for event data
543
- }
544
- ];
545
- } else {
546
- const event = eventMapper;
547
- return [
548
- {
549
- type: event,
550
- created: (/* @__PURE__ */ new Date()).getTime(),
551
- id: `paykit:webhook:${Math.random().toString(36).substring(2, 15)}`,
552
- data: eventData
553
- // todo: add mapper for event data
554
- }
555
- ];
556
- }
590
+ return results;
557
591
  };
558
592
  };
559
593
 
@@ -1,6 +1,18 @@
1
1
  import { PaykitProviderOptions, AbstractPayKitProvider, PayKitProvider, ProviderMetadataRegistry, CreateCheckoutSchema, Checkout, UpdateCheckoutSchema, CreateCustomerParams, Customer, UpdateCustomerParams, CreateSubscriptionSchema, Subscription, UpdateSubscriptionSchema, CreatePaymentSchema, Payment, UpdatePaymentSchema, CapturePaymentSchema, CreateRefundSchema, Refund, WebhookHandlerConfig, WebhookEventPayload } from '@paykit-sdk/core';
2
2
 
3
3
  interface MonnifyMetadata extends ProviderMetadataRegistry {
4
+ checkout?: {
5
+ amount?: string;
6
+ currency?: string;
7
+ };
8
+ payment?: {
9
+ /**
10
+ * Monnify only supports hosted/redirect transactions - a
11
+ * direct createPayment call still needs somewhere to send the
12
+ * customer, same as createCheckout's success_url.
13
+ */
14
+ success_url?: string;
15
+ };
4
16
  }
5
17
  interface MonnifyRawEvents extends Record<string, any> {
6
18
  }
@@ -39,6 +51,13 @@ declare class MonnifyProvider extends AbstractPayKitProvider implements PayKitPr
39
51
  * Queries transaction by transactionReference or paymentReference (with fallback)
40
52
  */
41
53
  private queryTransaction;
54
+ /**
55
+ * Monnify only has one way to move money: a hosted redirect
56
+ * transaction. createCheckout and createPayment both boil down to
57
+ * this same call - only the input validation and the response
58
+ * mapper differ between the two.
59
+ */
60
+ private initializeTransaction;
42
61
  createCheckout: (params: CreateCheckoutSchema) => Promise<Checkout>;
43
62
  retrieveCheckout: (id: string) => Promise<Checkout>;
44
63
  updateCheckout: (id: string, params: UpdateCheckoutSchema) => Promise<Checkout>;
@@ -52,7 +71,7 @@ declare class MonnifyProvider extends AbstractPayKitProvider implements PayKitPr
52
71
  cancelSubscription: (id: string) => Promise<Subscription>;
53
72
  deleteSubscription: (id: string) => Promise<null>;
54
73
  retrieveSubscription: (id: string) => Promise<Subscription | null>;
55
- createPayment: (params: CreatePaymentSchema) => Promise<Payment>;
74
+ createPayment: (params: CreatePaymentSchema<MonnifyMetadata["payment"]>) => Promise<Payment>;
56
75
  retrievePayment: (id: string) => Promise<Payment | null>;
57
76
  updatePayment: (id: string, params: UpdatePaymentSchema) => Promise<Payment>;
58
77
  deletePayment: (id: string) => Promise<null>;
@@ -1,6 +1,18 @@
1
1
  import { PaykitProviderOptions, AbstractPayKitProvider, PayKitProvider, ProviderMetadataRegistry, CreateCheckoutSchema, Checkout, UpdateCheckoutSchema, CreateCustomerParams, Customer, UpdateCustomerParams, CreateSubscriptionSchema, Subscription, UpdateSubscriptionSchema, CreatePaymentSchema, Payment, UpdatePaymentSchema, CapturePaymentSchema, CreateRefundSchema, Refund, WebhookHandlerConfig, WebhookEventPayload } from '@paykit-sdk/core';
2
2
 
3
3
  interface MonnifyMetadata extends ProviderMetadataRegistry {
4
+ checkout?: {
5
+ amount?: string;
6
+ currency?: string;
7
+ };
8
+ payment?: {
9
+ /**
10
+ * Monnify only supports hosted/redirect transactions - a
11
+ * direct createPayment call still needs somewhere to send the
12
+ * customer, same as createCheckout's success_url.
13
+ */
14
+ success_url?: string;
15
+ };
4
16
  }
5
17
  interface MonnifyRawEvents extends Record<string, any> {
6
18
  }
@@ -39,6 +51,13 @@ declare class MonnifyProvider extends AbstractPayKitProvider implements PayKitPr
39
51
  * Queries transaction by transactionReference or paymentReference (with fallback)
40
52
  */
41
53
  private queryTransaction;
54
+ /**
55
+ * Monnify only has one way to move money: a hosted redirect
56
+ * transaction. createCheckout and createPayment both boil down to
57
+ * this same call - only the input validation and the response
58
+ * mapper differ between the two.
59
+ */
60
+ private initializeTransaction;
42
61
  createCheckout: (params: CreateCheckoutSchema) => Promise<Checkout>;
43
62
  retrieveCheckout: (id: string) => Promise<Checkout>;
44
63
  updateCheckout: (id: string, params: UpdateCheckoutSchema) => Promise<Checkout>;
@@ -52,7 +71,7 @@ declare class MonnifyProvider extends AbstractPayKitProvider implements PayKitPr
52
71
  cancelSubscription: (id: string) => Promise<Subscription>;
53
72
  deleteSubscription: (id: string) => Promise<null>;
54
73
  retrieveSubscription: (id: string) => Promise<Subscription | null>;
55
- createPayment: (params: CreatePaymentSchema) => Promise<Payment>;
74
+ createPayment: (params: CreatePaymentSchema<MonnifyMetadata["payment"]>) => Promise<Payment>;
56
75
  retrievePayment: (id: string) => Promise<Payment | null>;
57
76
  updatePayment: (id: string, params: UpdatePaymentSchema) => Promise<Payment>;
58
77
  deletePayment: (id: string) => Promise<null>;
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var core = require('@paykit-sdk/core');
4
+ var crypto$1 = require('crypto');
4
5
  var jsSha512 = require('js-sha512');
5
6
 
6
7
  // src/monnify-provider.ts
@@ -210,6 +211,48 @@ var MonnifyProvider = class extends core.AbstractPayKitProvider {
210
211
  }
211
212
  return altResponse.value.responseBody;
212
213
  }
214
+ /**
215
+ * Monnify only has one way to move money: a hosted redirect
216
+ * transaction. createCheckout and createPayment both boil down to
217
+ * this same call - only the input validation and the response
218
+ * mapper differ between the two.
219
+ */
220
+ async initializeTransaction(params) {
221
+ const paymentReference = crypto.randomUUID();
222
+ const body = {
223
+ amount: params.amount,
224
+ paymentReference,
225
+ paymentDescription: params.description,
226
+ currencyCode: params.currency,
227
+ redirectUrl: params.redirectUrl,
228
+ paymentMethods: ["CARD", "ACCOUNT_TRANSFER"],
229
+ metadata: params.metadata,
230
+ customerEmail: params.email
231
+ };
232
+ const response = await this._client.post(
233
+ "/v1/merchant/transactions/init-transaction",
234
+ {
235
+ body: JSON.stringify(body),
236
+ headers: await this.tokenManager.getAuthHeaders()
237
+ }
238
+ );
239
+ const responseBody = this.ensureResponse(
240
+ response,
241
+ "initializeTransaction"
242
+ );
243
+ const transactionReference = responseBody.transactionReference;
244
+ const checkoutUrl = responseBody.checkoutUrl;
245
+ const transactionResponse = await this._client.get(
246
+ `/v2/merchant/transactions/query?paymentReference=${paymentReference}`,
247
+ { headers: await this.tokenManager.getAuthHeaders() }
248
+ );
249
+ const transactionData = this.ensureResponse(
250
+ transactionResponse,
251
+ "initializeTransaction",
252
+ "Failed to retrieve transaction details"
253
+ );
254
+ return { ...transactionData, checkoutUrl, transactionReference };
255
+ }
213
256
  createCheckout = async (params) => {
214
257
  const data = this.validateSchema(
215
258
  core.createCheckoutSchema,
@@ -232,50 +275,21 @@ var MonnifyProvider = class extends core.AbstractPayKitProvider {
232
275
  data.provider_metadata ?? { currency: "NGN" },
233
276
  "The following fields must be present in the provider_metadata of createCheckout: {keys}"
234
277
  );
235
- const paymentReference = crypto.randomUUID();
236
- const body = {
278
+ const transactionData = await this.initializeTransaction({
279
+ email: data.customer.email,
237
280
  amount,
238
- paymentReference,
239
- paymentDescription: `Checkout for ${data.item_id} x ${data.quantity} item${data.quantity > 1 ? "s" : ""}`,
240
- currencyCode: currency,
281
+ currency,
241
282
  redirectUrl: data.success_url,
242
- paymentMethods: ["CARD", "ACCOUNT_TRANSFER"],
283
+ description: `Checkout for ${data.item_id} x ${data.quantity} item${data.quantity > 1 ? "s" : ""}`,
243
284
  metadata: {
244
285
  ...params.metadata,
245
286
  [core.PAYKIT_METADATA_KEY]: JSON.stringify({
246
287
  item: data.item_id,
247
288
  qty: data.quantity
248
289
  })
249
- },
250
- customerEmail: data.customer.email
251
- };
252
- const response = await this._client.post(
253
- "/v1/merchant/transactions/init-transaction",
254
- {
255
- body: JSON.stringify(body),
256
- headers: await this.tokenManager.getAuthHeaders()
257
290
  }
258
- );
259
- const responseBody = this.ensureResponse(
260
- response,
261
- "createCheckout"
262
- );
263
- const transactionReference = responseBody.transactionReference;
264
- const checkoutUrl = responseBody.checkoutUrl;
265
- const checkoutResponse = await this._client.get(
266
- `/v2/merchant/transactions/query?paymentReference=${paymentReference}`,
267
- { headers: await this.tokenManager.getAuthHeaders() }
268
- );
269
- const checkoutData = this.ensureResponse(
270
- checkoutResponse,
271
- "createCheckout",
272
- "Failed to retrieve checkout details"
273
- );
274
- return Checkout$inboundSchema({
275
- ...checkoutData,
276
- checkoutUrl,
277
- transactionReference
278
291
  });
292
+ return Checkout$inboundSchema(transactionData);
279
293
  };
280
294
  retrieveCheckout = async (id) => {
281
295
  const transactionData = await this.queryTransaction(
@@ -394,14 +408,39 @@ var MonnifyProvider = class extends core.AbstractPayKitProvider {
394
408
  );
395
409
  };
396
410
  createPayment = async (params) => {
397
- throw new core.ProviderNotSupportedError(
398
- "createPayment",
399
- "Moniepoint",
400
- {
401
- reason: "Moniepoint doesn't support creating payments",
402
- alternative: "Use the createPayment method instead"
403
- }
411
+ const data = this.validateSchema(
412
+ core.createPaymentSchema,
413
+ params,
414
+ "createPayment"
404
415
  );
416
+ if (!core.isEmailCustomer(data.customer)) {
417
+ throw new core.InvalidTypeError(
418
+ "customer",
419
+ "object (customer) with email",
420
+ "string (customer ID)",
421
+ {
422
+ provider: this.providerName,
423
+ method: "createPayment"
424
+ }
425
+ );
426
+ }
427
+ const { success_url } = core.validateRequiredKeys(
428
+ ["success_url"],
429
+ data.provider_metadata ?? {},
430
+ "The following fields must be present in the provider_metadata of createPayment: {keys}"
431
+ );
432
+ const transactionData = await this.initializeTransaction({
433
+ email: data.customer.email,
434
+ amount: data.amount.toString(),
435
+ currency: data.currency,
436
+ redirectUrl: success_url,
437
+ description: `Payment for ${data.item_id}`,
438
+ metadata: {
439
+ ...data.metadata,
440
+ [core.PAYKIT_METADATA_KEY]: JSON.stringify({ item: data.item_id })
441
+ }
442
+ });
443
+ return Payment$inboundSchema(transactionData);
405
444
  };
406
445
  retrievePayment = async (id) => {
407
446
  try {
@@ -503,11 +542,10 @@ var MonnifyProvider = class extends core.AbstractPayKitProvider {
503
542
  provider: this.providerName
504
543
  });
505
544
  }
506
- const computedHash = jsSha512.sha512.hmac(
507
- webhookSecret,
508
- JSON.stringify(body)
509
- );
510
- if (computedHash !== receivedHash)
545
+ const computedHash = jsSha512.sha512.hmac(webhookSecret, body);
546
+ const computedBuf = Buffer.from(computedHash, "hex");
547
+ const receivedBuf = Buffer.from(receivedHash, "hex");
548
+ if (computedBuf.length !== receivedBuf.length || !crypto$1.timingSafeEqual(computedBuf, receivedBuf))
511
549
  throw new core.WebhookError("Invalid Monnify signature", {
512
550
  provider: this.providerName
513
551
  });
@@ -527,35 +565,31 @@ var MonnifyProvider = class extends core.AbstractPayKitProvider {
527
565
  );
528
566
  }
529
567
  const { eventType, eventData } = parsedBody;
568
+ const created = Math.floor(Date.now() / 1e3);
569
+ const results = [];
570
+ results.push({
571
+ id: `monnify:${eventType}:${crypto.randomUUID()}`,
572
+ type: `monnify.${eventType}`,
573
+ created,
574
+ data: eventData,
575
+ is_raw: true
576
+ });
530
577
  const eventMapper = monnifyToPaykitEventMap[eventType];
531
- if (!eventMapper) {
532
- throw new core.WebhookError("Unknown Monnify event type", {
533
- provider: this.providerName
578
+ const standardType = typeof eventMapper === "function" ? eventMapper(eventData) : eventMapper;
579
+ if (standardType) {
580
+ results.push({
581
+ id: `paykit:${eventType}:${crypto.randomUUID()}`,
582
+ type: standardType,
583
+ created,
584
+ data: eventData
585
+ // todo: add mapper for event data
534
586
  });
587
+ } else if (this.opts.debug) {
588
+ console.info(
589
+ `[Monnify] No standard mapping for event: ${eventType}. Available as raw event.`
590
+ );
535
591
  }
536
- if (typeof eventMapper === "function") {
537
- const event = eventMapper(eventData);
538
- return [
539
- {
540
- type: event,
541
- created: (/* @__PURE__ */ new Date()).getTime(),
542
- id: `paykit:webhook:${Math.random().toString(36).substring(2, 15)}`,
543
- data: eventData
544
- // todo: add mapper for event data
545
- }
546
- ];
547
- } else {
548
- const event = eventMapper;
549
- return [
550
- {
551
- type: event,
552
- created: (/* @__PURE__ */ new Date()).getTime(),
553
- id: `paykit:webhook:${Math.random().toString(36).substring(2, 15)}`,
554
- data: eventData
555
- // todo: add mapper for event data
556
- }
557
- ];
558
- }
592
+ return results;
559
593
  };
560
594
  };
561
595
 
@@ -1,4 +1,5 @@
1
- import { schema, Schema, AbstractPayKitProvider, HTTPClient, OAuth2TokenManager, ValidationError, OperationFailedError, createCheckoutSchema, isEmailCustomer, InvalidTypeError, validateRequiredKeys, PAYKIT_METADATA_KEY, ProviderNotSupportedError, createRefundSchema, WebhookError, parseJSON, omitInternalMetadata } from '@paykit-sdk/core';
1
+ import { schema, Schema, AbstractPayKitProvider, HTTPClient, OAuth2TokenManager, ValidationError, OperationFailedError, createCheckoutSchema, isEmailCustomer, InvalidTypeError, validateRequiredKeys, PAYKIT_METADATA_KEY, ProviderNotSupportedError, createPaymentSchema, createRefundSchema, WebhookError, parseJSON, omitInternalMetadata } from '@paykit-sdk/core';
2
+ import { timingSafeEqual } from 'crypto';
2
3
  import { sha512 } from 'js-sha512';
3
4
 
4
5
  // src/monnify-provider.ts
@@ -208,6 +209,48 @@ var MonnifyProvider = class extends AbstractPayKitProvider {
208
209
  }
209
210
  return altResponse.value.responseBody;
210
211
  }
212
+ /**
213
+ * Monnify only has one way to move money: a hosted redirect
214
+ * transaction. createCheckout and createPayment both boil down to
215
+ * this same call - only the input validation and the response
216
+ * mapper differ between the two.
217
+ */
218
+ async initializeTransaction(params) {
219
+ const paymentReference = crypto.randomUUID();
220
+ const body = {
221
+ amount: params.amount,
222
+ paymentReference,
223
+ paymentDescription: params.description,
224
+ currencyCode: params.currency,
225
+ redirectUrl: params.redirectUrl,
226
+ paymentMethods: ["CARD", "ACCOUNT_TRANSFER"],
227
+ metadata: params.metadata,
228
+ customerEmail: params.email
229
+ };
230
+ const response = await this._client.post(
231
+ "/v1/merchant/transactions/init-transaction",
232
+ {
233
+ body: JSON.stringify(body),
234
+ headers: await this.tokenManager.getAuthHeaders()
235
+ }
236
+ );
237
+ const responseBody = this.ensureResponse(
238
+ response,
239
+ "initializeTransaction"
240
+ );
241
+ const transactionReference = responseBody.transactionReference;
242
+ const checkoutUrl = responseBody.checkoutUrl;
243
+ const transactionResponse = await this._client.get(
244
+ `/v2/merchant/transactions/query?paymentReference=${paymentReference}`,
245
+ { headers: await this.tokenManager.getAuthHeaders() }
246
+ );
247
+ const transactionData = this.ensureResponse(
248
+ transactionResponse,
249
+ "initializeTransaction",
250
+ "Failed to retrieve transaction details"
251
+ );
252
+ return { ...transactionData, checkoutUrl, transactionReference };
253
+ }
211
254
  createCheckout = async (params) => {
212
255
  const data = this.validateSchema(
213
256
  createCheckoutSchema,
@@ -230,50 +273,21 @@ var MonnifyProvider = class extends AbstractPayKitProvider {
230
273
  data.provider_metadata ?? { currency: "NGN" },
231
274
  "The following fields must be present in the provider_metadata of createCheckout: {keys}"
232
275
  );
233
- const paymentReference = crypto.randomUUID();
234
- const body = {
276
+ const transactionData = await this.initializeTransaction({
277
+ email: data.customer.email,
235
278
  amount,
236
- paymentReference,
237
- paymentDescription: `Checkout for ${data.item_id} x ${data.quantity} item${data.quantity > 1 ? "s" : ""}`,
238
- currencyCode: currency,
279
+ currency,
239
280
  redirectUrl: data.success_url,
240
- paymentMethods: ["CARD", "ACCOUNT_TRANSFER"],
281
+ description: `Checkout for ${data.item_id} x ${data.quantity} item${data.quantity > 1 ? "s" : ""}`,
241
282
  metadata: {
242
283
  ...params.metadata,
243
284
  [PAYKIT_METADATA_KEY]: JSON.stringify({
244
285
  item: data.item_id,
245
286
  qty: data.quantity
246
287
  })
247
- },
248
- customerEmail: data.customer.email
249
- };
250
- const response = await this._client.post(
251
- "/v1/merchant/transactions/init-transaction",
252
- {
253
- body: JSON.stringify(body),
254
- headers: await this.tokenManager.getAuthHeaders()
255
288
  }
256
- );
257
- const responseBody = this.ensureResponse(
258
- response,
259
- "createCheckout"
260
- );
261
- const transactionReference = responseBody.transactionReference;
262
- const checkoutUrl = responseBody.checkoutUrl;
263
- const checkoutResponse = await this._client.get(
264
- `/v2/merchant/transactions/query?paymentReference=${paymentReference}`,
265
- { headers: await this.tokenManager.getAuthHeaders() }
266
- );
267
- const checkoutData = this.ensureResponse(
268
- checkoutResponse,
269
- "createCheckout",
270
- "Failed to retrieve checkout details"
271
- );
272
- return Checkout$inboundSchema({
273
- ...checkoutData,
274
- checkoutUrl,
275
- transactionReference
276
289
  });
290
+ return Checkout$inboundSchema(transactionData);
277
291
  };
278
292
  retrieveCheckout = async (id) => {
279
293
  const transactionData = await this.queryTransaction(
@@ -392,14 +406,39 @@ var MonnifyProvider = class extends AbstractPayKitProvider {
392
406
  );
393
407
  };
394
408
  createPayment = async (params) => {
395
- throw new ProviderNotSupportedError(
396
- "createPayment",
397
- "Moniepoint",
398
- {
399
- reason: "Moniepoint doesn't support creating payments",
400
- alternative: "Use the createPayment method instead"
401
- }
409
+ const data = this.validateSchema(
410
+ createPaymentSchema,
411
+ params,
412
+ "createPayment"
402
413
  );
414
+ if (!isEmailCustomer(data.customer)) {
415
+ throw new InvalidTypeError(
416
+ "customer",
417
+ "object (customer) with email",
418
+ "string (customer ID)",
419
+ {
420
+ provider: this.providerName,
421
+ method: "createPayment"
422
+ }
423
+ );
424
+ }
425
+ const { success_url } = validateRequiredKeys(
426
+ ["success_url"],
427
+ data.provider_metadata ?? {},
428
+ "The following fields must be present in the provider_metadata of createPayment: {keys}"
429
+ );
430
+ const transactionData = await this.initializeTransaction({
431
+ email: data.customer.email,
432
+ amount: data.amount.toString(),
433
+ currency: data.currency,
434
+ redirectUrl: success_url,
435
+ description: `Payment for ${data.item_id}`,
436
+ metadata: {
437
+ ...data.metadata,
438
+ [PAYKIT_METADATA_KEY]: JSON.stringify({ item: data.item_id })
439
+ }
440
+ });
441
+ return Payment$inboundSchema(transactionData);
403
442
  };
404
443
  retrievePayment = async (id) => {
405
444
  try {
@@ -501,11 +540,10 @@ var MonnifyProvider = class extends AbstractPayKitProvider {
501
540
  provider: this.providerName
502
541
  });
503
542
  }
504
- const computedHash = sha512.hmac(
505
- webhookSecret,
506
- JSON.stringify(body)
507
- );
508
- if (computedHash !== receivedHash)
543
+ const computedHash = sha512.hmac(webhookSecret, body);
544
+ const computedBuf = Buffer.from(computedHash, "hex");
545
+ const receivedBuf = Buffer.from(receivedHash, "hex");
546
+ if (computedBuf.length !== receivedBuf.length || !timingSafeEqual(computedBuf, receivedBuf))
509
547
  throw new WebhookError("Invalid Monnify signature", {
510
548
  provider: this.providerName
511
549
  });
@@ -525,35 +563,31 @@ var MonnifyProvider = class extends AbstractPayKitProvider {
525
563
  );
526
564
  }
527
565
  const { eventType, eventData } = parsedBody;
566
+ const created = Math.floor(Date.now() / 1e3);
567
+ const results = [];
568
+ results.push({
569
+ id: `monnify:${eventType}:${crypto.randomUUID()}`,
570
+ type: `monnify.${eventType}`,
571
+ created,
572
+ data: eventData,
573
+ is_raw: true
574
+ });
528
575
  const eventMapper = monnifyToPaykitEventMap[eventType];
529
- if (!eventMapper) {
530
- throw new WebhookError("Unknown Monnify event type", {
531
- provider: this.providerName
576
+ const standardType = typeof eventMapper === "function" ? eventMapper(eventData) : eventMapper;
577
+ if (standardType) {
578
+ results.push({
579
+ id: `paykit:${eventType}:${crypto.randomUUID()}`,
580
+ type: standardType,
581
+ created,
582
+ data: eventData
583
+ // todo: add mapper for event data
532
584
  });
585
+ } else if (this.opts.debug) {
586
+ console.info(
587
+ `[Monnify] No standard mapping for event: ${eventType}. Available as raw event.`
588
+ );
533
589
  }
534
- if (typeof eventMapper === "function") {
535
- const event = eventMapper(eventData);
536
- return [
537
- {
538
- type: event,
539
- created: (/* @__PURE__ */ new Date()).getTime(),
540
- id: `paykit:webhook:${Math.random().toString(36).substring(2, 15)}`,
541
- data: eventData
542
- // todo: add mapper for event data
543
- }
544
- ];
545
- } else {
546
- const event = eventMapper;
547
- return [
548
- {
549
- type: event,
550
- created: (/* @__PURE__ */ new Date()).getTime(),
551
- id: `paykit:webhook:${Math.random().toString(36).substring(2, 15)}`,
552
- data: eventData
553
- // todo: add mapper for event data
554
- }
555
- ];
556
- }
590
+ return results;
557
591
  };
558
592
  };
559
593
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paykit-sdk/monnify",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "Moniepoint provider for PayKit",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -18,19 +18,16 @@
18
18
  "paykit": {
19
19
  "type": "provider"
20
20
  },
21
- "scripts": {
22
- "build": "tsup"
23
- },
24
21
  "keywords": [],
25
22
  "license": "ISC",
26
23
  "author": "Emmanuel Odii",
27
24
  "peerDependencies": {
28
- "@paykit-sdk/core": ">=1.2.0"
25
+ "@paykit-sdk/core": "^1.2.3"
29
26
  },
30
27
  "devDependencies": {
31
- "@paykit-sdk/core": "workspace:*",
32
28
  "tsup": "^8.5.0",
33
- "typescript": "^5.9.2"
29
+ "typescript": "^5.9.2",
30
+ "@paykit-sdk/core": "1.3.1"
34
31
  },
35
32
  "dependencies": {
36
33
  "js-sha512": "^0.9.0"
@@ -44,5 +41,10 @@
44
41
  },
45
42
  "bugs": {
46
43
  "url": "https://github.com/usepaykit/paykit-sdk/issues"
44
+ },
45
+ "scripts": {
46
+ "build": "tsup",
47
+ "test": "vitest run --config ../../vitest.config.ts packages/monnify/src",
48
+ "typecheck": "tsc --noEmit"
47
49
  }
48
- }
50
+ }