@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 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: number;
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(params)
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/v1';
259
+ this.baseURL = config.baseURL || 'https://null-pay-rs8i.vercel.app/api';
84
260
  }
85
261
  }
86
262
  exports.NullPay = NullPay;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nullpay/node",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "NullPay SDK for Node.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",