@devizovaburza/payments-api-sdk 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.
- package/README.md +88 -0
- package/dist/v1/index.cjs +376 -0
- package/dist/v1/index.d.cts +3394 -0
- package/dist/v1/index.d.mts +3394 -0
- package/dist/v1/index.d.ts +3394 -0
- package/dist/v1/index.mjs +372 -0
- package/package.json +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Payments API SDK
|
|
2
|
+
|
|
3
|
+
Type-safe SDK for integrating with the Payments API. Provides a pre-configured [Hono RPC client](https://hono.dev/docs/guides/rpc) and cryptographic helpers for request signing.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @devizovaburza/payments-api-sdk
|
|
9
|
+
# or
|
|
10
|
+
yarn add @devizovaburza/payments-api-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### API Client
|
|
16
|
+
|
|
17
|
+
The SDK exports a type-safe client generated from the Payments API schema:
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { createPartnerApiClient } from '@devizovaburza/payments-api-sdk/v1';
|
|
21
|
+
|
|
22
|
+
const client = createPartnerApiClient('https://api.payments.devizovka.cz');
|
|
23
|
+
|
|
24
|
+
// Authenticate
|
|
25
|
+
const { accessToken } = await client.v1.auth.token.$post({
|
|
26
|
+
json: { email: '...', password: '...' },
|
|
27
|
+
}).then(res => res.json());
|
|
28
|
+
|
|
29
|
+
// Send a payment
|
|
30
|
+
const payment = await client.v1.organizations[':id'].payments.$post({
|
|
31
|
+
param: { id: orgId },
|
|
32
|
+
json: { ... },
|
|
33
|
+
header: { 'x-idempotency-key': '...' },
|
|
34
|
+
}).then(res => res.json());
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Type Inference
|
|
38
|
+
|
|
39
|
+
You can infer request and response types from any endpoint using Hono's built-in helpers:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import type { InferRequestType, InferResponseType } from 'hono/client';
|
|
43
|
+
import { createPartnerApiClient } from '@devizovaburza/payments-api-sdk/v1';
|
|
44
|
+
|
|
45
|
+
const client = createPartnerApiClient('https://api.payments.devizovka.cz');
|
|
46
|
+
|
|
47
|
+
// Infer types from a specific endpoint
|
|
48
|
+
type SendPaymentRequest = InferRequestType<typeof client.v1.organizations[':id'].payments.$post>;
|
|
49
|
+
type SendPaymentResponse = InferResponseType<typeof client.v1.organizations[':id'].payments.$post>;
|
|
50
|
+
|
|
51
|
+
// Use in your code
|
|
52
|
+
async function sendPayment(orgId: string, body: SendPaymentRequest['json']) {
|
|
53
|
+
const res = await client.v1.organizations[':id'].payments.$post({
|
|
54
|
+
param: { id: orgId },
|
|
55
|
+
json: body,
|
|
56
|
+
header: { 'x-idempotency-key': crypto.randomUUID() },
|
|
57
|
+
});
|
|
58
|
+
const data: SendPaymentResponse = await res.json();
|
|
59
|
+
return data;
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Sign Payload
|
|
64
|
+
|
|
65
|
+
Signs a JSON payload using a base64-encoded RSA private key. Use this to generate the `X-Signature` header.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { signPayload } from '@devizovaburza/payments-api-sdk/v1';
|
|
69
|
+
|
|
70
|
+
const signature = await signPayload({
|
|
71
|
+
payload: JSON.stringify({ amount: 1000 }),
|
|
72
|
+
privateKey: 'BASE64_ENCODED_PRIVATE_KEY',
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Verify Payload Signature
|
|
77
|
+
|
|
78
|
+
Verifies a signature using a base64-encoded RSA public key.
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { verifyPayloadSignature } from '@devizovaburza/payments-api-sdk/v1';
|
|
82
|
+
|
|
83
|
+
const isValid = await verifyPayloadSignature({
|
|
84
|
+
payload: JSON.stringify({ amount: 1000 }),
|
|
85
|
+
signature: 'GENERATED_SIGNATURE',
|
|
86
|
+
publicKey: 'BASE64_ENCODED_PUBLIC_KEY',
|
|
87
|
+
});
|
|
88
|
+
```
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/utils/cookie.ts
|
|
4
|
+
var _serialize = (name, value, opt = {}) => {
|
|
5
|
+
let cookie = `${name}=${value}`;
|
|
6
|
+
if (name.startsWith("__Secure-") && !opt.secure) {
|
|
7
|
+
throw new Error("__Secure- Cookie must have Secure attributes");
|
|
8
|
+
}
|
|
9
|
+
if (name.startsWith("__Host-")) {
|
|
10
|
+
if (!opt.secure) {
|
|
11
|
+
throw new Error("__Host- Cookie must have Secure attributes");
|
|
12
|
+
}
|
|
13
|
+
if (opt.path !== "/") {
|
|
14
|
+
throw new Error('__Host- Cookie must have Path attributes with "/"');
|
|
15
|
+
}
|
|
16
|
+
if (opt.domain) {
|
|
17
|
+
throw new Error("__Host- Cookie must not have Domain attributes");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (opt && typeof opt.maxAge === "number" && opt.maxAge >= 0) {
|
|
21
|
+
if (opt.maxAge > 3456e4) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
"Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration."
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
cookie += `; Max-Age=${opt.maxAge | 0}`;
|
|
27
|
+
}
|
|
28
|
+
if (opt.domain && opt.prefix !== "host") {
|
|
29
|
+
cookie += `; Domain=${opt.domain}`;
|
|
30
|
+
}
|
|
31
|
+
if (opt.path) {
|
|
32
|
+
cookie += `; Path=${opt.path}`;
|
|
33
|
+
}
|
|
34
|
+
if (opt.expires) {
|
|
35
|
+
if (opt.expires.getTime() - Date.now() > 3456e7) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
"Cookies Expires SHOULD NOT be greater than 400 days (34560000 seconds) in the future."
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
cookie += `; Expires=${opt.expires.toUTCString()}`;
|
|
41
|
+
}
|
|
42
|
+
if (opt.httpOnly) {
|
|
43
|
+
cookie += "; HttpOnly";
|
|
44
|
+
}
|
|
45
|
+
if (opt.secure) {
|
|
46
|
+
cookie += "; Secure";
|
|
47
|
+
}
|
|
48
|
+
if (opt.sameSite) {
|
|
49
|
+
cookie += `; SameSite=${opt.sameSite.charAt(0).toUpperCase() + opt.sameSite.slice(1)}`;
|
|
50
|
+
}
|
|
51
|
+
if (opt.priority) {
|
|
52
|
+
cookie += `; Priority=${opt.priority.charAt(0).toUpperCase() + opt.priority.slice(1)}`;
|
|
53
|
+
}
|
|
54
|
+
if (opt.partitioned) {
|
|
55
|
+
if (!opt.secure) {
|
|
56
|
+
throw new Error("Partitioned Cookie must have Secure attributes");
|
|
57
|
+
}
|
|
58
|
+
cookie += "; Partitioned";
|
|
59
|
+
}
|
|
60
|
+
return cookie;
|
|
61
|
+
};
|
|
62
|
+
var serialize = (name, value, opt) => {
|
|
63
|
+
value = encodeURIComponent(value);
|
|
64
|
+
return _serialize(name, value, opt);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// src/client/utils.ts
|
|
68
|
+
var mergePath = (base, path) => {
|
|
69
|
+
base = base.replace(/\/+$/, "");
|
|
70
|
+
base = base + "/";
|
|
71
|
+
path = path.replace(/^\/+/, "");
|
|
72
|
+
return base + path;
|
|
73
|
+
};
|
|
74
|
+
var replaceUrlParam = (urlString, params) => {
|
|
75
|
+
for (const [k, v] of Object.entries(params)) {
|
|
76
|
+
const reg = new RegExp("/:" + k + "(?:{[^/]+})?\\??");
|
|
77
|
+
urlString = urlString.replace(reg, v ? `/${v}` : "");
|
|
78
|
+
}
|
|
79
|
+
return urlString;
|
|
80
|
+
};
|
|
81
|
+
var buildSearchParams = (query) => {
|
|
82
|
+
const searchParams = new URLSearchParams();
|
|
83
|
+
for (const [k, v] of Object.entries(query)) {
|
|
84
|
+
if (v === void 0) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (Array.isArray(v)) {
|
|
88
|
+
for (const v2 of v) {
|
|
89
|
+
searchParams.append(k, v2);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
searchParams.set(k, v);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return searchParams;
|
|
96
|
+
};
|
|
97
|
+
var replaceUrlProtocol = (urlString, protocol) => {
|
|
98
|
+
switch (protocol) {
|
|
99
|
+
case "ws":
|
|
100
|
+
return urlString.replace(/^http/, "ws");
|
|
101
|
+
case "http":
|
|
102
|
+
return urlString.replace(/^ws/, "http");
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
var removeIndexString = (urlString) => {
|
|
106
|
+
if (/^https?:\/\/[^\/]+?\/index(?=\?|$)/.test(urlString)) {
|
|
107
|
+
return urlString.replace(/\/index(?=\?|$)/, "/");
|
|
108
|
+
}
|
|
109
|
+
return urlString.replace(/\/index(?=\?|$)/, "");
|
|
110
|
+
};
|
|
111
|
+
function isObject(item) {
|
|
112
|
+
return typeof item === "object" && item !== null && !Array.isArray(item);
|
|
113
|
+
}
|
|
114
|
+
function deepMerge(target, source) {
|
|
115
|
+
if (!isObject(target) && !isObject(source)) {
|
|
116
|
+
return source;
|
|
117
|
+
}
|
|
118
|
+
const merged = { ...target };
|
|
119
|
+
for (const key in source) {
|
|
120
|
+
const value = source[key];
|
|
121
|
+
if (isObject(merged[key]) && isObject(value)) {
|
|
122
|
+
merged[key] = deepMerge(merged[key], value);
|
|
123
|
+
} else {
|
|
124
|
+
merged[key] = value;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return merged;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/client/client.ts
|
|
131
|
+
var createProxy = (callback, path) => {
|
|
132
|
+
const proxy = new Proxy(() => {
|
|
133
|
+
}, {
|
|
134
|
+
get(_obj, key) {
|
|
135
|
+
if (typeof key !== "string" || key === "then") {
|
|
136
|
+
return void 0;
|
|
137
|
+
}
|
|
138
|
+
return createProxy(callback, [...path, key]);
|
|
139
|
+
},
|
|
140
|
+
apply(_1, _2, args) {
|
|
141
|
+
return callback({
|
|
142
|
+
path,
|
|
143
|
+
args
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
return proxy;
|
|
148
|
+
};
|
|
149
|
+
var ClientRequestImpl = class {
|
|
150
|
+
url;
|
|
151
|
+
method;
|
|
152
|
+
buildSearchParams;
|
|
153
|
+
queryParams = void 0;
|
|
154
|
+
pathParams = {};
|
|
155
|
+
rBody;
|
|
156
|
+
cType = void 0;
|
|
157
|
+
constructor(url, method, options) {
|
|
158
|
+
this.url = url;
|
|
159
|
+
this.method = method;
|
|
160
|
+
this.buildSearchParams = options.buildSearchParams;
|
|
161
|
+
}
|
|
162
|
+
fetch = async (args, opt) => {
|
|
163
|
+
if (args) {
|
|
164
|
+
if (args.query) {
|
|
165
|
+
this.queryParams = this.buildSearchParams(args.query);
|
|
166
|
+
}
|
|
167
|
+
if (args.form) {
|
|
168
|
+
const form = new FormData();
|
|
169
|
+
for (const [k, v] of Object.entries(args.form)) {
|
|
170
|
+
if (Array.isArray(v)) {
|
|
171
|
+
for (const v2 of v) {
|
|
172
|
+
form.append(k, v2);
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
form.append(k, v);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
this.rBody = form;
|
|
179
|
+
}
|
|
180
|
+
if (args.json) {
|
|
181
|
+
this.rBody = JSON.stringify(args.json);
|
|
182
|
+
this.cType = "application/json";
|
|
183
|
+
}
|
|
184
|
+
if (args.param) {
|
|
185
|
+
this.pathParams = args.param;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
let methodUpperCase = this.method.toUpperCase();
|
|
189
|
+
const headerValues = {
|
|
190
|
+
...args?.header,
|
|
191
|
+
...typeof opt?.headers === "function" ? await opt.headers() : opt?.headers
|
|
192
|
+
};
|
|
193
|
+
if (args?.cookie) {
|
|
194
|
+
const cookies = [];
|
|
195
|
+
for (const [key, value] of Object.entries(args.cookie)) {
|
|
196
|
+
cookies.push(serialize(key, value, { path: "/" }));
|
|
197
|
+
}
|
|
198
|
+
headerValues["Cookie"] = cookies.join(",");
|
|
199
|
+
}
|
|
200
|
+
if (this.cType) {
|
|
201
|
+
headerValues["Content-Type"] = this.cType;
|
|
202
|
+
}
|
|
203
|
+
const headers = new Headers(headerValues ?? void 0);
|
|
204
|
+
let url = this.url;
|
|
205
|
+
url = removeIndexString(url);
|
|
206
|
+
url = replaceUrlParam(url, this.pathParams);
|
|
207
|
+
if (this.queryParams) {
|
|
208
|
+
url = url + "?" + this.queryParams.toString();
|
|
209
|
+
}
|
|
210
|
+
methodUpperCase = this.method.toUpperCase();
|
|
211
|
+
const setBody = !(methodUpperCase === "GET" || methodUpperCase === "HEAD");
|
|
212
|
+
return (opt?.fetch || fetch)(url, {
|
|
213
|
+
body: setBody ? this.rBody : void 0,
|
|
214
|
+
method: methodUpperCase,
|
|
215
|
+
headers,
|
|
216
|
+
...opt?.init
|
|
217
|
+
});
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
var hc = (baseUrl, options) => createProxy(function proxyCallback(opts) {
|
|
221
|
+
const buildSearchParamsOption = options?.buildSearchParams ?? buildSearchParams;
|
|
222
|
+
const parts = [...opts.path];
|
|
223
|
+
const lastParts = parts.slice(-3).reverse();
|
|
224
|
+
if (lastParts[0] === "toString") {
|
|
225
|
+
if (lastParts[1] === "name") {
|
|
226
|
+
return lastParts[2] || "";
|
|
227
|
+
}
|
|
228
|
+
return proxyCallback.toString();
|
|
229
|
+
}
|
|
230
|
+
if (lastParts[0] === "valueOf") {
|
|
231
|
+
if (lastParts[1] === "name") {
|
|
232
|
+
return lastParts[2] || "";
|
|
233
|
+
}
|
|
234
|
+
return proxyCallback;
|
|
235
|
+
}
|
|
236
|
+
let method = "";
|
|
237
|
+
if (/^\$/.test(lastParts[0])) {
|
|
238
|
+
const last = parts.pop();
|
|
239
|
+
if (last) {
|
|
240
|
+
method = last.replace(/^\$/, "");
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const path = parts.join("/");
|
|
244
|
+
const url = mergePath(baseUrl, path);
|
|
245
|
+
if (method === "url") {
|
|
246
|
+
let result = url;
|
|
247
|
+
if (opts.args[0]) {
|
|
248
|
+
if (opts.args[0].param) {
|
|
249
|
+
result = replaceUrlParam(url, opts.args[0].param);
|
|
250
|
+
}
|
|
251
|
+
if (opts.args[0].query) {
|
|
252
|
+
result = result + "?" + buildSearchParamsOption(opts.args[0].query).toString();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
result = removeIndexString(result);
|
|
256
|
+
return new URL(result);
|
|
257
|
+
}
|
|
258
|
+
if (method === "ws") {
|
|
259
|
+
const webSocketUrl = replaceUrlProtocol(
|
|
260
|
+
opts.args[0] && opts.args[0].param ? replaceUrlParam(url, opts.args[0].param) : url,
|
|
261
|
+
"ws"
|
|
262
|
+
);
|
|
263
|
+
const targetUrl = new URL(webSocketUrl);
|
|
264
|
+
const queryParams = opts.args[0]?.query;
|
|
265
|
+
if (queryParams) {
|
|
266
|
+
Object.entries(queryParams).forEach(([key, value]) => {
|
|
267
|
+
if (Array.isArray(value)) {
|
|
268
|
+
value.forEach((item) => targetUrl.searchParams.append(key, item));
|
|
269
|
+
} else {
|
|
270
|
+
targetUrl.searchParams.set(key, value);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
const establishWebSocket = (...args) => {
|
|
275
|
+
if (options?.webSocket !== void 0 && typeof options.webSocket === "function") {
|
|
276
|
+
return options.webSocket(...args);
|
|
277
|
+
}
|
|
278
|
+
return new WebSocket(...args);
|
|
279
|
+
};
|
|
280
|
+
return establishWebSocket(targetUrl.toString());
|
|
281
|
+
}
|
|
282
|
+
const req = new ClientRequestImpl(url, method, {
|
|
283
|
+
buildSearchParams: buildSearchParamsOption
|
|
284
|
+
});
|
|
285
|
+
if (method) {
|
|
286
|
+
options ??= {};
|
|
287
|
+
const args = deepMerge(options, { ...opts.args[1] });
|
|
288
|
+
return req.fetch(opts.args[0], args);
|
|
289
|
+
}
|
|
290
|
+
return req;
|
|
291
|
+
}, []);
|
|
292
|
+
|
|
293
|
+
const createPartnerApiClient = (...args) => {
|
|
294
|
+
return hc(...args);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const signPayload = async ({
|
|
298
|
+
payload,
|
|
299
|
+
privateKey
|
|
300
|
+
}) => {
|
|
301
|
+
const binaryPrivateKey = Uint8Array.from(
|
|
302
|
+
atob(privateKey),
|
|
303
|
+
(c) => c.charCodeAt(0)
|
|
304
|
+
);
|
|
305
|
+
const importedPrivateKey = await crypto.subtle.importKey(
|
|
306
|
+
"pkcs8",
|
|
307
|
+
binaryPrivateKey,
|
|
308
|
+
{
|
|
309
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
310
|
+
hash: "SHA-256"
|
|
311
|
+
},
|
|
312
|
+
false,
|
|
313
|
+
["sign"]
|
|
314
|
+
);
|
|
315
|
+
const encodedPayload = new TextEncoder().encode(payload);
|
|
316
|
+
const signature = await crypto.subtle.sign(
|
|
317
|
+
{
|
|
318
|
+
name: "RSASSA-PKCS1-v1_5"
|
|
319
|
+
},
|
|
320
|
+
importedPrivateKey,
|
|
321
|
+
encodedPayload
|
|
322
|
+
);
|
|
323
|
+
const base64Signature = btoa(
|
|
324
|
+
String.fromCharCode(...new Uint8Array(signature))
|
|
325
|
+
);
|
|
326
|
+
return base64Signature;
|
|
327
|
+
};
|
|
328
|
+
const algParams = {
|
|
329
|
+
RSA: {
|
|
330
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
331
|
+
hash: { name: "SHA-256" }
|
|
332
|
+
},
|
|
333
|
+
EC: {
|
|
334
|
+
name: "ECDSA",
|
|
335
|
+
namedCurve: "P-256"
|
|
336
|
+
},
|
|
337
|
+
HMAC: {
|
|
338
|
+
name: "HMAC",
|
|
339
|
+
hash: { name: "SHA-256" }
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
const verifyPayloadSignature = async ({
|
|
343
|
+
signature,
|
|
344
|
+
data,
|
|
345
|
+
publicKey,
|
|
346
|
+
algorithm = "RSA"
|
|
347
|
+
}) => {
|
|
348
|
+
const binaryPublicKey = Uint8Array.from(
|
|
349
|
+
atob(publicKey),
|
|
350
|
+
(c) => c.charCodeAt(0)
|
|
351
|
+
);
|
|
352
|
+
const format = algorithm === "HMAC" ? "raw" : "spki";
|
|
353
|
+
const importedPublicKey = await crypto.subtle.importKey(
|
|
354
|
+
format,
|
|
355
|
+
binaryPublicKey,
|
|
356
|
+
algParams[algorithm],
|
|
357
|
+
false,
|
|
358
|
+
["verify"]
|
|
359
|
+
);
|
|
360
|
+
const encodedPayload = new TextEncoder().encode(data);
|
|
361
|
+
const decodedSignature = Uint8Array.from(
|
|
362
|
+
atob(signature),
|
|
363
|
+
(c) => c.charCodeAt(0)
|
|
364
|
+
);
|
|
365
|
+
const isValid = await crypto.subtle.verify(
|
|
366
|
+
algParams[algorithm],
|
|
367
|
+
importedPublicKey,
|
|
368
|
+
decodedSignature,
|
|
369
|
+
encodedPayload
|
|
370
|
+
);
|
|
371
|
+
return isValid;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
exports.createPartnerApiClient = createPartnerApiClient;
|
|
375
|
+
exports.signPayload = signPayload;
|
|
376
|
+
exports.verifyPayloadSignature = verifyPayloadSignature;
|