@cashela/payin 0.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 ADDED
@@ -0,0 +1,86 @@
1
+ # @cashela/payin
2
+
3
+ Zero-runtime-dependency Node.js SDK for the Cashela Pay-In API. Uses global
4
+ `fetch` and `node:crypto`. Requires Node >= 18.
5
+
6
+ **Server-only.** This package is meant to run on your backend. `api_secret`
7
+ and your webhook signing secret must never be sent to, embedded in, or
8
+ otherwise reach the browser. Nothing exported here is safe for client-side
9
+ use.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install @cashela/payin
15
+ ```
16
+
17
+ ## Usage: `CashelaPayIn`
18
+
19
+ ```ts
20
+ import { CashelaPayIn } from "@cashela/payin";
21
+
22
+ const payin = new CashelaPayIn({
23
+ environment: "sandbox", // "dev" | "staging" | "sandbox" | "production"
24
+ apiKey: process.env.CASHELA_API_KEY!,
25
+ apiSecret: process.env.CASHELA_API_SECRET!,
26
+ });
27
+
28
+ const deposit = await payin.createDeposit(
29
+ {
30
+ payment_type: "bank_transfer",
31
+ payment_method: "pse",
32
+ amount: 50000,
33
+ country: "COL",
34
+ external_identifier: "order-1234",
35
+ invoice_description: "Order #1234",
36
+ notification_url: "https://example.com/webhooks/cashela",
37
+ redirect_url: "https://example.com/checkout/return",
38
+ logo: "https://example.com/logo.png",
39
+ mobile: false,
40
+ language: "es",
41
+ fee_on_payer: true,
42
+ client_ip: "203.0.113.10",
43
+ },
44
+ { idempotencyKey: "order-1234" },
45
+ );
46
+
47
+ const transaction = await payin.getTransaction(deposit.data.reference);
48
+ ```
49
+
50
+ `createDeposit` is never retried automatically by the SDK — retrying a
51
+ deposit-creation POST can move money twice. If a request times out or the
52
+ network fails, check `getTransaction` before deciding whether to resend.
53
+
54
+ ## Usage: `verifyWebhookSignature` in a handler
55
+
56
+ ```ts
57
+ import { verifyWebhookSignature, WebhookSignatureError } from "@cashela/payin";
58
+
59
+ // Example: a plain Node http handler receiving the raw request body.
60
+ app.post("/webhooks/cashela", async (req, res) => {
61
+ const rawBody = await readRawBody(req); // read bytes BEFORE any JSON parsing
62
+
63
+ try {
64
+ const payload = verifyWebhookSignature({
65
+ rawBody,
66
+ signature: req.headers["x-cashela-signature"] as string,
67
+ timestamp: req.headers["x-cashela-timestamp"] as string,
68
+ nonce: req.headers["x-cashela-nonce"] as string,
69
+ secret: process.env.CASHELA_WEBHOOK_SECRET!,
70
+ });
71
+
72
+ // payload.status is one of COMPLETED | EXPIRED | CANCELLED | VOID | REFUND
73
+ await handleTransactionUpdate(payload);
74
+ res.sendStatus(200);
75
+ } catch (err) {
76
+ if (err instanceof WebhookSignatureError) {
77
+ res.sendStatus(400); // missing-header | bad-signature | stale-timestamp
78
+ return;
79
+ }
80
+ throw err;
81
+ }
82
+ });
83
+ ```
84
+
85
+ Verify against the **raw, unparsed** request body — re-serializing JSON
86
+ before verifying will break the signature check.
package/dist/index.cjs ADDED
@@ -0,0 +1,139 @@
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 index_exports = {};
22
+ __export(index_exports, {
23
+ CashelaApiError: () => CashelaApiError,
24
+ CashelaPayIn: () => CashelaPayIn,
25
+ WebhookSignatureError: () => WebhookSignatureError,
26
+ resolveBaseUrl: () => resolveBaseUrl,
27
+ verifyWebhookSignature: () => verifyWebhookSignature
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+
31
+ // src/environment.ts
32
+ var ENVS = ["dev", "staging", "sandbox", "production"];
33
+ function resolveBaseUrl(env, override) {
34
+ if (override) return override;
35
+ if (!ENVS.includes(env)) throw new Error(`unknown environment: ${env}`);
36
+ return `https://${env}-api.cashela.com/api/v1/pay-in`;
37
+ }
38
+
39
+ // src/errors.ts
40
+ var CashelaApiError = class extends Error {
41
+ constructor(message, status, raw) {
42
+ super(message);
43
+ this.status = status;
44
+ this.raw = raw;
45
+ this.name = "CashelaApiError";
46
+ }
47
+ status;
48
+ raw;
49
+ };
50
+ var WebhookSignatureError = class extends Error {
51
+ constructor(reason) {
52
+ super(`webhook signature invalid: ${reason}`);
53
+ this.reason = reason;
54
+ this.name = "WebhookSignatureError";
55
+ }
56
+ reason;
57
+ };
58
+
59
+ // src/client.ts
60
+ var CashelaPayIn = class {
61
+ baseUrl;
62
+ auth;
63
+ fetch;
64
+ timeoutMs;
65
+ constructor(opts) {
66
+ this.baseUrl = resolveBaseUrl(opts.environment, opts.baseUrl);
67
+ this.auth = "Basic " + Buffer.from(`${opts.apiKey}:${opts.apiSecret}`).toString("base64");
68
+ this.fetch = opts.fetch ?? globalThis.fetch;
69
+ this.timeoutMs = opts.timeoutMs ?? 3e4;
70
+ }
71
+ async request(method, path, body, extraHeaders) {
72
+ const controller = new AbortController();
73
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
74
+ try {
75
+ const res = await this.fetch(`${this.baseUrl}${path}`, {
76
+ method,
77
+ headers: { Authorization: this.auth, "Content-Type": "application/json", Accept: "application/json", ...extraHeaders },
78
+ body: body === void 0 ? void 0 : JSON.stringify(body),
79
+ signal: controller.signal
80
+ });
81
+ const json = await res.json().catch(() => ({}));
82
+ if (!res.ok || json?.success === false) {
83
+ throw new CashelaApiError(json?.message ?? `HTTP ${res.status}`, res.status, json);
84
+ }
85
+ return json;
86
+ } finally {
87
+ clearTimeout(timer);
88
+ }
89
+ }
90
+ listCountries(params) {
91
+ const q = new URLSearchParams();
92
+ params?.payment_types?.forEach((t) => q.append("payment_types", t));
93
+ params?.countries?.forEach((c) => q.append("countries", c));
94
+ const qs = q.toString();
95
+ return this.request("GET", `/deposit-creation/countries${qs ? `?${qs}` : ""}`);
96
+ }
97
+ listPaymentMethods(body) {
98
+ return this.request("POST", "/deposit-creation/available-payment-methods", body);
99
+ }
100
+ getExchangeRates(body) {
101
+ return this.request("POST", "/deposit-creation/exchange-rates", body);
102
+ }
103
+ createDeposit(request, opts) {
104
+ return this.request("POST", "/deposit-creation", request, opts?.idempotencyKey ? { "Idempotency-Key": opts.idempotencyKey } : void 0);
105
+ }
106
+ getTransaction(reference) {
107
+ return this.request("GET", `/transactions/${encodeURIComponent(reference)}`);
108
+ }
109
+ resendCallback(reference) {
110
+ return this.request("POST", `/transactions/${encodeURIComponent(reference)}/callback`);
111
+ }
112
+ };
113
+
114
+ // src/webhook.ts
115
+ var import_node_crypto = require("crypto");
116
+ function verifyWebhookSignature(input) {
117
+ const { rawBody, signature, timestamp, nonce, secret } = input;
118
+ if (!signature || !timestamp || !nonce || rawBody == null || secret == null) {
119
+ throw new WebhookSignatureError("missing-header");
120
+ }
121
+ const tolerance = input.toleranceSeconds ?? 300;
122
+ const now = input.now ?? Math.floor(Date.now() / 1e3);
123
+ const ts = Number(timestamp);
124
+ if (!Number.isFinite(ts) || Math.abs(now - ts) > tolerance) throw new WebhookSignatureError("stale-timestamp");
125
+ const body = typeof rawBody === "string" ? rawBody : rawBody.toString("utf8");
126
+ const expected = (0, import_node_crypto.createHmac)("sha256", secret).update(`${timestamp}.${nonce}.${body}`).digest("hex");
127
+ const a = Buffer.from(expected, "utf8");
128
+ const b = Buffer.from(signature, "utf8");
129
+ if (a.length !== b.length || !(0, import_node_crypto.timingSafeEqual)(a, b)) throw new WebhookSignatureError("bad-signature");
130
+ return JSON.parse(body);
131
+ }
132
+ // Annotate the CommonJS export names for ESM import in node:
133
+ 0 && (module.exports = {
134
+ CashelaApiError,
135
+ CashelaPayIn,
136
+ WebhookSignatureError,
137
+ resolveBaseUrl,
138
+ verifyWebhookSignature
139
+ });
@@ -0,0 +1,416 @@
1
+ type CashelaEnvironment = "dev" | "staging" | "sandbox" | "production";
2
+ declare function resolveBaseUrl(env: CashelaEnvironment, override?: string): string;
3
+
4
+ interface WebhookPayload {
5
+ ref: string;
6
+ external_identifier: string;
7
+ status: "COMPLETED" | "EXPIRED" | "CANCELLED" | "VOID" | "REFUND";
8
+ date: string;
9
+ amount: number;
10
+ currency: string;
11
+ settlement_currency?: string;
12
+ net_amount?: number;
13
+ processing_fee_amount?: number;
14
+ fx_spread_amount?: number;
15
+ tax_amount?: number;
16
+ }
17
+ interface VerifyInput {
18
+ rawBody: string | Buffer;
19
+ signature: string;
20
+ timestamp: string;
21
+ nonce: string;
22
+ secret: string;
23
+ toleranceSeconds?: number;
24
+ now?: number;
25
+ }
26
+ declare function verifyWebhookSignature(input: VerifyInput): WebhookPayload;
27
+
28
+ type FetchLike = (url: string, init?: any) => Promise<any>;
29
+ interface CashelaPayInOptions {
30
+ environment: CashelaEnvironment;
31
+ apiKey: string;
32
+ apiSecret: string;
33
+ baseUrl?: string;
34
+ timeoutMs?: number;
35
+ fetch?: FetchLike;
36
+ }
37
+ interface CreateDepositOptions {
38
+ idempotencyKey?: string;
39
+ }
40
+ declare class CashelaPayIn {
41
+ private readonly baseUrl;
42
+ private readonly auth;
43
+ private readonly fetch;
44
+ private readonly timeoutMs;
45
+ constructor(opts: CashelaPayInOptions);
46
+ private request;
47
+ listCountries(params?: {
48
+ payment_types?: string[];
49
+ countries?: string[];
50
+ }): Promise<any>;
51
+ listPaymentMethods(body: {
52
+ country: string;
53
+ amount: number;
54
+ fee_on_payer?: boolean;
55
+ currency?: string;
56
+ }): Promise<any>;
57
+ getExchangeRates(body: {
58
+ country: string;
59
+ payment_type: string;
60
+ amount: number;
61
+ }): Promise<any>;
62
+ createDeposit(request: Record<string, unknown>, opts?: CreateDepositOptions): Promise<any>;
63
+ getTransaction(reference: string): Promise<any>;
64
+ resendCallback(reference: string): Promise<any>;
65
+ }
66
+
67
+ declare class CashelaApiError extends Error {
68
+ readonly status: number;
69
+ readonly raw: unknown;
70
+ constructor(message: string, status: number, raw: unknown);
71
+ }
72
+ type WebhookSignatureReason = "missing-header" | "bad-signature" | "stale-timestamp";
73
+ declare class WebhookSignatureError extends Error {
74
+ readonly reason: WebhookSignatureReason;
75
+ constructor(reason: WebhookSignatureReason);
76
+ }
77
+
78
+ /**
79
+ * This file was auto-generated by openapi-typescript.
80
+ * Do not make direct changes to the file.
81
+ */
82
+
83
+ interface paths {
84
+ "/deposit-creation/countries": {
85
+ parameters: {
86
+ query?: never;
87
+ header?: never;
88
+ path?: never;
89
+ cookie?: never;
90
+ };
91
+ /** Lista países disponibles */
92
+ get: operations["listCountries"];
93
+ put?: never;
94
+ post?: never;
95
+ delete?: never;
96
+ options?: never;
97
+ head?: never;
98
+ patch?: never;
99
+ trace?: never;
100
+ };
101
+ "/deposit-creation/available-payment-methods": {
102
+ parameters: {
103
+ query?: never;
104
+ header?: never;
105
+ path?: never;
106
+ cookie?: never;
107
+ };
108
+ get?: never;
109
+ put?: never;
110
+ /** Métodos cotizables para país + monto */
111
+ post: operations["listPaymentMethods"];
112
+ delete?: never;
113
+ options?: never;
114
+ head?: never;
115
+ patch?: never;
116
+ trace?: never;
117
+ };
118
+ "/deposit-creation/exchange-rates": {
119
+ parameters: {
120
+ query?: never;
121
+ header?: never;
122
+ path?: never;
123
+ cookie?: never;
124
+ };
125
+ get?: never;
126
+ put?: never;
127
+ /** Tasa de cambio de un corredor */
128
+ post: operations["getExchangeRates"];
129
+ delete?: never;
130
+ options?: never;
131
+ head?: never;
132
+ patch?: never;
133
+ trace?: never;
134
+ };
135
+ "/deposit-creation": {
136
+ parameters: {
137
+ query?: never;
138
+ header?: never;
139
+ path?: never;
140
+ cookie?: never;
141
+ };
142
+ get?: never;
143
+ put?: never;
144
+ /** Crear depósito (mueve dinero) */
145
+ post: operations["createDeposit"];
146
+ delete?: never;
147
+ options?: never;
148
+ head?: never;
149
+ patch?: never;
150
+ trace?: never;
151
+ };
152
+ "/transactions/{reference}": {
153
+ parameters: {
154
+ query?: never;
155
+ header?: never;
156
+ path?: never;
157
+ cookie?: never;
158
+ };
159
+ /** Leer un depósito (ref, external_identifier o id interno) */
160
+ get: operations["getTransaction"];
161
+ put?: never;
162
+ post?: never;
163
+ delete?: never;
164
+ options?: never;
165
+ head?: never;
166
+ patch?: never;
167
+ trace?: never;
168
+ };
169
+ "/transactions/{reference}/callback": {
170
+ parameters: {
171
+ query?: never;
172
+ header?: never;
173
+ path?: never;
174
+ cookie?: never;
175
+ };
176
+ get?: never;
177
+ put?: never;
178
+ /** Reenviar la notificación de resultado (solo si terminal) */
179
+ post: operations["resendCallback"];
180
+ delete?: never;
181
+ options?: never;
182
+ head?: never;
183
+ patch?: never;
184
+ trace?: never;
185
+ };
186
+ }
187
+ interface components {
188
+ schemas: {
189
+ Envelope: {
190
+ success: boolean;
191
+ message: string;
192
+ data?: unknown;
193
+ };
194
+ DepositCreationRequest: {
195
+ payment_type: string;
196
+ payment_method: string;
197
+ amount: number;
198
+ country: string;
199
+ external_identifier: string;
200
+ invoice_description: string;
201
+ /** Format: uri */
202
+ notification_url: string;
203
+ /** Format: uri */
204
+ redirect_url: string;
205
+ /** Format: uri */
206
+ logo: string;
207
+ mobile: boolean;
208
+ /** @enum {string} */
209
+ language: "en" | "es";
210
+ fee_on_payer: boolean;
211
+ client_ip: string;
212
+ first_name?: string;
213
+ last_name?: string;
214
+ /** Format: email */
215
+ email?: string;
216
+ phone?: string;
217
+ /** @description Clave (no etiqueta). Ej: CHL=RUT, COL=CC|CE|NIT. */
218
+ document_key?: string;
219
+ document_number?: string;
220
+ address?: string;
221
+ zip?: string;
222
+ city_id?: string;
223
+ };
224
+ WebhookPayload: {
225
+ ref: string;
226
+ external_identifier: string;
227
+ /** @enum {string} */
228
+ status: "COMPLETED" | "EXPIRED" | "CANCELLED" | "VOID" | "REFUND";
229
+ date: string;
230
+ amount: number;
231
+ currency: string;
232
+ settlement_currency?: string;
233
+ net_amount?: number;
234
+ processing_fee_amount?: number;
235
+ fx_spread_amount?: number;
236
+ tax_amount?: number;
237
+ };
238
+ };
239
+ responses: never;
240
+ parameters: never;
241
+ requestBodies: never;
242
+ headers: never;
243
+ pathItems: never;
244
+ }
245
+ interface operations {
246
+ listCountries: {
247
+ parameters: {
248
+ query?: {
249
+ payment_types?: string[];
250
+ countries?: string[];
251
+ };
252
+ header?: never;
253
+ path?: never;
254
+ cookie?: never;
255
+ };
256
+ requestBody?: never;
257
+ responses: {
258
+ /** @description OK */
259
+ 200: {
260
+ headers: {
261
+ [name: string]: unknown;
262
+ };
263
+ content: {
264
+ "application/json": components["schemas"]["Envelope"];
265
+ };
266
+ };
267
+ };
268
+ };
269
+ listPaymentMethods: {
270
+ parameters: {
271
+ query?: never;
272
+ header?: never;
273
+ path?: never;
274
+ cookie?: never;
275
+ };
276
+ requestBody: {
277
+ content: {
278
+ "application/json": {
279
+ country: string;
280
+ amount: number;
281
+ fee_on_payer?: boolean;
282
+ currency?: string;
283
+ };
284
+ };
285
+ };
286
+ responses: {
287
+ /** @description OK */
288
+ 200: {
289
+ headers: {
290
+ [name: string]: unknown;
291
+ };
292
+ content: {
293
+ "application/json": components["schemas"]["Envelope"];
294
+ };
295
+ };
296
+ };
297
+ };
298
+ getExchangeRates: {
299
+ parameters: {
300
+ query?: never;
301
+ header?: never;
302
+ path?: never;
303
+ cookie?: never;
304
+ };
305
+ requestBody: {
306
+ content: {
307
+ "application/json": {
308
+ country: string;
309
+ payment_type: string;
310
+ amount: number;
311
+ };
312
+ };
313
+ };
314
+ responses: {
315
+ /** @description OK */
316
+ 200: {
317
+ headers: {
318
+ [name: string]: unknown;
319
+ };
320
+ content: {
321
+ "application/json": components["schemas"]["Envelope"];
322
+ };
323
+ };
324
+ };
325
+ };
326
+ createDeposit: {
327
+ parameters: {
328
+ query?: never;
329
+ header?: {
330
+ "Idempotency-Key"?: string;
331
+ };
332
+ path?: never;
333
+ cookie?: never;
334
+ };
335
+ requestBody: {
336
+ content: {
337
+ "application/json": components["schemas"]["DepositCreationRequest"];
338
+ };
339
+ };
340
+ responses: {
341
+ /** @description Creado */
342
+ 200: {
343
+ headers: {
344
+ [name: string]: unknown;
345
+ };
346
+ content: {
347
+ "application/json": components["schemas"]["Envelope"];
348
+ };
349
+ };
350
+ /** @description Validación */
351
+ 422: {
352
+ headers: {
353
+ [name: string]: unknown;
354
+ };
355
+ content: {
356
+ "application/json": components["schemas"]["Envelope"];
357
+ };
358
+ };
359
+ };
360
+ };
361
+ getTransaction: {
362
+ parameters: {
363
+ query?: never;
364
+ header?: never;
365
+ path: {
366
+ reference: string;
367
+ };
368
+ cookie?: never;
369
+ };
370
+ requestBody?: never;
371
+ responses: {
372
+ /** @description OK */
373
+ 200: {
374
+ headers: {
375
+ [name: string]: unknown;
376
+ };
377
+ content: {
378
+ "application/json": components["schemas"]["Envelope"];
379
+ };
380
+ };
381
+ /** @description No encontrado */
382
+ 404: {
383
+ headers: {
384
+ [name: string]: unknown;
385
+ };
386
+ content: {
387
+ "application/json": components["schemas"]["Envelope"];
388
+ };
389
+ };
390
+ };
391
+ };
392
+ resendCallback: {
393
+ parameters: {
394
+ query?: never;
395
+ header?: never;
396
+ path: {
397
+ reference: string;
398
+ };
399
+ cookie?: never;
400
+ };
401
+ requestBody?: never;
402
+ responses: {
403
+ /** @description Reencolado */
404
+ 200: {
405
+ headers: {
406
+ [name: string]: unknown;
407
+ };
408
+ content: {
409
+ "application/json": components["schemas"]["Envelope"];
410
+ };
411
+ };
412
+ };
413
+ };
414
+ }
415
+
416
+ export { CashelaApiError, type CashelaEnvironment, CashelaPayIn, type CashelaPayInOptions, type CreateDepositOptions, type VerifyInput, type WebhookPayload, WebhookSignatureError, type WebhookSignatureReason, type components, type paths, resolveBaseUrl, verifyWebhookSignature };
@@ -0,0 +1,416 @@
1
+ type CashelaEnvironment = "dev" | "staging" | "sandbox" | "production";
2
+ declare function resolveBaseUrl(env: CashelaEnvironment, override?: string): string;
3
+
4
+ interface WebhookPayload {
5
+ ref: string;
6
+ external_identifier: string;
7
+ status: "COMPLETED" | "EXPIRED" | "CANCELLED" | "VOID" | "REFUND";
8
+ date: string;
9
+ amount: number;
10
+ currency: string;
11
+ settlement_currency?: string;
12
+ net_amount?: number;
13
+ processing_fee_amount?: number;
14
+ fx_spread_amount?: number;
15
+ tax_amount?: number;
16
+ }
17
+ interface VerifyInput {
18
+ rawBody: string | Buffer;
19
+ signature: string;
20
+ timestamp: string;
21
+ nonce: string;
22
+ secret: string;
23
+ toleranceSeconds?: number;
24
+ now?: number;
25
+ }
26
+ declare function verifyWebhookSignature(input: VerifyInput): WebhookPayload;
27
+
28
+ type FetchLike = (url: string, init?: any) => Promise<any>;
29
+ interface CashelaPayInOptions {
30
+ environment: CashelaEnvironment;
31
+ apiKey: string;
32
+ apiSecret: string;
33
+ baseUrl?: string;
34
+ timeoutMs?: number;
35
+ fetch?: FetchLike;
36
+ }
37
+ interface CreateDepositOptions {
38
+ idempotencyKey?: string;
39
+ }
40
+ declare class CashelaPayIn {
41
+ private readonly baseUrl;
42
+ private readonly auth;
43
+ private readonly fetch;
44
+ private readonly timeoutMs;
45
+ constructor(opts: CashelaPayInOptions);
46
+ private request;
47
+ listCountries(params?: {
48
+ payment_types?: string[];
49
+ countries?: string[];
50
+ }): Promise<any>;
51
+ listPaymentMethods(body: {
52
+ country: string;
53
+ amount: number;
54
+ fee_on_payer?: boolean;
55
+ currency?: string;
56
+ }): Promise<any>;
57
+ getExchangeRates(body: {
58
+ country: string;
59
+ payment_type: string;
60
+ amount: number;
61
+ }): Promise<any>;
62
+ createDeposit(request: Record<string, unknown>, opts?: CreateDepositOptions): Promise<any>;
63
+ getTransaction(reference: string): Promise<any>;
64
+ resendCallback(reference: string): Promise<any>;
65
+ }
66
+
67
+ declare class CashelaApiError extends Error {
68
+ readonly status: number;
69
+ readonly raw: unknown;
70
+ constructor(message: string, status: number, raw: unknown);
71
+ }
72
+ type WebhookSignatureReason = "missing-header" | "bad-signature" | "stale-timestamp";
73
+ declare class WebhookSignatureError extends Error {
74
+ readonly reason: WebhookSignatureReason;
75
+ constructor(reason: WebhookSignatureReason);
76
+ }
77
+
78
+ /**
79
+ * This file was auto-generated by openapi-typescript.
80
+ * Do not make direct changes to the file.
81
+ */
82
+
83
+ interface paths {
84
+ "/deposit-creation/countries": {
85
+ parameters: {
86
+ query?: never;
87
+ header?: never;
88
+ path?: never;
89
+ cookie?: never;
90
+ };
91
+ /** Lista países disponibles */
92
+ get: operations["listCountries"];
93
+ put?: never;
94
+ post?: never;
95
+ delete?: never;
96
+ options?: never;
97
+ head?: never;
98
+ patch?: never;
99
+ trace?: never;
100
+ };
101
+ "/deposit-creation/available-payment-methods": {
102
+ parameters: {
103
+ query?: never;
104
+ header?: never;
105
+ path?: never;
106
+ cookie?: never;
107
+ };
108
+ get?: never;
109
+ put?: never;
110
+ /** Métodos cotizables para país + monto */
111
+ post: operations["listPaymentMethods"];
112
+ delete?: never;
113
+ options?: never;
114
+ head?: never;
115
+ patch?: never;
116
+ trace?: never;
117
+ };
118
+ "/deposit-creation/exchange-rates": {
119
+ parameters: {
120
+ query?: never;
121
+ header?: never;
122
+ path?: never;
123
+ cookie?: never;
124
+ };
125
+ get?: never;
126
+ put?: never;
127
+ /** Tasa de cambio de un corredor */
128
+ post: operations["getExchangeRates"];
129
+ delete?: never;
130
+ options?: never;
131
+ head?: never;
132
+ patch?: never;
133
+ trace?: never;
134
+ };
135
+ "/deposit-creation": {
136
+ parameters: {
137
+ query?: never;
138
+ header?: never;
139
+ path?: never;
140
+ cookie?: never;
141
+ };
142
+ get?: never;
143
+ put?: never;
144
+ /** Crear depósito (mueve dinero) */
145
+ post: operations["createDeposit"];
146
+ delete?: never;
147
+ options?: never;
148
+ head?: never;
149
+ patch?: never;
150
+ trace?: never;
151
+ };
152
+ "/transactions/{reference}": {
153
+ parameters: {
154
+ query?: never;
155
+ header?: never;
156
+ path?: never;
157
+ cookie?: never;
158
+ };
159
+ /** Leer un depósito (ref, external_identifier o id interno) */
160
+ get: operations["getTransaction"];
161
+ put?: never;
162
+ post?: never;
163
+ delete?: never;
164
+ options?: never;
165
+ head?: never;
166
+ patch?: never;
167
+ trace?: never;
168
+ };
169
+ "/transactions/{reference}/callback": {
170
+ parameters: {
171
+ query?: never;
172
+ header?: never;
173
+ path?: never;
174
+ cookie?: never;
175
+ };
176
+ get?: never;
177
+ put?: never;
178
+ /** Reenviar la notificación de resultado (solo si terminal) */
179
+ post: operations["resendCallback"];
180
+ delete?: never;
181
+ options?: never;
182
+ head?: never;
183
+ patch?: never;
184
+ trace?: never;
185
+ };
186
+ }
187
+ interface components {
188
+ schemas: {
189
+ Envelope: {
190
+ success: boolean;
191
+ message: string;
192
+ data?: unknown;
193
+ };
194
+ DepositCreationRequest: {
195
+ payment_type: string;
196
+ payment_method: string;
197
+ amount: number;
198
+ country: string;
199
+ external_identifier: string;
200
+ invoice_description: string;
201
+ /** Format: uri */
202
+ notification_url: string;
203
+ /** Format: uri */
204
+ redirect_url: string;
205
+ /** Format: uri */
206
+ logo: string;
207
+ mobile: boolean;
208
+ /** @enum {string} */
209
+ language: "en" | "es";
210
+ fee_on_payer: boolean;
211
+ client_ip: string;
212
+ first_name?: string;
213
+ last_name?: string;
214
+ /** Format: email */
215
+ email?: string;
216
+ phone?: string;
217
+ /** @description Clave (no etiqueta). Ej: CHL=RUT, COL=CC|CE|NIT. */
218
+ document_key?: string;
219
+ document_number?: string;
220
+ address?: string;
221
+ zip?: string;
222
+ city_id?: string;
223
+ };
224
+ WebhookPayload: {
225
+ ref: string;
226
+ external_identifier: string;
227
+ /** @enum {string} */
228
+ status: "COMPLETED" | "EXPIRED" | "CANCELLED" | "VOID" | "REFUND";
229
+ date: string;
230
+ amount: number;
231
+ currency: string;
232
+ settlement_currency?: string;
233
+ net_amount?: number;
234
+ processing_fee_amount?: number;
235
+ fx_spread_amount?: number;
236
+ tax_amount?: number;
237
+ };
238
+ };
239
+ responses: never;
240
+ parameters: never;
241
+ requestBodies: never;
242
+ headers: never;
243
+ pathItems: never;
244
+ }
245
+ interface operations {
246
+ listCountries: {
247
+ parameters: {
248
+ query?: {
249
+ payment_types?: string[];
250
+ countries?: string[];
251
+ };
252
+ header?: never;
253
+ path?: never;
254
+ cookie?: never;
255
+ };
256
+ requestBody?: never;
257
+ responses: {
258
+ /** @description OK */
259
+ 200: {
260
+ headers: {
261
+ [name: string]: unknown;
262
+ };
263
+ content: {
264
+ "application/json": components["schemas"]["Envelope"];
265
+ };
266
+ };
267
+ };
268
+ };
269
+ listPaymentMethods: {
270
+ parameters: {
271
+ query?: never;
272
+ header?: never;
273
+ path?: never;
274
+ cookie?: never;
275
+ };
276
+ requestBody: {
277
+ content: {
278
+ "application/json": {
279
+ country: string;
280
+ amount: number;
281
+ fee_on_payer?: boolean;
282
+ currency?: string;
283
+ };
284
+ };
285
+ };
286
+ responses: {
287
+ /** @description OK */
288
+ 200: {
289
+ headers: {
290
+ [name: string]: unknown;
291
+ };
292
+ content: {
293
+ "application/json": components["schemas"]["Envelope"];
294
+ };
295
+ };
296
+ };
297
+ };
298
+ getExchangeRates: {
299
+ parameters: {
300
+ query?: never;
301
+ header?: never;
302
+ path?: never;
303
+ cookie?: never;
304
+ };
305
+ requestBody: {
306
+ content: {
307
+ "application/json": {
308
+ country: string;
309
+ payment_type: string;
310
+ amount: number;
311
+ };
312
+ };
313
+ };
314
+ responses: {
315
+ /** @description OK */
316
+ 200: {
317
+ headers: {
318
+ [name: string]: unknown;
319
+ };
320
+ content: {
321
+ "application/json": components["schemas"]["Envelope"];
322
+ };
323
+ };
324
+ };
325
+ };
326
+ createDeposit: {
327
+ parameters: {
328
+ query?: never;
329
+ header?: {
330
+ "Idempotency-Key"?: string;
331
+ };
332
+ path?: never;
333
+ cookie?: never;
334
+ };
335
+ requestBody: {
336
+ content: {
337
+ "application/json": components["schemas"]["DepositCreationRequest"];
338
+ };
339
+ };
340
+ responses: {
341
+ /** @description Creado */
342
+ 200: {
343
+ headers: {
344
+ [name: string]: unknown;
345
+ };
346
+ content: {
347
+ "application/json": components["schemas"]["Envelope"];
348
+ };
349
+ };
350
+ /** @description Validación */
351
+ 422: {
352
+ headers: {
353
+ [name: string]: unknown;
354
+ };
355
+ content: {
356
+ "application/json": components["schemas"]["Envelope"];
357
+ };
358
+ };
359
+ };
360
+ };
361
+ getTransaction: {
362
+ parameters: {
363
+ query?: never;
364
+ header?: never;
365
+ path: {
366
+ reference: string;
367
+ };
368
+ cookie?: never;
369
+ };
370
+ requestBody?: never;
371
+ responses: {
372
+ /** @description OK */
373
+ 200: {
374
+ headers: {
375
+ [name: string]: unknown;
376
+ };
377
+ content: {
378
+ "application/json": components["schemas"]["Envelope"];
379
+ };
380
+ };
381
+ /** @description No encontrado */
382
+ 404: {
383
+ headers: {
384
+ [name: string]: unknown;
385
+ };
386
+ content: {
387
+ "application/json": components["schemas"]["Envelope"];
388
+ };
389
+ };
390
+ };
391
+ };
392
+ resendCallback: {
393
+ parameters: {
394
+ query?: never;
395
+ header?: never;
396
+ path: {
397
+ reference: string;
398
+ };
399
+ cookie?: never;
400
+ };
401
+ requestBody?: never;
402
+ responses: {
403
+ /** @description Reencolado */
404
+ 200: {
405
+ headers: {
406
+ [name: string]: unknown;
407
+ };
408
+ content: {
409
+ "application/json": components["schemas"]["Envelope"];
410
+ };
411
+ };
412
+ };
413
+ };
414
+ }
415
+
416
+ export { CashelaApiError, type CashelaEnvironment, CashelaPayIn, type CashelaPayInOptions, type CreateDepositOptions, type VerifyInput, type WebhookPayload, WebhookSignatureError, type WebhookSignatureReason, type components, type paths, resolveBaseUrl, verifyWebhookSignature };
package/dist/index.js ADDED
@@ -0,0 +1,108 @@
1
+ // src/environment.ts
2
+ var ENVS = ["dev", "staging", "sandbox", "production"];
3
+ function resolveBaseUrl(env, override) {
4
+ if (override) return override;
5
+ if (!ENVS.includes(env)) throw new Error(`unknown environment: ${env}`);
6
+ return `https://${env}-api.cashela.com/api/v1/pay-in`;
7
+ }
8
+
9
+ // src/errors.ts
10
+ var CashelaApiError = class extends Error {
11
+ constructor(message, status, raw) {
12
+ super(message);
13
+ this.status = status;
14
+ this.raw = raw;
15
+ this.name = "CashelaApiError";
16
+ }
17
+ status;
18
+ raw;
19
+ };
20
+ var WebhookSignatureError = class extends Error {
21
+ constructor(reason) {
22
+ super(`webhook signature invalid: ${reason}`);
23
+ this.reason = reason;
24
+ this.name = "WebhookSignatureError";
25
+ }
26
+ reason;
27
+ };
28
+
29
+ // src/client.ts
30
+ var CashelaPayIn = class {
31
+ baseUrl;
32
+ auth;
33
+ fetch;
34
+ timeoutMs;
35
+ constructor(opts) {
36
+ this.baseUrl = resolveBaseUrl(opts.environment, opts.baseUrl);
37
+ this.auth = "Basic " + Buffer.from(`${opts.apiKey}:${opts.apiSecret}`).toString("base64");
38
+ this.fetch = opts.fetch ?? globalThis.fetch;
39
+ this.timeoutMs = opts.timeoutMs ?? 3e4;
40
+ }
41
+ async request(method, path, body, extraHeaders) {
42
+ const controller = new AbortController();
43
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
44
+ try {
45
+ const res = await this.fetch(`${this.baseUrl}${path}`, {
46
+ method,
47
+ headers: { Authorization: this.auth, "Content-Type": "application/json", Accept: "application/json", ...extraHeaders },
48
+ body: body === void 0 ? void 0 : JSON.stringify(body),
49
+ signal: controller.signal
50
+ });
51
+ const json = await res.json().catch(() => ({}));
52
+ if (!res.ok || json?.success === false) {
53
+ throw new CashelaApiError(json?.message ?? `HTTP ${res.status}`, res.status, json);
54
+ }
55
+ return json;
56
+ } finally {
57
+ clearTimeout(timer);
58
+ }
59
+ }
60
+ listCountries(params) {
61
+ const q = new URLSearchParams();
62
+ params?.payment_types?.forEach((t) => q.append("payment_types", t));
63
+ params?.countries?.forEach((c) => q.append("countries", c));
64
+ const qs = q.toString();
65
+ return this.request("GET", `/deposit-creation/countries${qs ? `?${qs}` : ""}`);
66
+ }
67
+ listPaymentMethods(body) {
68
+ return this.request("POST", "/deposit-creation/available-payment-methods", body);
69
+ }
70
+ getExchangeRates(body) {
71
+ return this.request("POST", "/deposit-creation/exchange-rates", body);
72
+ }
73
+ createDeposit(request, opts) {
74
+ return this.request("POST", "/deposit-creation", request, opts?.idempotencyKey ? { "Idempotency-Key": opts.idempotencyKey } : void 0);
75
+ }
76
+ getTransaction(reference) {
77
+ return this.request("GET", `/transactions/${encodeURIComponent(reference)}`);
78
+ }
79
+ resendCallback(reference) {
80
+ return this.request("POST", `/transactions/${encodeURIComponent(reference)}/callback`);
81
+ }
82
+ };
83
+
84
+ // src/webhook.ts
85
+ import { createHmac, timingSafeEqual } from "crypto";
86
+ function verifyWebhookSignature(input) {
87
+ const { rawBody, signature, timestamp, nonce, secret } = input;
88
+ if (!signature || !timestamp || !nonce || rawBody == null || secret == null) {
89
+ throw new WebhookSignatureError("missing-header");
90
+ }
91
+ const tolerance = input.toleranceSeconds ?? 300;
92
+ const now = input.now ?? Math.floor(Date.now() / 1e3);
93
+ const ts = Number(timestamp);
94
+ if (!Number.isFinite(ts) || Math.abs(now - ts) > tolerance) throw new WebhookSignatureError("stale-timestamp");
95
+ const body = typeof rawBody === "string" ? rawBody : rawBody.toString("utf8");
96
+ const expected = createHmac("sha256", secret).update(`${timestamp}.${nonce}.${body}`).digest("hex");
97
+ const a = Buffer.from(expected, "utf8");
98
+ const b = Buffer.from(signature, "utf8");
99
+ if (a.length !== b.length || !timingSafeEqual(a, b)) throw new WebhookSignatureError("bad-signature");
100
+ return JSON.parse(body);
101
+ }
102
+ export {
103
+ CashelaApiError,
104
+ CashelaPayIn,
105
+ WebhookSignatureError,
106
+ resolveBaseUrl,
107
+ verifyWebhookSignature
108
+ };
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@cashela/payin",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "engines": { "node": ">=18" },
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" } },
10
+ "files": ["dist"],
11
+ "scripts": {
12
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
13
+ "typecheck": "tsc --noEmit",
14
+ "test": "node --import tsx --test test/**/*.test.ts",
15
+ "gen:types": "node scripts/gen-types.mjs",
16
+ "sync:vectors": "node scripts/sync-vectors.mjs"
17
+ },
18
+ "devDependencies": {
19
+ "tsup": "^8.0.0",
20
+ "tsx": "^4.19.0",
21
+ "typescript": "^5.6.0",
22
+ "openapi-typescript": "^7.4.0",
23
+ "@types/node": "^20.14.0"
24
+ }
25
+ }