@sneekin/sdk 0.3.2 → 1.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.
@@ -0,0 +1,170 @@
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/messages/index.ts
21
+ var messages_exports = {};
22
+ __export(messages_exports, {
23
+ SneekApiError: () => SneekApiError,
24
+ SneekClient: () => SneekClient,
25
+ SneekMessagesClient: () => SneekMessagesClient,
26
+ deriveKeys: () => deriveKeys
27
+ });
28
+ module.exports = __toCommonJS(messages_exports);
29
+
30
+ // src/core/client.ts
31
+ var SneekApiError = class extends Error {
32
+ constructor(status, type, title, detail) {
33
+ super(`${title}${detail ? `: ${detail}` : ""} [${status}]`);
34
+ this.status = status;
35
+ this.type = type;
36
+ this.title = title;
37
+ this.detail = detail;
38
+ this.name = "SneekApiError";
39
+ }
40
+ status;
41
+ type;
42
+ title;
43
+ detail;
44
+ };
45
+ var SneekClient = class {
46
+ apiUrl;
47
+ apiKey;
48
+ fetchImpl;
49
+ maxRetries;
50
+ retryBaseMs;
51
+ constructor(options) {
52
+ if (!options.apiUrl) throw new Error("apiUrl is required");
53
+ if (!options.apiKey) throw new Error("apiKey is required");
54
+ this.apiUrl = options.apiUrl.replace(/\/+$/, "");
55
+ this.apiKey = options.apiKey;
56
+ this.fetchImpl = options.fetch ?? fetch;
57
+ this.maxRetries = options.maxRetries ?? 2;
58
+ this.retryBaseMs = options.retryBaseMs ?? 200;
59
+ }
60
+ async request(method, path, body, extraHeaders) {
61
+ const raw = body === void 0 ? void 0 : JSON.stringify(body);
62
+ let attempt = 0;
63
+ let lastError;
64
+ for (; ; ) {
65
+ try {
66
+ const response = await this.fetchImpl(`${this.apiUrl}${path}`, {
67
+ method,
68
+ headers: {
69
+ "content-type": "application/json",
70
+ authorization: `Bearer ${this.apiKey}`,
71
+ ...extraHeaders ?? {}
72
+ },
73
+ body: raw
74
+ });
75
+ const text = await response.text();
76
+ const parsed = text ? parseJson(text) : void 0;
77
+ if (!response.ok) {
78
+ const problem = asProblem(parsed);
79
+ const error = new SneekApiError(
80
+ response.status,
81
+ problem.type ?? "about:blank",
82
+ problem.title ?? response.statusText,
83
+ problem.detail
84
+ );
85
+ if (response.status >= 500 && attempt < this.maxRetries) {
86
+ lastError = error;
87
+ await this.backoff(attempt++);
88
+ continue;
89
+ }
90
+ throw error;
91
+ }
92
+ return parsed;
93
+ } catch (error) {
94
+ if (error instanceof SneekApiError) throw error;
95
+ if (attempt < this.maxRetries) {
96
+ lastError = error;
97
+ await this.backoff(attempt++);
98
+ continue;
99
+ }
100
+ throw lastError ?? error;
101
+ }
102
+ }
103
+ }
104
+ backoff(attempt) {
105
+ return new Promise((resolve) => setTimeout(resolve, this.retryBaseMs * 2 ** attempt));
106
+ }
107
+ };
108
+ function parseJson(text) {
109
+ try {
110
+ return JSON.parse(text);
111
+ } catch {
112
+ return void 0;
113
+ }
114
+ }
115
+ function asProblem(value) {
116
+ if (typeof value !== "object" || value === null) return {};
117
+ const problem = value;
118
+ return {
119
+ type: typeof problem.type === "string" ? problem.type : void 0,
120
+ title: typeof problem.title === "string" ? problem.title : void 0,
121
+ detail: typeof problem.detail === "string" ? problem.detail : void 0
122
+ };
123
+ }
124
+
125
+ // src/core/derive-keys.ts
126
+ var import_node_crypto = require("crypto");
127
+ var KEY_LABELS = {
128
+ qrSigningKey: "sneek-qr-v1",
129
+ webhookSigningKey: "sneek-webhook-v1",
130
+ proofKey: "sneek-proof-v1"
131
+ };
132
+ function deriveKeys(apiKey) {
133
+ if (typeof apiKey !== "string" || apiKey.length === 0) {
134
+ throw new TypeError("apiKey must be a nonempty string");
135
+ }
136
+ const inputKeyMaterial = Buffer.from(apiKey, "utf8");
137
+ const derive = (label) => Buffer.from(
138
+ (0, import_node_crypto.hkdfSync)("sha256", inputKeyMaterial, Buffer.alloc(0), label, 32)
139
+ );
140
+ return {
141
+ qrSigningKey: derive(KEY_LABELS.qrSigningKey),
142
+ webhookSigningKey: derive(KEY_LABELS.webhookSigningKey),
143
+ proofKey: derive(KEY_LABELS.proofKey)
144
+ };
145
+ }
146
+
147
+ // src/messages/client.ts
148
+ var SneekMessagesClient = class {
149
+ client;
150
+ constructor(options) {
151
+ this.client = new SneekClient(options);
152
+ }
153
+ send(input, options = {}) {
154
+ const headers = options.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : void 0;
155
+ return this.client.request("POST", "/api/messages/send", input, headers);
156
+ }
157
+ get(id) {
158
+ return this.client.request(
159
+ "GET",
160
+ `/api/messages/${encodeURIComponent(id)}`
161
+ );
162
+ }
163
+ };
164
+ // Annotate the CommonJS export names for ESM import in node:
165
+ 0 && (module.exports = {
166
+ SneekApiError,
167
+ SneekClient,
168
+ SneekMessagesClient,
169
+ deriveKeys
170
+ });
@@ -0,0 +1,2 @@
1
+ export * from '../core/index.js';
2
+ export * from './client.js';
@@ -0,0 +1,140 @@
1
+ // src/core/client.ts
2
+ var SneekApiError = class extends Error {
3
+ constructor(status, type, title, detail) {
4
+ super(`${title}${detail ? `: ${detail}` : ""} [${status}]`);
5
+ this.status = status;
6
+ this.type = type;
7
+ this.title = title;
8
+ this.detail = detail;
9
+ this.name = "SneekApiError";
10
+ }
11
+ status;
12
+ type;
13
+ title;
14
+ detail;
15
+ };
16
+ var SneekClient = class {
17
+ apiUrl;
18
+ apiKey;
19
+ fetchImpl;
20
+ maxRetries;
21
+ retryBaseMs;
22
+ constructor(options) {
23
+ if (!options.apiUrl) throw new Error("apiUrl is required");
24
+ if (!options.apiKey) throw new Error("apiKey is required");
25
+ this.apiUrl = options.apiUrl.replace(/\/+$/, "");
26
+ this.apiKey = options.apiKey;
27
+ this.fetchImpl = options.fetch ?? fetch;
28
+ this.maxRetries = options.maxRetries ?? 2;
29
+ this.retryBaseMs = options.retryBaseMs ?? 200;
30
+ }
31
+ async request(method, path, body, extraHeaders) {
32
+ const raw = body === void 0 ? void 0 : JSON.stringify(body);
33
+ let attempt = 0;
34
+ let lastError;
35
+ for (; ; ) {
36
+ try {
37
+ const response = await this.fetchImpl(`${this.apiUrl}${path}`, {
38
+ method,
39
+ headers: {
40
+ "content-type": "application/json",
41
+ authorization: `Bearer ${this.apiKey}`,
42
+ ...extraHeaders ?? {}
43
+ },
44
+ body: raw
45
+ });
46
+ const text = await response.text();
47
+ const parsed = text ? parseJson(text) : void 0;
48
+ if (!response.ok) {
49
+ const problem = asProblem(parsed);
50
+ const error = new SneekApiError(
51
+ response.status,
52
+ problem.type ?? "about:blank",
53
+ problem.title ?? response.statusText,
54
+ problem.detail
55
+ );
56
+ if (response.status >= 500 && attempt < this.maxRetries) {
57
+ lastError = error;
58
+ await this.backoff(attempt++);
59
+ continue;
60
+ }
61
+ throw error;
62
+ }
63
+ return parsed;
64
+ } catch (error) {
65
+ if (error instanceof SneekApiError) throw error;
66
+ if (attempt < this.maxRetries) {
67
+ lastError = error;
68
+ await this.backoff(attempt++);
69
+ continue;
70
+ }
71
+ throw lastError ?? error;
72
+ }
73
+ }
74
+ }
75
+ backoff(attempt) {
76
+ return new Promise((resolve) => setTimeout(resolve, this.retryBaseMs * 2 ** attempt));
77
+ }
78
+ };
79
+ function parseJson(text) {
80
+ try {
81
+ return JSON.parse(text);
82
+ } catch {
83
+ return void 0;
84
+ }
85
+ }
86
+ function asProblem(value) {
87
+ if (typeof value !== "object" || value === null) return {};
88
+ const problem = value;
89
+ return {
90
+ type: typeof problem.type === "string" ? problem.type : void 0,
91
+ title: typeof problem.title === "string" ? problem.title : void 0,
92
+ detail: typeof problem.detail === "string" ? problem.detail : void 0
93
+ };
94
+ }
95
+
96
+ // src/core/derive-keys.ts
97
+ import { hkdfSync } from "crypto";
98
+ var KEY_LABELS = {
99
+ qrSigningKey: "sneek-qr-v1",
100
+ webhookSigningKey: "sneek-webhook-v1",
101
+ proofKey: "sneek-proof-v1"
102
+ };
103
+ function deriveKeys(apiKey) {
104
+ if (typeof apiKey !== "string" || apiKey.length === 0) {
105
+ throw new TypeError("apiKey must be a nonempty string");
106
+ }
107
+ const inputKeyMaterial = Buffer.from(apiKey, "utf8");
108
+ const derive = (label) => Buffer.from(
109
+ hkdfSync("sha256", inputKeyMaterial, Buffer.alloc(0), label, 32)
110
+ );
111
+ return {
112
+ qrSigningKey: derive(KEY_LABELS.qrSigningKey),
113
+ webhookSigningKey: derive(KEY_LABELS.webhookSigningKey),
114
+ proofKey: derive(KEY_LABELS.proofKey)
115
+ };
116
+ }
117
+
118
+ // src/messages/client.ts
119
+ var SneekMessagesClient = class {
120
+ client;
121
+ constructor(options) {
122
+ this.client = new SneekClient(options);
123
+ }
124
+ send(input, options = {}) {
125
+ const headers = options.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : void 0;
126
+ return this.client.request("POST", "/api/messages/send", input, headers);
127
+ }
128
+ get(id) {
129
+ return this.client.request(
130
+ "GET",
131
+ `/api/messages/${encodeURIComponent(id)}`
132
+ );
133
+ }
134
+ };
135
+ export {
136
+ SneekApiError,
137
+ SneekClient,
138
+ SneekMessagesClient,
139
+ deriveKeys
140
+ };
@@ -0,0 +1,223 @@
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/pay/index.ts
21
+ var pay_exports = {};
22
+ __export(pay_exports, {
23
+ OrdersClient: () => OrdersClient,
24
+ SneekApiError: () => SneekApiError,
25
+ SneekClient: () => SneekClient,
26
+ SneekPayClient: () => SneekPayClient,
27
+ deriveKeys: () => deriveKeys
28
+ });
29
+ module.exports = __toCommonJS(pay_exports);
30
+
31
+ // src/core/client.ts
32
+ var SneekApiError = class extends Error {
33
+ constructor(status, type, title, detail) {
34
+ super(`${title}${detail ? `: ${detail}` : ""} [${status}]`);
35
+ this.status = status;
36
+ this.type = type;
37
+ this.title = title;
38
+ this.detail = detail;
39
+ this.name = "SneekApiError";
40
+ }
41
+ status;
42
+ type;
43
+ title;
44
+ detail;
45
+ };
46
+ var SneekClient = class {
47
+ apiUrl;
48
+ apiKey;
49
+ fetchImpl;
50
+ maxRetries;
51
+ retryBaseMs;
52
+ constructor(options) {
53
+ if (!options.apiUrl) throw new Error("apiUrl is required");
54
+ if (!options.apiKey) throw new Error("apiKey is required");
55
+ this.apiUrl = options.apiUrl.replace(/\/+$/, "");
56
+ this.apiKey = options.apiKey;
57
+ this.fetchImpl = options.fetch ?? fetch;
58
+ this.maxRetries = options.maxRetries ?? 2;
59
+ this.retryBaseMs = options.retryBaseMs ?? 200;
60
+ }
61
+ async request(method, path, body, extraHeaders) {
62
+ const raw = body === void 0 ? void 0 : JSON.stringify(body);
63
+ let attempt = 0;
64
+ let lastError;
65
+ for (; ; ) {
66
+ try {
67
+ const response = await this.fetchImpl(`${this.apiUrl}${path}`, {
68
+ method,
69
+ headers: {
70
+ "content-type": "application/json",
71
+ authorization: `Bearer ${this.apiKey}`,
72
+ ...extraHeaders ?? {}
73
+ },
74
+ body: raw
75
+ });
76
+ const text = await response.text();
77
+ const parsed = text ? parseJson(text) : void 0;
78
+ if (!response.ok) {
79
+ const problem = asProblem(parsed);
80
+ const error = new SneekApiError(
81
+ response.status,
82
+ problem.type ?? "about:blank",
83
+ problem.title ?? response.statusText,
84
+ problem.detail
85
+ );
86
+ if (response.status >= 500 && attempt < this.maxRetries) {
87
+ lastError = error;
88
+ await this.backoff(attempt++);
89
+ continue;
90
+ }
91
+ throw error;
92
+ }
93
+ return parsed;
94
+ } catch (error) {
95
+ if (error instanceof SneekApiError) throw error;
96
+ if (attempt < this.maxRetries) {
97
+ lastError = error;
98
+ await this.backoff(attempt++);
99
+ continue;
100
+ }
101
+ throw lastError ?? error;
102
+ }
103
+ }
104
+ }
105
+ backoff(attempt) {
106
+ return new Promise((resolve) => setTimeout(resolve, this.retryBaseMs * 2 ** attempt));
107
+ }
108
+ };
109
+ function parseJson(text) {
110
+ try {
111
+ return JSON.parse(text);
112
+ } catch {
113
+ return void 0;
114
+ }
115
+ }
116
+ function asProblem(value) {
117
+ if (typeof value !== "object" || value === null) return {};
118
+ const problem = value;
119
+ return {
120
+ type: typeof problem.type === "string" ? problem.type : void 0,
121
+ title: typeof problem.title === "string" ? problem.title : void 0,
122
+ detail: typeof problem.detail === "string" ? problem.detail : void 0
123
+ };
124
+ }
125
+
126
+ // src/core/derive-keys.ts
127
+ var import_node_crypto = require("crypto");
128
+ var KEY_LABELS = {
129
+ qrSigningKey: "sneek-qr-v1",
130
+ webhookSigningKey: "sneek-webhook-v1",
131
+ proofKey: "sneek-proof-v1"
132
+ };
133
+ function deriveKeys(apiKey) {
134
+ if (typeof apiKey !== "string" || apiKey.length === 0) {
135
+ throw new TypeError("apiKey must be a nonempty string");
136
+ }
137
+ const inputKeyMaterial = Buffer.from(apiKey, "utf8");
138
+ const derive = (label) => Buffer.from(
139
+ (0, import_node_crypto.hkdfSync)("sha256", inputKeyMaterial, Buffer.alloc(0), label, 32)
140
+ );
141
+ return {
142
+ qrSigningKey: derive(KEY_LABELS.qrSigningKey),
143
+ webhookSigningKey: derive(KEY_LABELS.webhookSigningKey),
144
+ proofKey: derive(KEY_LABELS.proofKey)
145
+ };
146
+ }
147
+
148
+ // src/pay/payments.ts
149
+ var SneekPayClient = class {
150
+ client;
151
+ orders;
152
+ constructor(options) {
153
+ this.client = new SneekClient(options);
154
+ this.orders = new OrdersClient(this);
155
+ }
156
+ /** @internal */
157
+ async request(method, path, body, extraHeaders) {
158
+ return this.client.request(method, path, body, extraHeaders);
159
+ }
160
+ /**
161
+ * Verify the checkout handler payload and settle the order. Idempotent:
162
+ * calling it twice with the same pair returns the same result.
163
+ */
164
+ verify(input) {
165
+ return this.request("POST", "/api/payments/verify", {
166
+ order_id: input.orderId,
167
+ payment_id: input.paymentId,
168
+ signature: input.signature
169
+ });
170
+ }
171
+ };
172
+ var OrdersClient = class {
173
+ constructor(pay) {
174
+ this.pay = pay;
175
+ }
176
+ pay;
177
+ create(input) {
178
+ const payload = {
179
+ amount: String(input.amount),
180
+ currency: input.currency ?? "INR"
181
+ };
182
+ if (input.receipt !== void 0) payload.receipt = input.receipt;
183
+ if (input.clientRef !== void 0) payload.client_ref = input.clientRef;
184
+ if (input.notes !== void 0) payload.notes = input.notes;
185
+ if (input.settleAfter !== void 0) {
186
+ payload.settle_after = input.settleAfter instanceof Date ? input.settleAfter.toISOString() : input.settleAfter;
187
+ }
188
+ const headers = input.idempotencyKey ? { "idempotency-key": input.idempotencyKey } : void 0;
189
+ return this.pay.request(
190
+ "POST",
191
+ "/api/payments/orders",
192
+ payload,
193
+ headers
194
+ );
195
+ }
196
+ get(id) {
197
+ return this.pay.request(
198
+ "GET",
199
+ `/api/payments/orders/${encodeURIComponent(id)}`
200
+ );
201
+ }
202
+ /**
203
+ * The event moved: hold the organizer share until the new end plus the
204
+ * platform refund window. Only works while the payout is still on hold;
205
+ * a released payout answers 409.
206
+ */
207
+ reschedule(id, input) {
208
+ const settleAfter = input.settleAfter instanceof Date ? input.settleAfter.toISOString() : input.settleAfter;
209
+ return this.pay.request(
210
+ "PATCH",
211
+ `/api/payments/orders/${encodeURIComponent(id)}/settlement`,
212
+ { settle_after: settleAfter }
213
+ );
214
+ }
215
+ };
216
+ // Annotate the CommonJS export names for ESM import in node:
217
+ 0 && (module.exports = {
218
+ OrdersClient,
219
+ SneekApiError,
220
+ SneekClient,
221
+ SneekPayClient,
222
+ deriveKeys
223
+ });
@@ -0,0 +1,2 @@
1
+ export * from '../core/index.js';
2
+ export * from './payments.js';