gardeshpay-sdk 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GardeshPay / PayGate365
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,212 @@
1
- # Temporary Holding Version
1
+ # GardeshPay Node SDK (`gardeshpay-sdk`)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Official **Node.js SDK** for the **GardeshPay Merchant API**.
4
+
5
+ It wraps the payment APIs merchants call from their backend: create → redirect buyer → verify before fulfill, with **API key + HMAC-SHA256** request signing (and webhook signature helpers).
6
+
7
+ ```bash
8
+ npm install gardeshpay-sdk
9
+ ```
10
+
11
+ > The unscoped npm name `gardeshpay` is already taken by an unrelated 2020 package (`1.0.2`). This official SDK publishes as **`gardeshpay-sdk`**. Import class name is still `GardeshPay`.
12
+
13
+ > **Server-side only.** Never put `apiKey` / `hmacSecret` in browser code.
14
+
15
+ ---
16
+
17
+ ## What this package does
18
+
19
+ | You call | SDK does |
20
+ |----------|----------|
21
+ | `createPayment(...)` | `POST /v1/payments` + HMAC headers + idempotency |
22
+ | `verifyPayment(id)` | `POST /v1/payments/{id}/verify` (fulfillment gate) |
23
+ | `getPayment(id)` | `GET /v1/payments/{id}` |
24
+ | `createRefund(...)` | `POST /v1/payments/{id}/refunds` |
25
+ | `verifyWebhook(...)` | Check inbound webhook HMAC (notification only) |
26
+
27
+ Same HTTP API as cURL / OpenAPI — just less boilerplate.
28
+
29
+ ---
30
+
31
+ ## Quick start
32
+
33
+ ```ts
34
+ import { GardeshPay } from "gardeshpay-sdk";
35
+
36
+ const gp = new GardeshPay({
37
+ apiKey: process.env.GARDESHPAY_API_KEY!,
38
+ hmacSecret: process.env.GARDESHPAY_HMAC_SECRET!,
39
+ baseUrl: process.env.GARDESHPAY_BASE_URL!, // e.g. https://api.example.com/v1
40
+ });
41
+
42
+ // 1) Create payment
43
+ const payment = await gp.createPayment({
44
+ orderRef: "ORD-1001",
45
+ amountRial: 250_000,
46
+ returnUrl: "https://shop.example.com/return",
47
+ description: "Order 1001",
48
+ });
49
+
50
+ // 2) Redirect the buyer
51
+ const redirectTo = payment.startPayUrl ?? payment.checkoutUrl;
52
+ // res.redirect(redirectTo)
53
+
54
+ // 3) On returnUrl — ALWAYS verify before fulfill
55
+ const verified = await gp.verifyPayment(payment.paymentId);
56
+ if (verified.status === "SUCCEEDED") {
57
+ // ship / unlock order
58
+ }
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Auth (HMAC)
64
+
65
+ Every request is signed as:
66
+
67
+ ```text
68
+ METHOD\nPATH?QUERY\nTIMESTAMP\nNONCE\nSHA256_HEX(body)
69
+ X-Signature = Base64(HMAC-SHA256(hmacSecret, canonical))
70
+ ```
71
+
72
+ Headers sent automatically:
73
+
74
+ - `X-Api-Key`
75
+ - `X-Timestamp` (unix seconds)
76
+ - `X-Nonce`
77
+ - `X-Signature`
78
+ - `Idempotency-Key` on create / verify / refund
79
+
80
+ Get keys from the merchant portal after your gateway is **ACTIVE** (API keys page).
81
+
82
+ ---
83
+
84
+ ## Webhooks (optional notifications)
85
+
86
+ Webhooks are **not** proof of payment. Always `verifyPayment` (or GET status) before fulfill.
87
+
88
+ ```ts
89
+ import { verifyWebhook } from "gardeshpay-sdk";
90
+ import express from "express";
91
+
92
+ const app = express();
93
+ app.post(
94
+ "/webhooks/gardeshpay",
95
+ express.raw({ type: "application/json" }),
96
+ (req, res) => {
97
+ const result = verifyWebhook({
98
+ secret: process.env.GARDESHPAY_WEBHOOK_SECRET!,
99
+ rawBody: req.body as Buffer,
100
+ headers: req.headers,
101
+ });
102
+ if (!result.ok) return res.status(401).send(result.reason);
103
+
104
+ // enqueue background verifyPayment(paymentId) — then fulfill
105
+ res.sendStatus(200);
106
+ },
107
+ );
108
+ ```
109
+
110
+ Configure the webhook URL + secret on the gateway settings page in the portal.
111
+
112
+ `verifyWebhook` validates the signed delivery headers for you. You still must verify the payment; delivery success ≠ money settled.
113
+
114
+ ---
115
+
116
+ ## API reference
117
+
118
+ ### `new GardeshPay(options)`
119
+
120
+ | Option | Required | Description |
121
+ |--------|----------|-------------|
122
+ | `apiKey` | yes | Portal API key (`pk_…`) |
123
+ | `hmacSecret` | yes | HMAC secret for that key |
124
+ | `baseUrl` | yes | Base URL **including** `/v1` |
125
+ | `timeoutMs` | no | Default `30000` |
126
+ | `fetch` | no | Custom fetch (tests) |
127
+
128
+ ### `createPayment(input)`
129
+
130
+ | Field | Required | Notes |
131
+ |-------|----------|-------|
132
+ | `orderRef` | yes | Your order id |
133
+ | `amountRial` | yes | Integer Rials |
134
+ | `returnUrl` | yes | Buyer browser return (not proof) |
135
+ | `callbackUrl` | no | Optional webhook override if allowed |
136
+ | `description` | no | |
137
+ | `expiresInSec` | no | |
138
+ | `metadata` | no | string map |
139
+ | `idempotencyKey` | no | Auto UUID if omitted |
140
+
141
+ Returns `paymentId`, `authority`, `checkoutUrl`, `startPayUrl` (when present), `status`, …
142
+
143
+ ### `verifyPayment(paymentId)`
144
+
145
+ Fulfill only when `status === "SUCCEEDED"`. Safe to retry (idempotent).
146
+
147
+ ### `getPayment(paymentId)` / `createRefund({ paymentId, amountRial })`
148
+
149
+ Status inquiry and refunds.
150
+
151
+ ### Errors
152
+
153
+ Failed HTTP calls throw `GardeshPayError` with `status`, optional `code` / `correlationId`, and problem-details body when present.
154
+
155
+ ---
156
+
157
+ ## Payment flow
158
+
159
+ ```text
160
+ Merchant backend GardeshPay API Buyer
161
+ |-- createPayment ---------->| |
162
+ |<-- paymentId + URLs -------| |
163
+ |--------- redirect buyer ------------------------------>|
164
+ | |<-- pay at bank/checkout ---|
165
+ |<-- returnUrl?paymentId ----| |
166
+ |-- verifyPayment ---------->| |
167
+ |<-- SUCCEEDED / … ----------| |
168
+ |-- fulfill order -----------| |
169
+ ```
170
+
171
+ Optional: webhook `payment.succeeded` may arrive in parallel — still verify.
172
+
173
+ ---
174
+
175
+ ## TypeScript
176
+
177
+ Published with full `.d.ts`. Works with ESM and CommonJS:
178
+
179
+ ```ts
180
+ import { GardeshPay } from "gardeshpay-sdk";
181
+ // or
182
+ const { GardeshPay } = require("gardeshpay-sdk");
183
+ ```
184
+
185
+ Node **18+** (native `fetch` + `node:crypto`).
186
+
187
+ ---
188
+
189
+ ## Development
190
+
191
+ ```bash
192
+ npm install
193
+ npm test
194
+ npm run build
195
+ ```
196
+
197
+ ---
198
+
199
+ ## Security checklist
200
+
201
+ - [ ] Store secrets in env / secret manager — never in git or frontend
202
+ - [ ] Use HTTPS `returnUrl` / webhook URL
203
+ - [ ] Treat `returnUrl` query as a hint only
204
+ - [ ] Treat webhook as a hint only
205
+ - [ ] Fulfill only after `verifyPayment` → `SUCCEEDED`
206
+ - [ ] Use unique `orderRef` / idempotency keys
207
+
208
+ ---
209
+
210
+ ## License
211
+
212
+ MIT © GardeshPay
package/dist/index.cjs ADDED
@@ -0,0 +1,307 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ GardeshPay: () => GardeshPay,
24
+ GardeshPayError: () => GardeshPayError,
25
+ hmacSha256Base64: () => hmacSha256Base64,
26
+ hmacSha256Hex: () => hmacSha256Hex,
27
+ merchantCanonical: () => merchantCanonical,
28
+ newIdempotencyKey: () => newIdempotencyKey,
29
+ newNonce: () => newNonce,
30
+ sha256Hex: () => sha256Hex,
31
+ signMerchantRequest: () => signMerchantRequest,
32
+ unixSeconds: () => unixSeconds,
33
+ verifyWebhook: () => verifyWebhook
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/errors.ts
38
+ var GardeshPayError = class extends Error {
39
+ status;
40
+ code;
41
+ correlationId;
42
+ problem;
43
+ bodyText;
44
+ constructor(message, opts) {
45
+ super(message);
46
+ this.name = "GardeshPayError";
47
+ this.status = opts.status;
48
+ this.code = opts.code;
49
+ this.correlationId = opts.correlationId;
50
+ this.problem = opts.problem;
51
+ this.bodyText = opts.bodyText;
52
+ }
53
+ };
54
+
55
+ // src/hmac.ts
56
+ var import_node_crypto = require("crypto");
57
+ function sha256Hex(body) {
58
+ return (0, import_node_crypto.createHash)("sha256").update(body ?? "").digest("hex");
59
+ }
60
+ function merchantCanonical(method, pathWithQuery, timestamp, nonce, bodySha256Hex) {
61
+ return [method, pathWithQuery, timestamp, nonce, bodySha256Hex].join("\n");
62
+ }
63
+ function hmacSha256Base64(secret, canonical) {
64
+ return (0, import_node_crypto.createHmac)("sha256", secret).update(canonical, "utf8").digest("base64");
65
+ }
66
+ function hmacSha256Hex(secret, message) {
67
+ return (0, import_node_crypto.createHmac)("sha256", secret).update(message, "utf8").digest("hex");
68
+ }
69
+ function signMerchantRequest(opts) {
70
+ const bodyHash = sha256Hex(opts.body);
71
+ const canonical = merchantCanonical(
72
+ opts.method.toUpperCase(),
73
+ opts.pathWithQuery,
74
+ opts.timestamp,
75
+ opts.nonce,
76
+ bodyHash
77
+ );
78
+ return hmacSha256Base64(opts.secret, canonical);
79
+ }
80
+ function newNonce() {
81
+ return `n-${(0, import_node_crypto.randomUUID)()}`;
82
+ }
83
+ function newIdempotencyKey() {
84
+ return (0, import_node_crypto.randomUUID)();
85
+ }
86
+ function unixSeconds(now = Date.now()) {
87
+ return String(Math.floor(now / 1e3));
88
+ }
89
+ function safeEqualHex(a, b) {
90
+ try {
91
+ const left = Buffer.from(a, "utf8");
92
+ const right = Buffer.from(b, "utf8");
93
+ if (left.length !== right.length) return false;
94
+ return (0, import_node_crypto.timingSafeEqual)(left, right);
95
+ } catch {
96
+ return false;
97
+ }
98
+ }
99
+
100
+ // src/client.ts
101
+ var GardeshPay = class {
102
+ apiKey;
103
+ hmacSecret;
104
+ baseUrl;
105
+ fetchImpl;
106
+ timeoutMs;
107
+ constructor(options) {
108
+ if (!options?.apiKey?.trim()) throw new Error("apiKey is required");
109
+ if (!options?.hmacSecret?.trim()) throw new Error("hmacSecret is required");
110
+ if (!options?.baseUrl?.trim()) throw new Error("baseUrl is required (include /v1)");
111
+ this.apiKey = options.apiKey.trim();
112
+ this.hmacSecret = options.hmacSecret.trim();
113
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
114
+ this.fetchImpl = options.fetch ?? fetch;
115
+ this.timeoutMs = options.timeoutMs ?? 3e4;
116
+ }
117
+ /** Create a payment and get checkout / start-pay URLs. */
118
+ async createPayment(input) {
119
+ const data = await this.request({
120
+ method: "POST",
121
+ path: "/payments",
122
+ body: {
123
+ orderRef: input.orderRef,
124
+ amountRial: input.amountRial,
125
+ returnUrl: input.returnUrl,
126
+ callbackUrl: input.callbackUrl,
127
+ description: input.description,
128
+ expiresInSec: input.expiresInSec,
129
+ metadata: input.metadata,
130
+ gatewayId: input.gatewayId
131
+ },
132
+ idempotencyKey: input.idempotencyKey ?? newIdempotencyKey(),
133
+ correlationId: input.correlationId
134
+ });
135
+ return data;
136
+ }
137
+ /** GET payment status (masked). */
138
+ async getPayment(paymentId) {
139
+ return this.request({
140
+ method: "GET",
141
+ path: `/payments/${encodeURIComponent(paymentId)}`
142
+ });
143
+ }
144
+ /**
145
+ * Authoritative verify before fulfillment (idempotent).
146
+ * Fulfill only when `status === "SUCCEEDED"`.
147
+ */
148
+ async verifyPayment(paymentId, opts) {
149
+ return this.request({
150
+ method: "POST",
151
+ path: `/payments/${encodeURIComponent(paymentId)}/verify`,
152
+ // Empty body — matches typical verify clients; HMAC hashes empty string.
153
+ idempotencyKey: opts?.idempotencyKey ?? newIdempotencyKey()
154
+ });
155
+ }
156
+ /** Create a refund against a succeeded payment. */
157
+ async createRefund(input) {
158
+ return this.request({
159
+ method: "POST",
160
+ path: `/payments/${encodeURIComponent(input.paymentId)}/refunds`,
161
+ body: {
162
+ amountRial: input.amountRial,
163
+ reason: input.reason
164
+ },
165
+ idempotencyKey: input.idempotencyKey ?? newIdempotencyKey()
166
+ });
167
+ }
168
+ async request(opts) {
169
+ const pathWithQuery = opts.path.startsWith("/") ? opts.path : `/${opts.path}`;
170
+ const url = `${this.baseUrl}${pathWithQuery}`;
171
+ const bodyText = opts.method === "GET" || opts.body === void 0 ? "" : JSON.stringify(opts.body ?? {});
172
+ const timestamp = unixSeconds();
173
+ const nonce = newNonce();
174
+ const signPath = `/v1${pathWithQuery}`;
175
+ const signature = signMerchantRequest({
176
+ secret: this.hmacSecret,
177
+ method: opts.method,
178
+ pathWithQuery: signPath,
179
+ timestamp,
180
+ nonce,
181
+ body: bodyText
182
+ });
183
+ const headers = {
184
+ Accept: "application/json",
185
+ "X-Api-Key": this.apiKey,
186
+ "X-Timestamp": timestamp,
187
+ "X-Nonce": nonce,
188
+ "X-Signature": signature
189
+ };
190
+ if (bodyText) headers["Content-Type"] = "application/json";
191
+ if (opts.idempotencyKey) headers["Idempotency-Key"] = opts.idempotencyKey;
192
+ if (opts.correlationId) headers["X-Correlation-Id"] = opts.correlationId;
193
+ const controller = new AbortController();
194
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
195
+ let res;
196
+ try {
197
+ res = await this.fetchImpl(url, {
198
+ method: opts.method,
199
+ headers,
200
+ body: bodyText || void 0,
201
+ signal: controller.signal
202
+ });
203
+ } catch (err) {
204
+ clearTimeout(timer);
205
+ throw new GardeshPayError(
206
+ err instanceof Error ? err.message : "network error",
207
+ { status: 0 }
208
+ );
209
+ } finally {
210
+ clearTimeout(timer);
211
+ }
212
+ const text = await res.text();
213
+ let json = void 0;
214
+ if (text) {
215
+ try {
216
+ json = JSON.parse(text);
217
+ } catch {
218
+ json = void 0;
219
+ }
220
+ }
221
+ if (!res.ok) {
222
+ const problem = json && typeof json === "object" ? json : void 0;
223
+ throw new GardeshPayError(problem?.detail || problem?.title || `HTTP ${res.status}`, {
224
+ status: res.status,
225
+ code: problem?.code,
226
+ correlationId: problem?.correlationId,
227
+ problem,
228
+ bodyText: text
229
+ });
230
+ }
231
+ if (json && typeof json === "object" && "data" in json) {
232
+ return json.data;
233
+ }
234
+ return json;
235
+ }
236
+ };
237
+
238
+ // src/webhooks.ts
239
+ function header(headers, name) {
240
+ const direct = headers[name] ?? headers[name.toLowerCase()];
241
+ if (Array.isArray(direct)) return direct[0];
242
+ if (typeof direct === "string") return direct;
243
+ const key = Object.keys(headers).find((k) => k.toLowerCase() === name.toLowerCase());
244
+ if (!key) return void 0;
245
+ const v = headers[key];
246
+ return Array.isArray(v) ? v[0] : v;
247
+ }
248
+ function verifyWebhook(opts) {
249
+ const skew = opts.skewSeconds ?? 300;
250
+ const now = opts.nowSeconds ?? Math.floor(Date.now() / 1e3);
251
+ const raw = typeof opts.rawBody === "string" ? opts.rawBody : opts.rawBody.toString("utf8");
252
+ const paygateSig = header(opts.headers, "X-PayGate-Signature");
253
+ const paygateTs = header(opts.headers, "X-PayGate-Timestamp");
254
+ if (paygateSig && paygateTs) {
255
+ const ts = Number(paygateTs);
256
+ if (!Number.isFinite(ts) || Math.abs(now - ts) > skew) {
257
+ return { ok: false, reason: "timestamp skew" };
258
+ }
259
+ const expectedHex = hmacSha256Hex(opts.secret, `${paygateTs}.${raw}`);
260
+ const presented = paygateSig.replace(/^sha256=/i, "").trim();
261
+ if (!safeEqualHex(expectedHex, presented)) {
262
+ return { ok: false, reason: "invalid signature" };
263
+ }
264
+ return {
265
+ ok: true,
266
+ timestamp: paygateTs,
267
+ eventType: header(opts.headers, "X-PayGate-Event")
268
+ };
269
+ }
270
+ const pg365 = header(opts.headers, "X-PG365-Signature");
271
+ if (pg365) {
272
+ const parts = Object.fromEntries(
273
+ pg365.split(",").map((p) => {
274
+ const [k, ...rest] = p.trim().split("=");
275
+ return [k, rest.join("=")];
276
+ })
277
+ );
278
+ const t = parts.t;
279
+ const v1 = parts.v1;
280
+ if (!t || !v1) return { ok: false, reason: "malformed X-PG365-Signature" };
281
+ const ts = Number(t);
282
+ if (!Number.isFinite(ts) || Math.abs(now - ts) > skew) {
283
+ return { ok: false, reason: "timestamp skew" };
284
+ }
285
+ const expected = hmacSha256Hex(opts.secret, `${t}.${raw}`);
286
+ if (!safeEqualHex(expected, v1)) {
287
+ return { ok: false, reason: "invalid signature" };
288
+ }
289
+ return { ok: true, timestamp: t };
290
+ }
291
+ return { ok: false, reason: "missing webhook signature headers" };
292
+ }
293
+ // Annotate the CommonJS export names for ESM import in node:
294
+ 0 && (module.exports = {
295
+ GardeshPay,
296
+ GardeshPayError,
297
+ hmacSha256Base64,
298
+ hmacSha256Hex,
299
+ merchantCanonical,
300
+ newIdempotencyKey,
301
+ newNonce,
302
+ sha256Hex,
303
+ signMerchantRequest,
304
+ unixSeconds,
305
+ verifyWebhook
306
+ });
307
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/hmac.ts","../src/client.ts","../src/webhooks.ts"],"sourcesContent":["export { GardeshPay } from \"./client.js\";\nexport { GardeshPayError } from \"./errors.js\";\nexport {\n hmacSha256Base64,\n hmacSha256Hex,\n merchantCanonical,\n newIdempotencyKey,\n newNonce,\n sha256Hex,\n signMerchantRequest,\n unixSeconds,\n} from \"./hmac.js\";\nexport { verifyWebhook } from \"./webhooks.js\";\nexport type {\n CreatePaymentInput,\n CreatePaymentResult,\n CreateRefundInput,\n GardeshPayOptions,\n PaymentResult,\n PaymentStatus,\n ProblemDetails,\n RefundResult,\n VerifyPaymentResult,\n} from \"./types.js\";\n","import type { ProblemDetails } from \"./types.js\";\n\nexport class GardeshPayError extends Error {\n readonly status: number;\n readonly code?: string;\n readonly correlationId?: string;\n readonly problem?: ProblemDetails;\n readonly bodyText?: string;\n\n constructor(\n message: string,\n opts: {\n status: number;\n code?: string;\n correlationId?: string;\n problem?: ProblemDetails;\n bodyText?: string;\n },\n ) {\n super(message);\n this.name = \"GardeshPayError\";\n this.status = opts.status;\n this.code = opts.code;\n this.correlationId = opts.correlationId;\n this.problem = opts.problem;\n this.bodyText = opts.bodyText;\n }\n}\n","import { createHash, createHmac, randomUUID, timingSafeEqual } from \"node:crypto\";\n\n/** SHA-256 hex of UTF-8 body (empty string when body is empty). */\nexport function sha256Hex(body: string | Buffer | Uint8Array): string {\n return createHash(\"sha256\").update(body ?? \"\").digest(\"hex\");\n}\n\n/**\n * Merchant API canonical string (must match Java `HmacSigner.merchantCanonical`):\n * `METHOD\\nPATH?QUERY\\nTIMESTAMP\\nNONCE\\nSHA256_HEX(body)`\n */\nexport function merchantCanonical(\n method: string,\n pathWithQuery: string,\n timestamp: string,\n nonce: string,\n bodySha256Hex: string,\n): string {\n return [method, pathWithQuery, timestamp, nonce, bodySha256Hex].join(\"\\n\");\n}\n\n/** Base64(HMAC-SHA256(secret, canonical)) — value for `X-Signature`. */\nexport function hmacSha256Base64(secret: string, canonical: string): string {\n return createHmac(\"sha256\", secret).update(canonical, \"utf8\").digest(\"base64\");\n}\n\n/** Hex(HMAC-SHA256(secret, message)) — used for live webhook signatures. */\nexport function hmacSha256Hex(secret: string, message: string): string {\n return createHmac(\"sha256\", secret).update(message, \"utf8\").digest(\"hex\");\n}\n\nexport function signMerchantRequest(opts: {\n secret: string;\n method: string;\n pathWithQuery: string;\n timestamp: string;\n nonce: string;\n body: string;\n}): string {\n const bodyHash = sha256Hex(opts.body);\n const canonical = merchantCanonical(\n opts.method.toUpperCase(),\n opts.pathWithQuery,\n opts.timestamp,\n opts.nonce,\n bodyHash,\n );\n return hmacSha256Base64(opts.secret, canonical);\n}\n\nexport function newNonce(): string {\n return `n-${randomUUID()}`;\n}\n\nexport function newIdempotencyKey(): string {\n return randomUUID();\n}\n\nexport function unixSeconds(now = Date.now()): string {\n return String(Math.floor(now / 1000));\n}\n\nexport function safeEqualHex(a: string, b: string): boolean {\n try {\n const left = Buffer.from(a, \"utf8\");\n const right = Buffer.from(b, \"utf8\");\n if (left.length !== right.length) return false;\n return timingSafeEqual(left, right);\n } catch {\n return false;\n }\n}\n","import { GardeshPayError } from \"./errors.js\";\nimport { newIdempotencyKey, newNonce, signMerchantRequest, unixSeconds } from \"./hmac.js\";\nimport type {\n CreatePaymentInput,\n CreatePaymentResult,\n CreateRefundInput,\n GardeshPayOptions,\n PaymentResult,\n ProblemDetails,\n RefundResult,\n VerifyPaymentResult,\n} from \"./types.js\";\n\ntype RequestOpts = {\n method: \"GET\" | \"POST\";\n path: string;\n body?: unknown;\n idempotencyKey?: string;\n correlationId?: string;\n};\n\nexport class GardeshPay {\n readonly apiKey: string;\n readonly hmacSecret: string;\n readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n\n constructor(options: GardeshPayOptions) {\n if (!options?.apiKey?.trim()) throw new Error(\"apiKey is required\");\n if (!options?.hmacSecret?.trim()) throw new Error(\"hmacSecret is required\");\n if (!options?.baseUrl?.trim()) throw new Error(\"baseUrl is required (include /v1)\");\n this.apiKey = options.apiKey.trim();\n this.hmacSecret = options.hmacSecret.trim();\n this.baseUrl = options.baseUrl.replace(/\\/+$/, \"\");\n this.fetchImpl = options.fetch ?? fetch;\n this.timeoutMs = options.timeoutMs ?? 30_000;\n }\n\n /** Create a payment and get checkout / start-pay URLs. */\n async createPayment(input: CreatePaymentInput): Promise<CreatePaymentResult> {\n const data = await this.request<CreatePaymentResult>({\n method: \"POST\",\n path: \"/payments\",\n body: {\n orderRef: input.orderRef,\n amountRial: input.amountRial,\n returnUrl: input.returnUrl,\n callbackUrl: input.callbackUrl,\n description: input.description,\n expiresInSec: input.expiresInSec,\n metadata: input.metadata,\n gatewayId: input.gatewayId,\n },\n idempotencyKey: input.idempotencyKey ?? newIdempotencyKey(),\n correlationId: input.correlationId,\n });\n return data;\n }\n\n /** GET payment status (masked). */\n async getPayment(paymentId: string): Promise<PaymentResult> {\n return this.request<PaymentResult>({\n method: \"GET\",\n path: `/payments/${encodeURIComponent(paymentId)}`,\n });\n }\n\n /**\n * Authoritative verify before fulfillment (idempotent).\n * Fulfill only when `status === \"SUCCEEDED\"`.\n */\n async verifyPayment(\n paymentId: string,\n opts?: { idempotencyKey?: string },\n ): Promise<VerifyPaymentResult> {\n return this.request<VerifyPaymentResult>({\n method: \"POST\",\n path: `/payments/${encodeURIComponent(paymentId)}/verify`,\n // Empty body — matches typical verify clients; HMAC hashes empty string.\n idempotencyKey: opts?.idempotencyKey ?? newIdempotencyKey(),\n });\n }\n\n /** Create a refund against a succeeded payment. */\n async createRefund(input: CreateRefundInput): Promise<RefundResult> {\n return this.request<RefundResult>({\n method: \"POST\",\n path: `/payments/${encodeURIComponent(input.paymentId)}/refunds`,\n body: {\n amountRial: input.amountRial,\n reason: input.reason,\n },\n idempotencyKey: input.idempotencyKey ?? newIdempotencyKey(),\n });\n }\n\n private async request<T>(opts: RequestOpts): Promise<T> {\n const pathWithQuery = opts.path.startsWith(\"/\") ? opts.path : `/${opts.path}`;\n const url = `${this.baseUrl}${pathWithQuery}`;\n const bodyText =\n opts.method === \"GET\" || opts.body === undefined ? \"\" : JSON.stringify(opts.body ?? {});\n const timestamp = unixSeconds();\n const nonce = newNonce();\n // Sign against the public path as exposed under /v1 (matches Java filter).\n const signPath = `/v1${pathWithQuery}`;\n const signature = signMerchantRequest({\n secret: this.hmacSecret,\n method: opts.method,\n pathWithQuery: signPath,\n timestamp,\n nonce,\n body: bodyText,\n });\n\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"X-Api-Key\": this.apiKey,\n \"X-Timestamp\": timestamp,\n \"X-Nonce\": nonce,\n \"X-Signature\": signature,\n };\n if (bodyText) headers[\"Content-Type\"] = \"application/json\";\n if (opts.idempotencyKey) headers[\"Idempotency-Key\"] = opts.idempotencyKey;\n if (opts.correlationId) headers[\"X-Correlation-Id\"] = opts.correlationId;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n let res: Response;\n try {\n res = await this.fetchImpl(url, {\n method: opts.method,\n headers,\n body: bodyText || undefined,\n signal: controller.signal,\n });\n } catch (err) {\n clearTimeout(timer);\n throw new GardeshPayError(\n err instanceof Error ? err.message : \"network error\",\n { status: 0 },\n );\n } finally {\n clearTimeout(timer);\n }\n\n const text = await res.text();\n let json: unknown = undefined;\n if (text) {\n try {\n json = JSON.parse(text);\n } catch {\n json = undefined;\n }\n }\n\n if (!res.ok) {\n const problem = (json && typeof json === \"object\" ? json : undefined) as\n | ProblemDetails\n | undefined;\n throw new GardeshPayError(problem?.detail || problem?.title || `HTTP ${res.status}`, {\n status: res.status,\n code: problem?.code,\n correlationId: problem?.correlationId,\n problem,\n bodyText: text,\n });\n }\n\n if (json && typeof json === \"object\" && \"data\" in json) {\n return (json as { data: T }).data;\n }\n return json as T;\n }\n}\n","import { hmacSha256Hex, safeEqualHex } from \"./hmac.js\";\n\nexport type WebhookHeaders = Record<string, string | string[] | undefined>;\n\nfunction header(headers: WebhookHeaders, name: string): string | undefined {\n const direct = headers[name] ?? headers[name.toLowerCase()];\n if (Array.isArray(direct)) return direct[0];\n if (typeof direct === \"string\") return direct;\n // case-insensitive fallback\n const key = Object.keys(headers).find((k) => k.toLowerCase() === name.toLowerCase());\n if (!key) return undefined;\n const v = headers[key];\n return Array.isArray(v) ? v[0] : v;\n}\n\n/**\n * Verify an inbound GardeshPay / PayGate365 webhook.\n *\n * Live notification-service signs with:\n * - `X-PayGate-Signature: sha256=<hex(HMAC-SHA256(secret, `${t}.${rawBody}`))>`\n * - `X-PayGate-Timestamp: <unix seconds>`\n *\n * Docs also mention `X-PG365-Signature: t=…,v1=…` — both are accepted.\n *\n * Webhooks are **notifications only**. Always call `payments.verify` (or GET)\n * before fulfilling an order.\n */\nexport function verifyWebhook(opts: {\n secret: string;\n rawBody: string | Buffer;\n headers: WebhookHeaders;\n /** Max clock skew in seconds (default 300). */\n skewSeconds?: number;\n nowSeconds?: number;\n}): { ok: true; timestamp: string; eventType?: string } | { ok: false; reason: string } {\n const skew = opts.skewSeconds ?? 300;\n const now = opts.nowSeconds ?? Math.floor(Date.now() / 1000);\n const raw = typeof opts.rawBody === \"string\" ? opts.rawBody : opts.rawBody.toString(\"utf8\");\n\n const paygateSig = header(opts.headers, \"X-PayGate-Signature\");\n const paygateTs = header(opts.headers, \"X-PayGate-Timestamp\");\n if (paygateSig && paygateTs) {\n const ts = Number(paygateTs);\n if (!Number.isFinite(ts) || Math.abs(now - ts) > skew) {\n return { ok: false, reason: \"timestamp skew\" };\n }\n const expectedHex = hmacSha256Hex(opts.secret, `${paygateTs}.${raw}`);\n const presented = paygateSig.replace(/^sha256=/i, \"\").trim();\n if (!safeEqualHex(expectedHex, presented)) {\n return { ok: false, reason: \"invalid signature\" };\n }\n return {\n ok: true,\n timestamp: paygateTs,\n eventType: header(opts.headers, \"X-PayGate-Event\"),\n };\n }\n\n const pg365 = header(opts.headers, \"X-PG365-Signature\");\n if (pg365) {\n // Format: t=<unix>,v1=<hex>\n const parts = Object.fromEntries(\n pg365.split(\",\").map((p) => {\n const [k, ...rest] = p.trim().split(\"=\");\n return [k, rest.join(\"=\")];\n }),\n ) as Record<string, string>;\n const t = parts.t;\n const v1 = parts.v1;\n if (!t || !v1) return { ok: false, reason: \"malformed X-PG365-Signature\" };\n const ts = Number(t);\n if (!Number.isFinite(ts) || Math.abs(now - ts) > skew) {\n return { ok: false, reason: \"timestamp skew\" };\n }\n const expected = hmacSha256Hex(opts.secret, `${t}.${raw}`);\n if (!safeEqualHex(expected, v1)) {\n return { ok: false, reason: \"invalid signature\" };\n }\n return { ok: true, timestamp: t };\n }\n\n return { ok: false, reason: \"missing webhook signature headers\" };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,177 @@
1
+ type PaymentStatus = "CREATED" | "AWAITING_PAYMENT" | "SUCCEEDED" | "FAILED" | "EXPIRED" | "REVIEW" | "REFUNDED" | "PARTIALLY_REFUNDED" | "CANCELLED";
2
+ type GardeshPayOptions = {
3
+ /** API key from the merchant portal (per gateway). */
4
+ apiKey: string;
5
+ /** HMAC secret shown with the API key — used to sign every request. */
6
+ hmacSecret: string;
7
+ /**
8
+ * Public Merchant API base including `/v1`, e.g.
9
+ * `https://api.example.com/v1`
10
+ */
11
+ baseUrl: string;
12
+ /** Optional fetch override (tests / custom agents). */
13
+ fetch?: typeof fetch;
14
+ /** Default timeout in ms (default 30_000). */
15
+ timeoutMs?: number;
16
+ };
17
+ type CreatePaymentInput = {
18
+ orderRef: string;
19
+ /** Integer amount in Iranian Rials. */
20
+ amountRial: number;
21
+ /** Buyer return URL after bank/checkout. Not proof of payment. */
22
+ returnUrl: string;
23
+ callbackUrl?: string;
24
+ description?: string;
25
+ expiresInSec?: number;
26
+ metadata?: Record<string, string>;
27
+ gatewayId?: string;
28
+ /** Optional idempotency key; auto-generated UUID when omitted. */
29
+ idempotencyKey?: string;
30
+ correlationId?: string;
31
+ };
32
+ type CreatePaymentResult = {
33
+ paymentId: string;
34
+ authority: string;
35
+ checkoutUrl: string;
36
+ /** Prefer this for redirect when present. */
37
+ startPayUrl?: string;
38
+ redirectUrl?: string;
39
+ status: PaymentStatus;
40
+ amountRial: number;
41
+ organizationId?: string;
42
+ expiresAt?: string;
43
+ };
44
+ type PaymentResult = {
45
+ paymentId: string;
46
+ authority?: string;
47
+ status: PaymentStatus;
48
+ amountRial: number;
49
+ orderRef?: string;
50
+ checkoutUrl?: string;
51
+ startPayUrl?: string;
52
+ expiresAt?: string;
53
+ verifiedAt?: string | null;
54
+ reviewReason?: string | null;
55
+ [key: string]: unknown;
56
+ };
57
+ type VerifyPaymentResult = {
58
+ paymentId: string;
59
+ status: PaymentStatus;
60
+ amountRial: number;
61
+ verifiedAt?: string | null;
62
+ reviewReason?: string | null;
63
+ };
64
+ type CreateRefundInput = {
65
+ paymentId: string;
66
+ amountRial: number;
67
+ reason?: string;
68
+ idempotencyKey?: string;
69
+ };
70
+ type RefundResult = {
71
+ refundId?: string;
72
+ paymentId?: string;
73
+ status?: string;
74
+ amountRial?: number;
75
+ [key: string]: unknown;
76
+ };
77
+ type ProblemDetails = {
78
+ type?: string;
79
+ title?: string;
80
+ status?: number;
81
+ detail?: string;
82
+ code?: string;
83
+ correlationId?: string;
84
+ [key: string]: unknown;
85
+ };
86
+
87
+ declare class GardeshPay {
88
+ readonly apiKey: string;
89
+ readonly hmacSecret: string;
90
+ readonly baseUrl: string;
91
+ private readonly fetchImpl;
92
+ private readonly timeoutMs;
93
+ constructor(options: GardeshPayOptions);
94
+ /** Create a payment and get checkout / start-pay URLs. */
95
+ createPayment(input: CreatePaymentInput): Promise<CreatePaymentResult>;
96
+ /** GET payment status (masked). */
97
+ getPayment(paymentId: string): Promise<PaymentResult>;
98
+ /**
99
+ * Authoritative verify before fulfillment (idempotent).
100
+ * Fulfill only when `status === "SUCCEEDED"`.
101
+ */
102
+ verifyPayment(paymentId: string, opts?: {
103
+ idempotencyKey?: string;
104
+ }): Promise<VerifyPaymentResult>;
105
+ /** Create a refund against a succeeded payment. */
106
+ createRefund(input: CreateRefundInput): Promise<RefundResult>;
107
+ private request;
108
+ }
109
+
110
+ declare class GardeshPayError extends Error {
111
+ readonly status: number;
112
+ readonly code?: string;
113
+ readonly correlationId?: string;
114
+ readonly problem?: ProblemDetails;
115
+ readonly bodyText?: string;
116
+ constructor(message: string, opts: {
117
+ status: number;
118
+ code?: string;
119
+ correlationId?: string;
120
+ problem?: ProblemDetails;
121
+ bodyText?: string;
122
+ });
123
+ }
124
+
125
+ /** SHA-256 hex of UTF-8 body (empty string when body is empty). */
126
+ declare function sha256Hex(body: string | Buffer | Uint8Array): string;
127
+ /**
128
+ * Merchant API canonical string (must match Java `HmacSigner.merchantCanonical`):
129
+ * `METHOD\nPATH?QUERY\nTIMESTAMP\nNONCE\nSHA256_HEX(body)`
130
+ */
131
+ declare function merchantCanonical(method: string, pathWithQuery: string, timestamp: string, nonce: string, bodySha256Hex: string): string;
132
+ /** Base64(HMAC-SHA256(secret, canonical)) — value for `X-Signature`. */
133
+ declare function hmacSha256Base64(secret: string, canonical: string): string;
134
+ /** Hex(HMAC-SHA256(secret, message)) — used for live webhook signatures. */
135
+ declare function hmacSha256Hex(secret: string, message: string): string;
136
+ declare function signMerchantRequest(opts: {
137
+ secret: string;
138
+ method: string;
139
+ pathWithQuery: string;
140
+ timestamp: string;
141
+ nonce: string;
142
+ body: string;
143
+ }): string;
144
+ declare function newNonce(): string;
145
+ declare function newIdempotencyKey(): string;
146
+ declare function unixSeconds(now?: number): string;
147
+
148
+ type WebhookHeaders = Record<string, string | string[] | undefined>;
149
+ /**
150
+ * Verify an inbound GardeshPay / PayGate365 webhook.
151
+ *
152
+ * Live notification-service signs with:
153
+ * - `X-PayGate-Signature: sha256=<hex(HMAC-SHA256(secret, `${t}.${rawBody}`))>`
154
+ * - `X-PayGate-Timestamp: <unix seconds>`
155
+ *
156
+ * Docs also mention `X-PG365-Signature: t=…,v1=…` — both are accepted.
157
+ *
158
+ * Webhooks are **notifications only**. Always call `payments.verify` (or GET)
159
+ * before fulfilling an order.
160
+ */
161
+ declare function verifyWebhook(opts: {
162
+ secret: string;
163
+ rawBody: string | Buffer;
164
+ headers: WebhookHeaders;
165
+ /** Max clock skew in seconds (default 300). */
166
+ skewSeconds?: number;
167
+ nowSeconds?: number;
168
+ }): {
169
+ ok: true;
170
+ timestamp: string;
171
+ eventType?: string;
172
+ } | {
173
+ ok: false;
174
+ reason: string;
175
+ };
176
+
177
+ export { type CreatePaymentInput, type CreatePaymentResult, type CreateRefundInput, GardeshPay, GardeshPayError, type GardeshPayOptions, type PaymentResult, type PaymentStatus, type ProblemDetails, type RefundResult, type VerifyPaymentResult, hmacSha256Base64, hmacSha256Hex, merchantCanonical, newIdempotencyKey, newNonce, sha256Hex, signMerchantRequest, unixSeconds, verifyWebhook };
@@ -0,0 +1,177 @@
1
+ type PaymentStatus = "CREATED" | "AWAITING_PAYMENT" | "SUCCEEDED" | "FAILED" | "EXPIRED" | "REVIEW" | "REFUNDED" | "PARTIALLY_REFUNDED" | "CANCELLED";
2
+ type GardeshPayOptions = {
3
+ /** API key from the merchant portal (per gateway). */
4
+ apiKey: string;
5
+ /** HMAC secret shown with the API key — used to sign every request. */
6
+ hmacSecret: string;
7
+ /**
8
+ * Public Merchant API base including `/v1`, e.g.
9
+ * `https://api.example.com/v1`
10
+ */
11
+ baseUrl: string;
12
+ /** Optional fetch override (tests / custom agents). */
13
+ fetch?: typeof fetch;
14
+ /** Default timeout in ms (default 30_000). */
15
+ timeoutMs?: number;
16
+ };
17
+ type CreatePaymentInput = {
18
+ orderRef: string;
19
+ /** Integer amount in Iranian Rials. */
20
+ amountRial: number;
21
+ /** Buyer return URL after bank/checkout. Not proof of payment. */
22
+ returnUrl: string;
23
+ callbackUrl?: string;
24
+ description?: string;
25
+ expiresInSec?: number;
26
+ metadata?: Record<string, string>;
27
+ gatewayId?: string;
28
+ /** Optional idempotency key; auto-generated UUID when omitted. */
29
+ idempotencyKey?: string;
30
+ correlationId?: string;
31
+ };
32
+ type CreatePaymentResult = {
33
+ paymentId: string;
34
+ authority: string;
35
+ checkoutUrl: string;
36
+ /** Prefer this for redirect when present. */
37
+ startPayUrl?: string;
38
+ redirectUrl?: string;
39
+ status: PaymentStatus;
40
+ amountRial: number;
41
+ organizationId?: string;
42
+ expiresAt?: string;
43
+ };
44
+ type PaymentResult = {
45
+ paymentId: string;
46
+ authority?: string;
47
+ status: PaymentStatus;
48
+ amountRial: number;
49
+ orderRef?: string;
50
+ checkoutUrl?: string;
51
+ startPayUrl?: string;
52
+ expiresAt?: string;
53
+ verifiedAt?: string | null;
54
+ reviewReason?: string | null;
55
+ [key: string]: unknown;
56
+ };
57
+ type VerifyPaymentResult = {
58
+ paymentId: string;
59
+ status: PaymentStatus;
60
+ amountRial: number;
61
+ verifiedAt?: string | null;
62
+ reviewReason?: string | null;
63
+ };
64
+ type CreateRefundInput = {
65
+ paymentId: string;
66
+ amountRial: number;
67
+ reason?: string;
68
+ idempotencyKey?: string;
69
+ };
70
+ type RefundResult = {
71
+ refundId?: string;
72
+ paymentId?: string;
73
+ status?: string;
74
+ amountRial?: number;
75
+ [key: string]: unknown;
76
+ };
77
+ type ProblemDetails = {
78
+ type?: string;
79
+ title?: string;
80
+ status?: number;
81
+ detail?: string;
82
+ code?: string;
83
+ correlationId?: string;
84
+ [key: string]: unknown;
85
+ };
86
+
87
+ declare class GardeshPay {
88
+ readonly apiKey: string;
89
+ readonly hmacSecret: string;
90
+ readonly baseUrl: string;
91
+ private readonly fetchImpl;
92
+ private readonly timeoutMs;
93
+ constructor(options: GardeshPayOptions);
94
+ /** Create a payment and get checkout / start-pay URLs. */
95
+ createPayment(input: CreatePaymentInput): Promise<CreatePaymentResult>;
96
+ /** GET payment status (masked). */
97
+ getPayment(paymentId: string): Promise<PaymentResult>;
98
+ /**
99
+ * Authoritative verify before fulfillment (idempotent).
100
+ * Fulfill only when `status === "SUCCEEDED"`.
101
+ */
102
+ verifyPayment(paymentId: string, opts?: {
103
+ idempotencyKey?: string;
104
+ }): Promise<VerifyPaymentResult>;
105
+ /** Create a refund against a succeeded payment. */
106
+ createRefund(input: CreateRefundInput): Promise<RefundResult>;
107
+ private request;
108
+ }
109
+
110
+ declare class GardeshPayError extends Error {
111
+ readonly status: number;
112
+ readonly code?: string;
113
+ readonly correlationId?: string;
114
+ readonly problem?: ProblemDetails;
115
+ readonly bodyText?: string;
116
+ constructor(message: string, opts: {
117
+ status: number;
118
+ code?: string;
119
+ correlationId?: string;
120
+ problem?: ProblemDetails;
121
+ bodyText?: string;
122
+ });
123
+ }
124
+
125
+ /** SHA-256 hex of UTF-8 body (empty string when body is empty). */
126
+ declare function sha256Hex(body: string | Buffer | Uint8Array): string;
127
+ /**
128
+ * Merchant API canonical string (must match Java `HmacSigner.merchantCanonical`):
129
+ * `METHOD\nPATH?QUERY\nTIMESTAMP\nNONCE\nSHA256_HEX(body)`
130
+ */
131
+ declare function merchantCanonical(method: string, pathWithQuery: string, timestamp: string, nonce: string, bodySha256Hex: string): string;
132
+ /** Base64(HMAC-SHA256(secret, canonical)) — value for `X-Signature`. */
133
+ declare function hmacSha256Base64(secret: string, canonical: string): string;
134
+ /** Hex(HMAC-SHA256(secret, message)) — used for live webhook signatures. */
135
+ declare function hmacSha256Hex(secret: string, message: string): string;
136
+ declare function signMerchantRequest(opts: {
137
+ secret: string;
138
+ method: string;
139
+ pathWithQuery: string;
140
+ timestamp: string;
141
+ nonce: string;
142
+ body: string;
143
+ }): string;
144
+ declare function newNonce(): string;
145
+ declare function newIdempotencyKey(): string;
146
+ declare function unixSeconds(now?: number): string;
147
+
148
+ type WebhookHeaders = Record<string, string | string[] | undefined>;
149
+ /**
150
+ * Verify an inbound GardeshPay / PayGate365 webhook.
151
+ *
152
+ * Live notification-service signs with:
153
+ * - `X-PayGate-Signature: sha256=<hex(HMAC-SHA256(secret, `${t}.${rawBody}`))>`
154
+ * - `X-PayGate-Timestamp: <unix seconds>`
155
+ *
156
+ * Docs also mention `X-PG365-Signature: t=…,v1=…` — both are accepted.
157
+ *
158
+ * Webhooks are **notifications only**. Always call `payments.verify` (or GET)
159
+ * before fulfilling an order.
160
+ */
161
+ declare function verifyWebhook(opts: {
162
+ secret: string;
163
+ rawBody: string | Buffer;
164
+ headers: WebhookHeaders;
165
+ /** Max clock skew in seconds (default 300). */
166
+ skewSeconds?: number;
167
+ nowSeconds?: number;
168
+ }): {
169
+ ok: true;
170
+ timestamp: string;
171
+ eventType?: string;
172
+ } | {
173
+ ok: false;
174
+ reason: string;
175
+ };
176
+
177
+ export { type CreatePaymentInput, type CreatePaymentResult, type CreateRefundInput, GardeshPay, GardeshPayError, type GardeshPayOptions, type PaymentResult, type PaymentStatus, type ProblemDetails, type RefundResult, type VerifyPaymentResult, hmacSha256Base64, hmacSha256Hex, merchantCanonical, newIdempotencyKey, newNonce, sha256Hex, signMerchantRequest, unixSeconds, verifyWebhook };
package/dist/index.js ADDED
@@ -0,0 +1,270 @@
1
+ // src/errors.ts
2
+ var GardeshPayError = class extends Error {
3
+ status;
4
+ code;
5
+ correlationId;
6
+ problem;
7
+ bodyText;
8
+ constructor(message, opts) {
9
+ super(message);
10
+ this.name = "GardeshPayError";
11
+ this.status = opts.status;
12
+ this.code = opts.code;
13
+ this.correlationId = opts.correlationId;
14
+ this.problem = opts.problem;
15
+ this.bodyText = opts.bodyText;
16
+ }
17
+ };
18
+
19
+ // src/hmac.ts
20
+ import { createHash, createHmac, randomUUID, timingSafeEqual } from "crypto";
21
+ function sha256Hex(body) {
22
+ return createHash("sha256").update(body ?? "").digest("hex");
23
+ }
24
+ function merchantCanonical(method, pathWithQuery, timestamp, nonce, bodySha256Hex) {
25
+ return [method, pathWithQuery, timestamp, nonce, bodySha256Hex].join("\n");
26
+ }
27
+ function hmacSha256Base64(secret, canonical) {
28
+ return createHmac("sha256", secret).update(canonical, "utf8").digest("base64");
29
+ }
30
+ function hmacSha256Hex(secret, message) {
31
+ return createHmac("sha256", secret).update(message, "utf8").digest("hex");
32
+ }
33
+ function signMerchantRequest(opts) {
34
+ const bodyHash = sha256Hex(opts.body);
35
+ const canonical = merchantCanonical(
36
+ opts.method.toUpperCase(),
37
+ opts.pathWithQuery,
38
+ opts.timestamp,
39
+ opts.nonce,
40
+ bodyHash
41
+ );
42
+ return hmacSha256Base64(opts.secret, canonical);
43
+ }
44
+ function newNonce() {
45
+ return `n-${randomUUID()}`;
46
+ }
47
+ function newIdempotencyKey() {
48
+ return randomUUID();
49
+ }
50
+ function unixSeconds(now = Date.now()) {
51
+ return String(Math.floor(now / 1e3));
52
+ }
53
+ function safeEqualHex(a, b) {
54
+ try {
55
+ const left = Buffer.from(a, "utf8");
56
+ const right = Buffer.from(b, "utf8");
57
+ if (left.length !== right.length) return false;
58
+ return timingSafeEqual(left, right);
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+
64
+ // src/client.ts
65
+ var GardeshPay = class {
66
+ apiKey;
67
+ hmacSecret;
68
+ baseUrl;
69
+ fetchImpl;
70
+ timeoutMs;
71
+ constructor(options) {
72
+ if (!options?.apiKey?.trim()) throw new Error("apiKey is required");
73
+ if (!options?.hmacSecret?.trim()) throw new Error("hmacSecret is required");
74
+ if (!options?.baseUrl?.trim()) throw new Error("baseUrl is required (include /v1)");
75
+ this.apiKey = options.apiKey.trim();
76
+ this.hmacSecret = options.hmacSecret.trim();
77
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
78
+ this.fetchImpl = options.fetch ?? fetch;
79
+ this.timeoutMs = options.timeoutMs ?? 3e4;
80
+ }
81
+ /** Create a payment and get checkout / start-pay URLs. */
82
+ async createPayment(input) {
83
+ const data = await this.request({
84
+ method: "POST",
85
+ path: "/payments",
86
+ body: {
87
+ orderRef: input.orderRef,
88
+ amountRial: input.amountRial,
89
+ returnUrl: input.returnUrl,
90
+ callbackUrl: input.callbackUrl,
91
+ description: input.description,
92
+ expiresInSec: input.expiresInSec,
93
+ metadata: input.metadata,
94
+ gatewayId: input.gatewayId
95
+ },
96
+ idempotencyKey: input.idempotencyKey ?? newIdempotencyKey(),
97
+ correlationId: input.correlationId
98
+ });
99
+ return data;
100
+ }
101
+ /** GET payment status (masked). */
102
+ async getPayment(paymentId) {
103
+ return this.request({
104
+ method: "GET",
105
+ path: `/payments/${encodeURIComponent(paymentId)}`
106
+ });
107
+ }
108
+ /**
109
+ * Authoritative verify before fulfillment (idempotent).
110
+ * Fulfill only when `status === "SUCCEEDED"`.
111
+ */
112
+ async verifyPayment(paymentId, opts) {
113
+ return this.request({
114
+ method: "POST",
115
+ path: `/payments/${encodeURIComponent(paymentId)}/verify`,
116
+ // Empty body — matches typical verify clients; HMAC hashes empty string.
117
+ idempotencyKey: opts?.idempotencyKey ?? newIdempotencyKey()
118
+ });
119
+ }
120
+ /** Create a refund against a succeeded payment. */
121
+ async createRefund(input) {
122
+ return this.request({
123
+ method: "POST",
124
+ path: `/payments/${encodeURIComponent(input.paymentId)}/refunds`,
125
+ body: {
126
+ amountRial: input.amountRial,
127
+ reason: input.reason
128
+ },
129
+ idempotencyKey: input.idempotencyKey ?? newIdempotencyKey()
130
+ });
131
+ }
132
+ async request(opts) {
133
+ const pathWithQuery = opts.path.startsWith("/") ? opts.path : `/${opts.path}`;
134
+ const url = `${this.baseUrl}${pathWithQuery}`;
135
+ const bodyText = opts.method === "GET" || opts.body === void 0 ? "" : JSON.stringify(opts.body ?? {});
136
+ const timestamp = unixSeconds();
137
+ const nonce = newNonce();
138
+ const signPath = `/v1${pathWithQuery}`;
139
+ const signature = signMerchantRequest({
140
+ secret: this.hmacSecret,
141
+ method: opts.method,
142
+ pathWithQuery: signPath,
143
+ timestamp,
144
+ nonce,
145
+ body: bodyText
146
+ });
147
+ const headers = {
148
+ Accept: "application/json",
149
+ "X-Api-Key": this.apiKey,
150
+ "X-Timestamp": timestamp,
151
+ "X-Nonce": nonce,
152
+ "X-Signature": signature
153
+ };
154
+ if (bodyText) headers["Content-Type"] = "application/json";
155
+ if (opts.idempotencyKey) headers["Idempotency-Key"] = opts.idempotencyKey;
156
+ if (opts.correlationId) headers["X-Correlation-Id"] = opts.correlationId;
157
+ const controller = new AbortController();
158
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
159
+ let res;
160
+ try {
161
+ res = await this.fetchImpl(url, {
162
+ method: opts.method,
163
+ headers,
164
+ body: bodyText || void 0,
165
+ signal: controller.signal
166
+ });
167
+ } catch (err) {
168
+ clearTimeout(timer);
169
+ throw new GardeshPayError(
170
+ err instanceof Error ? err.message : "network error",
171
+ { status: 0 }
172
+ );
173
+ } finally {
174
+ clearTimeout(timer);
175
+ }
176
+ const text = await res.text();
177
+ let json = void 0;
178
+ if (text) {
179
+ try {
180
+ json = JSON.parse(text);
181
+ } catch {
182
+ json = void 0;
183
+ }
184
+ }
185
+ if (!res.ok) {
186
+ const problem = json && typeof json === "object" ? json : void 0;
187
+ throw new GardeshPayError(problem?.detail || problem?.title || `HTTP ${res.status}`, {
188
+ status: res.status,
189
+ code: problem?.code,
190
+ correlationId: problem?.correlationId,
191
+ problem,
192
+ bodyText: text
193
+ });
194
+ }
195
+ if (json && typeof json === "object" && "data" in json) {
196
+ return json.data;
197
+ }
198
+ return json;
199
+ }
200
+ };
201
+
202
+ // src/webhooks.ts
203
+ function header(headers, name) {
204
+ const direct = headers[name] ?? headers[name.toLowerCase()];
205
+ if (Array.isArray(direct)) return direct[0];
206
+ if (typeof direct === "string") return direct;
207
+ const key = Object.keys(headers).find((k) => k.toLowerCase() === name.toLowerCase());
208
+ if (!key) return void 0;
209
+ const v = headers[key];
210
+ return Array.isArray(v) ? v[0] : v;
211
+ }
212
+ function verifyWebhook(opts) {
213
+ const skew = opts.skewSeconds ?? 300;
214
+ const now = opts.nowSeconds ?? Math.floor(Date.now() / 1e3);
215
+ const raw = typeof opts.rawBody === "string" ? opts.rawBody : opts.rawBody.toString("utf8");
216
+ const paygateSig = header(opts.headers, "X-PayGate-Signature");
217
+ const paygateTs = header(opts.headers, "X-PayGate-Timestamp");
218
+ if (paygateSig && paygateTs) {
219
+ const ts = Number(paygateTs);
220
+ if (!Number.isFinite(ts) || Math.abs(now - ts) > skew) {
221
+ return { ok: false, reason: "timestamp skew" };
222
+ }
223
+ const expectedHex = hmacSha256Hex(opts.secret, `${paygateTs}.${raw}`);
224
+ const presented = paygateSig.replace(/^sha256=/i, "").trim();
225
+ if (!safeEqualHex(expectedHex, presented)) {
226
+ return { ok: false, reason: "invalid signature" };
227
+ }
228
+ return {
229
+ ok: true,
230
+ timestamp: paygateTs,
231
+ eventType: header(opts.headers, "X-PayGate-Event")
232
+ };
233
+ }
234
+ const pg365 = header(opts.headers, "X-PG365-Signature");
235
+ if (pg365) {
236
+ const parts = Object.fromEntries(
237
+ pg365.split(",").map((p) => {
238
+ const [k, ...rest] = p.trim().split("=");
239
+ return [k, rest.join("=")];
240
+ })
241
+ );
242
+ const t = parts.t;
243
+ const v1 = parts.v1;
244
+ if (!t || !v1) return { ok: false, reason: "malformed X-PG365-Signature" };
245
+ const ts = Number(t);
246
+ if (!Number.isFinite(ts) || Math.abs(now - ts) > skew) {
247
+ return { ok: false, reason: "timestamp skew" };
248
+ }
249
+ const expected = hmacSha256Hex(opts.secret, `${t}.${raw}`);
250
+ if (!safeEqualHex(expected, v1)) {
251
+ return { ok: false, reason: "invalid signature" };
252
+ }
253
+ return { ok: true, timestamp: t };
254
+ }
255
+ return { ok: false, reason: "missing webhook signature headers" };
256
+ }
257
+ export {
258
+ GardeshPay,
259
+ GardeshPayError,
260
+ hmacSha256Base64,
261
+ hmacSha256Hex,
262
+ merchantCanonical,
263
+ newIdempotencyKey,
264
+ newNonce,
265
+ sha256Hex,
266
+ signMerchantRequest,
267
+ unixSeconds,
268
+ verifyWebhook
269
+ };
270
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/hmac.ts","../src/client.ts","../src/webhooks.ts"],"sourcesContent":["import type { ProblemDetails } from \"./types.js\";\n\nexport class GardeshPayError extends Error {\n readonly status: number;\n readonly code?: string;\n readonly correlationId?: string;\n readonly problem?: ProblemDetails;\n readonly bodyText?: string;\n\n constructor(\n message: string,\n opts: {\n status: number;\n code?: string;\n correlationId?: string;\n problem?: ProblemDetails;\n bodyText?: string;\n },\n ) {\n super(message);\n this.name = \"GardeshPayError\";\n this.status = opts.status;\n this.code = opts.code;\n this.correlationId = opts.correlationId;\n this.problem = opts.problem;\n this.bodyText = opts.bodyText;\n }\n}\n","import { createHash, createHmac, randomUUID, timingSafeEqual } from \"node:crypto\";\n\n/** SHA-256 hex of UTF-8 body (empty string when body is empty). */\nexport function sha256Hex(body: string | Buffer | Uint8Array): string {\n return createHash(\"sha256\").update(body ?? \"\").digest(\"hex\");\n}\n\n/**\n * Merchant API canonical string (must match Java `HmacSigner.merchantCanonical`):\n * `METHOD\\nPATH?QUERY\\nTIMESTAMP\\nNONCE\\nSHA256_HEX(body)`\n */\nexport function merchantCanonical(\n method: string,\n pathWithQuery: string,\n timestamp: string,\n nonce: string,\n bodySha256Hex: string,\n): string {\n return [method, pathWithQuery, timestamp, nonce, bodySha256Hex].join(\"\\n\");\n}\n\n/** Base64(HMAC-SHA256(secret, canonical)) — value for `X-Signature`. */\nexport function hmacSha256Base64(secret: string, canonical: string): string {\n return createHmac(\"sha256\", secret).update(canonical, \"utf8\").digest(\"base64\");\n}\n\n/** Hex(HMAC-SHA256(secret, message)) — used for live webhook signatures. */\nexport function hmacSha256Hex(secret: string, message: string): string {\n return createHmac(\"sha256\", secret).update(message, \"utf8\").digest(\"hex\");\n}\n\nexport function signMerchantRequest(opts: {\n secret: string;\n method: string;\n pathWithQuery: string;\n timestamp: string;\n nonce: string;\n body: string;\n}): string {\n const bodyHash = sha256Hex(opts.body);\n const canonical = merchantCanonical(\n opts.method.toUpperCase(),\n opts.pathWithQuery,\n opts.timestamp,\n opts.nonce,\n bodyHash,\n );\n return hmacSha256Base64(opts.secret, canonical);\n}\n\nexport function newNonce(): string {\n return `n-${randomUUID()}`;\n}\n\nexport function newIdempotencyKey(): string {\n return randomUUID();\n}\n\nexport function unixSeconds(now = Date.now()): string {\n return String(Math.floor(now / 1000));\n}\n\nexport function safeEqualHex(a: string, b: string): boolean {\n try {\n const left = Buffer.from(a, \"utf8\");\n const right = Buffer.from(b, \"utf8\");\n if (left.length !== right.length) return false;\n return timingSafeEqual(left, right);\n } catch {\n return false;\n }\n}\n","import { GardeshPayError } from \"./errors.js\";\nimport { newIdempotencyKey, newNonce, signMerchantRequest, unixSeconds } from \"./hmac.js\";\nimport type {\n CreatePaymentInput,\n CreatePaymentResult,\n CreateRefundInput,\n GardeshPayOptions,\n PaymentResult,\n ProblemDetails,\n RefundResult,\n VerifyPaymentResult,\n} from \"./types.js\";\n\ntype RequestOpts = {\n method: \"GET\" | \"POST\";\n path: string;\n body?: unknown;\n idempotencyKey?: string;\n correlationId?: string;\n};\n\nexport class GardeshPay {\n readonly apiKey: string;\n readonly hmacSecret: string;\n readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n\n constructor(options: GardeshPayOptions) {\n if (!options?.apiKey?.trim()) throw new Error(\"apiKey is required\");\n if (!options?.hmacSecret?.trim()) throw new Error(\"hmacSecret is required\");\n if (!options?.baseUrl?.trim()) throw new Error(\"baseUrl is required (include /v1)\");\n this.apiKey = options.apiKey.trim();\n this.hmacSecret = options.hmacSecret.trim();\n this.baseUrl = options.baseUrl.replace(/\\/+$/, \"\");\n this.fetchImpl = options.fetch ?? fetch;\n this.timeoutMs = options.timeoutMs ?? 30_000;\n }\n\n /** Create a payment and get checkout / start-pay URLs. */\n async createPayment(input: CreatePaymentInput): Promise<CreatePaymentResult> {\n const data = await this.request<CreatePaymentResult>({\n method: \"POST\",\n path: \"/payments\",\n body: {\n orderRef: input.orderRef,\n amountRial: input.amountRial,\n returnUrl: input.returnUrl,\n callbackUrl: input.callbackUrl,\n description: input.description,\n expiresInSec: input.expiresInSec,\n metadata: input.metadata,\n gatewayId: input.gatewayId,\n },\n idempotencyKey: input.idempotencyKey ?? newIdempotencyKey(),\n correlationId: input.correlationId,\n });\n return data;\n }\n\n /** GET payment status (masked). */\n async getPayment(paymentId: string): Promise<PaymentResult> {\n return this.request<PaymentResult>({\n method: \"GET\",\n path: `/payments/${encodeURIComponent(paymentId)}`,\n });\n }\n\n /**\n * Authoritative verify before fulfillment (idempotent).\n * Fulfill only when `status === \"SUCCEEDED\"`.\n */\n async verifyPayment(\n paymentId: string,\n opts?: { idempotencyKey?: string },\n ): Promise<VerifyPaymentResult> {\n return this.request<VerifyPaymentResult>({\n method: \"POST\",\n path: `/payments/${encodeURIComponent(paymentId)}/verify`,\n // Empty body — matches typical verify clients; HMAC hashes empty string.\n idempotencyKey: opts?.idempotencyKey ?? newIdempotencyKey(),\n });\n }\n\n /** Create a refund against a succeeded payment. */\n async createRefund(input: CreateRefundInput): Promise<RefundResult> {\n return this.request<RefundResult>({\n method: \"POST\",\n path: `/payments/${encodeURIComponent(input.paymentId)}/refunds`,\n body: {\n amountRial: input.amountRial,\n reason: input.reason,\n },\n idempotencyKey: input.idempotencyKey ?? newIdempotencyKey(),\n });\n }\n\n private async request<T>(opts: RequestOpts): Promise<T> {\n const pathWithQuery = opts.path.startsWith(\"/\") ? opts.path : `/${opts.path}`;\n const url = `${this.baseUrl}${pathWithQuery}`;\n const bodyText =\n opts.method === \"GET\" || opts.body === undefined ? \"\" : JSON.stringify(opts.body ?? {});\n const timestamp = unixSeconds();\n const nonce = newNonce();\n // Sign against the public path as exposed under /v1 (matches Java filter).\n const signPath = `/v1${pathWithQuery}`;\n const signature = signMerchantRequest({\n secret: this.hmacSecret,\n method: opts.method,\n pathWithQuery: signPath,\n timestamp,\n nonce,\n body: bodyText,\n });\n\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"X-Api-Key\": this.apiKey,\n \"X-Timestamp\": timestamp,\n \"X-Nonce\": nonce,\n \"X-Signature\": signature,\n };\n if (bodyText) headers[\"Content-Type\"] = \"application/json\";\n if (opts.idempotencyKey) headers[\"Idempotency-Key\"] = opts.idempotencyKey;\n if (opts.correlationId) headers[\"X-Correlation-Id\"] = opts.correlationId;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n let res: Response;\n try {\n res = await this.fetchImpl(url, {\n method: opts.method,\n headers,\n body: bodyText || undefined,\n signal: controller.signal,\n });\n } catch (err) {\n clearTimeout(timer);\n throw new GardeshPayError(\n err instanceof Error ? err.message : \"network error\",\n { status: 0 },\n );\n } finally {\n clearTimeout(timer);\n }\n\n const text = await res.text();\n let json: unknown = undefined;\n if (text) {\n try {\n json = JSON.parse(text);\n } catch {\n json = undefined;\n }\n }\n\n if (!res.ok) {\n const problem = (json && typeof json === \"object\" ? json : undefined) as\n | ProblemDetails\n | undefined;\n throw new GardeshPayError(problem?.detail || problem?.title || `HTTP ${res.status}`, {\n status: res.status,\n code: problem?.code,\n correlationId: problem?.correlationId,\n problem,\n bodyText: text,\n });\n }\n\n if (json && typeof json === \"object\" && \"data\" in json) {\n return (json as { data: T }).data;\n }\n return json as T;\n }\n}\n","import { hmacSha256Hex, safeEqualHex } from \"./hmac.js\";\n\nexport type WebhookHeaders = Record<string, string | string[] | undefined>;\n\nfunction header(headers: WebhookHeaders, name: string): string | undefined {\n const direct = headers[name] ?? headers[name.toLowerCase()];\n if (Array.isArray(direct)) return direct[0];\n if (typeof direct === \"string\") return direct;\n // case-insensitive fallback\n const key = Object.keys(headers).find((k) => k.toLowerCase() === name.toLowerCase());\n if (!key) return undefined;\n const v = headers[key];\n return Array.isArray(v) ? v[0] : v;\n}\n\n/**\n * Verify an inbound GardeshPay / PayGate365 webhook.\n *\n * Live notification-service signs with:\n * - `X-PayGate-Signature: sha256=<hex(HMAC-SHA256(secret, `${t}.${rawBody}`))>`\n * - `X-PayGate-Timestamp: <unix seconds>`\n *\n * Docs also mention `X-PG365-Signature: t=…,v1=…` — both are accepted.\n *\n * Webhooks are **notifications only**. Always call `payments.verify` (or GET)\n * before fulfilling an order.\n */\nexport function verifyWebhook(opts: {\n secret: string;\n rawBody: string | Buffer;\n headers: WebhookHeaders;\n /** Max clock skew in seconds (default 300). */\n skewSeconds?: number;\n nowSeconds?: number;\n}): { ok: true; timestamp: string; eventType?: string } | { ok: false; reason: string } {\n const skew = opts.skewSeconds ?? 300;\n const now = opts.nowSeconds ?? Math.floor(Date.now() / 1000);\n const raw = typeof opts.rawBody === \"string\" ? opts.rawBody : opts.rawBody.toString(\"utf8\");\n\n const paygateSig = header(opts.headers, \"X-PayGate-Signature\");\n const paygateTs = header(opts.headers, \"X-PayGate-Timestamp\");\n if (paygateSig && paygateTs) {\n const ts = Number(paygateTs);\n if (!Number.isFinite(ts) || Math.abs(now - ts) > skew) {\n return { ok: false, reason: \"timestamp skew\" };\n }\n const expectedHex = hmacSha256Hex(opts.secret, `${paygateTs}.${raw}`);\n const presented = paygateSig.replace(/^sha256=/i, \"\").trim();\n if (!safeEqualHex(expectedHex, presented)) {\n return { ok: false, reason: \"invalid signature\" };\n }\n return {\n ok: true,\n timestamp: paygateTs,\n eventType: header(opts.headers, \"X-PayGate-Event\"),\n };\n }\n\n const pg365 = header(opts.headers, \"X-PG365-Signature\");\n if (pg365) {\n // Format: t=<unix>,v1=<hex>\n const parts = Object.fromEntries(\n pg365.split(\",\").map((p) => {\n const [k, ...rest] = p.trim().split(\"=\");\n return [k, rest.join(\"=\")];\n }),\n ) as Record<string, string>;\n const t = parts.t;\n const v1 = parts.v1;\n if (!t || !v1) return { ok: false, reason: \"malformed X-PG365-Signature\" };\n const ts = Number(t);\n if (!Number.isFinite(ts) || Math.abs(now - ts) > skew) {\n return { ok: false, reason: \"timestamp skew\" };\n }\n const expected = hmacSha256Hex(opts.secret, `${t}.${raw}`);\n if (!safeEqualHex(expected, v1)) {\n return { ok: false, reason: \"invalid signature\" };\n }\n return { ok: true, timestamp: t };\n }\n\n return { ok: false, reason: \"missing webhook signature headers\" };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,59 @@
1
1
  {
2
2
  "name": "gardeshpay-sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Official Node.js SDK for GardeshPay / PayGate365 Merchant API — payments, verify, refunds, HMAC auth, webhook signature helpers. (npm name gardeshpay was already taken by an unrelated 2020 package.)",
5
+ "license": "MIT",
6
+ "author": "GardeshPay / PayGate365",
7
+ "keywords": [
8
+ "gardeshpay",
9
+ "paygate365",
10
+ "payment",
11
+ "iran",
12
+ "ipg",
13
+ "hmac",
14
+ "merchant-api",
15
+ "psp"
16
+ ],
17
+ "homepage": "https://source365.ir/paygate365",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://source365.ir/paygate365/gardeshpay.git"
21
+ },
22
+ "bugs": {
23
+ "url": "https://source365.ir/paygate365/gardeshpay/issues"
24
+ },
25
+ "type": "module",
26
+ "main": "./dist/index.cjs",
27
+ "module": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js",
33
+ "require": "./dist/index.cjs"
34
+ }
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "README.md",
39
+ "LICENSE"
40
+ ],
41
+ "engines": {
42
+ "node": ">=18"
43
+ },
44
+ "scripts": {
45
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean --sourcemap",
46
+ "test": "node --import tsx --test test/**/*.test.ts",
47
+ "prepublishOnly": "npm run build && npm test",
48
+ "typecheck": "tsc --noEmit"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^22.13.10",
52
+ "tsup": "^8.4.0",
53
+ "tsx": "^4.19.3",
54
+ "typescript": "^5.8.2"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ }
59
+ }