@paybetaby/node-sdk 0.1.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 +666 -0
- package/dist/index.d.mts +437 -0
- package/dist/index.d.ts +437 -0
- package/dist/index.js +313 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +309 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
# @paybetaby/node-sdk
|
|
2
|
+
|
|
3
|
+
Official Node.js SDK for the [Paybeta](https://usepaybeta.com) payments API. Fully typed TypeScript library with zero runtime dependencies, native `fetch`, and dual CJS/ESM output.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Requirements](#requirements)
|
|
10
|
+
- [Installation](#installation)
|
|
11
|
+
- [Quick Start](#quick-start)
|
|
12
|
+
- [Authentication](#authentication)
|
|
13
|
+
- [Configuration](#configuration)
|
|
14
|
+
- [Resources](#resources)
|
|
15
|
+
- [Transactions](#transactions)
|
|
16
|
+
- [Payments](#payments)
|
|
17
|
+
- [Escrows](#escrows)
|
|
18
|
+
- [Disputes](#disputes)
|
|
19
|
+
- [Webhooks](#webhooks)
|
|
20
|
+
- [Error Handling](#error-handling)
|
|
21
|
+
- [TypeScript](#typescript)
|
|
22
|
+
- [Building from Source](#building-from-source)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Requirements
|
|
27
|
+
|
|
28
|
+
- Node.js **18** or later (uses native `fetch`)
|
|
29
|
+
- A Paybeta merchant account — [sign up at usepaybeta.com](https://usepaybeta.com)
|
|
30
|
+
- An API key from your Paybeta dashboard (`pb_live_…` for production, `pb_test_…` for sandbox)
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install @paybetaby/node-sdk
|
|
38
|
+
# or
|
|
39
|
+
yarn add @paybetaby/node-sdk
|
|
40
|
+
# or
|
|
41
|
+
pnpm add @paybetaby/node-sdk
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { PaybetaClient } from '@paybetaby/node-sdk';
|
|
50
|
+
|
|
51
|
+
const paybeta = new PaybetaClient({
|
|
52
|
+
apiKey: process.env.PAYBETA_API_KEY!,
|
|
53
|
+
webhookSecret: process.env.PAYBETA_WEBHOOK_SECRET,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// 1. Create a transaction
|
|
57
|
+
const transaction = await paybeta.transactions.create({
|
|
58
|
+
merchantId: 'your-merchant-id',
|
|
59
|
+
buyerEmail: 'buyer@example.com',
|
|
60
|
+
buyerPhone: '+2348012345678',
|
|
61
|
+
sellerEmail: 'seller@example.com',
|
|
62
|
+
amount: 500, // decimal naira (₦500.00) — unlike Payment.amount, which is kobo
|
|
63
|
+
currency: 'NGN',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// 2. Initiate payment — get a redirect URL for your customer
|
|
67
|
+
const payment = await paybeta.payments.initiate({
|
|
68
|
+
merchantId: 'your-merchant-id',
|
|
69
|
+
transactionId: transaction.id,
|
|
70
|
+
amount: 50_000,
|
|
71
|
+
currency: 'NGN',
|
|
72
|
+
paymentMethod: 'CARD',
|
|
73
|
+
pspType: 'PAYSTACK',
|
|
74
|
+
customerEmail: 'buyer@example.com',
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Redirect the customer to complete payment
|
|
78
|
+
console.log(payment.authorizationUrl);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Authentication
|
|
84
|
+
|
|
85
|
+
Paybeta uses **API key authentication**. Pass your key once when constructing the client — all subsequent requests carry it automatically via the `X-API-Key` header.
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const paybeta = new PaybetaClient({ apiKey: 'pb_live_...' });
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
| Key prefix | Environment |
|
|
92
|
+
|-------------|----------------------|
|
|
93
|
+
| `pb_live_…` | Production (live) |
|
|
94
|
+
| `pb_test_…` | Sandbox (test mode) |
|
|
95
|
+
|
|
96
|
+
> **Keep your API key secret.** Never embed it in client-side code or commit it to version control. Use environment variables.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Configuration
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
const paybeta = new PaybetaClient({
|
|
104
|
+
// Required
|
|
105
|
+
apiKey: 'pb_live_...',
|
|
106
|
+
|
|
107
|
+
// Optional — defaults to https://api.usepaybeta.com
|
|
108
|
+
baseUrl: 'https://api.usepaybeta.com',
|
|
109
|
+
|
|
110
|
+
// Optional — required only for webhook.constructEvent()
|
|
111
|
+
webhookSecret: 'your-webhook-secret',
|
|
112
|
+
|
|
113
|
+
// Optional — request timeout in milliseconds (default: 30000)
|
|
114
|
+
timeout: 30_000,
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `PaybetaClientConfig`
|
|
119
|
+
|
|
120
|
+
| Option | Type | Default | Description |
|
|
121
|
+
|-----------------|----------|--------------------------------|-----------------------------------------------|
|
|
122
|
+
| `apiKey` | `string` | **required** | Your Paybeta API key |
|
|
123
|
+
| `baseUrl` | `string` | `https://api.usepaybeta.com` | Override for staging or self-hosted instances |
|
|
124
|
+
| `webhookSecret` | `string` | `undefined` | HMAC secret for webhook signature verification|
|
|
125
|
+
| `timeout` | `number` | `30000` | Request timeout in milliseconds |
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Resources
|
|
130
|
+
|
|
131
|
+
### Transactions
|
|
132
|
+
|
|
133
|
+
A **transaction** represents the commercial relationship between a buyer and seller. Create one before initiating a payment or creating an escrow.
|
|
134
|
+
|
|
135
|
+
#### `paybeta.transactions.create(params)`
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
const transaction = await paybeta.transactions.create({
|
|
139
|
+
merchantId: 'your-merchant-id',
|
|
140
|
+
buyerEmail: 'buyer@example.com',
|
|
141
|
+
buyerPhone: '+2348012345678',
|
|
142
|
+
sellerEmail: 'seller@example.com',
|
|
143
|
+
amount: 1_500, // ₦1,500.00 — decimal naira, not kobo
|
|
144
|
+
currency: 'NGN',
|
|
145
|
+
metadata: {
|
|
146
|
+
orderId: 'ORD-001',
|
|
147
|
+
productName: 'MacBook Air',
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
console.log(transaction.id); // UUID
|
|
152
|
+
console.log(transaction.status); // 'INITIATED'
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### `paybeta.transactions.list(params?)`
|
|
156
|
+
|
|
157
|
+
Returns a bare array — there is no pagination envelope on this endpoint. Passing `merchantId` is required for an API-key caller: the API only allows merchant credentials to call `/transactions/merchant/:id`, not the bare `/transactions` list (that one's platform-role only and 403s an API key).
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
const transactions = await paybeta.transactions.list({
|
|
161
|
+
merchantId: 'your-merchant-id',
|
|
162
|
+
status: 'FUNDED',
|
|
163
|
+
limit: 20,
|
|
164
|
+
offset: 0,
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### `paybeta.transactions.retrieve(id)`
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const transaction = await paybeta.transactions.retrieve('txn-uuid');
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
#### `paybeta.transactions.listHistory(id)`
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
const events = await paybeta.transactions.listHistory('txn-uuid');
|
|
178
|
+
// Returns TransactionEvent[] — status changes, saga steps, etc.
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Transaction statuses:** `INITIATED` → `FUNDED` → `IN_ESCROW` → `RELEASED` / `DISPUTED` / `REFUNDED`
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
### Payments
|
|
186
|
+
|
|
187
|
+
A **payment** records a customer's attempt to fund a transaction via a PSP (Paystack, Flutterwave).
|
|
188
|
+
|
|
189
|
+
#### `paybeta.payments.initiate(params)`
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
const payment = await paybeta.payments.initiate({
|
|
193
|
+
merchantId: 'your-merchant-id',
|
|
194
|
+
transactionId: transaction.id,
|
|
195
|
+
amount: 150_000, // kobo — integer minor-unit, unlike Transaction.amount
|
|
196
|
+
currency: 'NGN',
|
|
197
|
+
paymentMethod: 'CARD', // 'CARD' | 'BANK_TRANSFER' | 'USSD' | 'MOBILE_MONEY' | 'BANK_ACCOUNT'
|
|
198
|
+
pspType: 'PAYSTACK', // 'PAYSTACK' | 'FLUTTERWAVE' | 'BANK_DIRECT'
|
|
199
|
+
customerEmail: 'buyer@example.com',
|
|
200
|
+
customerName: 'Jane Doe', // optional
|
|
201
|
+
redirectUrl: 'https://yourapp.com/payment/callback', // optional
|
|
202
|
+
metadata: { sessionId: 'abc' }, // optional
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Redirect your customer to complete the payment
|
|
206
|
+
window.location.href = payment.authorizationUrl!;
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### `paybeta.payments.verify(id)`
|
|
210
|
+
|
|
211
|
+
Call this when the customer returns from the PSP redirect to confirm the payment status.
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
const payment = await paybeta.payments.verify(paymentId);
|
|
215
|
+
|
|
216
|
+
if (payment.status === 'COMPLETED') {
|
|
217
|
+
// fulfil the order
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
#### `paybeta.payments.retrieve(id)`
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
const payment = await paybeta.payments.retrieve(paymentId);
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
#### `paybeta.payments.list(params?)`
|
|
228
|
+
|
|
229
|
+
Returns a bare array. `merchantId` is required for an API-key caller — same reasoning as transactions above.
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
const payments = await paybeta.payments.list({ merchantId: 'your-merchant-id', limit: 50 });
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
#### `paybeta.payments.retry(id)`
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
const retried = await paybeta.payments.retry(paymentId);
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
#### `paybeta.payments.listAttempts(id)`
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
const attempts = await paybeta.payments.listAttempts(paymentId);
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Payment statuses:** `PENDING` → `PROCESSING` → `COMPLETED` / `FAILED` / `CANCELLED`
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### Escrows
|
|
252
|
+
|
|
253
|
+
**Escrows** hold funds securely between buyer and seller until configurable release conditions are met. Available on **Growth** and **Enterprise** plans.
|
|
254
|
+
|
|
255
|
+
#### `paybeta.escrows.create(params)`
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
const escrow = await paybeta.escrows.create({
|
|
259
|
+
transactionId: transaction.id,
|
|
260
|
+
merchantId: 'your-merchant-id',
|
|
261
|
+
buyerEmail: 'buyer@example.com',
|
|
262
|
+
buyerPhone: '+2348012345678', // required — guaranteed WhatsApp/SMS delivery channel
|
|
263
|
+
sellerEmail: 'seller@example.com',
|
|
264
|
+
amount: 1_500, // decimal naira — the API converts to kobo itself
|
|
265
|
+
currency: 'NGN',
|
|
266
|
+
releasePolicy: { // optional
|
|
267
|
+
conditionLogic: 'AND', // release only when ALL conditions are met
|
|
268
|
+
conditions: [
|
|
269
|
+
{ type: 'DELIVERY_CONFIRMATION' },
|
|
270
|
+
{ type: 'BUYER_CONFIRMATION' },
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Condition types:**
|
|
277
|
+
|
|
278
|
+
| Type | Description |
|
|
279
|
+
|-------------------------|-----------------------------------------------------|
|
|
280
|
+
| `DELIVERY_CONFIRMATION` | Seller confirms goods/services delivered |
|
|
281
|
+
| `BUYER_CONFIRMATION` | Buyer explicitly approves release |
|
|
282
|
+
| `TIME_BASED` | Auto-release after a configured time window |
|
|
283
|
+
| `MANUAL_APPROVAL` | Merchant triggers release manually |
|
|
284
|
+
|
|
285
|
+
**Condition logic:**
|
|
286
|
+
|
|
287
|
+
| Value | Meaning |
|
|
288
|
+
|-------|-------------------------------------------|
|
|
289
|
+
| `AND` | All conditions must be met before release |
|
|
290
|
+
| `OR` | Any one condition triggers release |
|
|
291
|
+
|
|
292
|
+
#### `paybeta.escrows.release(id, params?)`
|
|
293
|
+
|
|
294
|
+
`actorId`/`actorType` are accepted for forward-compat only — the API always attributes the action to the authenticated caller, never a client-supplied value.
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
await paybeta.escrows.release(escrowId, { idempotencyKey: 'release-once' });
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
#### `paybeta.escrows.confirmDelivery(id, params?)`
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
await paybeta.escrows.confirmDelivery(escrowId, { trackingReference: 'DHL123456' });
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
#### `paybeta.escrows.confirmBuyer(id, params?)`
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
await paybeta.escrows.confirmBuyer(escrowId);
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
#### `paybeta.escrows.dispute(id)`
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
await paybeta.escrows.dispute(escrowId);
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
#### `paybeta.escrows.refund(id)`
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
await paybeta.escrows.refund(escrowId);
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
#### `paybeta.escrows.cancel(id)`
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
await paybeta.escrows.cancel(escrowId);
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
#### `paybeta.escrows.retrieve(id)`
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
const escrow = await paybeta.escrows.retrieve(escrowId);
|
|
334
|
+
console.log(escrow.status); // lowercase: 'funded' | 'pending_release' | 'released' | ...
|
|
335
|
+
console.log(escrow.amount); // kobo (integer) — not divided down on the way out, unlike on create
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
#### `paybeta.escrows.retrieveBalance(id)`
|
|
339
|
+
|
|
340
|
+
All three amounts are decimal strings (already divided from kobo), not numbers.
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
const { heldAmount, releasedAmount, refundedAmount, currency } = await paybeta.escrows.retrieveBalance(escrowId);
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
#### `paybeta.escrows.retrieveConditions(id)`
|
|
347
|
+
|
|
348
|
+
Returns an envelope, not a bare array.
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
const { conditions, canRelease } = await paybeta.escrows.retrieveConditions(escrowId);
|
|
352
|
+
conditions.forEach(c => console.log(c.type, c.isMet));
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
#### `paybeta.escrows.list(params?)`
|
|
356
|
+
|
|
357
|
+
Unlike payments/transactions/disputes, this returns a `{ escrows, total, limit, offset }` envelope, not a bare array. `merchantId` routes to the merchant-scoped endpoint, same as the other resources.
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
const { escrows, total } = await paybeta.escrows.list({
|
|
361
|
+
merchantId: 'your-merchant-id',
|
|
362
|
+
status: 'funded',
|
|
363
|
+
limit: 20,
|
|
364
|
+
});
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**Escrow statuses (lowercase):** `created` → `funded` → `pending_release` → `released` / `disputed` / `refunded` / `cancelled`
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
### Disputes
|
|
372
|
+
|
|
373
|
+
A **dispute** is opened when buyer and seller cannot agree. Paybeta provides a structured arbitration workflow.
|
|
374
|
+
|
|
375
|
+
#### `paybeta.disputes.open(params)`
|
|
376
|
+
|
|
377
|
+
All of the fields below are required by the API — there's no partial/inferred version of opening a dispute.
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
const dispute = await paybeta.disputes.open({
|
|
381
|
+
transactionId: transaction.id,
|
|
382
|
+
escrowId: escrow.id,
|
|
383
|
+
merchantId: 'your-merchant-id',
|
|
384
|
+
buyerEmail: 'buyer@example.com',
|
|
385
|
+
sellerEmail: 'seller@example.com',
|
|
386
|
+
disputeType: 'NON_DELIVERY',
|
|
387
|
+
priority: 'HIGH',
|
|
388
|
+
description: 'Item not as described.',
|
|
389
|
+
amount: 150_000, // kobo
|
|
390
|
+
currency: 'NGN',
|
|
391
|
+
openedBy: 'BUYER', // 'BUYER' | 'SELLER'
|
|
392
|
+
});
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
#### `paybeta.disputes.uploadEvidence(id, params)`
|
|
396
|
+
|
|
397
|
+
Field names match the API's JSON body exactly — not a generic `fileBase64`/`fileType`/`submittedBy` shape.
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
await paybeta.disputes.uploadEvidence(disputeId, {
|
|
401
|
+
evidenceType: 'IMAGE', // 'IMAGE' | 'DOCUMENT' | 'VIDEO' | 'OTHER'
|
|
402
|
+
uploadedBy: 'BUYER', // 'BUYER' | 'SELLER' | 'ARBITRATOR'
|
|
403
|
+
fileName: 'packaging.jpg',
|
|
404
|
+
fileData: Buffer.from(fileBytes).toString('base64'),
|
|
405
|
+
mimeType: 'image/jpeg',
|
|
406
|
+
description: 'Photo of damaged packaging',
|
|
407
|
+
});
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
#### `paybeta.disputes.resolve(id, params)`
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
await paybeta.disputes.resolve(disputeId, {
|
|
414
|
+
outcome: 'BUYER_WINS', // 'BUYER_WINS' | 'SELLER_WINS' | 'PARTIAL_REFUND' | 'PARTIAL_RELEASE' | 'SPLIT' | 'CANCELLED'
|
|
415
|
+
notes: 'Evidence confirmed item was not delivered.',
|
|
416
|
+
});
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
#### `paybeta.disputes.cancel(id, params)`
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
await paybeta.disputes.cancel(disputeId, { reason: 'Parties reached mutual agreement.' });
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
#### `paybeta.disputes.retrieve(id)` / `paybeta.disputes.list(params?)`
|
|
426
|
+
|
|
427
|
+
`list()` returns a bare array. `merchantId` is required for an API-key caller — same reasoning as transactions/payments above.
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
const dispute = await paybeta.disputes.retrieve(disputeId);
|
|
431
|
+
const disputes = await paybeta.disputes.list({ merchantId: 'your-merchant-id', status: 'OPENED' });
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
### Webhooks
|
|
437
|
+
|
|
438
|
+
Paybeta sends signed webhook events to your server when key state changes occur (payment completed, escrow released, dispute opened, etc.). Use `webhooks.constructEvent()` to verify the signature and parse the payload safely.
|
|
439
|
+
|
|
440
|
+
#### Setup
|
|
441
|
+
|
|
442
|
+
Configure your client with a `webhookSecret`:
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
const paybeta = new PaybetaClient({
|
|
446
|
+
apiKey: process.env.PAYBETA_API_KEY!,
|
|
447
|
+
webhookSecret: process.env.PAYBETA_WEBHOOK_SECRET!,
|
|
448
|
+
});
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
#### Express example
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
import express from 'express';
|
|
455
|
+
import { PaybetaClient, PaybetaError, type WebhookEvent } from '@paybetaby/node-sdk';
|
|
456
|
+
|
|
457
|
+
const app = express();
|
|
458
|
+
const paybeta = new PaybetaClient({
|
|
459
|
+
apiKey: process.env.PAYBETA_API_KEY!,
|
|
460
|
+
webhookSecret: process.env.PAYBETA_WEBHOOK_SECRET!,
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// IMPORTANT: use raw body — do NOT use express.json() for this route
|
|
464
|
+
app.post(
|
|
465
|
+
'/webhooks/paybeta',
|
|
466
|
+
express.raw({ type: '*/*' }),
|
|
467
|
+
(req, res) => {
|
|
468
|
+
const signature = req.headers['x-paybeta-signature'] as string;
|
|
469
|
+
const timestamp = req.headers['x-paybeta-timestamp'] as string;
|
|
470
|
+
|
|
471
|
+
let event: WebhookEvent;
|
|
472
|
+
try {
|
|
473
|
+
event = paybeta.webhooks.constructEvent(req.body, signature, timestamp);
|
|
474
|
+
} catch (err) {
|
|
475
|
+
if (err instanceof PaybetaError) {
|
|
476
|
+
console.error('Webhook signature invalid:', err.message);
|
|
477
|
+
return res.status(400).send('Webhook signature verification failed');
|
|
478
|
+
}
|
|
479
|
+
throw err;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
switch (event.eventType) {
|
|
483
|
+
case 'payment.received':
|
|
484
|
+
console.log('Payment received:', event.data);
|
|
485
|
+
// fulfil order, send confirmation email, etc.
|
|
486
|
+
break;
|
|
487
|
+
case 'transaction.funded':
|
|
488
|
+
console.log('Transaction funded:', event.data);
|
|
489
|
+
break;
|
|
490
|
+
case 'escrow.released':
|
|
491
|
+
console.log('Escrow released:', event.data);
|
|
492
|
+
break;
|
|
493
|
+
case 'dispute.opened':
|
|
494
|
+
console.log('Dispute opened:', event.data);
|
|
495
|
+
break;
|
|
496
|
+
default:
|
|
497
|
+
console.log('Unhandled event type:', event.eventType);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
res.json({ received: true });
|
|
501
|
+
}
|
|
502
|
+
);
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
`constructEvent` verifies `HMAC-SHA256(webhookSecret, "${timestamp}.${rawBody}")` against the `X-PayBeta-Signature` header (sent as `sha256=<hex>`) — both the signature *and* timestamp headers are required, since the timestamp is part of what's actually signed, not just metadata.
|
|
506
|
+
|
|
507
|
+
#### Event types
|
|
508
|
+
|
|
509
|
+
Exactly the events PayBeta can emit — there is no `dispute.cancelled` or any `escrow.*` event besides `escrow.released`.
|
|
510
|
+
|
|
511
|
+
| Event type | Description |
|
|
512
|
+
|---------------------------|--------------------------------------------------|
|
|
513
|
+
| `transaction.created` | New transaction created |
|
|
514
|
+
| `transaction.funded` | Customer's payment cleared; funds received |
|
|
515
|
+
| `transaction.escrowed` | Funds moved into escrow hold |
|
|
516
|
+
| `transaction.released` | Funds released to seller |
|
|
517
|
+
| `transaction.disputed` | Dispute opened on transaction |
|
|
518
|
+
| `transaction.refunded` | Transaction refunded to buyer |
|
|
519
|
+
| `payment.received` | Payment confirmed as successful |
|
|
520
|
+
| `payment.failed` | Payment failed or declined |
|
|
521
|
+
| `dispute.opened` | Dispute opened |
|
|
522
|
+
| `dispute.resolved` | Dispute resolved with outcome |
|
|
523
|
+
| `escrow.released` | Escrow funds disbursed to seller |
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
## Error Handling
|
|
528
|
+
|
|
529
|
+
The SDK throws two error types:
|
|
530
|
+
|
|
531
|
+
### `PaybetaApiError`
|
|
532
|
+
|
|
533
|
+
Thrown when the API returns a non-2xx response.
|
|
534
|
+
|
|
535
|
+
```typescript
|
|
536
|
+
import { PaybetaApiError } from '@paybetaby/node-sdk';
|
|
537
|
+
|
|
538
|
+
try {
|
|
539
|
+
const escrow = await paybeta.escrows.create({ ... });
|
|
540
|
+
} catch (err) {
|
|
541
|
+
if (err instanceof PaybetaApiError) {
|
|
542
|
+
console.error(err.message); // Human-readable error message
|
|
543
|
+
console.error(err.status); // HTTP status code (e.g. 402, 403, 404)
|
|
544
|
+
console.error(err.code); // Machine-readable code (e.g. 'FEATURE_NOT_AVAILABLE')
|
|
545
|
+
console.error(err.traceId); // Paybeta trace ID for support
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
**Common error codes:**
|
|
551
|
+
|
|
552
|
+
| Code | Status | Meaning |
|
|
553
|
+
|---------------------------|--------|--------------------------------------------------------------|
|
|
554
|
+
| `FEATURE_NOT_AVAILABLE` | 403 | Feature not enabled on your plan (e.g. escrow on Starter) |
|
|
555
|
+
| `VOLUME_LIMIT_EXCEEDED` | 402 | Monthly volume limit reached — upgrade your plan |
|
|
556
|
+
| `API_KEY_LIMIT_EXCEEDED` | 402 | API key count limit reached for your plan |
|
|
557
|
+
| `NOT_FOUND` | 404 | Resource not found |
|
|
558
|
+
| `UNAUTHORIZED` | 401 | Invalid or missing API key |
|
|
559
|
+
| `TOO_MANY_REQUESTS` | 429 | Rate limit exceeded |
|
|
560
|
+
| `BAD_REQUEST` | 400 | Validation error — check the error message for field details |
|
|
561
|
+
|
|
562
|
+
### `PaybetaError`
|
|
563
|
+
|
|
564
|
+
Thrown for client-side errors: request timeout, webhook signature failure, missing configuration.
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
import { PaybetaError } from '@paybetaby/node-sdk';
|
|
568
|
+
|
|
569
|
+
try {
|
|
570
|
+
const event = paybeta.webhooks.constructEvent(rawBody, signature);
|
|
571
|
+
} catch (err) {
|
|
572
|
+
if (err instanceof PaybetaError) {
|
|
573
|
+
// signature mismatch, missing webhookSecret, etc.
|
|
574
|
+
console.error(err.message);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
## TypeScript
|
|
582
|
+
|
|
583
|
+
The SDK is written in TypeScript and ships full type declarations. No `@types` package required.
|
|
584
|
+
|
|
585
|
+
All parameter and response types are exported from the package root:
|
|
586
|
+
|
|
587
|
+
```typescript
|
|
588
|
+
import type {
|
|
589
|
+
// Client
|
|
590
|
+
PaybetaClientConfig,
|
|
591
|
+
|
|
592
|
+
// Common
|
|
593
|
+
RequestOptions,
|
|
594
|
+
|
|
595
|
+
// Transactions
|
|
596
|
+
Transaction,
|
|
597
|
+
TransactionStatus,
|
|
598
|
+
TransactionEvent,
|
|
599
|
+
CreateTransactionParams,
|
|
600
|
+
ListTransactionsParams,
|
|
601
|
+
|
|
602
|
+
// Payments
|
|
603
|
+
Payment,
|
|
604
|
+
PaymentStatus,
|
|
605
|
+
PaymentMethod,
|
|
606
|
+
PSPType,
|
|
607
|
+
InitiatePaymentParams,
|
|
608
|
+
|
|
609
|
+
// Escrows
|
|
610
|
+
Escrow,
|
|
611
|
+
EscrowStatus,
|
|
612
|
+
EscrowBalance,
|
|
613
|
+
EscrowListResponse,
|
|
614
|
+
EscrowConditionsResponse,
|
|
615
|
+
ReleasePolicy,
|
|
616
|
+
ReleaseCondition,
|
|
617
|
+
ConditionType,
|
|
618
|
+
ConditionLogic,
|
|
619
|
+
CreateEscrowParams,
|
|
620
|
+
|
|
621
|
+
// Disputes
|
|
622
|
+
Dispute,
|
|
623
|
+
DisputeStatus,
|
|
624
|
+
DisputeStage,
|
|
625
|
+
DisputeType,
|
|
626
|
+
OpenDisputeParams,
|
|
627
|
+
ResolveDisputeParams,
|
|
628
|
+
|
|
629
|
+
// Webhooks
|
|
630
|
+
WebhookEvent,
|
|
631
|
+
WebhookEventType,
|
|
632
|
+
TransactionWebhookEvent,
|
|
633
|
+
PaymentWebhookEvent,
|
|
634
|
+
EscrowWebhookEvent,
|
|
635
|
+
DisputeWebhookEvent,
|
|
636
|
+
} from '@paybetaby/node-sdk';
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
---
|
|
640
|
+
|
|
641
|
+
## Building from Source
|
|
642
|
+
|
|
643
|
+
```bash
|
|
644
|
+
git clone https://github.com/Besaiem/paybeta-node-sdk
|
|
645
|
+
cd paybeta-node-sdk
|
|
646
|
+
|
|
647
|
+
npm install
|
|
648
|
+
npm run build # outputs to dist/
|
|
649
|
+
npm run typecheck # type-check without emitting
|
|
650
|
+
npm run dev # watch mode
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
The build uses [tsup](https://tsup.egoist.dev/) and produces:
|
|
654
|
+
|
|
655
|
+
| File | Format | Description |
|
|
656
|
+
|-----------------------|--------|------------------------------|
|
|
657
|
+
| `dist/index.js` | CJS | CommonJS (require) |
|
|
658
|
+
| `dist/index.mjs` | ESM | ES Modules (import) |
|
|
659
|
+
| `dist/index.d.ts` | DTS | TypeScript declarations |
|
|
660
|
+
| `dist/index.d.mts` | DTS | ESM TypeScript declarations |
|
|
661
|
+
|
|
662
|
+
---
|
|
663
|
+
|
|
664
|
+
## License
|
|
665
|
+
|
|
666
|
+
MIT © Paybeta
|