@hello-bill/node 1.0.0 → 1.0.1
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/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +216 -0
- package/dist/express/index.d.cts +34 -0
- package/dist/express/index.d.ts +34 -0
- package/dist/express/index.js +1019 -0
- package/dist/express/index.js.map +1 -0
- package/dist/express/index.mjs +1016 -0
- package/dist/express/index.mjs.map +1 -0
- package/dist/hono/index.d.cts +37 -0
- package/dist/hono/index.d.ts +37 -0
- package/dist/hono/index.js +1036 -0
- package/dist/hono/index.js.map +1 -0
- package/dist/hono/index.mjs +1033 -0
- package/dist/hono/index.mjs.map +1 -0
- package/dist/index-BrfG82zs.d.cts +341 -0
- package/dist/index-EXetQn1f.d.ts +341 -0
- package/dist/index.d.cts +164 -0
- package/dist/index.d.ts +164 -0
- package/dist/index.js +1178 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1164 -0
- package/dist/index.mjs.map +1 -0
- package/dist/koa/index.d.cts +42 -0
- package/dist/koa/index.d.ts +42 -0
- package/dist/koa/index.js +1042 -0
- package/dist/koa/index.js.map +1 -0
- package/dist/koa/index.mjs +1039 -0
- package/dist/koa/index.mjs.map +1 -0
- package/dist/next/index.d.cts +60 -0
- package/dist/next/index.d.ts +60 -0
- package/dist/next/index.js +1092 -0
- package/dist/next/index.js.map +1 -0
- package/dist/next/index.mjs +1090 -0
- package/dist/next/index.mjs.map +1 -0
- package/dist/types/index.d.cts +51 -0
- package/dist/types/index.d.ts +51 -0
- package/dist/types/index.js +12 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/index.mjs +9 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/webhook/index.d.cts +2 -0
- package/dist/webhook/index.d.ts +2 -0
- package/dist/webhook/index.js +342 -0
- package/dist/webhook/index.js.map +1 -0
- package/dist/webhook/index.mjs +336 -0
- package/dist/webhook/index.mjs.map +1 -0
- package/dist/webhooks-DPpqf7d2.d.cts +751 -0
- package/dist/webhooks-DPpqf7d2.d.ts +751 -0
- package/package.json +164 -7
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@hello-bill/node` will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.0.1] - 2026-05-16
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Initial pre-release surface of the HelloBill Partner API SDK.
|
|
13
|
+
- `core` entry (`@hello-bill/node`): framework-agnostic
|
|
14
|
+
`createHellobillHandler` returning a handler set (`init`, `session`,
|
|
15
|
+
`loa`, `customer`, callbacks), plus `TokenManager`, `InMemoryTokenStorage`,
|
|
16
|
+
retry/idempotency helpers, and typed error classes.
|
|
17
|
+
- `express` entry (`@hello-bill/node/express`): `createHellobillRouter`
|
|
18
|
+
Express 4/5 adapter (peer-dep, optional) that wires the handler set onto
|
|
19
|
+
an `express.Router`.
|
|
20
|
+
- `webhook` entry (`@hello-bill/node/webhook`): `verifyWebhook` with
|
|
21
|
+
`WebhookVerificationError` and timing-safe HMAC checks; raw-body verifier
|
|
22
|
+
matching the Partner API signature header format.
|
|
23
|
+
- `types` entry (`@hello-bill/node/types`): spec-derived TypeScript surface
|
|
24
|
+
for Partner API v11 (sessions, products, LOA, customers, webhooks,
|
|
25
|
+
envelopes, error shapes).
|
|
26
|
+
- ESM + CJS dual build via `tsup` with per-entry `.d.ts` declarations and
|
|
27
|
+
sourcemaps. ESM-first; CJS provided for server-side consumers on older
|
|
28
|
+
bundlers.
|
|
29
|
+
|
|
30
|
+
### Notes
|
|
31
|
+
|
|
32
|
+
- `0.0.x` releases are pre-release and may change without notice. Promote
|
|
33
|
+
to `0.1.0` once the public surface is frozen.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 The Bunch
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# @hello-bill/node
|
|
2
|
+
|
|
3
|
+
Server-side SDK for the **HelloBill Partner API (Revision 1.13)** (`api_version 2026-05-10`).
|
|
4
|
+
Framework-agnostic core with an Express adapter; ships a webhook verifier and
|
|
5
|
+
the spec-derived TypeScript types.
|
|
6
|
+
|
|
7
|
+
- ESM only, bare TypeScript source — no runtime dependencies.
|
|
8
|
+
- Subpath imports: `@hello-bill/node/express`, `@hello-bill/node/webhook`, `@hello-bill/node/types`.
|
|
9
|
+
- Auto OAuth token caching, single-flight refresh, idempotency, retry with
|
|
10
|
+
`Retry-After`, per-attempt timeout.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @hello-bill/node
|
|
16
|
+
# or
|
|
17
|
+
bun add @hello-bill/node
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
> The package is currently `private: true` and consumed via the monorepo
|
|
21
|
+
> workspace. See **Pre-publish checklist** below.
|
|
22
|
+
|
|
23
|
+
## Quick start (Express)
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import express from 'express';
|
|
27
|
+
import { createHellobillRouter } from '@hello-bill/node/express';
|
|
28
|
+
|
|
29
|
+
const app = express();
|
|
30
|
+
|
|
31
|
+
app.use('/api/hellobill', createHellobillRouter({
|
|
32
|
+
clientId: process.env.HELLOBILL_CLIENT_ID!,
|
|
33
|
+
clientSecret: process.env.HELLOBILL_CLIENT_SECRET!,
|
|
34
|
+
apiBaseUrl: process.env.HELLOBILL_API_BASE_URL!,
|
|
35
|
+
buildSessionPayload: async (req) => {
|
|
36
|
+
// Map whatever your host page posted → canonical snake_case SessionPayload.
|
|
37
|
+
const body = req.body as { firstName: string; lastName: string; email: string };
|
|
38
|
+
return {
|
|
39
|
+
customer: {
|
|
40
|
+
first_name: body.firstName,
|
|
41
|
+
last_name: body.lastName,
|
|
42
|
+
email: body.email,
|
|
43
|
+
},
|
|
44
|
+
addresses: { current: { /* ... */ } },
|
|
45
|
+
move: { in: { move_in_date: '2026-06-01' } },
|
|
46
|
+
consent: { data_sharing_accepted: true },
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
app.listen(3000);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The router applies `express.json({ limit: '1mb' })` internally — do not wrap
|
|
55
|
+
it with another body parser.
|
|
56
|
+
|
|
57
|
+
## Endpoints mounted
|
|
58
|
+
|
|
59
|
+
`createHellobillRouter(opts)` mounts these paths under the prefix you choose:
|
|
60
|
+
|
|
61
|
+
| Method | Path | Upstream |
|
|
62
|
+
|--------|--------------|-----------------------------------------------------|
|
|
63
|
+
| POST | `/session` | `POST /partner/sessions` |
|
|
64
|
+
| GET | `/products` | `GET /partner/sessions/:session_id/products` |
|
|
65
|
+
| GET | `/loa` | `GET /partner/sessions/:session_id/loa` |
|
|
66
|
+
| POST | `/customers` | `POST /partner/sessions/:session_id/customers` |
|
|
67
|
+
| GET | `/status` | `GET /partner/customers/:customer_id/status` |
|
|
68
|
+
|
|
69
|
+
`/products`, `/loa`, `/customers` and `/status` require a session_token
|
|
70
|
+
Bearer (issued from `POST /session`). The SDK decodes it without verification
|
|
71
|
+
to extract `session_id` and forwards the upstream call with a fresh OAuth
|
|
72
|
+
access token.
|
|
73
|
+
|
|
74
|
+
## Framework-agnostic core
|
|
75
|
+
|
|
76
|
+
Partners on Hono / Koa / Fastify / Nitro / raw Node can skip the Express
|
|
77
|
+
adapter and use the core directly:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { createHellobillHandler } from '@hello-bill/node';
|
|
81
|
+
|
|
82
|
+
const handlers = createHellobillHandler({
|
|
83
|
+
clientId: '…', clientSecret: '…', apiBaseUrl: '…',
|
|
84
|
+
buildSessionPayload: async (req) => ({ /* … */ }),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Each handler takes the SDK's portable HellobillRequest / HellobillResponse.
|
|
88
|
+
// You write the ~15-line adapter that shapes your framework's req/res into them.
|
|
89
|
+
await handlers.session(req, res);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Webhook verification
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import express from 'express';
|
|
96
|
+
import { verifyWebhook, WebhookVerificationError } from '@hello-bill/node/webhook';
|
|
97
|
+
|
|
98
|
+
app.post(
|
|
99
|
+
'/api/hellobill/webhooks',
|
|
100
|
+
express.raw({ type: 'application/json' }), // ← raw bytes, not parsed JSON
|
|
101
|
+
(req, res) => {
|
|
102
|
+
try {
|
|
103
|
+
const event = verifyWebhook(
|
|
104
|
+
req.body,
|
|
105
|
+
req.header('x-hellobill-signature')!,
|
|
106
|
+
process.env.HELLOBILL_WEBHOOK_SECRET!,
|
|
107
|
+
{ tolerance: 300, supportedVersions: ['v1'] },
|
|
108
|
+
);
|
|
109
|
+
// event is typed as WebhookEvent — switch on event.type
|
|
110
|
+
res.status(204).end();
|
|
111
|
+
} catch (err) {
|
|
112
|
+
if (err instanceof WebhookVerificationError) return res.status(400).end();
|
|
113
|
+
throw err;
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- **Algorithm**: HMAC-SHA256 over `${unix_timestamp}.${raw_body}` (Stripe-style).
|
|
120
|
+
- **Header format**: `t=<unix>,v1=<hex>` (and optionally `v2=<hex>` during rotation).
|
|
121
|
+
- **Replay window**: 300s by default (`tolerance`).
|
|
122
|
+
- **Key rotation**: pass `supportedVersions: ['v1', 'v2']` and accept either; cut over once `v2` is verified end-to-end.
|
|
123
|
+
- **Constant-time compare**: implemented via `crypto.timingSafeEqual`.
|
|
124
|
+
|
|
125
|
+
**Never** parse and re-stringify the body before verification — the HMAC is
|
|
126
|
+
computed over the exact bytes the server sent.
|
|
127
|
+
|
|
128
|
+
## Token management
|
|
129
|
+
|
|
130
|
+
- Automatic `client_credentials` OAuth grant against `POST /auth/partner/token`.
|
|
131
|
+
- Cached in-process by default; refreshed at `expires_in − 30s`.
|
|
132
|
+
- **Single-flight**: parallel API calls during a refresh share one upstream request.
|
|
133
|
+
- Pluggable via `tokenStorage`:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import type { TokenStorage } from '@hello-bill/node';
|
|
137
|
+
|
|
138
|
+
const redisStorage: TokenStorage = {
|
|
139
|
+
async get() { /* return { accessToken, expiresAt } | null */ },
|
|
140
|
+
async set(tok) { /* persist */ },
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
createHellobillRouter({ /* …, */ tokenStorage: redisStorage });
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Retry policy
|
|
147
|
+
|
|
148
|
+
Applied to every upstream call:
|
|
149
|
+
|
|
150
|
+
- **Retried statuses**: `429`, `500`, `502`, `503`, `504`, plus
|
|
151
|
+
`auth.token_expired` (refreshes the token then retries once).
|
|
152
|
+
- **Backoff**: exponential with jitter; honours `Retry-After` (seconds or HTTP date).
|
|
153
|
+
- **Attempts**: up to 5 per call.
|
|
154
|
+
- **Timeout**: per-attempt, default 30 000 ms; override with `upstreamTimeoutMs`.
|
|
155
|
+
- **Network failures**: a hung / refused / aborted fetch becomes a clean
|
|
156
|
+
`internal.error` envelope (502/504 depending on failure kind).
|
|
157
|
+
|
|
158
|
+
## Idempotency
|
|
159
|
+
|
|
160
|
+
- The SDK auto-generates a UUID v4 `Idempotency-Key` for every POST.
|
|
161
|
+
- Override with `idempotencyKeyFactory: () => string`.
|
|
162
|
+
|
|
163
|
+
**Gotcha**: the Partner API caches the first response keyed by `Idempotency-Key`
|
|
164
|
+
*and ignores the body on subsequent requests with the same key*. If you reuse
|
|
165
|
+
the same key for two semantically-different POSTs (e.g. two different customer
|
|
166
|
+
payloads), you get the **first** response back — not an error. Partners must
|
|
167
|
+
mint a distinct key per logical operation.
|
|
168
|
+
|
|
169
|
+
## Types
|
|
170
|
+
|
|
171
|
+
The full spec surface is re-exported under a stable subpath:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import type {
|
|
175
|
+
SessionPayload,
|
|
176
|
+
SessionResponse,
|
|
177
|
+
ProductsResponse,
|
|
178
|
+
LoaResponse,
|
|
179
|
+
CustomersPayload,
|
|
180
|
+
CustomersResponse,
|
|
181
|
+
StatusResponse,
|
|
182
|
+
WebhookEvent,
|
|
183
|
+
} from '@hello-bill/node/types';
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Coverage
|
|
187
|
+
|
|
188
|
+
Measured with `bun run test:coverage` (vitest + v8). 69 / 69 tests passing.
|
|
189
|
+
|
|
190
|
+
| Area | Statements | Branches | Functions | Lines |
|
|
191
|
+
|-----------|------------|----------|-----------|--------|
|
|
192
|
+
| **All** | 90.90 % | 84.95 % | 96.49 % | 94.70 % |
|
|
193
|
+
| `core/` | 91.63 % | 85.02 % | 97.43 % | 95.79 % |
|
|
194
|
+
| `express/`| 83.33 % | n/a | 90.90 % | 86.20 % |
|
|
195
|
+
| `webhook/`| 90.90 % | 89.18 % | 100.00 % | 94.11 % |
|
|
196
|
+
|
|
197
|
+
Uncovered lines are exhaustively defensive branches (e.g. unreachable
|
|
198
|
+
`else` in error envelope shaping, top-level Express error middleware that
|
|
199
|
+
fires only on developer mis-wiring).
|
|
200
|
+
|
|
201
|
+
## Pre-publish checklist
|
|
202
|
+
|
|
203
|
+
Before the first `npm publish`:
|
|
204
|
+
|
|
205
|
+
- [ ] Remove `"private": true` from `package.json`.
|
|
206
|
+
- [ ] Confirm the `"files"` field (currently `["src", "README.md"]`) — add a `dist/` if you ship compiled output.
|
|
207
|
+
- [ ] Add a build step (`tsup` or `tsc`) and point `"types"` at the emitted `.d.ts`.
|
|
208
|
+
- [ ] Verify all `"exports"` subpaths resolve post-build (`.`, `/express`, `/webhook`, `/types`).
|
|
209
|
+
- [ ] Choose a licence + add `LICENSE` file.
|
|
210
|
+
- [ ] Add `"repository"` and `"homepage"` fields.
|
|
211
|
+
- [ ] Seed `CHANGELOG.md` (recommend Keep a Changelog).
|
|
212
|
+
- [ ] Run `npm pack --dry-run` and review the tarball contents.
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
TBD.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { C as CreateHellobillHandlerOptions } from '../index-BrfG82zs.cjs';
|
|
2
|
+
import '../webhooks-DPpqf7d2.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Express adapter for the framework-agnostic SDK core.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { createHellobillRouter } from '@hello-bill/node/express';
|
|
9
|
+
* app.use('/api/hellobill', createHellobillRouter({ ... }));
|
|
10
|
+
*
|
|
11
|
+
* `express` is an OPTIONAL peer dependency — it's loaded lazily via `require`
|
|
12
|
+
* so consumers that never import this adapter pay nothing.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
type AnyRouter = any;
|
|
16
|
+
/**
|
|
17
|
+
* Creates an Express Router that mounts the HelloBill endpoints:
|
|
18
|
+
* POST /webhooks (optional — only when opts.webhook is provided)
|
|
19
|
+
* POST /session
|
|
20
|
+
* GET /loa
|
|
21
|
+
* POST /customers
|
|
22
|
+
* GET /status
|
|
23
|
+
* GET /products
|
|
24
|
+
* GET /property
|
|
25
|
+
* POST /bank-details
|
|
26
|
+
* GET /bank-details
|
|
27
|
+
* GET /sessions (additive — spec §3.6 + §9.2.1)
|
|
28
|
+
* GET /sessions/:id (additive — spec §3.7 + §9.2.1)
|
|
29
|
+
*
|
|
30
|
+
* Mount it under a path of your choice, e.g. `/api/hellobill`.
|
|
31
|
+
*/
|
|
32
|
+
declare function createHellobillRouter(opts: CreateHellobillHandlerOptions): AnyRouter;
|
|
33
|
+
|
|
34
|
+
export { createHellobillRouter };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { C as CreateHellobillHandlerOptions } from '../index-EXetQn1f.js';
|
|
2
|
+
import '../webhooks-DPpqf7d2.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Express adapter for the framework-agnostic SDK core.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { createHellobillRouter } from '@hello-bill/node/express';
|
|
9
|
+
* app.use('/api/hellobill', createHellobillRouter({ ... }));
|
|
10
|
+
*
|
|
11
|
+
* `express` is an OPTIONAL peer dependency — it's loaded lazily via `require`
|
|
12
|
+
* so consumers that never import this adapter pay nothing.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
type AnyRouter = any;
|
|
16
|
+
/**
|
|
17
|
+
* Creates an Express Router that mounts the HelloBill endpoints:
|
|
18
|
+
* POST /webhooks (optional — only when opts.webhook is provided)
|
|
19
|
+
* POST /session
|
|
20
|
+
* GET /loa
|
|
21
|
+
* POST /customers
|
|
22
|
+
* GET /status
|
|
23
|
+
* GET /products
|
|
24
|
+
* GET /property
|
|
25
|
+
* POST /bank-details
|
|
26
|
+
* GET /bank-details
|
|
27
|
+
* GET /sessions (additive — spec §3.6 + §9.2.1)
|
|
28
|
+
* GET /sessions/:id (additive — spec §3.7 + §9.2.1)
|
|
29
|
+
*
|
|
30
|
+
* Mount it under a path of your choice, e.g. `/api/hellobill`.
|
|
31
|
+
*/
|
|
32
|
+
declare function createHellobillRouter(opts: CreateHellobillHandlerOptions): AnyRouter;
|
|
33
|
+
|
|
34
|
+
export { createHellobillRouter };
|