@sinnu-dev/sdk 0.0.0-stage → 0.2.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 +161 -2
- package/dist/index.cjs +446 -0
- package/dist/index.d.cts +1285 -0
- package/dist/index.d.ts +1285 -0
- package/dist/index.js +391 -0
- package/package.json +63 -5
package/README.md
CHANGED
|
@@ -1,3 +1,162 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Sinnu SDK para Node.js
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
SDK oficial da [Sinnu](https://sinnu.com.br) para receber com **Pix** e **cartão de crédito**
|
|
4
|
+
direto do seu sistema.
|
|
5
|
+
|
|
6
|
+
Feito para **agências**, **prestadores de serviço**, **SaaS** e qualquer negócio que
|
|
7
|
+
cobra clientes pela internet: gere um link de pagamento, mande para o cliente e saiba na
|
|
8
|
+
hora quando o dinheiro entrou.
|
|
9
|
+
|
|
10
|
+
📘 **Documentação completa:** [docs.sinnu.com.br](https://docs.sinnu.com.br)
|
|
11
|
+
|
|
12
|
+
## O que dá para fazer
|
|
13
|
+
|
|
14
|
+
- **Cobrar** com links de pagamento: Pix e cartão em até 12x, no checkout hospedado da Sinnu.
|
|
15
|
+
- **Acompanhar** as transações: status, valor bruto, taxas e valor líquido.
|
|
16
|
+
- **Estornar** uma venda pelo próprio sistema.
|
|
17
|
+
- **Ser avisado** de cada pagamento por webhook, com assinatura verificada.
|
|
18
|
+
|
|
19
|
+
## Instalação
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @sinnu-dev/sdk
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Requer Node.js 18 ou mais recente. Funciona com TypeScript e JavaScript (ESM e CommonJS).
|
|
26
|
+
|
|
27
|
+
## Primeiros passos
|
|
28
|
+
|
|
29
|
+
Gere sua **chave secreta** (`sk_live_...`) no painel da Sinnu, em **Chave de API**, e guarde
|
|
30
|
+
em uma variável de ambiente do servidor.
|
|
31
|
+
|
|
32
|
+
> A chave secreta dá acesso à sua conta. Use só no backend: nunca no navegador nem no app.
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { Sinnu } from "@sinnu-dev/sdk";
|
|
36
|
+
|
|
37
|
+
const sinnu = new Sinnu({ apiKey: process.env.SINNU_API_KEY! });
|
|
38
|
+
|
|
39
|
+
const link = await sinnu.links.create({
|
|
40
|
+
title: "Consultoria inicial",
|
|
41
|
+
amountCents: 9900, // R$ 99,00
|
|
42
|
+
methods: ["pix", "credit_card"],
|
|
43
|
+
maxInstallments: 12,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Envie esta URL para o seu cliente pagar.
|
|
47
|
+
console.log(sinnu.checkout.url(link.slug));
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Valores são sempre em **centavos**: `9900` é R$ 99,00.
|
|
51
|
+
|
|
52
|
+
## Cobrar pedidos do seu sistema
|
|
53
|
+
|
|
54
|
+
O link é o produto e pode ser vendido muitas vezes. Para saber qual pedido cada venda
|
|
55
|
+
pagou, abra o checkout com a referência do pedido:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const link = await sinnu.links.create({
|
|
59
|
+
title: "Consultoria inicial",
|
|
60
|
+
amountCents: 9900,
|
|
61
|
+
externalReference: "CONSULTORIA-1H", // código do produto no seu sistema
|
|
62
|
+
redirectUrl: "https://sualoja.com.br/obrigado", // ou o deep link do app: meuapp://pedido
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Um checkout por pedido: a referência volta na transação e no webhook.
|
|
66
|
+
const url = sinnu.checkout.url(link.slug, { reference: "pedido-123" });
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Depois do pagamento confirmado, o comprador volta para o `redirectUrl` (liberado por empresa:
|
|
70
|
+
confirme com a Sinnu se já está ativo para a sua) com
|
|
71
|
+
`transactionId`, `reference` e `status=approved` na query. Use esses parâmetros só para
|
|
72
|
+
mostrar a tela certa: libere o pedido pelo webhook ou por `transactions.get(transactionId)`.
|
|
73
|
+
|
|
74
|
+
## Acompanhar transações
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
const page = await sinnu.transactions.list({ status: "approved" });
|
|
78
|
+
console.log(`${page.total} vendas aprovadas`);
|
|
79
|
+
|
|
80
|
+
// Ou percorra todas as páginas:
|
|
81
|
+
for await (const tx of sinnu.transactions.listAll({ status: "approved" })) {
|
|
82
|
+
console.log(tx.id, tx.method, tx.amount, tx.net);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Estornar uma venda
|
|
87
|
+
|
|
88
|
+
O estorno é sempre do valor total da transação.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
const tx = await sinnu.transactions.refund("ID_DA_TRANSACAO", {
|
|
92
|
+
reason: "Pedido cancelado pelo cliente",
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Receber webhooks
|
|
97
|
+
|
|
98
|
+
A Sinnu avisa seu sistema a cada evento de pagamento. Verifique a assinatura com o
|
|
99
|
+
**corpo cru** da requisição antes de processar:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { constructEvent, SinnuWebhookSignatureError } from "@sinnu-dev/sdk";
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const event = constructEvent({
|
|
106
|
+
payload: rawBody,
|
|
107
|
+
signature: req.headers["x-sinnu-signature"],
|
|
108
|
+
secret: process.env.SINNU_WEBHOOK_SECRET!,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (event.type === "transaction.approved") {
|
|
112
|
+
// event.data.reference é o pedido do seu sistema (o ?ref= do checkout):
|
|
113
|
+
// libere o pedido, envie o acesso, emita a nota...
|
|
114
|
+
}
|
|
115
|
+
} catch (err) {
|
|
116
|
+
if (err instanceof SinnuWebhookSignatureError) {
|
|
117
|
+
// assinatura inválida: responda 400 e descarte
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Tratamento de erros
|
|
123
|
+
|
|
124
|
+
Toda falha da API vira um `SinnuError` (ou uma subclasse), com o status HTTP em
|
|
125
|
+
`statusCode`:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { SinnuAuthError, SinnuError, SinnuValidationError } from "@sinnu-dev/sdk";
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
await sinnu.links.create({ title: "Teste", amountCents: 500 });
|
|
132
|
+
} catch (err) {
|
|
133
|
+
if (err instanceof SinnuAuthError) console.error("Chave de API inválida");
|
|
134
|
+
else if (err instanceof SinnuValidationError) console.error(err.body);
|
|
135
|
+
else if (err instanceof SinnuError) console.error(err.statusCode, err.message);
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Também existem `SinnuNotFoundError` e `SinnuRateLimitError`.
|
|
140
|
+
|
|
141
|
+
## Recursos
|
|
142
|
+
|
|
143
|
+
| Recurso | Métodos |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `sinnu.links` | `create`, `get`, `update`, `archive`, `list`, `listAll`, `transactions` |
|
|
146
|
+
| `sinnu.transactions` | `list`, `listAll`, `get`, `refund` |
|
|
147
|
+
| `sinnu.checkout` | `url` |
|
|
148
|
+
| `sinnu.webhooks` | `constructEvent`, `verifySignature`, `computeSignature` |
|
|
149
|
+
|
|
150
|
+
Para chamar direto qualquer rota da API pública com tipos, use `sinnu.http`.
|
|
151
|
+
|
|
152
|
+
## Links úteis
|
|
153
|
+
|
|
154
|
+
- [Guia do SDK](https://docs.sinnu.com.br/sdk)
|
|
155
|
+
- [Início rápido](https://docs.sinnu.com.br/guias/inicio-rapido)
|
|
156
|
+
- [Autenticação](https://docs.sinnu.com.br/guias/autenticacao)
|
|
157
|
+
- [Webhooks](https://docs.sinnu.com.br/guias/webhooks)
|
|
158
|
+
- [Referência da API](https://docs.sinnu.com.br/api-reference)
|
|
159
|
+
|
|
160
|
+
## Licença
|
|
161
|
+
|
|
162
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
Checkout: () => Checkout,
|
|
34
|
+
DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
|
|
35
|
+
DEFAULT_CHECKOUT_BASE_URL: () => DEFAULT_CHECKOUT_BASE_URL,
|
|
36
|
+
SINNU_SIGNATURE_HEADER: () => SINNU_SIGNATURE_HEADER,
|
|
37
|
+
Sinnu: () => Sinnu,
|
|
38
|
+
SinnuAuthError: () => SinnuAuthError,
|
|
39
|
+
SinnuError: () => SinnuError,
|
|
40
|
+
SinnuNotFoundError: () => SinnuNotFoundError,
|
|
41
|
+
SinnuRateLimitError: () => SinnuRateLimitError,
|
|
42
|
+
SinnuValidationError: () => SinnuValidationError,
|
|
43
|
+
SinnuWebhookSignatureError: () => SinnuWebhookSignatureError,
|
|
44
|
+
collectAll: () => collectAll,
|
|
45
|
+
computeSignature: () => computeSignature,
|
|
46
|
+
constructEvent: () => constructEvent,
|
|
47
|
+
createTypedClient: () => createTypedClient,
|
|
48
|
+
default: () => index_default,
|
|
49
|
+
paginate: () => paginate,
|
|
50
|
+
unwrap: () => unwrap,
|
|
51
|
+
verifySignature: () => verifySignature,
|
|
52
|
+
webhooks: () => webhooks
|
|
53
|
+
});
|
|
54
|
+
module.exports = __toCommonJS(index_exports);
|
|
55
|
+
|
|
56
|
+
// src/client.ts
|
|
57
|
+
var import_openapi_fetch = __toESM(require("openapi-fetch"), 1);
|
|
58
|
+
|
|
59
|
+
// src/errors.ts
|
|
60
|
+
var SinnuError = class extends Error {
|
|
61
|
+
constructor(message, statusCode, body, response) {
|
|
62
|
+
super(message);
|
|
63
|
+
this.name = "SinnuError";
|
|
64
|
+
this.statusCode = statusCode;
|
|
65
|
+
this.body = body;
|
|
66
|
+
this.response = response;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
var SinnuAuthError = class extends SinnuError {
|
|
70
|
+
constructor(message, statusCode = 401, body, response) {
|
|
71
|
+
super(message, statusCode, body, response);
|
|
72
|
+
this.name = "SinnuAuthError";
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var SinnuNotFoundError = class extends SinnuError {
|
|
76
|
+
constructor(message, body, response) {
|
|
77
|
+
super(message, 404, body, response);
|
|
78
|
+
this.name = "SinnuNotFoundError";
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
var SinnuValidationError = class extends SinnuError {
|
|
82
|
+
constructor(message, statusCode, body, response) {
|
|
83
|
+
super(message, statusCode, body, response);
|
|
84
|
+
this.name = "SinnuValidationError";
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var SinnuRateLimitError = class extends SinnuError {
|
|
88
|
+
constructor(message, body, response) {
|
|
89
|
+
super(message, 429, body, response);
|
|
90
|
+
this.name = "SinnuRateLimitError";
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
function toSinnuError(statusCode, body, response) {
|
|
94
|
+
const message = body?.message ?? `Requisi\xE7\xE3o falhou com status ${statusCode}`;
|
|
95
|
+
switch (statusCode) {
|
|
96
|
+
case 401:
|
|
97
|
+
case 403:
|
|
98
|
+
return new SinnuAuthError(message, statusCode, body, response);
|
|
99
|
+
case 404:
|
|
100
|
+
return new SinnuNotFoundError(message, body, response);
|
|
101
|
+
case 400:
|
|
102
|
+
case 422:
|
|
103
|
+
return new SinnuValidationError(message, statusCode, body, response);
|
|
104
|
+
case 429:
|
|
105
|
+
return new SinnuRateLimitError(message, body, response);
|
|
106
|
+
default:
|
|
107
|
+
return new SinnuError(message, statusCode, body, response);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/client.ts
|
|
112
|
+
var DEFAULT_BASE_URL = "https://api.sinnu.com.br";
|
|
113
|
+
var API_KEY_PREFIXES = ["sk_live_"];
|
|
114
|
+
function assertApiKey(apiKey) {
|
|
115
|
+
if (!apiKey || typeof apiKey !== "string") {
|
|
116
|
+
throw new Error(
|
|
117
|
+
"[@sinnu-dev/sdk] apiKey \xE9 obrigat\xF3ria. Gere uma chave secreta no painel sinnu (Integra\xE7\xF5es)."
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
if (!API_KEY_PREFIXES.some((p) => apiKey.startsWith(p))) {
|
|
121
|
+
console.warn(
|
|
122
|
+
"[@sinnu-dev/sdk] apiKey n\xE3o come\xE7a com 'sk_live_'. Use a chave secreta no backend, nunca uma chave no cliente."
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function createTypedClient(options) {
|
|
127
|
+
assertApiKey(options.apiKey);
|
|
128
|
+
return (0, import_openapi_fetch.default)({
|
|
129
|
+
baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
|
|
130
|
+
fetch: options.fetch,
|
|
131
|
+
headers: {
|
|
132
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
133
|
+
"Content-Type": "application/json",
|
|
134
|
+
...options.headers
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
async function unwrap(p) {
|
|
139
|
+
const { data, error, response } = await p;
|
|
140
|
+
if (error || !response.ok) {
|
|
141
|
+
throw toSinnuError(
|
|
142
|
+
response.status,
|
|
143
|
+
error ?? void 0,
|
|
144
|
+
response
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
if (data === void 0 || data === null) {
|
|
148
|
+
throw toSinnuError(response.status, { message: "Resposta sem corpo" }, response);
|
|
149
|
+
}
|
|
150
|
+
return data;
|
|
151
|
+
}
|
|
152
|
+
var RawHttp = class {
|
|
153
|
+
constructor(baseUrl, apiKey, extraHeaders, fetchImpl) {
|
|
154
|
+
this.baseUrl = baseUrl;
|
|
155
|
+
this.apiKey = apiKey;
|
|
156
|
+
this.extraHeaders = extraHeaders;
|
|
157
|
+
this.fetchImpl = fetchImpl;
|
|
158
|
+
}
|
|
159
|
+
async request(path, options = {}) {
|
|
160
|
+
const url = new URL(path.replace(/^\//, ""), ensureTrailingSlash(this.baseUrl));
|
|
161
|
+
if (options.query) {
|
|
162
|
+
for (const [key, value] of Object.entries(options.query)) {
|
|
163
|
+
if (value !== void 0) {
|
|
164
|
+
url.searchParams.set(key, String(value));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const response = await this.fetchImpl(url.toString(), {
|
|
169
|
+
method: options.method ?? "GET",
|
|
170
|
+
headers: {
|
|
171
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
172
|
+
"Content-Type": "application/json",
|
|
173
|
+
Accept: "application/json",
|
|
174
|
+
...this.extraHeaders
|
|
175
|
+
},
|
|
176
|
+
body: options.body !== void 0 ? JSON.stringify(options.body) : void 0
|
|
177
|
+
});
|
|
178
|
+
const text = await response.text();
|
|
179
|
+
const parsed = text ? safeJsonParse(text) : void 0;
|
|
180
|
+
if (!response.ok) {
|
|
181
|
+
throw toSinnuError(response.status, parsed, response);
|
|
182
|
+
}
|
|
183
|
+
return parsed;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
function ensureTrailingSlash(url) {
|
|
187
|
+
return url.endsWith("/") ? url : `${url}/`;
|
|
188
|
+
}
|
|
189
|
+
function safeJsonParse(text) {
|
|
190
|
+
try {
|
|
191
|
+
return JSON.parse(text);
|
|
192
|
+
} catch {
|
|
193
|
+
return text;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/resources/checkout.ts
|
|
198
|
+
var DEFAULT_CHECKOUT_BASE_URL = "https://pay.sinnu.com.br";
|
|
199
|
+
var Checkout = class {
|
|
200
|
+
constructor(options = {}) {
|
|
201
|
+
this.options = options;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Monta a URL pública do checkout hosted para um `slug` (8 chars) gerado
|
|
205
|
+
* ao criar um link (`sinnu.links.create(...).url` já vem pronto; este
|
|
206
|
+
* helper é útil quando você só tem o slug).
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* const url = sinnu.checkout.url("abcd1234", { reference: "pedido-123" });
|
|
210
|
+
* // => "https://pay.sinnu.com.br/abcd1234?ref=pedido-123"
|
|
211
|
+
*/
|
|
212
|
+
url(slug, options = {}) {
|
|
213
|
+
if (!slug || typeof slug !== "string") {
|
|
214
|
+
throw new Error("[@sinnu-dev/sdk] slug do checkout \xE9 obrigat\xF3rio.");
|
|
215
|
+
}
|
|
216
|
+
const base = options.checkoutBaseUrl ?? this.options.checkoutBaseUrl ?? DEFAULT_CHECKOUT_BASE_URL;
|
|
217
|
+
const url = new URL(encodeURIComponent(slug), ensureTrailingSlash2(base));
|
|
218
|
+
const query = { ...this.options.query, ...options.query };
|
|
219
|
+
for (const [key, value] of Object.entries(query)) {
|
|
220
|
+
if (value !== void 0) {
|
|
221
|
+
url.searchParams.set(key, String(value));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (options.reference !== void 0) {
|
|
225
|
+
if (options.reference.length > 255) {
|
|
226
|
+
throw new Error("[@sinnu-dev/sdk] reference tem no m\xE1ximo 255 caracteres.");
|
|
227
|
+
}
|
|
228
|
+
url.searchParams.set("ref", options.reference);
|
|
229
|
+
}
|
|
230
|
+
return url.toString();
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
function ensureTrailingSlash2(url) {
|
|
234
|
+
return url.endsWith("/") ? url : `${url}/`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/pagination.ts
|
|
238
|
+
async function* paginate(fetchPage) {
|
|
239
|
+
let page = 1;
|
|
240
|
+
let seen = 0;
|
|
241
|
+
while (true) {
|
|
242
|
+
const result = await fetchPage(page);
|
|
243
|
+
for (const item of result.data) {
|
|
244
|
+
yield item;
|
|
245
|
+
}
|
|
246
|
+
seen += result.data.length;
|
|
247
|
+
const pageSize = result.pageSize || result.data.length;
|
|
248
|
+
if (result.data.length === 0 || seen >= result.total || pageSize === 0) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
page += 1;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async function collectAll(fetchPage) {
|
|
255
|
+
const out = [];
|
|
256
|
+
for await (const item of paginate(fetchPage)) {
|
|
257
|
+
out.push(item);
|
|
258
|
+
}
|
|
259
|
+
return out;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/resources/links.ts
|
|
263
|
+
var Links = class {
|
|
264
|
+
constructor(http) {
|
|
265
|
+
this.http = http;
|
|
266
|
+
}
|
|
267
|
+
/** Cria um link de pagamento (valor em centavos). */
|
|
268
|
+
async create(body) {
|
|
269
|
+
return this.http.request("/api/payment-links", {
|
|
270
|
+
method: "POST",
|
|
271
|
+
body
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
/** Busca um link de pagamento pelo id. */
|
|
275
|
+
async get(id) {
|
|
276
|
+
return this.http.request(`/api/payment-links/${encodeURIComponent(id)}`);
|
|
277
|
+
}
|
|
278
|
+
/** Atualiza parcialmente um link de pagamento. */
|
|
279
|
+
async update(id, body) {
|
|
280
|
+
return this.http.request(`/api/payment-links/${encodeURIComponent(id)}`, {
|
|
281
|
+
method: "PATCH",
|
|
282
|
+
body
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
/** Arquiva um link de pagamento. */
|
|
286
|
+
async archive(id) {
|
|
287
|
+
await this.http.request(`/api/payment-links/${encodeURIComponent(id)}`, {
|
|
288
|
+
method: "DELETE"
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
/** Lista links de pagamento (paginado). */
|
|
292
|
+
async list(params = {}) {
|
|
293
|
+
return this.http.request("/api/payment-links", {
|
|
294
|
+
query: { page: params.page, pageSize: params.pageSize }
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
/** Lista transações de um link de pagamento (paginado). */
|
|
298
|
+
async transactions(id, params = {}) {
|
|
299
|
+
return this.http.request(
|
|
300
|
+
`/api/payment-links/${encodeURIComponent(id)}/transactions`,
|
|
301
|
+
{ query: { page: params.page, pageSize: params.pageSize } }
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
/** Itera todos os links de pagamento, página a página. */
|
|
305
|
+
listAll(params = {}) {
|
|
306
|
+
return paginate((page) => this.list({ ...params, page }));
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// src/resources/transactions.ts
|
|
311
|
+
var Transactions = class {
|
|
312
|
+
constructor(http) {
|
|
313
|
+
this.http = http;
|
|
314
|
+
}
|
|
315
|
+
/** Lista transações (paginado, com filtro opcional por status). */
|
|
316
|
+
async list(params = {}) {
|
|
317
|
+
return this.http.request("/api/transactions/", {
|
|
318
|
+
query: {
|
|
319
|
+
status: params.status,
|
|
320
|
+
search: params.search,
|
|
321
|
+
customer: params.customer,
|
|
322
|
+
page: params.page,
|
|
323
|
+
pageSize: params.pageSize
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
/** Busca uma transação pelo id. */
|
|
328
|
+
async get(id) {
|
|
329
|
+
return this.http.request(`/api/transactions/${encodeURIComponent(id)}/`);
|
|
330
|
+
}
|
|
331
|
+
/** Estorna (refund) uma transação. */
|
|
332
|
+
async refund(id, body = {}) {
|
|
333
|
+
return this.http.request(
|
|
334
|
+
`/api/transactions/${encodeURIComponent(id)}/refund/`,
|
|
335
|
+
{ method: "POST", body }
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
/** Itera todas as transações, página a página. */
|
|
339
|
+
listAll(params = {}) {
|
|
340
|
+
return paginate((page) => this.list({ ...params, page }));
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// src/webhooks.ts
|
|
345
|
+
var import_node_crypto = require("crypto");
|
|
346
|
+
var SINNU_SIGNATURE_HEADER = "x-sinnu-signature";
|
|
347
|
+
var SinnuWebhookSignatureError = class extends SinnuError {
|
|
348
|
+
constructor(message) {
|
|
349
|
+
super(message, 0);
|
|
350
|
+
this.name = "SinnuWebhookSignatureError";
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
function normalizeSignature(signature) {
|
|
354
|
+
if (signature === void 0) {
|
|
355
|
+
throw new SinnuWebhookSignatureError(
|
|
356
|
+
"[@sinnu-dev/sdk] header de assinatura ausente. Esperado 'X-Sinnu-Signature'."
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
const value = Array.isArray(signature) ? signature[0] : signature;
|
|
360
|
+
if (!value) {
|
|
361
|
+
throw new SinnuWebhookSignatureError("[@sinnu-dev/sdk] header de assinatura vazio.");
|
|
362
|
+
}
|
|
363
|
+
return value.startsWith("sha256=") ? value.slice("sha256=".length) : value;
|
|
364
|
+
}
|
|
365
|
+
function toBuffer(payload) {
|
|
366
|
+
return typeof payload === "string" ? Buffer.from(payload, "utf8") : payload;
|
|
367
|
+
}
|
|
368
|
+
function computeSignature(payload, secret) {
|
|
369
|
+
return (0, import_node_crypto.createHmac)("sha256", secret).update(toBuffer(payload)).digest("hex");
|
|
370
|
+
}
|
|
371
|
+
function verifySignature(options) {
|
|
372
|
+
const { payload, signature, secret } = options;
|
|
373
|
+
if (!secret) {
|
|
374
|
+
throw new SinnuWebhookSignatureError(
|
|
375
|
+
"[@sinnu-dev/sdk] webhook secret \xE9 obrigat\xF3rio para verificar a assinatura."
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
const provided = normalizeSignature(signature);
|
|
379
|
+
const expected = computeSignature(payload, secret);
|
|
380
|
+
const providedBuf = Buffer.from(provided, "hex");
|
|
381
|
+
const expectedBuf = Buffer.from(expected, "hex");
|
|
382
|
+
if (providedBuf.length !== expectedBuf.length) {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
return (0, import_node_crypto.timingSafeEqual)(providedBuf, expectedBuf);
|
|
386
|
+
}
|
|
387
|
+
function constructEvent(options) {
|
|
388
|
+
if (!verifySignature(options)) {
|
|
389
|
+
throw new SinnuWebhookSignatureError(
|
|
390
|
+
"[@sinnu-dev/sdk] assinatura de webhook inv\xE1lida. Requisi\xE7\xE3o rejeitada."
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
const text = typeof options.payload === "string" ? options.payload : options.payload.toString("utf8");
|
|
394
|
+
try {
|
|
395
|
+
return JSON.parse(text);
|
|
396
|
+
} catch {
|
|
397
|
+
throw new SinnuWebhookSignatureError("[@sinnu-dev/sdk] corpo do webhook n\xE3o \xE9 um JSON v\xE1lido.");
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
var webhooks = {
|
|
401
|
+
signatureHeader: SINNU_SIGNATURE_HEADER,
|
|
402
|
+
computeSignature,
|
|
403
|
+
verifySignature,
|
|
404
|
+
constructEvent
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// src/index.ts
|
|
408
|
+
var Sinnu = class {
|
|
409
|
+
constructor(options) {
|
|
410
|
+
/** Verificação de webhooks (HMAC SHA-256): não faz requisições. */
|
|
411
|
+
this.webhooks = webhooks;
|
|
412
|
+
this.http = createTypedClient(options);
|
|
413
|
+
const raw = new RawHttp(
|
|
414
|
+
options.baseUrl ?? DEFAULT_BASE_URL,
|
|
415
|
+
options.apiKey,
|
|
416
|
+
options.headers ?? {},
|
|
417
|
+
options.fetch ?? fetch
|
|
418
|
+
);
|
|
419
|
+
this.links = new Links(raw);
|
|
420
|
+
this.transactions = new Transactions(raw);
|
|
421
|
+
this.checkout = new Checkout({ checkoutBaseUrl: options.checkoutBaseUrl });
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
var index_default = Sinnu;
|
|
425
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
426
|
+
0 && (module.exports = {
|
|
427
|
+
Checkout,
|
|
428
|
+
DEFAULT_BASE_URL,
|
|
429
|
+
DEFAULT_CHECKOUT_BASE_URL,
|
|
430
|
+
SINNU_SIGNATURE_HEADER,
|
|
431
|
+
Sinnu,
|
|
432
|
+
SinnuAuthError,
|
|
433
|
+
SinnuError,
|
|
434
|
+
SinnuNotFoundError,
|
|
435
|
+
SinnuRateLimitError,
|
|
436
|
+
SinnuValidationError,
|
|
437
|
+
SinnuWebhookSignatureError,
|
|
438
|
+
collectAll,
|
|
439
|
+
computeSignature,
|
|
440
|
+
constructEvent,
|
|
441
|
+
createTypedClient,
|
|
442
|
+
paginate,
|
|
443
|
+
unwrap,
|
|
444
|
+
verifySignature,
|
|
445
|
+
webhooks
|
|
446
|
+
});
|