@snexon/payoniq-lite 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +190 -2
- package/dist/index.d.ts +60 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +166 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Snexon. All rights reserved.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,191 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @snexon/payoniq-lite
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Official Node.js & TypeScript SDK for the **Payoniq (Payonic)** payment gateway by [Snexon](https://snexon.com).
|
|
4
|
+
|
|
5
|
+
Integrate instant, peer-to-peer mobile payments (bKash, Nagad, Rocket, Upay) and crypto into your Node.js, Next.js, Express, or Remix applications with zero third-party gateway transaction cuts.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- 🚀 **Zero Boilerplate:** Initialize in seconds with typed methods for session creation and verification.
|
|
12
|
+
- 🔒 **Cryptographic Webhook Security:** Timing-safe HMAC-SHA256 signature verification with built-in replay-attack prevention.
|
|
13
|
+
- 📱 **Multi-Wallet Support:** Direct integration with bKash, Nagad, Rocket, Upay & crypto gateways.
|
|
14
|
+
- 📦 **Fully Typed:** 100% written in TypeScript with complete type definitions and JSDoc documentation.
|
|
15
|
+
- ⚡ **Lightweight & Dependency-Free:** Zero bloated dependencies, built directly on native Node.js crypto and modern fetch.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Using npm
|
|
23
|
+
npm install @snexon/payoniq-lite
|
|
24
|
+
|
|
25
|
+
# Using pnpm
|
|
26
|
+
pnpm add @snexon/payoniq-lite
|
|
27
|
+
|
|
28
|
+
# Using yarn
|
|
29
|
+
yarn add @snexon/payoniq-lite
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
### 1. Initialize the Client
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { PayoniqClient } from "@snexon/payoniq-lite";
|
|
40
|
+
|
|
41
|
+
const payoniq = new PayoniqClient({
|
|
42
|
+
apiKey: process.env.PAYONIQ_API_KEY!,
|
|
43
|
+
apiSecret: process.env.PAYONIQ_API_SECRET!,
|
|
44
|
+
baseUrl: process.env.PAYONIQ_BASE_URL || "http://localhost:4000",
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
### 2. Create a Checkout Session
|
|
51
|
+
|
|
52
|
+
#### Next.js App Router (`app/api/checkout/route.ts`)
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { NextResponse } from "next/server";
|
|
56
|
+
import { PayoniqClient } from "@snexon/payoniq-lite";
|
|
57
|
+
|
|
58
|
+
const payoniq = new PayoniqClient({
|
|
59
|
+
apiKey: process.env.PAYONIQ_API_KEY!,
|
|
60
|
+
apiSecret: process.env.PAYONIQ_API_SECRET!,
|
|
61
|
+
baseUrl: process.env.PAYONIQ_BASE_URL || "http://localhost:4000",
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export async function POST(req: Request) {
|
|
65
|
+
const { amount, orderId, customerName, customerPhone } = await req.json();
|
|
66
|
+
|
|
67
|
+
const session = await payoniq.sessions.create({
|
|
68
|
+
orderId: orderId || `ORD-${Date.now()}`,
|
|
69
|
+
amount: amount,
|
|
70
|
+
currency: "BDT",
|
|
71
|
+
method: "ANY", // "BKASH" | "NAGAD" | "ROCKET" | "UPAY" | "USDT" | "ANY"
|
|
72
|
+
customerName,
|
|
73
|
+
customerPhone,
|
|
74
|
+
successUrl: "https://yourstore.com/order/success",
|
|
75
|
+
cancelUrl: "https://yourstore.com/cart",
|
|
76
|
+
expiryMinutes: 15,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return NextResponse.json({
|
|
80
|
+
checkoutUrl: session.checkoutUrl,
|
|
81
|
+
orderHash: session.orderHash,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### Node.js / Express (`server.js`)
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
const express = require("express");
|
|
90
|
+
const { PayoniqClient } = require("@snexon/payoniq-lite");
|
|
91
|
+
|
|
92
|
+
const app = express();
|
|
93
|
+
app.use(express.json());
|
|
94
|
+
|
|
95
|
+
const payoniq = new PayoniqClient({
|
|
96
|
+
apiKey: process.env.PAYONIQ_API_KEY,
|
|
97
|
+
apiSecret: process.env.PAYONIQ_API_SECRET,
|
|
98
|
+
baseUrl: process.env.PAYONIQ_BASE_URL || "http://localhost:4000",
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
app.post("/api/checkout", async (req, res) => {
|
|
102
|
+
try {
|
|
103
|
+
const session = await payoniq.sessions.create({
|
|
104
|
+
orderId: `ORD-${Date.now()}`,
|
|
105
|
+
amount: req.body.amount,
|
|
106
|
+
currency: "BDT",
|
|
107
|
+
});
|
|
108
|
+
res.json({ checkoutUrl: session.checkoutUrl });
|
|
109
|
+
} catch (error) {
|
|
110
|
+
res.status(500).json({ error: error.message });
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
app.listen(3000, () => console.log("Server running on port 3000"));
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
### 3. Verify Webhooks & Process Payments
|
|
120
|
+
|
|
121
|
+
Protect your application against spoofing and replay attacks with timing-safe HMAC-SHA256 signature verification.
|
|
122
|
+
|
|
123
|
+
#### Next.js Route (`app/api/webhooks/payoniq/route.ts`)
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { NextResponse } from "next/server";
|
|
127
|
+
import { PayoniqClient } from "@snexon/payoniq-lite";
|
|
128
|
+
|
|
129
|
+
const payoniq = new PayoniqClient({
|
|
130
|
+
apiKey: process.env.PAYONIQ_API_KEY!,
|
|
131
|
+
apiSecret: process.env.PAYONIQ_API_SECRET!,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
export async function POST(req: Request) {
|
|
135
|
+
const rawBody = await req.text();
|
|
136
|
+
const signature = req.headers.get("x-payoniq-signature") || "";
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
// Validates signature and verifies timestamp (rejects if older than 300s)
|
|
140
|
+
const event = payoniq.webhooks.constructEvent(rawBody, signature);
|
|
141
|
+
|
|
142
|
+
if (event.event === "payment.settled") {
|
|
143
|
+
const { orderId, amount, trxId, method } = event.data;
|
|
144
|
+
console.log(`✅ Payment settled for ${orderId}: ${amount} BDT via ${method} (TrxID: ${trxId})`);
|
|
145
|
+
|
|
146
|
+
// Update order status in your database
|
|
147
|
+
// await markOrderAsPaid(orderId, trxId);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return NextResponse.json({ received: true });
|
|
151
|
+
} catch (err: any) {
|
|
152
|
+
return NextResponse.json(
|
|
153
|
+
{ error: "Webhook verification failed", message: err.message },
|
|
154
|
+
{ status: 400 }
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## API Reference
|
|
163
|
+
|
|
164
|
+
### `PayoniqClient(options)`
|
|
165
|
+
- `apiKey` *(string, required)*: Merchant API key from dashboard.
|
|
166
|
+
- `apiSecret` *(string, optional)*: Required for cryptographic webhook verification.
|
|
167
|
+
- `baseUrl` *(string, optional)*: Payoniq daemon/server endpoint (defaults to `http://localhost:4000`).
|
|
168
|
+
|
|
169
|
+
### `payoniq.sessions.create(options)`
|
|
170
|
+
Creates a non-custodial checkout session.
|
|
171
|
+
- `amount` *(number, required)*: Payment amount.
|
|
172
|
+
- `orderId` *(string, required)*: Unique merchant order reference.
|
|
173
|
+
- `currency` *(string)*: Currency code (default: `"BDT"`).
|
|
174
|
+
- `method` *(string)*: `"ANY"` | `"BKASH"` | `"NAGAD"` | `"ROCKET"` | `"UPAY"` | `"USDT"`.
|
|
175
|
+
- `customerName` *(string, optional)*: Customer full name.
|
|
176
|
+
- `customerPhone` *(string, optional)*: Customer phone number.
|
|
177
|
+
- `successUrl` *(string, optional)*: Redirect URL upon successful payment.
|
|
178
|
+
- `cancelUrl` *(string, optional)*: Redirect URL if cancelled.
|
|
179
|
+
- `expiryMinutes` *(number, optional)*: Session lifetime (default: 15).
|
|
180
|
+
|
|
181
|
+
### `payoniq.sessions.get(orderHash)`
|
|
182
|
+
Retrieves an existing payment session and live reconciliation state.
|
|
183
|
+
|
|
184
|
+
### `payoniq.webhooks.constructEvent(rawBody, signatureHeader)`
|
|
185
|
+
Validates the HMAC-SHA256 signature and returns typed event data. Throws `PayoniqError` if invalid or expired.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT © [Snexon](https://snexon.com)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export interface PayoniqClientOptions {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
apiSecret?: string;
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface CreateSessionOptions {
|
|
7
|
+
amount: number;
|
|
8
|
+
currency?: "BDT" | "USD" | "INR" | string;
|
|
9
|
+
orderId: string;
|
|
10
|
+
method?: string;
|
|
11
|
+
customerName?: string;
|
|
12
|
+
customerEmail?: string;
|
|
13
|
+
customerPhone?: string;
|
|
14
|
+
successUrl?: string;
|
|
15
|
+
cancelUrl?: string;
|
|
16
|
+
metadata?: Record<string, any>;
|
|
17
|
+
expiryMinutes?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface PaymentSession {
|
|
20
|
+
id: string;
|
|
21
|
+
orderHash: string;
|
|
22
|
+
orderId: string;
|
|
23
|
+
amount: number;
|
|
24
|
+
currency: string;
|
|
25
|
+
status: "PENDING" | "PAID" | "EXPIRED" | "FAILED";
|
|
26
|
+
method?: string;
|
|
27
|
+
checkoutUrl: string;
|
|
28
|
+
expiresAt: string;
|
|
29
|
+
createdAt: string;
|
|
30
|
+
metadata?: Record<string, any>;
|
|
31
|
+
}
|
|
32
|
+
export interface WebhookEvent<T = any> {
|
|
33
|
+
event: "payment.settled" | "payment.failed" | "payment.expired" | string;
|
|
34
|
+
timestamp: number;
|
|
35
|
+
data: T;
|
|
36
|
+
}
|
|
37
|
+
export declare class PayoniqError extends Error {
|
|
38
|
+
code?: string;
|
|
39
|
+
status?: number;
|
|
40
|
+
constructor(message: string, code?: string, status?: number);
|
|
41
|
+
}
|
|
42
|
+
export declare class PayoniqClient {
|
|
43
|
+
readonly apiKey: string;
|
|
44
|
+
readonly apiSecret?: string;
|
|
45
|
+
readonly baseUrl: string;
|
|
46
|
+
readonly sessions: {
|
|
47
|
+
create: (options: CreateSessionOptions) => Promise<PaymentSession>;
|
|
48
|
+
get: (orderHash: string) => Promise<PaymentSession>;
|
|
49
|
+
};
|
|
50
|
+
readonly webhooks: {
|
|
51
|
+
verifySignature: (rawBody: string | Buffer, signatureHeader: string, toleranceSec?: number) => boolean;
|
|
52
|
+
constructEvent: <T = any>(rawBody: string | Buffer, signatureHeader: string) => WebhookEvent<T>;
|
|
53
|
+
};
|
|
54
|
+
constructor(options: PayoniqClientOptions);
|
|
55
|
+
/**
|
|
56
|
+
* Internal request helper
|
|
57
|
+
*/
|
|
58
|
+
private request;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,GAAG;IACnC,KAAK,EAAE,iBAAiB,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,MAAM,CAAC;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,CAAC,CAAC;CACT;AAED,qBAAa,YAAa,SAAQ,KAAK;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;gBAEX,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAM5D;AAED,qBAAa,aAAa;IACxB,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnC,SAAgB,OAAO,EAAE,MAAM,CAAC;IAEhC,SAAgB,QAAQ,EAAE;QACxB,MAAM,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;QACnE,GAAG,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;KACrD,CAAC;IAEF,SAAgB,QAAQ,EAAE;QACxB,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;QACvG,cAAc,EAAE,CAAC,CAAC,GAAG,GAAG,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,eAAe,EAAE,MAAM,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC;KACjG,CAAC;gBAEU,OAAO,EAAE,oBAAoB;IA6IzC;;OAEG;YACW,OAAO;CA0BtB"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PayoniqClient = exports.PayoniqError = void 0;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
class PayoniqError extends Error {
|
|
9
|
+
code;
|
|
10
|
+
status;
|
|
11
|
+
constructor(message, code, status) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "PayoniqError";
|
|
14
|
+
this.code = code;
|
|
15
|
+
this.status = status;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.PayoniqError = PayoniqError;
|
|
19
|
+
class PayoniqClient {
|
|
20
|
+
apiKey;
|
|
21
|
+
apiSecret;
|
|
22
|
+
baseUrl;
|
|
23
|
+
sessions;
|
|
24
|
+
webhooks;
|
|
25
|
+
constructor(options) {
|
|
26
|
+
if (!options.apiKey) {
|
|
27
|
+
throw new PayoniqError("apiKey is required to initialize PayoniqClient", "MISSING_API_KEY");
|
|
28
|
+
}
|
|
29
|
+
this.apiKey = options.apiKey;
|
|
30
|
+
this.apiSecret = options.apiSecret;
|
|
31
|
+
this.baseUrl = (options.baseUrl || "http://localhost:4000").replace(/\/+$/, "");
|
|
32
|
+
this.sessions = {
|
|
33
|
+
create: async (opts) => {
|
|
34
|
+
const res = await this.request("/api/v1/sessions/create", {
|
|
35
|
+
method: "POST",
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
amount: opts.amount,
|
|
38
|
+
currency: opts.currency || "BDT",
|
|
39
|
+
orderId: opts.orderId,
|
|
40
|
+
method: opts.method || "ANY",
|
|
41
|
+
customerName: opts.customerName,
|
|
42
|
+
customerEmail: opts.customerEmail,
|
|
43
|
+
customerPhone: opts.customerPhone,
|
|
44
|
+
successUrl: opts.successUrl,
|
|
45
|
+
cancelUrl: opts.cancelUrl,
|
|
46
|
+
metadata: opts.metadata,
|
|
47
|
+
expiryMinutes: opts.expiryMinutes || 15,
|
|
48
|
+
}),
|
|
49
|
+
});
|
|
50
|
+
if (!res.data) {
|
|
51
|
+
throw new PayoniqError("Session creation returned empty response", "MALFORMED_RESPONSE");
|
|
52
|
+
}
|
|
53
|
+
const sess = res.data;
|
|
54
|
+
const checkoutHost = this.baseUrl.includes(":4000")
|
|
55
|
+
? this.baseUrl.replace(":4000", ":5173")
|
|
56
|
+
: this.baseUrl;
|
|
57
|
+
return {
|
|
58
|
+
id: sess.id,
|
|
59
|
+
orderHash: sess.orderHash,
|
|
60
|
+
orderId: sess.orderId,
|
|
61
|
+
amount: sess.amount,
|
|
62
|
+
currency: sess.currency,
|
|
63
|
+
status: sess.status,
|
|
64
|
+
method: sess.method,
|
|
65
|
+
checkoutUrl: `${checkoutHost}/${sess.orderHash}`,
|
|
66
|
+
expiresAt: sess.expiresAt,
|
|
67
|
+
createdAt: sess.createdAt,
|
|
68
|
+
metadata: sess.metadata,
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
get: async (orderHash) => {
|
|
72
|
+
const res = await this.request(`/api/v1/sessions/${encodeURIComponent(orderHash)}`, {
|
|
73
|
+
method: "GET",
|
|
74
|
+
});
|
|
75
|
+
if (!res.data) {
|
|
76
|
+
throw new PayoniqError("Session not found", "NOT_FOUND", 404);
|
|
77
|
+
}
|
|
78
|
+
const sess = res.data;
|
|
79
|
+
const checkoutHost = this.baseUrl.includes(":4000")
|
|
80
|
+
? this.baseUrl.replace(":4000", ":5173")
|
|
81
|
+
: this.baseUrl;
|
|
82
|
+
return {
|
|
83
|
+
id: sess.id,
|
|
84
|
+
orderHash: sess.orderHash,
|
|
85
|
+
orderId: sess.orderId,
|
|
86
|
+
amount: sess.amount,
|
|
87
|
+
currency: sess.currency,
|
|
88
|
+
status: sess.status,
|
|
89
|
+
method: sess.method,
|
|
90
|
+
checkoutUrl: `${checkoutHost}/${sess.orderHash}`,
|
|
91
|
+
expiresAt: sess.expiresAt,
|
|
92
|
+
createdAt: sess.createdAt,
|
|
93
|
+
metadata: sess.metadata,
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
this.webhooks = {
|
|
98
|
+
verifySignature: (rawBody, signatureHeader, toleranceSec = 300) => {
|
|
99
|
+
if (!this.apiSecret) {
|
|
100
|
+
throw new PayoniqError("apiSecret must be configured to verify webhook signatures", "MISSING_API_SECRET");
|
|
101
|
+
}
|
|
102
|
+
if (!signatureHeader)
|
|
103
|
+
return false;
|
|
104
|
+
// Signature Header format: t=1700000000,v1=abcdef...
|
|
105
|
+
const parts = signatureHeader.split(",").reduce((acc, part) => {
|
|
106
|
+
const [k, v] = part.split("=");
|
|
107
|
+
if (k && v)
|
|
108
|
+
acc[k.trim()] = v.trim();
|
|
109
|
+
return acc;
|
|
110
|
+
}, {});
|
|
111
|
+
const timestampStr = parts["t"];
|
|
112
|
+
const signature = parts["v1"];
|
|
113
|
+
if (!timestampStr || !signature)
|
|
114
|
+
return false;
|
|
115
|
+
const timestamp = parseInt(timestampStr, 10);
|
|
116
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
117
|
+
if (toleranceSec > 0 && Math.abs(nowSec - timestamp) > toleranceSec) {
|
|
118
|
+
return false; // Replay attack detected
|
|
119
|
+
}
|
|
120
|
+
const bodyStr = typeof rawBody === "string" ? rawBody : rawBody.toString("utf-8");
|
|
121
|
+
const payloadToSign = `${timestamp}.${bodyStr}`;
|
|
122
|
+
const expectedSignature = crypto_1.default
|
|
123
|
+
.createHmac("sha256", this.apiSecret)
|
|
124
|
+
.update(payloadToSign)
|
|
125
|
+
.digest("hex");
|
|
126
|
+
try {
|
|
127
|
+
return crypto_1.default.timingSafeEqual(Buffer.from(signature, "hex"), Buffer.from(expectedSignature, "hex"));
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
constructEvent: (rawBody, signatureHeader) => {
|
|
134
|
+
const isValid = this.webhooks.verifySignature(rawBody, signatureHeader);
|
|
135
|
+
if (!isValid) {
|
|
136
|
+
throw new PayoniqError("Invalid webhook signature or expired timestamp", "INVALID_SIGNATURE", 400);
|
|
137
|
+
}
|
|
138
|
+
const bodyStr = typeof rawBody === "string" ? rawBody : rawBody.toString("utf-8");
|
|
139
|
+
return JSON.parse(bodyStr);
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Internal request helper
|
|
145
|
+
*/
|
|
146
|
+
async request(path, init) {
|
|
147
|
+
const url = `${this.baseUrl}${path}`;
|
|
148
|
+
const headers = {
|
|
149
|
+
"Content-Type": "application/json",
|
|
150
|
+
"x-api-key": this.apiKey,
|
|
151
|
+
...(this.apiSecret ? { "x-api-secret": this.apiSecret } : {}),
|
|
152
|
+
...init?.headers,
|
|
153
|
+
};
|
|
154
|
+
const res = await fetch(url, {
|
|
155
|
+
...init,
|
|
156
|
+
headers,
|
|
157
|
+
});
|
|
158
|
+
const json = await res.json().catch(() => null);
|
|
159
|
+
if (!res.ok) {
|
|
160
|
+
throw new PayoniqError(json?.message || `Request failed with status ${res.status}`, json?.error || "REQUEST_FAILED", res.status);
|
|
161
|
+
}
|
|
162
|
+
return json;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
exports.PayoniqClient = PayoniqClient;
|
|
166
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;AAAA,oDAA4B;AA0C5B,MAAa,YAAa,SAAQ,KAAK;IAC9B,IAAI,CAAU;IACd,MAAM,CAAU;IAEvB,YAAY,OAAe,EAAE,IAAa,EAAE,MAAe;QACzD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAVD,oCAUC;AAED,MAAa,aAAa;IACR,MAAM,CAAS;IACf,SAAS,CAAU;IACnB,OAAO,CAAS;IAEhB,QAAQ,CAGtB;IAEc,QAAQ,CAGtB;IAEF,YAAY,OAA6B;QACvC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,MAAM,IAAI,YAAY,CAAC,gDAAgD,EAAE,iBAAiB,CAAC,CAAC;QAC9F,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACnC,IAAI,CAAC,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,uBAAuB,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAEhF,IAAI,CAAC,QAAQ,GAAG;YACd,MAAM,EAAE,KAAK,EAAE,IAA0B,EAA2B,EAAE;gBACpE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,yBAAyB,EAAE;oBACxD,MAAM,EAAE,MAAM;oBACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;wBACnB,MAAM,EAAE,IAAI,CAAC,MAAM;wBACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,KAAK;wBAChC,OAAO,EAAE,IAAI,CAAC,OAAO;wBACrB,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,KAAK;wBAC5B,YAAY,EAAE,IAAI,CAAC,YAAY;wBAC/B,aAAa,EAAE,IAAI,CAAC,aAAa;wBACjC,aAAa,EAAE,IAAI,CAAC,aAAa;wBACjC,UAAU,EAAE,IAAI,CAAC,UAAU;wBAC3B,SAAS,EAAE,IAAI,CAAC,SAAS;wBACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;wBACvB,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,EAAE;qBACxC,CAAC;iBACH,CAAC,CAAC;gBAEH,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;oBACd,MAAM,IAAI,YAAY,CAAC,0CAA0C,EAAE,oBAAoB,CAAC,CAAC;gBAC3F,CAAC;gBAED,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;gBACtB,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;oBACjD,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC;oBACxC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;gBAEjB,OAAO;oBACL,EAAE,EAAE,IAAI,CAAC,EAAE;oBACX,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,MAAM,EAAE,IAAI,CAAC,MAAM;oBACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;oBACvB,MAAM,EAAE,IAAI,CAAC,MAAM;oBACnB,MAAM,EAAE,IAAI,CAAC,MAAM;oBACnB,WAAW,EAAE,GAAG,YAAY,IAAI,IAAI,CAAC,SAAS,EAAE;oBAChD,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;iBACxB,CAAC;YACJ,CAAC;YAED,GAAG,EAAE,KAAK,EAAE,SAAiB,EAA2B,EAAE;gBACxD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,oBAAoB,kBAAkB,CAAC,SAAS,CAAC,EAAE,EAAE;oBAClF,MAAM,EAAE,KAAK;iBACd,CAAC,CAAC;gBAEH,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;oBACd,MAAM,IAAI,YAAY,CAAC,mBAAmB,EAAE,WAAW,EAAE,GAAG,CAAC,CAAC;gBAChE,CAAC;gBAED,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;gBACtB,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;oBACjD,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC;oBACxC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;gBAEjB,OAAO;oBACL,EAAE,EAAE,IAAI,CAAC,EAAE;oBACX,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,MAAM,EAAE,IAAI,CAAC,MAAM;oBACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;oBACvB,MAAM,EAAE,IAAI,CAAC,MAAM;oBACnB,MAAM,EAAE,IAAI,CAAC,MAAM;oBACnB,WAAW,EAAE,GAAG,YAAY,IAAI,IAAI,CAAC,SAAS,EAAE;oBAChD,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;iBACxB,CAAC;YACJ,CAAC;SACF,CAAC;QAEF,IAAI,CAAC,QAAQ,GAAG;YACd,eAAe,EAAE,CACf,OAAwB,EACxB,eAAuB,EACvB,eAAuB,GAAG,EACjB,EAAE;gBACX,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;oBACpB,MAAM,IAAI,YAAY,CAAC,2DAA2D,EAAE,oBAAoB,CAAC,CAAC;gBAC5G,CAAC;gBACD,IAAI,CAAC,eAAe;oBAAE,OAAO,KAAK,CAAC;gBAEnC,qDAAqD;gBACrD,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAyB,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;oBACpF,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;oBAC/B,IAAI,CAAC,IAAI,CAAC;wBAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;oBACrC,OAAO,GAAG,CAAC;gBACb,CAAC,EAAE,EAAE,CAAC,CAAC;gBAEP,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;gBAChC,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;gBAE9B,IAAI,CAAC,YAAY,IAAI,CAAC,SAAS;oBAAE,OAAO,KAAK,CAAC;gBAE9C,MAAM,SAAS,GAAG,QAAQ,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;gBAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;gBAE7C,IAAI,YAAY,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,YAAY,EAAE,CAAC;oBACpE,OAAO,KAAK,CAAC,CAAC,yBAAyB;gBACzC,CAAC;gBAED,MAAM,OAAO,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;gBAClF,MAAM,aAAa,GAAG,GAAG,SAAS,IAAI,OAAO,EAAE,CAAC;gBAEhD,MAAM,iBAAiB,GAAG,gBAAM;qBAC7B,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC;qBACpC,MAAM,CAAC,aAAa,CAAC;qBACrB,MAAM,CAAC,KAAK,CAAC,CAAC;gBAEjB,IAAI,CAAC;oBACH,OAAO,gBAAM,CAAC,eAAe,CAC3B,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,EAC7B,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE,KAAK,CAAC,CACtC,CAAC;gBACJ,CAAC;gBAAC,MAAM,CAAC;oBACP,OAAO,KAAK,CAAC;gBACf,CAAC;YACH,CAAC;YAED,cAAc,EAAE,CAAU,OAAwB,EAAE,eAAuB,EAAmB,EAAE;gBAC9F,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;gBACxE,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,MAAM,IAAI,YAAY,CAAC,gDAAgD,EAAE,mBAAmB,EAAE,GAAG,CAAC,CAAC;gBACrG,CAAC;gBAED,MAAM,OAAO,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;gBAClF,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAoB,CAAC;YAChD,CAAC;SACF,CAAC;IACJ,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,IAAkB;QACpD,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,CAAC;QACrC,MAAM,OAAO,GAA2B;YACtC,cAAc,EAAE,kBAAkB;YAClC,WAAW,EAAE,IAAI,CAAC,MAAM;YACxB,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,GAAI,IAAI,EAAE,OAA8C;SACzD,CAAC;QAEF,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,GAAG,IAAI;YACP,OAAO;SACR,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QAEhD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,YAAY,CACpB,IAAI,EAAE,OAAO,IAAI,8BAA8B,GAAG,CAAC,MAAM,EAAE,EAC3D,IAAI,EAAE,KAAK,IAAI,gBAAgB,EAC/B,GAAG,CAAC,MAAM,CACX,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAzLD,sCAyLC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snexon/payoniq-lite",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Official TypeScript & Node.js SDK for Payoniq Gateway by Snexon",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/snexon/payoniq"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://snexon.com",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/snexon/payoniq/issues"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"payoniq",
|
|
29
|
+
"payonic",
|
|
30
|
+
"snexon",
|
|
31
|
+
"payments",
|
|
32
|
+
"bkash",
|
|
33
|
+
"nagad",
|
|
34
|
+
"rocket",
|
|
35
|
+
"upay",
|
|
36
|
+
"crypto",
|
|
37
|
+
"gateway",
|
|
38
|
+
"sdk"
|
|
39
|
+
],
|
|
40
|
+
"author": "Snexon",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"typescript": "^5.6.3"
|
|
44
|
+
}
|
|
45
|
+
}
|