@nullpay/node 1.0.0 → 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/dist/index.d.ts +43 -5
- package/dist/index.js +182 -6
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -2,13 +2,33 @@ export interface NullPayConfig {
|
|
|
2
2
|
secretKey: string;
|
|
3
3
|
baseURL?: string;
|
|
4
4
|
}
|
|
5
|
+
export interface NullPayInvoice {
|
|
6
|
+
name: string;
|
|
7
|
+
type: 'multipay' | 'donation';
|
|
8
|
+
amount: number | null;
|
|
9
|
+
currency: string;
|
|
10
|
+
label?: string;
|
|
11
|
+
hash: string;
|
|
12
|
+
salt: string;
|
|
13
|
+
}
|
|
14
|
+
export interface NullPayJson {
|
|
15
|
+
merchant: string;
|
|
16
|
+
generated_at: string;
|
|
17
|
+
invoices: NullPayInvoice[];
|
|
18
|
+
}
|
|
19
|
+
export declare function loadNullPayConfig(projectRoot?: string): NullPayJson | null;
|
|
5
20
|
export interface CreateCheckoutSessionParams {
|
|
6
|
-
amount
|
|
7
|
-
currency?: 'CREDITS' | 'USDCX' | 'USAD';
|
|
21
|
+
amount?: number;
|
|
22
|
+
currency?: 'CREDITS' | 'USDCX' | 'USAD' | 'ANY';
|
|
23
|
+
type?: 'standard' | 'donation' | 'multipay';
|
|
8
24
|
success_url?: string;
|
|
9
25
|
cancel_url?: string;
|
|
10
26
|
invoice_hash?: string;
|
|
11
27
|
salt?: string;
|
|
28
|
+
/** Shorthand: look up invoice by name from nullpay.json (recommended) */
|
|
29
|
+
nullpay_invoice_name?: string;
|
|
30
|
+
/** Shorthand: look up invoice by index from nullpay.json (fallback) */
|
|
31
|
+
nullpay_invoice_index?: number;
|
|
12
32
|
}
|
|
13
33
|
export interface CheckoutSession {
|
|
14
34
|
id: string;
|
|
@@ -29,6 +49,27 @@ export declare class NullPay {
|
|
|
29
49
|
private secretKey;
|
|
30
50
|
private baseURL;
|
|
31
51
|
constructor(config: NullPayConfig);
|
|
52
|
+
/**
|
|
53
|
+
* Helpers for reading and querying the local nullpay.json config.
|
|
54
|
+
*/
|
|
55
|
+
invoices: {
|
|
56
|
+
/**
|
|
57
|
+
* Returns all invoices from nullpay.json, or throws if the file is missing.
|
|
58
|
+
*/
|
|
59
|
+
getAll: () => NullPayInvoice[];
|
|
60
|
+
/**
|
|
61
|
+
* Returns an invoice by its array index.
|
|
62
|
+
*/
|
|
63
|
+
getByIndex: (i: number) => NullPayInvoice;
|
|
64
|
+
/**
|
|
65
|
+
* Returns an invoice by its developer-defined name.
|
|
66
|
+
*/
|
|
67
|
+
getByName: (name: string) => NullPayInvoice;
|
|
68
|
+
/**
|
|
69
|
+
* Returns all invoices matching a given type.
|
|
70
|
+
*/
|
|
71
|
+
getByType: (type: "multipay" | "donation") => NullPayInvoice[];
|
|
72
|
+
};
|
|
32
73
|
checkout: {
|
|
33
74
|
sessions: {
|
|
34
75
|
create: (params: CreateCheckoutSessionParams) => Promise<CheckoutSession>;
|
|
@@ -38,9 +79,6 @@ export declare class NullPay {
|
|
|
38
79
|
webhooks: {
|
|
39
80
|
/**
|
|
40
81
|
* Verifies the HMAC-SHA256 signature attached to a NullPay webhook payload.
|
|
41
|
-
* @param payload The raw stringified JSON body of the webhook request.
|
|
42
|
-
* @param signature The hex signature from the `x-nullpay-signature` header.
|
|
43
|
-
* @returns true if the signature is valid and securely originates from NullPay.
|
|
44
82
|
*/
|
|
45
83
|
verifySignature: (payload: string, signature: string) => boolean;
|
|
46
84
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,203 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
5
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
39
|
exports.NullPay = void 0;
|
|
40
|
+
exports.loadNullPayConfig = loadNullPayConfig;
|
|
7
41
|
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
8
42
|
const crypto_1 = __importDefault(require("crypto"));
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
function loadNullPayConfig(projectRoot) {
|
|
46
|
+
const root = projectRoot || process.cwd();
|
|
47
|
+
const filePath = path.join(root, 'nullpay.json');
|
|
48
|
+
if (!fs.existsSync(filePath))
|
|
49
|
+
return null;
|
|
50
|
+
try {
|
|
51
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
52
|
+
return JSON.parse(raw);
|
|
53
|
+
}
|
|
54
|
+
catch (_a) {
|
|
55
|
+
throw new Error(`Failed to parse nullpay.json at ${filePath}. Ensure it is valid JSON.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
9
58
|
class NullPay {
|
|
10
59
|
constructor(config) {
|
|
60
|
+
/**
|
|
61
|
+
* Helpers for reading and querying the local nullpay.json config.
|
|
62
|
+
*/
|
|
63
|
+
this.invoices = {
|
|
64
|
+
/**
|
|
65
|
+
* Returns all invoices from nullpay.json, or throws if the file is missing.
|
|
66
|
+
*/
|
|
67
|
+
getAll: () => {
|
|
68
|
+
const config = loadNullPayConfig();
|
|
69
|
+
if (!config)
|
|
70
|
+
throw new Error('nullpay.json not found. Run "nullpay sdk onboard" first.');
|
|
71
|
+
return config.invoices;
|
|
72
|
+
},
|
|
73
|
+
/**
|
|
74
|
+
* Returns an invoice by its array index.
|
|
75
|
+
*/
|
|
76
|
+
getByIndex: (i) => {
|
|
77
|
+
const all = this.invoices.getAll();
|
|
78
|
+
if (i < 0 || i >= all.length) {
|
|
79
|
+
throw new Error(`Invoice index ${i} is out of range. nullpay.json has ${all.length} invoice(s).`);
|
|
80
|
+
}
|
|
81
|
+
return all[i];
|
|
82
|
+
},
|
|
83
|
+
/**
|
|
84
|
+
* Returns an invoice by its developer-defined name.
|
|
85
|
+
*/
|
|
86
|
+
getByName: (name) => {
|
|
87
|
+
const all = this.invoices.getAll();
|
|
88
|
+
const found = all.find(inv => inv.name === name);
|
|
89
|
+
if (!found) {
|
|
90
|
+
const available = all.map(inv => `"${inv.name}"`).join(', ');
|
|
91
|
+
throw new Error(`Invoice "${name}" not found in nullpay.json. Available: ${available}`);
|
|
92
|
+
}
|
|
93
|
+
return found;
|
|
94
|
+
},
|
|
95
|
+
/**
|
|
96
|
+
* Returns all invoices matching a given type.
|
|
97
|
+
*/
|
|
98
|
+
getByType: (type) => {
|
|
99
|
+
return this.invoices.getAll().filter(inv => inv.type === type);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
11
102
|
this.checkout = {
|
|
12
103
|
sessions: {
|
|
13
104
|
create: async (params) => {
|
|
105
|
+
var _a;
|
|
106
|
+
// ── Resolve from nullpay.json if shorthand is used ──────────
|
|
107
|
+
let resolvedParams = { ...params };
|
|
108
|
+
if (params.nullpay_invoice_name !== undefined || params.nullpay_invoice_index !== undefined) {
|
|
109
|
+
let inv;
|
|
110
|
+
if (params.nullpay_invoice_name !== undefined) {
|
|
111
|
+
inv = this.invoices.getByName(params.nullpay_invoice_name);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
inv = this.invoices.getByIndex(params.nullpay_invoice_index);
|
|
115
|
+
}
|
|
116
|
+
// Merge: nullpay.json values fill in the blanks; explicit params override
|
|
117
|
+
resolvedParams = {
|
|
118
|
+
...resolvedParams,
|
|
119
|
+
invoice_hash: resolvedParams.invoice_hash || inv.hash,
|
|
120
|
+
salt: resolvedParams.salt || inv.salt,
|
|
121
|
+
type: resolvedParams.type || inv.type,
|
|
122
|
+
amount: resolvedParams.amount !== undefined ? resolvedParams.amount : ((_a = inv.amount) !== null && _a !== void 0 ? _a : undefined),
|
|
123
|
+
currency: resolvedParams.currency || inv.currency,
|
|
124
|
+
};
|
|
125
|
+
// Clean up shorthand keys — don't send them to backend
|
|
126
|
+
delete resolvedParams.nullpay_invoice_name;
|
|
127
|
+
delete resolvedParams.nullpay_invoice_index;
|
|
128
|
+
}
|
|
129
|
+
const isDonation = resolvedParams.type === 'donation';
|
|
130
|
+
if (!isDonation && (resolvedParams.amount === undefined || resolvedParams.amount <= 0)) {
|
|
131
|
+
throw new Error("Amount is required and must be greater than 0 for standard invoices.");
|
|
132
|
+
}
|
|
133
|
+
let finalInvoiceHash = resolvedParams.invoice_hash;
|
|
134
|
+
let finalSalt = resolvedParams.salt;
|
|
135
|
+
// Automatic Pre-Generation using NullPay Relayer via DPS
|
|
136
|
+
if (!finalInvoiceHash || !finalSalt) {
|
|
137
|
+
const randomBuffer = crypto_1.default.randomBytes(16);
|
|
138
|
+
let randomBigInt = BigInt(0);
|
|
139
|
+
for (const byte of randomBuffer) {
|
|
140
|
+
randomBigInt = (randomBigInt << BigInt(8)) + BigInt(byte);
|
|
141
|
+
}
|
|
142
|
+
finalSalt = `${randomBigInt.toString()}field`;
|
|
143
|
+
let invoiceTypeNum = 0;
|
|
144
|
+
if (resolvedParams.type === 'donation')
|
|
145
|
+
invoiceTypeNum = 2;
|
|
146
|
+
else if (resolvedParams.type === 'multipay')
|
|
147
|
+
invoiceTypeNum = 1;
|
|
148
|
+
const relayerRes = await (0, node_fetch_1.default)(`${this.baseURL}/dps/relayer/create-invoice`, {
|
|
149
|
+
method: 'POST',
|
|
150
|
+
headers: {
|
|
151
|
+
'Content-Type': 'application/json',
|
|
152
|
+
'Authorization': `Bearer ${this.secretKey}`
|
|
153
|
+
},
|
|
154
|
+
body: JSON.stringify({
|
|
155
|
+
amount: isDonation ? 0 : resolvedParams.amount,
|
|
156
|
+
currency: resolvedParams.currency || 'CREDITS',
|
|
157
|
+
salt: finalSalt,
|
|
158
|
+
invoice_type: invoiceTypeNum
|
|
159
|
+
})
|
|
160
|
+
});
|
|
161
|
+
if (!relayerRes.ok) {
|
|
162
|
+
const errorData = await relayerRes.json().catch(() => ({}));
|
|
163
|
+
throw new Error(`NullPay Relayer Pre-gen Error: ${relayerRes.status} - ${errorData.error || relayerRes.statusText}`);
|
|
164
|
+
}
|
|
165
|
+
let hashStr = null;
|
|
166
|
+
let retries = 0;
|
|
167
|
+
const MAX_RETRIES = 60;
|
|
168
|
+
while (!hashStr && retries < MAX_RETRIES) {
|
|
169
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
170
|
+
try {
|
|
171
|
+
const mapRes = await (0, node_fetch_1.default)(`https://api.provable.com/v2/testnet/program/zk_pay_proofs_privacy_v22.aleo/mapping/salt_to_invoice/${finalSalt}`);
|
|
172
|
+
if (mapRes.ok) {
|
|
173
|
+
const textVal = await mapRes.json();
|
|
174
|
+
if (textVal)
|
|
175
|
+
hashStr = textVal.toString().replace(/(['"'])/g, '');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (_) {
|
|
179
|
+
// transient, ignore
|
|
180
|
+
}
|
|
181
|
+
retries++;
|
|
182
|
+
}
|
|
183
|
+
if (!hashStr) {
|
|
184
|
+
throw new Error("Timed out waiting for Aleo network blockchain confirmation. Invoice was sent, but hash was not resolved.");
|
|
185
|
+
}
|
|
186
|
+
finalInvoiceHash = hashStr;
|
|
187
|
+
}
|
|
188
|
+
const sessionPayload = {
|
|
189
|
+
...resolvedParams,
|
|
190
|
+
amount: isDonation ? 0 : resolvedParams.amount,
|
|
191
|
+
invoice_hash: finalInvoiceHash,
|
|
192
|
+
salt: finalSalt
|
|
193
|
+
};
|
|
14
194
|
const response = await (0, node_fetch_1.default)(`${this.baseURL}/checkout/sessions`, {
|
|
15
195
|
method: 'POST',
|
|
16
196
|
headers: {
|
|
17
197
|
'Content-Type': 'application/json',
|
|
18
198
|
'Authorization': `Bearer ${this.secretKey}`
|
|
19
199
|
},
|
|
20
|
-
body: JSON.stringify(
|
|
200
|
+
body: JSON.stringify(sessionPayload)
|
|
21
201
|
});
|
|
22
202
|
if (!response.ok) {
|
|
23
203
|
const errorData = await response.json().catch(() => ({}));
|
|
@@ -43,9 +223,6 @@ class NullPay {
|
|
|
43
223
|
this.webhooks = {
|
|
44
224
|
/**
|
|
45
225
|
* Verifies the HMAC-SHA256 signature attached to a NullPay webhook payload.
|
|
46
|
-
* @param payload The raw stringified JSON body of the webhook request.
|
|
47
|
-
* @param signature The hex signature from the `x-nullpay-signature` header.
|
|
48
|
-
* @returns true if the signature is valid and securely originates from NullPay.
|
|
49
226
|
*/
|
|
50
227
|
verifySignature: (payload, signature) => {
|
|
51
228
|
if (!payload || !signature)
|
|
@@ -55,7 +232,6 @@ class NullPay {
|
|
|
55
232
|
.createHmac('sha256', this.secretKey)
|
|
56
233
|
.update(payload)
|
|
57
234
|
.digest('hex');
|
|
58
|
-
// Constant-time string comparison to prevent timing attacks
|
|
59
235
|
if (expectedSignature.length !== signature.length)
|
|
60
236
|
return false;
|
|
61
237
|
return crypto_1.default.timingSafeEqual(Buffer.from(signature, 'utf8'), Buffer.from(expectedSignature, 'utf8'));
|
|
@@ -80,7 +256,7 @@ class NullPay {
|
|
|
80
256
|
throw new Error("NullPay API Key is required.");
|
|
81
257
|
}
|
|
82
258
|
this.secretKey = config.secretKey;
|
|
83
|
-
this.baseURL = config.baseURL || 'https://null-pay-rs8i.vercel.app/api
|
|
259
|
+
this.baseURL = config.baseURL || 'https://null-pay-rs8i.vercel.app/api';
|
|
84
260
|
}
|
|
85
261
|
}
|
|
86
262
|
exports.NullPay = NullPay;
|