@havenpay/server 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/README.md +190 -96
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -1,160 +1,254 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @havenpay/server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Server-side TypeScript SDK for Havenpay.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`@havenpay/server` is the backend SDK for secret-key operations: PaymentIntent creation, project management, API key lifecycle, account lifecycle, provider account credential-reference management, refund requests, ledger-backed balance transaction reads, webhook endpoint management, event replay, and raw-body webhook signature verification.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Use it only from trusted backend infrastructure. Never bundle it into browsers, React Native apps, edge pages that expose client code, or public UI packages.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
bun add @havenpay/server
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm install @havenpay/server
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Use it for
|
|
20
|
+
|
|
21
|
+
- Creating PaymentIntents from a merchant server.
|
|
22
|
+
- Returning a short-lived payment `clientSecret` to browser or mobile clients.
|
|
23
|
+
- Managing projects, API keys, connected accounts, webhook endpoints, and events.
|
|
24
|
+
- Verifying Havenpay webhook signatures against the exact raw request body.
|
|
25
|
+
- Managing provider account encrypted credential references.
|
|
26
|
+
- Reading balance transactions and creating refund requests through server-only APIs.
|
|
27
|
+
|
|
28
|
+
## Quickstart
|
|
8
29
|
|
|
9
30
|
```ts
|
|
31
|
+
import { randomUUID } from 'node:crypto'
|
|
10
32
|
import { Havenpay } from '@havenpay/server'
|
|
11
33
|
|
|
12
|
-
const
|
|
13
|
-
secretKey: process.env.HAVENPAY_SECRET_KEY
|
|
34
|
+
const havenpay = new Havenpay({
|
|
35
|
+
secretKey: process.env.HAVENPAY_SECRET_KEY,
|
|
14
36
|
apiVersion: '2026-05-24.haven',
|
|
37
|
+
retry: {
|
|
38
|
+
maxAttempts: 3,
|
|
39
|
+
initialDelayMs: 100,
|
|
40
|
+
maxDelayMs: 2000,
|
|
41
|
+
},
|
|
42
|
+
timeoutMs: 30000,
|
|
15
43
|
})
|
|
16
44
|
|
|
17
|
-
const
|
|
45
|
+
const intent = await havenpay.paymentIntents.create({
|
|
18
46
|
projectId: 'proj_...',
|
|
19
47
|
amount: 1000,
|
|
20
48
|
currency: 'usd',
|
|
21
|
-
customer: {
|
|
22
|
-
|
|
23
|
-
|
|
49
|
+
customer: {
|
|
50
|
+
phone: '+263771234567',
|
|
51
|
+
},
|
|
52
|
+
mobileMoney: {
|
|
53
|
+
provider: 'omari',
|
|
54
|
+
phone: '+263771234567',
|
|
55
|
+
},
|
|
56
|
+
metadata: {
|
|
57
|
+
orderId: 'order_123',
|
|
58
|
+
},
|
|
59
|
+
idempotencyKey: randomUUID(),
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
return Response.json({
|
|
63
|
+
paymentIntentId: intent.id,
|
|
64
|
+
clientSecret: intent.clientSecret,
|
|
24
65
|
})
|
|
25
66
|
```
|
|
26
67
|
|
|
27
|
-
|
|
68
|
+
Send `clientSecret` only to the client that is completing this one payment. Keep `secretKey` on the server.
|
|
28
69
|
|
|
29
|
-
|
|
30
|
-
- `apiKeys.create/list/revoke`
|
|
31
|
-
- `balanceTransactions.list`
|
|
32
|
-
- `events.list/replay`
|
|
33
|
-
- `paymentIntents.create/retrieve`
|
|
34
|
-
- `projects.create/list/update/archive/getConfig`
|
|
35
|
-
- `providerAccounts.create/list/rotate/revoke`
|
|
36
|
-
- `refunds.create/retrieve/list`
|
|
37
|
-
- `webhookEndpoints.create/list/update/delete`
|
|
38
|
-
- `webhooks.constructEvent`
|
|
70
|
+
## Webhook verification
|
|
39
71
|
|
|
40
|
-
|
|
72
|
+
Webhook verification requires the exact raw body bytes sent by Havenpay. Do not parse JSON before signature verification.
|
|
41
73
|
|
|
42
|
-
|
|
74
|
+
```ts
|
|
75
|
+
import { Havenpay } from '@havenpay/server'
|
|
43
76
|
|
|
44
|
-
|
|
45
|
-
|
|
77
|
+
const havenpay = new Havenpay({
|
|
78
|
+
secretKey: process.env.HAVENPAY_SECRET_KEY,
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const event = havenpay.webhooks.constructEvent({
|
|
82
|
+
rawBody,
|
|
83
|
+
signature: request.headers.get('Havenpay-Signature') ?? '',
|
|
84
|
+
secret: process.env.HAVENPAY_WEBHOOK_SECRET,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
switch (event.type) {
|
|
88
|
+
case 'payment_intent.succeeded':
|
|
89
|
+
// Fulfil the order in an idempotent server-side transaction.
|
|
90
|
+
break
|
|
91
|
+
case 'payment_intent.failed':
|
|
92
|
+
// Mark the order as failed only after validating the event payload.
|
|
93
|
+
break
|
|
94
|
+
}
|
|
46
95
|
```
|
|
47
96
|
|
|
48
|
-
|
|
97
|
+
## Main resources
|
|
49
98
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
- `type`
|
|
55
|
-
- `code`
|
|
99
|
+
```text
|
|
100
|
+
havenpay.accounts.create(...)
|
|
101
|
+
havenpay.accounts.list(...)
|
|
102
|
+
havenpay.accounts.update(...)
|
|
56
103
|
|
|
57
|
-
|
|
58
|
-
|
|
104
|
+
havenpay.projects.create(...)
|
|
105
|
+
havenpay.projects.list(...)
|
|
106
|
+
havenpay.projects.update(...)
|
|
107
|
+
havenpay.projects.archive(...)
|
|
108
|
+
havenpay.projects.getConfig(...)
|
|
59
109
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
catch (error) {
|
|
64
|
-
if (error instanceof HavenpayApiError) {
|
|
65
|
-
console.error(error.status, error.requestId, error.code)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
```
|
|
110
|
+
havenpay.apiKeys.create(...)
|
|
111
|
+
havenpay.apiKeys.list(...)
|
|
112
|
+
havenpay.apiKeys.revoke(...)
|
|
69
113
|
|
|
70
|
-
|
|
114
|
+
havenpay.paymentIntents.create(...)
|
|
115
|
+
havenpay.paymentIntents.retrieve(...)
|
|
71
116
|
|
|
72
|
-
|
|
73
|
-
|
|
117
|
+
havenpay.refunds.create(...)
|
|
118
|
+
havenpay.refunds.retrieve(...)
|
|
119
|
+
havenpay.refunds.list(...)
|
|
74
120
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
121
|
+
havenpay.providerAccounts.create(...)
|
|
122
|
+
havenpay.providerAccounts.list(...)
|
|
123
|
+
havenpay.providerAccounts.rotate(...)
|
|
124
|
+
havenpay.providerAccounts.revoke(...)
|
|
125
|
+
|
|
126
|
+
havenpay.balanceTransactions.list(...)
|
|
127
|
+
|
|
128
|
+
havenpay.webhookEndpoints.create(...)
|
|
129
|
+
havenpay.webhookEndpoints.list(...)
|
|
130
|
+
havenpay.webhookEndpoints.update(...)
|
|
131
|
+
havenpay.webhookEndpoints.rotateSecret(...)
|
|
132
|
+
havenpay.webhookEndpoints.delete(...)
|
|
133
|
+
|
|
134
|
+
havenpay.events.list(...)
|
|
135
|
+
havenpay.events.replay(...)
|
|
136
|
+
|
|
137
|
+
havenpay.webhooks.constructEvent(...)
|
|
83
138
|
```
|
|
84
139
|
|
|
85
|
-
##
|
|
140
|
+
## Provider account credential references
|
|
86
141
|
|
|
87
|
-
|
|
142
|
+
Provider account APIs accept encrypted credential references and key fingerprints. They must not accept or return raw provider secret material.
|
|
88
143
|
|
|
89
144
|
```ts
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
145
|
+
await havenpay.providerAccounts.create({
|
|
146
|
+
projectId: 'proj_...',
|
|
147
|
+
provider: 'omari',
|
|
148
|
+
encryptedCredentialRef: 'kms://havenpay/provider-credentials/...',
|
|
149
|
+
keyFingerprint: 'fp_...',
|
|
150
|
+
metadata: {
|
|
151
|
+
label: 'Primary test credential',
|
|
96
152
|
},
|
|
97
153
|
})
|
|
98
154
|
```
|
|
99
155
|
|
|
100
|
-
|
|
101
|
-
- Mutating requests retry only when they carry an `Idempotency-Key`.
|
|
102
|
-
- Do not retry validation, authentication, authorization, tenant-scope, or not-found errors without changing the request or credentials.
|
|
156
|
+
Live provider capability should be enabled only when the provider adapter, credentials, contract tests, and operational reconciliation path prove that lane.
|
|
103
157
|
|
|
104
|
-
##
|
|
158
|
+
## Error handling
|
|
105
159
|
|
|
106
|
-
|
|
160
|
+
Non-2xx API responses throw `HavenpayApiError`.
|
|
107
161
|
|
|
108
162
|
```ts
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
163
|
+
import type { Havenpay } from '@havenpay/server'
|
|
164
|
+
import { HavenpayApiError } from '@havenpay/server'
|
|
165
|
+
|
|
166
|
+
async function _retrievePaymentIntent(havenpay: Havenpay) {
|
|
167
|
+
try {
|
|
168
|
+
return await havenpay.paymentIntents.retrieve('pi_...')
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
if (error instanceof HavenpayApiError) {
|
|
172
|
+
console.error(error.status, error.code, error.requestId)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
throw error
|
|
176
|
+
}
|
|
177
|
+
}
|
|
113
178
|
```
|
|
114
179
|
|
|
115
|
-
|
|
180
|
+
Errors expose request IDs and typed API metadata when the API returns the standard error envelope. Do not log request bodies, API keys, idempotency keys, webhook secrets, provider credentials, or client secrets.
|
|
116
181
|
|
|
117
|
-
|
|
182
|
+
## Retry and timeout policy
|
|
118
183
|
|
|
119
|
-
|
|
120
|
-
const _havenpay = new Havenpay({
|
|
121
|
-
secretKey: process.env.HAVENPAY_SECRET_KEY!,
|
|
122
|
-
timeoutMs: 30000,
|
|
123
|
-
})
|
|
124
|
-
```
|
|
184
|
+
Retries are disabled unless `retry` is configured.
|
|
125
185
|
|
|
126
|
-
|
|
186
|
+
- Network failures and HTTP 5xx responses may retry.
|
|
187
|
+
- Safe reads may retry.
|
|
188
|
+
- Mutating requests retry only when they include an `Idempotency-Key`.
|
|
189
|
+
- Validation, authentication, authorization, tenant-scope, and not-found errors should not be retried without changing the request or credentials.
|
|
190
|
+
- `timeoutMs` applies per request attempt.
|
|
127
191
|
|
|
128
192
|
## Observability
|
|
129
193
|
|
|
130
|
-
|
|
194
|
+
`onRequestEvent` emits redacted server-side request lifecycle events.
|
|
131
195
|
|
|
132
196
|
```ts
|
|
133
197
|
const _havenpay = new Havenpay({
|
|
134
|
-
secretKey: process.env.HAVENPAY_SECRET_KEY
|
|
198
|
+
secretKey: process.env.HAVENPAY_SECRET_KEY,
|
|
135
199
|
onRequestEvent: (event) => {
|
|
136
|
-
console.info(
|
|
200
|
+
console.info({
|
|
201
|
+
type: event.type,
|
|
202
|
+
operationName: event.operationName,
|
|
203
|
+
requestId: event.requestId,
|
|
204
|
+
status: event.status,
|
|
205
|
+
})
|
|
137
206
|
},
|
|
138
207
|
})
|
|
139
208
|
```
|
|
140
209
|
|
|
141
|
-
Events include method, path,
|
|
210
|
+
Events include method, path, operation name, attempt number, status, error kind, and API response request ID only. They do not include headers, request bodies, API keys, idempotency keys, provider credentials, webhook secrets, or client secrets.
|
|
211
|
+
|
|
212
|
+
## Security boundary
|
|
213
|
+
|
|
214
|
+
This is a server-only package. It may use Node/Bun server capabilities and secret-key authentication. It must never be imported by:
|
|
215
|
+
|
|
216
|
+
- `@havenpay/web`
|
|
217
|
+
- `@havenpay/web-elements`
|
|
218
|
+
- `@havenpay/react-native`
|
|
219
|
+
- browser bundles
|
|
220
|
+
- mobile client bundles
|
|
221
|
+
|
|
222
|
+
Keep these values server-side:
|
|
142
223
|
|
|
143
|
-
|
|
224
|
+
- `HAVENPAY_SECRET_KEY`
|
|
225
|
+
- webhook signing secrets
|
|
226
|
+
- provider credential references and fingerprints
|
|
227
|
+
- account admin tokens
|
|
228
|
+
- raw webhook request bodies
|
|
229
|
+
- raw provider response payloads
|
|
144
230
|
|
|
145
|
-
|
|
231
|
+
## Package boundaries
|
|
232
|
+
|
|
233
|
+
Use:
|
|
234
|
+
|
|
235
|
+
- `@havenpay/server` for trusted backend operations.
|
|
236
|
+
- `@havenpay/web` for browser payment-sheet flows.
|
|
237
|
+
- `@havenpay/web-elements` for optional browser custom elements.
|
|
238
|
+
- `@havenpay/react-native` for Expo and bare React Native payment flows.
|
|
239
|
+
- `@havenpay/core` for shared public types.
|
|
240
|
+
|
|
241
|
+
Client apps should receive only a public `projectId` and the short-lived payment `clientSecret` required for one payment flow.
|
|
242
|
+
|
|
243
|
+
## API-version pinning
|
|
244
|
+
|
|
245
|
+
Pass `apiVersion` to send `X-Haven-API-Version` with SDK requests:
|
|
146
246
|
|
|
147
247
|
```ts
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
secret: process.env.HAVENPAY_WEBHOOK_SECRET!,
|
|
248
|
+
const _havenpay = new Havenpay({
|
|
249
|
+
secretKey: process.env.HAVENPAY_SECRET_KEY,
|
|
250
|
+
apiVersion: '2026-05-24.haven',
|
|
152
251
|
})
|
|
153
252
|
```
|
|
154
253
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
- Do not use this package in browser or React Native code.
|
|
158
|
-
- Do not pass provider credentials or webhook secrets to client SDKs.
|
|
159
|
-
- Do not claim live provider support unless the provider adapter and tests prove it.
|
|
160
|
-
- Public client integrations should use `projectId` plus a short-lived payment `clientSecret` through the Web or React Native SDKs.
|
|
254
|
+
Pinning keeps backend integrations explicit as Havenpay evolves.
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@havenpay/server",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
5
|
-
"description": "
|
|
4
|
+
"version": "1.0.1",
|
|
5
|
+
"description": "Server-side TypeScript SDK for Havenpay payment, project, webhook, and account APIs",
|
|
6
6
|
"author": "7Haven",
|
|
7
7
|
"license": "MIT",
|
|
8
|
-
"homepage": "https://pay
|
|
8
|
+
"homepage": "https://github.com/7haveeen/haven-pay#readme",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "git+https://github.com/7haveeen/haven-pay.git",
|
|
@@ -16,9 +16,11 @@
|
|
|
16
16
|
"havenpay",
|
|
17
17
|
"payments",
|
|
18
18
|
"zimbabwe",
|
|
19
|
+
"mobile-money",
|
|
20
|
+
"typescript",
|
|
21
|
+
"server-sdk",
|
|
19
22
|
"omari",
|
|
20
23
|
"onemoney",
|
|
21
|
-
"mobile-money",
|
|
22
24
|
"payment-gateway"
|
|
23
25
|
],
|
|
24
26
|
"sideEffects": false,
|
|
@@ -36,8 +38,7 @@
|
|
|
36
38
|
"dist"
|
|
37
39
|
],
|
|
38
40
|
"publishConfig": {
|
|
39
|
-
"access": "public"
|
|
40
|
-
"provenance": true
|
|
41
|
+
"access": "public"
|
|
41
42
|
},
|
|
42
43
|
"engines": {
|
|
43
44
|
"node": ">=18.0.0"
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
"prepublishOnly": "bun run build && bun run typecheck"
|
|
53
54
|
},
|
|
54
55
|
"dependencies": {
|
|
55
|
-
"@havenpay/core": "1.0.
|
|
56
|
+
"@havenpay/core": "1.0.1"
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
59
|
"@types/node": "24.9.1",
|