@paykit-sdk/monnify 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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
@@ -503,11 +504,10 @@ var MonnifyProvider = class extends core.AbstractPayKitProvider {
503
504
  provider: this.providerName
504
505
  });
505
506
  }
506
- const computedHash = jsSha512.sha512.hmac(
507
- webhookSecret,
508
- JSON.stringify(body)
509
- );
510
- if (computedHash !== receivedHash)
507
+ const computedHash = jsSha512.sha512.hmac(webhookSecret, body);
508
+ const computedBuf = Buffer.from(computedHash, "hex");
509
+ const receivedBuf = Buffer.from(receivedHash, "hex");
510
+ if (computedBuf.length !== receivedBuf.length || !crypto$1.timingSafeEqual(computedBuf, receivedBuf))
511
511
  throw new core.WebhookError("Invalid Monnify signature", {
512
512
  provider: this.providerName
513
513
  });
@@ -527,35 +527,31 @@ var MonnifyProvider = class extends core.AbstractPayKitProvider {
527
527
  );
528
528
  }
529
529
  const { eventType, eventData } = parsedBody;
530
+ const created = Math.floor(Date.now() / 1e3);
531
+ const results = [];
532
+ results.push({
533
+ id: `monnify:${eventType}:${crypto.randomUUID()}`,
534
+ type: `monnify.${eventType}`,
535
+ created,
536
+ data: eventData,
537
+ is_raw: true
538
+ });
530
539
  const eventMapper = monnifyToPaykitEventMap[eventType];
531
- if (!eventMapper) {
532
- throw new core.WebhookError("Unknown Monnify event type", {
533
- provider: this.providerName
540
+ const standardType = typeof eventMapper === "function" ? eventMapper(eventData) : eventMapper;
541
+ if (standardType) {
542
+ results.push({
543
+ id: `paykit:${eventType}:${crypto.randomUUID()}`,
544
+ type: standardType,
545
+ created,
546
+ data: eventData
547
+ // todo: add mapper for event data
534
548
  });
549
+ } else if (this.opts.debug) {
550
+ console.info(
551
+ `[Monnify] No standard mapping for event: ${eventType}. Available as raw event.`
552
+ );
535
553
  }
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
- }
554
+ return results;
559
555
  };
560
556
  };
561
557
 
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
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';
2
+ import { timingSafeEqual } from 'crypto';
2
3
  import { sha512 } from 'js-sha512';
3
4
 
4
5
  // src/index.ts
@@ -501,11 +502,10 @@ var MonnifyProvider = class extends AbstractPayKitProvider {
501
502
  provider: this.providerName
502
503
  });
503
504
  }
504
- const computedHash = sha512.hmac(
505
- webhookSecret,
506
- JSON.stringify(body)
507
- );
508
- if (computedHash !== receivedHash)
505
+ const computedHash = sha512.hmac(webhookSecret, body);
506
+ const computedBuf = Buffer.from(computedHash, "hex");
507
+ const receivedBuf = Buffer.from(receivedHash, "hex");
508
+ if (computedBuf.length !== receivedBuf.length || !timingSafeEqual(computedBuf, receivedBuf))
509
509
  throw new WebhookError("Invalid Monnify signature", {
510
510
  provider: this.providerName
511
511
  });
@@ -525,35 +525,31 @@ var MonnifyProvider = class extends AbstractPayKitProvider {
525
525
  );
526
526
  }
527
527
  const { eventType, eventData } = parsedBody;
528
+ const created = Math.floor(Date.now() / 1e3);
529
+ const results = [];
530
+ results.push({
531
+ id: `monnify:${eventType}:${crypto.randomUUID()}`,
532
+ type: `monnify.${eventType}`,
533
+ created,
534
+ data: eventData,
535
+ is_raw: true
536
+ });
528
537
  const eventMapper = monnifyToPaykitEventMap[eventType];
529
- if (!eventMapper) {
530
- throw new WebhookError("Unknown Monnify event type", {
531
- provider: this.providerName
538
+ const standardType = typeof eventMapper === "function" ? eventMapper(eventData) : eventMapper;
539
+ if (standardType) {
540
+ results.push({
541
+ id: `paykit:${eventType}:${crypto.randomUUID()}`,
542
+ type: standardType,
543
+ created,
544
+ data: eventData
545
+ // todo: add mapper for event data
532
546
  });
547
+ } else if (this.opts.debug) {
548
+ console.info(
549
+ `[Monnify] No standard mapping for event: ${eventType}. Available as raw event.`
550
+ );
533
551
  }
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
- }
552
+ return results;
557
553
  };
558
554
  };
559
555
 
@@ -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
@@ -503,11 +504,10 @@ var MonnifyProvider = class extends core.AbstractPayKitProvider {
503
504
  provider: this.providerName
504
505
  });
505
506
  }
506
- const computedHash = jsSha512.sha512.hmac(
507
- webhookSecret,
508
- JSON.stringify(body)
509
- );
510
- if (computedHash !== receivedHash)
507
+ const computedHash = jsSha512.sha512.hmac(webhookSecret, body);
508
+ const computedBuf = Buffer.from(computedHash, "hex");
509
+ const receivedBuf = Buffer.from(receivedHash, "hex");
510
+ if (computedBuf.length !== receivedBuf.length || !crypto$1.timingSafeEqual(computedBuf, receivedBuf))
511
511
  throw new core.WebhookError("Invalid Monnify signature", {
512
512
  provider: this.providerName
513
513
  });
@@ -527,35 +527,31 @@ var MonnifyProvider = class extends core.AbstractPayKitProvider {
527
527
  );
528
528
  }
529
529
  const { eventType, eventData } = parsedBody;
530
+ const created = Math.floor(Date.now() / 1e3);
531
+ const results = [];
532
+ results.push({
533
+ id: `monnify:${eventType}:${crypto.randomUUID()}`,
534
+ type: `monnify.${eventType}`,
535
+ created,
536
+ data: eventData,
537
+ is_raw: true
538
+ });
530
539
  const eventMapper = monnifyToPaykitEventMap[eventType];
531
- if (!eventMapper) {
532
- throw new core.WebhookError("Unknown Monnify event type", {
533
- provider: this.providerName
540
+ const standardType = typeof eventMapper === "function" ? eventMapper(eventData) : eventMapper;
541
+ if (standardType) {
542
+ results.push({
543
+ id: `paykit:${eventType}:${crypto.randomUUID()}`,
544
+ type: standardType,
545
+ created,
546
+ data: eventData
547
+ // todo: add mapper for event data
534
548
  });
549
+ } else if (this.opts.debug) {
550
+ console.info(
551
+ `[Monnify] No standard mapping for event: ${eventType}. Available as raw event.`
552
+ );
535
553
  }
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
- }
554
+ return results;
559
555
  };
560
556
  };
561
557
 
@@ -1,4 +1,5 @@
1
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';
2
+ import { timingSafeEqual } from 'crypto';
2
3
  import { sha512 } from 'js-sha512';
3
4
 
4
5
  // src/monnify-provider.ts
@@ -501,11 +502,10 @@ var MonnifyProvider = class extends AbstractPayKitProvider {
501
502
  provider: this.providerName
502
503
  });
503
504
  }
504
- const computedHash = sha512.hmac(
505
- webhookSecret,
506
- JSON.stringify(body)
507
- );
508
- if (computedHash !== receivedHash)
505
+ const computedHash = sha512.hmac(webhookSecret, body);
506
+ const computedBuf = Buffer.from(computedHash, "hex");
507
+ const receivedBuf = Buffer.from(receivedHash, "hex");
508
+ if (computedBuf.length !== receivedBuf.length || !timingSafeEqual(computedBuf, receivedBuf))
509
509
  throw new WebhookError("Invalid Monnify signature", {
510
510
  provider: this.providerName
511
511
  });
@@ -525,35 +525,31 @@ var MonnifyProvider = class extends AbstractPayKitProvider {
525
525
  );
526
526
  }
527
527
  const { eventType, eventData } = parsedBody;
528
+ const created = Math.floor(Date.now() / 1e3);
529
+ const results = [];
530
+ results.push({
531
+ id: `monnify:${eventType}:${crypto.randomUUID()}`,
532
+ type: `monnify.${eventType}`,
533
+ created,
534
+ data: eventData,
535
+ is_raw: true
536
+ });
528
537
  const eventMapper = monnifyToPaykitEventMap[eventType];
529
- if (!eventMapper) {
530
- throw new WebhookError("Unknown Monnify event type", {
531
- provider: this.providerName
538
+ const standardType = typeof eventMapper === "function" ? eventMapper(eventData) : eventMapper;
539
+ if (standardType) {
540
+ results.push({
541
+ id: `paykit:${eventType}:${crypto.randomUUID()}`,
542
+ type: standardType,
543
+ created,
544
+ data: eventData
545
+ // todo: add mapper for event data
532
546
  });
547
+ } else if (this.opts.debug) {
548
+ console.info(
549
+ `[Monnify] No standard mapping for event: ${eventType}. Available as raw event.`
550
+ );
533
551
  }
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
- }
552
+ return results;
557
553
  };
558
554
  };
559
555
 
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.0",
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.0"
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
+ }