@sneekin/sdk 0.3.2 → 1.0.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/dist/core/client.d.ts +24 -0
- package/dist/core/derive-keys.d.ts +6 -0
- package/dist/core/generated/api.d.ts +3490 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/index.cjs +223 -0
- package/dist/index.d.ts +2 -118
- package/dist/index.js +127 -129
- package/dist/messages/index.cjs +150 -0
- package/dist/messages/index.d.ts +1 -0
- package/dist/messages/index.js +121 -0
- package/dist/pay/index.cjs +223 -0
- package/dist/pay/index.d.ts +2 -0
- package/dist/pay/index.js +192 -0
- package/dist/pay/payments.d.ts +90 -0
- package/dist/verify/index.cjs +150 -0
- package/dist/verify/index.d.ts +1 -0
- package/dist/verify/index.js +121 -0
- package/package.json +67 -47
- package/README.md +0 -134
- package/dist/index.d.mts +0 -118
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -168
- package/dist/index.mjs.map +0 -1
package/dist/index.cjs
ADDED
|
@@ -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/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
OrdersClient: () => OrdersClient,
|
|
24
|
+
SneekApiError: () => SneekApiError,
|
|
25
|
+
SneekClient: () => SneekClient,
|
|
26
|
+
SneekPayClient: () => SneekPayClient,
|
|
27
|
+
deriveKeys: () => deriveKeys
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(src_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
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,118 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* const sneek = new Sneek({
|
|
5
|
-
* apiUrl: 'https://api.sneek.in',
|
|
6
|
-
* apiKey: process.env.SNEEK_API_KEY!, // snk_live_… / snk_test_…
|
|
7
|
-
* });
|
|
8
|
-
*
|
|
9
|
-
* await sneek.messages.send({ to: '+91…', body: 'Your OTP is 123456' });
|
|
10
|
-
* await sneek.sendSMS('+91…', 'Your OTP is 123456');
|
|
11
|
-
* await sneek.sendOTP({ to: '+91…', code: '123456', appName: 'ConfHub' });
|
|
12
|
-
*
|
|
13
|
-
* Authenticates with `Authorization: Bearer snk_…` against the server's
|
|
14
|
-
* `BearerApiKeyGuard`. No request signing — the bearer key is the credential.
|
|
15
|
-
*/
|
|
16
|
-
interface SneekOptions {
|
|
17
|
-
/** Base URL of the Sneek API, e.g. https://api.sneek.in */
|
|
18
|
-
apiUrl: string;
|
|
19
|
-
/** Bearer API key: `snk_live_…` or `snk_test_…`. */
|
|
20
|
-
apiKey: string;
|
|
21
|
-
/** Override the global fetch (for tests / non-Node runtimes). */
|
|
22
|
-
fetch?: typeof fetch;
|
|
23
|
-
/** Max retry attempts for transient failures (5xx / network). Default 2. */
|
|
24
|
-
maxRetries?: number;
|
|
25
|
-
/** Base backoff in ms between retries (exponential). Default 200. */
|
|
26
|
-
retryBaseMs?: number;
|
|
27
|
-
}
|
|
28
|
-
type MessageChannel = 'sms' | 'whatsapp' | 'email';
|
|
29
|
-
interface SendMessageInput {
|
|
30
|
-
to: string;
|
|
31
|
-
/** Free-form message body. Required unless `template` is given. */
|
|
32
|
-
body?: string;
|
|
33
|
-
channel?: 'auto' | MessageChannel;
|
|
34
|
-
clientRef?: string;
|
|
35
|
-
/**
|
|
36
|
-
* Declares intent so the right approved provider template is used:
|
|
37
|
-
* `otp` → auth/OTP template, `transactional` → utility/communication.
|
|
38
|
-
* When omitted, Sneek infers it from the body text.
|
|
39
|
-
*/
|
|
40
|
-
type?: 'otp' | 'transactional';
|
|
41
|
-
/** Named/DLT provider template key (server-rendered). */
|
|
42
|
-
template?: string;
|
|
43
|
-
/** Variables substituted into `template`. */
|
|
44
|
-
variables?: Record<string, string | number>;
|
|
45
|
-
/**
|
|
46
|
-
* Replay-protection key. Two sends with the same key (per application)
|
|
47
|
-
* deliver only once — the first result is returned for every retry. Pair
|
|
48
|
-
* with the SDK's automatic 5xx retries to make sends safely repeatable.
|
|
49
|
-
*/
|
|
50
|
-
idempotencyKey?: string;
|
|
51
|
-
}
|
|
52
|
-
interface SendMessageResult {
|
|
53
|
-
id: string;
|
|
54
|
-
status: 'accepted';
|
|
55
|
-
channel: MessageChannel;
|
|
56
|
-
provider: string;
|
|
57
|
-
accepted_at: string;
|
|
58
|
-
client_ref?: string;
|
|
59
|
-
}
|
|
60
|
-
interface SendOtpInput {
|
|
61
|
-
to: string;
|
|
62
|
-
code: string;
|
|
63
|
-
/** App/brand name shown inside the message. */
|
|
64
|
-
appName?: string;
|
|
65
|
-
channel?: 'auto' | MessageChannel;
|
|
66
|
-
clientRef?: string;
|
|
67
|
-
/**
|
|
68
|
-
* Override the default body. Receives `{appName, otp}`. When omitted a
|
|
69
|
-
* sensible default is used. Ignored if `template` is provided.
|
|
70
|
-
*/
|
|
71
|
-
body?: (vars: {
|
|
72
|
-
appName: string;
|
|
73
|
-
otp: string;
|
|
74
|
-
}) => string;
|
|
75
|
-
/** DLT/provider template key, when the partner uses server-side rendering. */
|
|
76
|
-
template?: string;
|
|
77
|
-
/** Extra template variables (merged with `{appName, otp}`). */
|
|
78
|
-
variables?: Record<string, string | number>;
|
|
79
|
-
}
|
|
80
|
-
declare class SneekApiError extends Error {
|
|
81
|
-
status: number;
|
|
82
|
-
type: string;
|
|
83
|
-
title: string;
|
|
84
|
-
detail?: string | undefined;
|
|
85
|
-
constructor(status: number, type: string, title: string, detail?: string | undefined);
|
|
86
|
-
}
|
|
87
|
-
declare class Sneek {
|
|
88
|
-
private readonly apiUrl;
|
|
89
|
-
private readonly apiKey;
|
|
90
|
-
private readonly fetchImpl;
|
|
91
|
-
private readonly maxRetries;
|
|
92
|
-
private readonly retryBaseMs;
|
|
93
|
-
readonly messages: MessagesClient;
|
|
94
|
-
constructor(opts: SneekOptions);
|
|
95
|
-
/** @internal — dispatches a bearer-authenticated request with retries. */
|
|
96
|
-
request<T>(method: string, path: string, body?: unknown, extraHeaders?: Record<string, string>): Promise<T>;
|
|
97
|
-
private backoff;
|
|
98
|
-
/** Convenience: send an SMS. */
|
|
99
|
-
sendSMS(to: string, body: string, clientRef?: string): Promise<SendMessageResult>;
|
|
100
|
-
/** Convenience: send a WhatsApp message. */
|
|
101
|
-
sendWhatsApp(to: string, body: string, clientRef?: string): Promise<SendMessageResult>;
|
|
102
|
-
/** Convenience: send an email. */
|
|
103
|
-
sendEmail(to: string, body: string, clientRef?: string): Promise<SendMessageResult>;
|
|
104
|
-
/**
|
|
105
|
-
* OTP body-formatting helper. Sneek only *delivers*; the partner generates
|
|
106
|
-
* and verifies the code. This formats `{appName, otp}` into a body (or
|
|
107
|
-
* forwards a template) and sends it.
|
|
108
|
-
*/
|
|
109
|
-
sendOTP(input: SendOtpInput): Promise<SendMessageResult>;
|
|
110
|
-
}
|
|
111
|
-
declare class MessagesClient {
|
|
112
|
-
private readonly sneek;
|
|
113
|
-
constructor(sneek: Sneek);
|
|
114
|
-
send(input: SendMessageInput): Promise<SendMessageResult>;
|
|
115
|
-
get(id: string): Promise<SendMessageResult>;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export { type MessageChannel, type SendMessageInput, type SendMessageResult, type SendOtpInput, Sneek, SneekApiError, type SneekOptions, Sneek as default };
|
|
1
|
+
export * from './core/index.js';
|
|
2
|
+
export * from './pay/index.js';
|
package/dist/index.js
CHANGED
|
@@ -1,30 +1,4 @@
|
|
|
1
|
-
|
|
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
|
-
Sneek: () => Sneek,
|
|
24
|
-
SneekApiError: () => SneekApiError,
|
|
25
|
-
default: () => index_default
|
|
26
|
-
});
|
|
27
|
-
module.exports = __toCommonJS(index_exports);
|
|
1
|
+
// src/core/client.ts
|
|
28
2
|
var SneekApiError = class extends Error {
|
|
29
3
|
constructor(status, type, title, detail) {
|
|
30
4
|
super(`${title}${detail ? `: ${detail}` : ""} [${status}]`);
|
|
@@ -34,34 +8,33 @@ var SneekApiError = class extends Error {
|
|
|
34
8
|
this.detail = detail;
|
|
35
9
|
this.name = "SneekApiError";
|
|
36
10
|
}
|
|
11
|
+
status;
|
|
12
|
+
type;
|
|
13
|
+
title;
|
|
14
|
+
detail;
|
|
37
15
|
};
|
|
38
|
-
var
|
|
39
|
-
var DEFAULT_RETRY_BASE_MS = 200;
|
|
40
|
-
var Sneek = class {
|
|
16
|
+
var SneekClient = class {
|
|
41
17
|
apiUrl;
|
|
42
18
|
apiKey;
|
|
43
19
|
fetchImpl;
|
|
44
20
|
maxRetries;
|
|
45
21
|
retryBaseMs;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (!
|
|
49
|
-
|
|
50
|
-
this.
|
|
51
|
-
this.
|
|
52
|
-
this.
|
|
53
|
-
this.
|
|
54
|
-
this.retryBaseMs = opts.retryBaseMs ?? DEFAULT_RETRY_BASE_MS;
|
|
55
|
-
this.messages = new MessagesClient(this);
|
|
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;
|
|
56
30
|
}
|
|
57
|
-
/** @internal — dispatches a bearer-authenticated request with retries. */
|
|
58
31
|
async request(method, path, body, extraHeaders) {
|
|
59
32
|
const raw = body === void 0 ? void 0 : JSON.stringify(body);
|
|
60
33
|
let attempt = 0;
|
|
61
|
-
let
|
|
34
|
+
let lastError;
|
|
62
35
|
for (; ; ) {
|
|
63
36
|
try {
|
|
64
|
-
const
|
|
37
|
+
const response = await this.fetchImpl(`${this.apiUrl}${path}`, {
|
|
65
38
|
method,
|
|
66
39
|
headers: {
|
|
67
40
|
"content-type": "application/json",
|
|
@@ -70,125 +43,150 @@ var Sneek = class {
|
|
|
70
43
|
},
|
|
71
44
|
body: raw
|
|
72
45
|
});
|
|
73
|
-
const text = await
|
|
74
|
-
const parsed = text ?
|
|
75
|
-
if (!
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
82
55
|
);
|
|
83
|
-
if (
|
|
84
|
-
|
|
56
|
+
if (response.status >= 500 && attempt < this.maxRetries) {
|
|
57
|
+
lastError = error;
|
|
85
58
|
await this.backoff(attempt++);
|
|
86
59
|
continue;
|
|
87
60
|
}
|
|
88
|
-
throw
|
|
61
|
+
throw error;
|
|
89
62
|
}
|
|
90
63
|
return parsed;
|
|
91
|
-
} catch (
|
|
92
|
-
if (
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error instanceof SneekApiError) throw error;
|
|
93
66
|
if (attempt < this.maxRetries) {
|
|
94
|
-
|
|
67
|
+
lastError = error;
|
|
95
68
|
await this.backoff(attempt++);
|
|
96
69
|
continue;
|
|
97
70
|
}
|
|
98
|
-
throw
|
|
71
|
+
throw lastError ?? error;
|
|
99
72
|
}
|
|
100
73
|
}
|
|
101
74
|
}
|
|
102
75
|
backoff(attempt) {
|
|
103
|
-
|
|
104
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
76
|
+
return new Promise((resolve) => setTimeout(resolve, this.retryBaseMs * 2 ** attempt));
|
|
105
77
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
78
|
+
};
|
|
79
|
+
function parseJson(text) {
|
|
80
|
+
try {
|
|
81
|
+
return JSON.parse(text);
|
|
82
|
+
} catch {
|
|
83
|
+
return void 0;
|
|
109
84
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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/pay/payments.ts
|
|
119
|
+
var SneekPayClient = class {
|
|
120
|
+
client;
|
|
121
|
+
orders;
|
|
122
|
+
constructor(options) {
|
|
123
|
+
this.client = new SneekClient(options);
|
|
124
|
+
this.orders = new OrdersClient(this);
|
|
113
125
|
}
|
|
114
|
-
/**
|
|
115
|
-
|
|
116
|
-
return this.
|
|
126
|
+
/** @internal */
|
|
127
|
+
async request(method, path, body, extraHeaders) {
|
|
128
|
+
return this.client.request(method, path, body, extraHeaders);
|
|
117
129
|
}
|
|
118
130
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* forwards a template) and sends it.
|
|
131
|
+
* Verify the checkout handler payload and settle the order. Idempotent:
|
|
132
|
+
* calling it twice with the same pair returns the same result.
|
|
122
133
|
*/
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
channel: input.channel ?? "auto",
|
|
129
|
-
clientRef: input.clientRef,
|
|
130
|
-
type: "otp",
|
|
131
|
-
template: input.template,
|
|
132
|
-
variables: {
|
|
133
|
-
appName,
|
|
134
|
-
otp: input.code,
|
|
135
|
-
...input.variables ?? {}
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
const body = input.body ? input.body({ appName, otp: input.code }) : `Your OTP to login to ${appName} is ${input.code}. Do not share it with anyone.`;
|
|
140
|
-
return this.messages.send({
|
|
141
|
-
to: input.to,
|
|
142
|
-
body,
|
|
143
|
-
channel: input.channel ?? "auto",
|
|
144
|
-
clientRef: input.clientRef,
|
|
145
|
-
type: "otp"
|
|
134
|
+
verify(input) {
|
|
135
|
+
return this.request("POST", "/api/payments/verify", {
|
|
136
|
+
order_id: input.orderId,
|
|
137
|
+
payment_id: input.paymentId,
|
|
138
|
+
signature: input.signature
|
|
146
139
|
});
|
|
147
140
|
}
|
|
148
141
|
};
|
|
149
|
-
var
|
|
150
|
-
constructor(
|
|
151
|
-
this.
|
|
142
|
+
var OrdersClient = class {
|
|
143
|
+
constructor(pay) {
|
|
144
|
+
this.pay = pay;
|
|
152
145
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
throw new Error("send() requires either `body` or `template`");
|
|
156
|
-
}
|
|
146
|
+
pay;
|
|
147
|
+
create(input) {
|
|
157
148
|
const payload = {
|
|
158
|
-
|
|
159
|
-
|
|
149
|
+
amount: String(input.amount),
|
|
150
|
+
currency: input.currency ?? "INR"
|
|
160
151
|
};
|
|
161
|
-
if (input.
|
|
162
|
-
if (input.
|
|
163
|
-
if (input.
|
|
164
|
-
if (input.
|
|
165
|
-
|
|
152
|
+
if (input.receipt !== void 0) payload.receipt = input.receipt;
|
|
153
|
+
if (input.clientRef !== void 0) payload.client_ref = input.clientRef;
|
|
154
|
+
if (input.notes !== void 0) payload.notes = input.notes;
|
|
155
|
+
if (input.settleAfter !== void 0) {
|
|
156
|
+
payload.settle_after = input.settleAfter instanceof Date ? input.settleAfter.toISOString() : input.settleAfter;
|
|
157
|
+
}
|
|
166
158
|
const headers = input.idempotencyKey ? { "idempotency-key": input.idempotencyKey } : void 0;
|
|
167
|
-
return this.
|
|
159
|
+
return this.pay.request(
|
|
168
160
|
"POST",
|
|
169
|
-
"/api/
|
|
161
|
+
"/api/payments/orders",
|
|
170
162
|
payload,
|
|
171
163
|
headers
|
|
172
164
|
);
|
|
173
165
|
}
|
|
174
|
-
|
|
175
|
-
return this.
|
|
166
|
+
get(id) {
|
|
167
|
+
return this.pay.request(
|
|
176
168
|
"GET",
|
|
177
|
-
`/api/
|
|
169
|
+
`/api/payments/orders/${encodeURIComponent(id)}`
|
|
178
170
|
);
|
|
179
171
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
172
|
+
/**
|
|
173
|
+
* The event moved: hold the organizer share until the new end plus the
|
|
174
|
+
* platform refund window. Only works while the payout is still on hold;
|
|
175
|
+
* a released payout answers 409.
|
|
176
|
+
*/
|
|
177
|
+
reschedule(id, input) {
|
|
178
|
+
const settleAfter = input.settleAfter instanceof Date ? input.settleAfter.toISOString() : input.settleAfter;
|
|
179
|
+
return this.pay.request(
|
|
180
|
+
"PATCH",
|
|
181
|
+
`/api/payments/orders/${encodeURIComponent(id)}/settlement`,
|
|
182
|
+
{ settle_after: settleAfter }
|
|
183
|
+
);
|
|
186
184
|
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
185
|
+
};
|
|
186
|
+
export {
|
|
187
|
+
OrdersClient,
|
|
188
|
+
SneekApiError,
|
|
189
|
+
SneekClient,
|
|
190
|
+
SneekPayClient,
|
|
191
|
+
deriveKeys
|
|
192
|
+
};
|