@delopay/sdk 0.62.0 → 0.63.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 +430 -430
- package/dist/{chunk-2OWZIFZO.js → chunk-JCO4CHY7.js} +43 -1
- package/dist/chunk-JCO4CHY7.js.map +1 -0
- package/dist/index.cjs +42 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +128 -3
- package/dist/index.d.ts +128 -3
- package/dist/index.js +1 -1
- package/dist/internal.cjs +42 -0
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/internal.js +1 -1
- package/dist/internal.js.map +1 -1
- package/package.json +63 -64
- package/dist/chunk-2OWZIFZO.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,430 +1,430 @@
|
|
|
1
|
-
# @delopay/sdk
|
|
2
|
-
|
|
3
|
-
TypeScript SDK for the [Delopay](https://delopay.net) payments API. Zero dependencies, works in Node 18+ and browsers.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
pnpm add @delopay/sdk
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm install @delopay/sdk
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
yarn add @delopay/sdk
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Quick Start
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
import { Delopay } from '@delopay/sdk';
|
|
23
|
-
|
|
24
|
-
const delopay = new Delopay(process.env.DELOPAY_API_KEY!);
|
|
25
|
-
|
|
26
|
-
const payment = await delopay.payments.create({
|
|
27
|
-
amount: 1000, // in minor units (€10.00)
|
|
28
|
-
currency: 'EUR',
|
|
29
|
-
description: 'Order #1234',
|
|
30
|
-
customer_id: 'cus_abc123',
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
console.log(payment.payment_id, payment.status);
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Configuration
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
const delopay = new Delopay(apiKey, {
|
|
40
|
-
sandbox: true, // Use https://sandbox.delopay.net (default: false → production)
|
|
41
|
-
baseUrl: 'https://…', // Override base URL entirely
|
|
42
|
-
timeout: 30_000, // Request timeout in ms (default: 30 000)
|
|
43
|
-
});
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
**API keys:**
|
|
47
|
-
|
|
48
|
-
- `prd_…` / `snd_…` — server-side secret key. Full API access. Keep this private.
|
|
49
|
-
- `pk_prd_…` / `pk_snd_…` — client-side publishable key. Restricted to browser-safe operations.
|
|
50
|
-
|
|
51
|
-
## Usage Examples
|
|
52
|
-
|
|
53
|
-
### Create a payment
|
|
54
|
-
|
|
55
|
-
> **Never send raw card numbers through the SDK.** Delopay's API is not a
|
|
56
|
-
> raw-PAN endpoint: cards are collected on the Delopay **hosted checkout**
|
|
57
|
-
> (or via a payment link), so card data never touches your server and stays
|
|
58
|
-
> out of your PCI scope. Server-side you create the payment and hand the buyer
|
|
59
|
-
> off; you confirm server-side only with a saved `payment_token`, a
|
|
60
|
-
> `mandate_id`, or a redirect method (e.g. a PayPal wallet) — never card data.
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
// Recommended: hosted checkout via a payment link.
|
|
64
|
-
const payment = await delopay.payments.create({
|
|
65
|
-
amount: 2500,
|
|
66
|
-
currency: 'EUR',
|
|
67
|
-
payment_link: true,
|
|
68
|
-
customer_id: 'cus_abc123',
|
|
69
|
-
description: 'Order #1234',
|
|
70
|
-
return_url: 'https://example.com/checkout/complete',
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// Send the buyer here — they pick a method and enter card details on the
|
|
74
|
-
// hosted page. Delopay handles 3-D Secure and redirects.
|
|
75
|
-
console.log(payment.payment_link?.link);
|
|
76
|
-
|
|
77
|
-
// Fulfil on the payment_succeeded webhook, or re-check server-side:
|
|
78
|
-
const final = await delopay.payments.retrieve(payment.payment_id);
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Confirm server-side **without card data** — with a saved token (off-session):
|
|
82
|
-
|
|
83
|
-
```typescript
|
|
84
|
-
const confirmed = await delopay.payments.confirm(pending.payment_id, {
|
|
85
|
-
payment_token: savedPaymentToken, // from paymentMethods.listForCustomer()
|
|
86
|
-
off_session: true,
|
|
87
|
-
return_url: 'https://example.com/checkout/complete',
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
console.log(confirmed.status); // 'succeeded' | 'requires_customer_action' | …
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
Or with a redirect payment method that involves no card data at all:
|
|
94
|
-
|
|
95
|
-
```typescript
|
|
96
|
-
const paypal = await delopay.payments.create({
|
|
97
|
-
amount: 2500,
|
|
98
|
-
currency: 'EUR',
|
|
99
|
-
confirm: true,
|
|
100
|
-
payment_method: 'wallet',
|
|
101
|
-
payment_method_type: 'paypal',
|
|
102
|
-
payment_method_data: { wallet: { paypal_redirect: {} } },
|
|
103
|
-
return_url: 'https://example.com/checkout/complete',
|
|
104
|
-
});
|
|
105
|
-
// paypal.next_action?.redirect_to_url → send the buyer there to approve
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### Create a refund
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
const refund = await delopay.refunds.create({
|
|
112
|
-
payment_id: 'pay_abc123',
|
|
113
|
-
amount: 1000, // partial refund; omit for full refund
|
|
114
|
-
reason: 'Customer request',
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
console.log(refund.refund_id, refund.status);
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### Manage customers
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
const customer = await delopay.customers.create({
|
|
124
|
-
name: 'Jane Doe',
|
|
125
|
-
email: 'jane@example.com',
|
|
126
|
-
metadata: { plan: 'pro' },
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// List saved payment methods
|
|
130
|
-
const { customer_payment_methods } = await delopay.paymentMethods.listForCustomer(
|
|
131
|
-
customer.customer_id,
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
// Use a saved method on a new payment
|
|
135
|
-
const payment = await delopay.payments.create({
|
|
136
|
-
amount: 1000,
|
|
137
|
-
currency: 'EUR',
|
|
138
|
-
customer_id: customer.customer_id,
|
|
139
|
-
payment_token: customer_payment_methods[0]?.payment_token,
|
|
140
|
-
confirm: true,
|
|
141
|
-
});
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### Handle disputes
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
// Disputes for one payment come from the payment object:
|
|
148
|
-
const payment = await delopay.payments.retrieve('pay_abc123');
|
|
149
|
-
|
|
150
|
-
for (const dispute of payment.disputes ?? []) {
|
|
151
|
-
console.log(dispute.dispute_id, dispute.dispute_stage, dispute.dispute_status);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Or list disputes across the account, filtered by status:
|
|
155
|
-
const open = await delopay.disputes.list({ dispute_status: 'dispute_opened' });
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
### Inspect why a payment failed
|
|
159
|
-
|
|
160
|
-
```typescript
|
|
161
|
-
// Every attempt on a payment — including retries across connectors — with its
|
|
162
|
-
// full failure detail (raw code + Delopay-unified, human-readable reason).
|
|
163
|
-
const { size, data } = await delopay.payments.listAttempts('pay_abc123');
|
|
164
|
-
|
|
165
|
-
for (const attempt of data) {
|
|
166
|
-
// e.g. "stripe failure — Insufficient funds (51)"
|
|
167
|
-
console.log(
|
|
168
|
-
`${attempt.connector ?? 'unknown'} ${attempt.status} — ` +
|
|
169
|
-
`${attempt.unified_message ?? attempt.error_message ?? 'no error'}` +
|
|
170
|
-
`${attempt.error_code ? ` (${attempt.error_code})` : ''}`,
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
### Manage shops and gateways
|
|
176
|
-
|
|
177
|
-
```typescript
|
|
178
|
-
// Create a shop (business profile)
|
|
179
|
-
const shop = await delopay.shops.create(merchantId, {
|
|
180
|
-
shop_name: 'My Online Store',
|
|
181
|
-
webhook_url: 'https://example.com/webhooks/delopay',
|
|
182
|
-
return_url: 'https://example.com/checkout/complete',
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
// Connect Stripe as a payment gateway
|
|
186
|
-
const gateway = await delopay.shops.gateways.connect(merchantId, shop.shop_id, {
|
|
187
|
-
connector_type: 'payment_processor',
|
|
188
|
-
connector_name: 'stripe',
|
|
189
|
-
connector_account_details: {
|
|
190
|
-
auth_type: 'HeaderKey',
|
|
191
|
-
api_key: process.env.STRIPE_SECRET_KEY,
|
|
192
|
-
},
|
|
193
|
-
test_mode: true,
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
// List connected gateways
|
|
197
|
-
const gateways = await delopay.shops.gateways.list(merchantId, shop.shop_id);
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
### Subscriptions
|
|
201
|
-
|
|
202
|
-
Recurring billing runs through a billing processor connected to the shop (Stripe
|
|
203
|
-
Billing or PayPal). Every subscription call is **profile-scoped** — pass the
|
|
204
|
-
shop's `X-Profile-Id` so the backend can resolve the billing processor (you get
|
|
205
|
-
`IR_04` otherwise):
|
|
206
|
-
|
|
207
|
-
```typescript
|
|
208
|
-
const opts = { headers: { 'X-Profile-Id': profileId } };
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
**Browse plans and estimate cost** before creating anything:
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
// List purchasable plans (or addons) with their prices
|
|
215
|
-
const plans = await delopay.subscriptions.getItems({ item_type: 'plan' }, opts);
|
|
216
|
-
const priceId = plans[0]?.price_id[0]?.price_id;
|
|
217
|
-
|
|
218
|
-
// Preview what the customer will be charged
|
|
219
|
-
const estimate = await delopay.subscriptions.getEstimate({ item_price_id: priceId }, opts);
|
|
220
|
-
console.log(estimate.amount, estimate.currency, estimate.interval); // 1500 'EUR' 'Month'
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
> **Never send raw card numbers.** The subscription API rejects
|
|
224
|
-
> `payment_method_data.card` with a raw PAN. Cards are collected client-side by
|
|
225
|
-
> the connector's hosted fields (Stripe Elements) so the card never touches your
|
|
226
|
-
> server, keeping raw card data out of your PCI scope. Confirm with a hosted
|
|
227
|
-
> checkout session or a previously-saved token, as shown below.
|
|
228
|
-
|
|
229
|
-
**Recommended: hosted checkout.** Create the subscription server-side, then send
|
|
230
|
-
the buyer to the Delopay hosted checkout with the returned `client_secret`. The
|
|
231
|
-
buyer enters their card in the connector iframe; you never handle the PAN:
|
|
232
|
-
|
|
233
|
-
```typescript
|
|
234
|
-
const pending = await delopay.subscriptions.create(
|
|
235
|
-
{
|
|
236
|
-
item_price_id: priceId,
|
|
237
|
-
customer_id: 'cus_abc123',
|
|
238
|
-
payment_details: { return_url: 'https://example.com/subscription/complete' },
|
|
239
|
-
},
|
|
240
|
-
opts,
|
|
241
|
-
);
|
|
242
|
-
|
|
243
|
-
// Redirect the buyer to the hosted checkout to enter their card.
|
|
244
|
-
const checkoutUrl =
|
|
245
|
-
`https://checkout.delopay.net/pay/${merchantId}/${pending.id}` +
|
|
246
|
-
`?cs=${encodeURIComponent(pending.client_secret ?? '')}`;
|
|
247
|
-
// → res.redirect(checkoutUrl)
|
|
248
|
-
|
|
249
|
-
// Activation arrives via the subscription/invoice webhooks; never trust the
|
|
250
|
-
// client. Reconcile with subscriptions.retrieve(pending.id, opts).
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
**Saved payment method (off-session).** If the customer already has a saved,
|
|
254
|
-
tokenized payment method, confirm server-side with the token — still no PAN:
|
|
255
|
-
|
|
256
|
-
```typescript
|
|
257
|
-
const sub = await delopay.subscriptions.createAndConfirm(
|
|
258
|
-
{
|
|
259
|
-
item_price_id: priceId,
|
|
260
|
-
customer_id: 'cus_abc123',
|
|
261
|
-
payment_details: {
|
|
262
|
-
payment_method: 'card',
|
|
263
|
-
payment_method_id: savedPaymentMethodId, // token, not a card number
|
|
264
|
-
setup_future_usage: 'off_session',
|
|
265
|
-
return_url: 'https://example.com/subscription/complete',
|
|
266
|
-
},
|
|
267
|
-
},
|
|
268
|
-
opts,
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
if (sub.redirect_url) {
|
|
272
|
-
// Some processors (e.g. PayPal) still need buyer approval — redirect there.
|
|
273
|
-
} else {
|
|
274
|
-
console.log(sub.status); // 'active'
|
|
275
|
-
}
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
You can also split create and confirm — call `subscriptions.confirm(id, …)` with
|
|
279
|
-
the `client_secret` and a `payment_token` once the buyer has a token. Same rule:
|
|
280
|
-
a `payment_token` / `payment_method_id`, never a raw card.
|
|
281
|
-
|
|
282
|
-
**Manage the lifecycle.** Pause, resume, and cancel take optional timing and
|
|
283
|
-
proration controls; called with no body they act immediately:
|
|
284
|
-
|
|
285
|
-
```typescript
|
|
286
|
-
await delopay.subscriptions.pause(sub.id, { pause_option: 'end_of_term' }, opts);
|
|
287
|
-
await delopay.subscriptions.resume(sub.id, undefined, opts);
|
|
288
|
-
await delopay.subscriptions.cancel(
|
|
289
|
-
sub.id,
|
|
290
|
-
{ cancel_option: 'immediately', credit_option_for_current_term_charges: 'prorate' },
|
|
291
|
-
opts,
|
|
292
|
-
);
|
|
293
|
-
|
|
294
|
-
// Retrieve one, or list for the profile
|
|
295
|
-
const current = await delopay.subscriptions.retrieve(sub.id, opts);
|
|
296
|
-
const all = await delopay.subscriptions.list({ limit: 20 }, opts);
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
Each billing cycle raises an invoice (`sub.invoice`) with its own payment leg
|
|
300
|
-
(`sub.payment`); track cycle outcomes via the subscription/invoice webhooks.
|
|
301
|
-
|
|
302
|
-
### Platform fee rules
|
|
303
|
-
|
|
304
|
-
Price the platform fee by payment method, connector, amount, currency or card
|
|
305
|
-
network. Build the rule program with `feeProgram()` — rules are tried in order,
|
|
306
|
-
first match wins, otherwise the default applies:
|
|
307
|
-
|
|
308
|
-
```typescript
|
|
309
|
-
import { Delopay, feeProgram } from '@delopay/sdk';
|
|
310
|
-
|
|
311
|
-
const delopay = new Delopay(process.env.DELOPAY_API_KEY ?? '');
|
|
312
|
-
|
|
313
|
-
const algorithm = feeProgram()
|
|
314
|
-
.rule({ name: 'crypto', when: { paymentMethod: 'crypto' }, fee: { percentage: 1.0 } })
|
|
315
|
-
.rule({
|
|
316
|
-
name: 'card_on_cryptomus',
|
|
317
|
-
when: { paymentMethod: 'card', connector: 'cryptomus' },
|
|
318
|
-
fee: { percentage: 2.0 },
|
|
319
|
-
})
|
|
320
|
-
.otherwise({ percentage: 3.0 })
|
|
321
|
-
.build();
|
|
322
|
-
|
|
323
|
-
await delopay.fees.rules.upsert({ algorithm }, 'merchant_abc123');
|
|
324
|
-
|
|
325
|
-
const program = await delopay.fees.rules.retrieve('merchant_abc123'); // or null
|
|
326
|
-
await delopay.fees.rules.delete('merchant_abc123'); // revert to flat schedules
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
Merchants without a rule program keep their existing flat fee schedules / volume
|
|
330
|
-
tier unchanged.
|
|
331
|
-
|
|
332
|
-
### Webhook verification
|
|
333
|
-
|
|
334
|
-
Delopay signs each outgoing webhook with HMAC-SHA512 over the raw request body and delivers the hex-encoded digest in the `X-Webhook-Signature-512` header. Use `express.raw()` (not `express.json()`) so the bytes reach the verifier unchanged.
|
|
335
|
-
|
|
336
|
-
The verified event matches the wire body: `{ merchant_id, event_id, event_type, content: { type, object }, timestamp }`. `event_type` says what happened (e.g. `'payment_succeeded'`); `content.type` tags the payload kind (e.g. `'payment_details'`) — narrow on it to get a typed `content.object` (the payment/refund/dispute, with `payment_id` etc.).
|
|
337
|
-
|
|
338
|
-
```typescript
|
|
339
|
-
import express from 'express';
|
|
340
|
-
import { Delopay } from '@delopay/sdk';
|
|
341
|
-
|
|
342
|
-
app.post('/webhooks/delopay', express.raw({ type: 'application/json' }), async (req, res) => {
|
|
343
|
-
const signature = req.header('x-webhook-signature-512') ?? '';
|
|
344
|
-
const secret = process.env.DELOPAY_WEBHOOK_SECRET!;
|
|
345
|
-
|
|
346
|
-
let event;
|
|
347
|
-
try {
|
|
348
|
-
event = await Delopay.webhooks.verify(req.body, signature, secret);
|
|
349
|
-
} catch {
|
|
350
|
-
return res.status(400).send('Invalid signature');
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (event.content.type === 'payment_details') {
|
|
354
|
-
const payment = event.content.object; // typed: PaymentResponse
|
|
355
|
-
switch (event.event_type) {
|
|
356
|
-
case 'payment_succeeded':
|
|
357
|
-
// fulfil order — payment.payment_id, payment.amount, payment.currency
|
|
358
|
-
break;
|
|
359
|
-
case 'payment_failed':
|
|
360
|
-
// notify customer — payment.error_message
|
|
361
|
-
break;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
res.json({ received: true });
|
|
366
|
-
});
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
## Error Handling
|
|
370
|
-
|
|
371
|
-
All errors are instances of `DelopayError`:
|
|
372
|
-
|
|
373
|
-
```typescript
|
|
374
|
-
import { Delopay, DelopayError, DelopayAuthenticationError } from '@delopay/sdk';
|
|
375
|
-
|
|
376
|
-
try {
|
|
377
|
-
const payment = await delopay.payments.retrieve('pay_does_not_exist');
|
|
378
|
-
} catch (err) {
|
|
379
|
-
if (err instanceof DelopayAuthenticationError) {
|
|
380
|
-
// status: 401 — invalid or missing API key
|
|
381
|
-
console.error('Check your API key');
|
|
382
|
-
} else if (err instanceof DelopayError) {
|
|
383
|
-
console.error(err.message); // human-readable message
|
|
384
|
-
console.error(err.status); // HTTP status code
|
|
385
|
-
console.error(err.code); // machine-readable error code
|
|
386
|
-
console.error(err.type); // error category (e.g. 'not_found')
|
|
387
|
-
console.error(err.data); // structured context for select codes (see below)
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
**Error classes:**
|
|
393
|
-
|
|
394
|
-
| Class | When |
|
|
395
|
-
| ---------------------------- | ---------------------------------- |
|
|
396
|
-
| `DelopayError` | Base class for all API errors |
|
|
397
|
-
| `DelopayAuthenticationError` | `401` — invalid or missing API key |
|
|
398
|
-
|
|
399
|
-
Network timeouts throw `DelopayError` with `code: 'TIMEOUT'`. Network failures throw with `code: 'NETWORK'`.
|
|
400
|
-
|
|
401
|
-
**Structured error context (`err.data`):** populated for a small set of codes that benefit from a machine-readable hint. Currently:
|
|
402
|
-
|
|
403
|
-
| `err.code` | `err.data` shape | Meaning |
|
|
404
|
-
| ---------- | ------------------------------ | -------------------------------------------------------------------------- |
|
|
405
|
-
| `UR_48` | `{ retry_after_secs: number }` | TOTP attempt counter locked out — wait this many seconds before retrying. |
|
|
406
|
-
| `UR_63` | `{ retry_after_secs: number }` | Auth-endpoint rate limit tripped — wait this many seconds before retrying. |
|
|
407
|
-
|
|
408
|
-
## TypeScript
|
|
409
|
-
|
|
410
|
-
The SDK is written in strict TypeScript. All request and response shapes are fully typed. Import types directly when needed:
|
|
411
|
-
|
|
412
|
-
```typescript
|
|
413
|
-
import type { PaymentResponse, PaymentCreateRequest, Currency } from '@delopay/sdk';
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
## Environments
|
|
417
|
-
|
|
418
|
-
| Environment | Base URL | API key prefix |
|
|
419
|
-
| ----------- | ----------------------------- | -------------------- |
|
|
420
|
-
| Production | `https://api.delopay.net` | `prd_…` / `pk_prd_…` |
|
|
421
|
-
| Sandbox | `https://sandbox.delopay.net` | `snd_…` / `pk_snd_…` |
|
|
422
|
-
|
|
423
|
-
```typescript
|
|
424
|
-
// Sandbox
|
|
425
|
-
const delopay = new Delopay(process.env.DELOPAY_API_KEY!, { sandbox: true });
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
## License
|
|
429
|
-
|
|
430
|
-
MIT
|
|
1
|
+
# @delopay/sdk
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for the [Delopay](https://delopay.net) payments API. Zero dependencies, works in Node 18+ and browsers.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @delopay/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @delopay/sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
yarn add @delopay/sdk
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { Delopay } from '@delopay/sdk';
|
|
23
|
+
|
|
24
|
+
const delopay = new Delopay(process.env.DELOPAY_API_KEY!);
|
|
25
|
+
|
|
26
|
+
const payment = await delopay.payments.create({
|
|
27
|
+
amount: 1000, // in minor units (€10.00)
|
|
28
|
+
currency: 'EUR',
|
|
29
|
+
description: 'Order #1234',
|
|
30
|
+
customer_id: 'cus_abc123',
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
console.log(payment.payment_id, payment.status);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Configuration
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
const delopay = new Delopay(apiKey, {
|
|
40
|
+
sandbox: true, // Use https://sandbox.delopay.net (default: false → production)
|
|
41
|
+
baseUrl: 'https://…', // Override base URL entirely
|
|
42
|
+
timeout: 30_000, // Request timeout in ms (default: 30 000)
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**API keys:**
|
|
47
|
+
|
|
48
|
+
- `prd_…` / `snd_…` — server-side secret key. Full API access. Keep this private.
|
|
49
|
+
- `pk_prd_…` / `pk_snd_…` — client-side publishable key. Restricted to browser-safe operations.
|
|
50
|
+
|
|
51
|
+
## Usage Examples
|
|
52
|
+
|
|
53
|
+
### Create a payment
|
|
54
|
+
|
|
55
|
+
> **Never send raw card numbers through the SDK.** Delopay's API is not a
|
|
56
|
+
> raw-PAN endpoint: cards are collected on the Delopay **hosted checkout**
|
|
57
|
+
> (or via a payment link), so card data never touches your server and stays
|
|
58
|
+
> out of your PCI scope. Server-side you create the payment and hand the buyer
|
|
59
|
+
> off; you confirm server-side only with a saved `payment_token`, a
|
|
60
|
+
> `mandate_id`, or a redirect method (e.g. a PayPal wallet) — never card data.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// Recommended: hosted checkout via a payment link.
|
|
64
|
+
const payment = await delopay.payments.create({
|
|
65
|
+
amount: 2500,
|
|
66
|
+
currency: 'EUR',
|
|
67
|
+
payment_link: true,
|
|
68
|
+
customer_id: 'cus_abc123',
|
|
69
|
+
description: 'Order #1234',
|
|
70
|
+
return_url: 'https://example.com/checkout/complete',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Send the buyer here — they pick a method and enter card details on the
|
|
74
|
+
// hosted page. Delopay handles 3-D Secure and redirects.
|
|
75
|
+
console.log(payment.payment_link?.link);
|
|
76
|
+
|
|
77
|
+
// Fulfil on the payment_succeeded webhook, or re-check server-side:
|
|
78
|
+
const final = await delopay.payments.retrieve(payment.payment_id);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Confirm server-side **without card data** — with a saved token (off-session):
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
const confirmed = await delopay.payments.confirm(pending.payment_id, {
|
|
85
|
+
payment_token: savedPaymentToken, // from paymentMethods.listForCustomer()
|
|
86
|
+
off_session: true,
|
|
87
|
+
return_url: 'https://example.com/checkout/complete',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
console.log(confirmed.status); // 'succeeded' | 'requires_customer_action' | …
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Or with a redirect payment method that involves no card data at all:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
const paypal = await delopay.payments.create({
|
|
97
|
+
amount: 2500,
|
|
98
|
+
currency: 'EUR',
|
|
99
|
+
confirm: true,
|
|
100
|
+
payment_method: 'wallet',
|
|
101
|
+
payment_method_type: 'paypal',
|
|
102
|
+
payment_method_data: { wallet: { paypal_redirect: {} } },
|
|
103
|
+
return_url: 'https://example.com/checkout/complete',
|
|
104
|
+
});
|
|
105
|
+
// paypal.next_action?.redirect_to_url → send the buyer there to approve
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Create a refund
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
const refund = await delopay.refunds.create({
|
|
112
|
+
payment_id: 'pay_abc123',
|
|
113
|
+
amount: 1000, // partial refund; omit for full refund
|
|
114
|
+
reason: 'Customer request',
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
console.log(refund.refund_id, refund.status);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Manage customers
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
const customer = await delopay.customers.create({
|
|
124
|
+
name: 'Jane Doe',
|
|
125
|
+
email: 'jane@example.com',
|
|
126
|
+
metadata: { plan: 'pro' },
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// List saved payment methods
|
|
130
|
+
const { customer_payment_methods } = await delopay.paymentMethods.listForCustomer(
|
|
131
|
+
customer.customer_id,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// Use a saved method on a new payment
|
|
135
|
+
const payment = await delopay.payments.create({
|
|
136
|
+
amount: 1000,
|
|
137
|
+
currency: 'EUR',
|
|
138
|
+
customer_id: customer.customer_id,
|
|
139
|
+
payment_token: customer_payment_methods[0]?.payment_token,
|
|
140
|
+
confirm: true,
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Handle disputes
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// Disputes for one payment come from the payment object:
|
|
148
|
+
const payment = await delopay.payments.retrieve('pay_abc123');
|
|
149
|
+
|
|
150
|
+
for (const dispute of payment.disputes ?? []) {
|
|
151
|
+
console.log(dispute.dispute_id, dispute.dispute_stage, dispute.dispute_status);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Or list disputes across the account, filtered by status:
|
|
155
|
+
const open = await delopay.disputes.list({ dispute_status: 'dispute_opened' });
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Inspect why a payment failed
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// Every attempt on a payment — including retries across connectors — with its
|
|
162
|
+
// full failure detail (raw code + Delopay-unified, human-readable reason).
|
|
163
|
+
const { size, data } = await delopay.payments.listAttempts('pay_abc123');
|
|
164
|
+
|
|
165
|
+
for (const attempt of data) {
|
|
166
|
+
// e.g. "stripe failure — Insufficient funds (51)"
|
|
167
|
+
console.log(
|
|
168
|
+
`${attempt.connector ?? 'unknown'} ${attempt.status} — ` +
|
|
169
|
+
`${attempt.unified_message ?? attempt.error_message ?? 'no error'}` +
|
|
170
|
+
`${attempt.error_code ? ` (${attempt.error_code})` : ''}`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Manage shops and gateways
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// Create a shop (business profile)
|
|
179
|
+
const shop = await delopay.shops.create(merchantId, {
|
|
180
|
+
shop_name: 'My Online Store',
|
|
181
|
+
webhook_url: 'https://example.com/webhooks/delopay',
|
|
182
|
+
return_url: 'https://example.com/checkout/complete',
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Connect Stripe as a payment gateway
|
|
186
|
+
const gateway = await delopay.shops.gateways.connect(merchantId, shop.shop_id, {
|
|
187
|
+
connector_type: 'payment_processor',
|
|
188
|
+
connector_name: 'stripe',
|
|
189
|
+
connector_account_details: {
|
|
190
|
+
auth_type: 'HeaderKey',
|
|
191
|
+
api_key: process.env.STRIPE_SECRET_KEY,
|
|
192
|
+
},
|
|
193
|
+
test_mode: true,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// List connected gateways
|
|
197
|
+
const gateways = await delopay.shops.gateways.list(merchantId, shop.shop_id);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Subscriptions
|
|
201
|
+
|
|
202
|
+
Recurring billing runs through a billing processor connected to the shop (Stripe
|
|
203
|
+
Billing or PayPal). Every subscription call is **profile-scoped** — pass the
|
|
204
|
+
shop's `X-Profile-Id` so the backend can resolve the billing processor (you get
|
|
205
|
+
`IR_04` otherwise):
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
const opts = { headers: { 'X-Profile-Id': profileId } };
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Browse plans and estimate cost** before creating anything:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// List purchasable plans (or addons) with their prices
|
|
215
|
+
const plans = await delopay.subscriptions.getItems({ item_type: 'plan' }, opts);
|
|
216
|
+
const priceId = plans[0]?.price_id[0]?.price_id;
|
|
217
|
+
|
|
218
|
+
// Preview what the customer will be charged
|
|
219
|
+
const estimate = await delopay.subscriptions.getEstimate({ item_price_id: priceId }, opts);
|
|
220
|
+
console.log(estimate.amount, estimate.currency, estimate.interval); // 1500 'EUR' 'Month'
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
> **Never send raw card numbers.** The subscription API rejects
|
|
224
|
+
> `payment_method_data.card` with a raw PAN. Cards are collected client-side by
|
|
225
|
+
> the connector's hosted fields (Stripe Elements) so the card never touches your
|
|
226
|
+
> server, keeping raw card data out of your PCI scope. Confirm with a hosted
|
|
227
|
+
> checkout session or a previously-saved token, as shown below.
|
|
228
|
+
|
|
229
|
+
**Recommended: hosted checkout.** Create the subscription server-side, then send
|
|
230
|
+
the buyer to the Delopay hosted checkout with the returned `client_secret`. The
|
|
231
|
+
buyer enters their card in the connector iframe; you never handle the PAN:
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
const pending = await delopay.subscriptions.create(
|
|
235
|
+
{
|
|
236
|
+
item_price_id: priceId,
|
|
237
|
+
customer_id: 'cus_abc123',
|
|
238
|
+
payment_details: { return_url: 'https://example.com/subscription/complete' },
|
|
239
|
+
},
|
|
240
|
+
opts,
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// Redirect the buyer to the hosted checkout to enter their card.
|
|
244
|
+
const checkoutUrl =
|
|
245
|
+
`https://checkout.delopay.net/pay/${merchantId}/${pending.id}` +
|
|
246
|
+
`?cs=${encodeURIComponent(pending.client_secret ?? '')}`;
|
|
247
|
+
// → res.redirect(checkoutUrl)
|
|
248
|
+
|
|
249
|
+
// Activation arrives via the subscription/invoice webhooks; never trust the
|
|
250
|
+
// client. Reconcile with subscriptions.retrieve(pending.id, opts).
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Saved payment method (off-session).** If the customer already has a saved,
|
|
254
|
+
tokenized payment method, confirm server-side with the token — still no PAN:
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
const sub = await delopay.subscriptions.createAndConfirm(
|
|
258
|
+
{
|
|
259
|
+
item_price_id: priceId,
|
|
260
|
+
customer_id: 'cus_abc123',
|
|
261
|
+
payment_details: {
|
|
262
|
+
payment_method: 'card',
|
|
263
|
+
payment_method_id: savedPaymentMethodId, // token, not a card number
|
|
264
|
+
setup_future_usage: 'off_session',
|
|
265
|
+
return_url: 'https://example.com/subscription/complete',
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
opts,
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
if (sub.redirect_url) {
|
|
272
|
+
// Some processors (e.g. PayPal) still need buyer approval — redirect there.
|
|
273
|
+
} else {
|
|
274
|
+
console.log(sub.status); // 'active'
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
You can also split create and confirm — call `subscriptions.confirm(id, …)` with
|
|
279
|
+
the `client_secret` and a `payment_token` once the buyer has a token. Same rule:
|
|
280
|
+
a `payment_token` / `payment_method_id`, never a raw card.
|
|
281
|
+
|
|
282
|
+
**Manage the lifecycle.** Pause, resume, and cancel take optional timing and
|
|
283
|
+
proration controls; called with no body they act immediately:
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
await delopay.subscriptions.pause(sub.id, { pause_option: 'end_of_term' }, opts);
|
|
287
|
+
await delopay.subscriptions.resume(sub.id, undefined, opts);
|
|
288
|
+
await delopay.subscriptions.cancel(
|
|
289
|
+
sub.id,
|
|
290
|
+
{ cancel_option: 'immediately', credit_option_for_current_term_charges: 'prorate' },
|
|
291
|
+
opts,
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
// Retrieve one, or list for the profile
|
|
295
|
+
const current = await delopay.subscriptions.retrieve(sub.id, opts);
|
|
296
|
+
const all = await delopay.subscriptions.list({ limit: 20 }, opts);
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Each billing cycle raises an invoice (`sub.invoice`) with its own payment leg
|
|
300
|
+
(`sub.payment`); track cycle outcomes via the subscription/invoice webhooks.
|
|
301
|
+
|
|
302
|
+
### Platform fee rules
|
|
303
|
+
|
|
304
|
+
Price the platform fee by payment method, connector, amount, currency or card
|
|
305
|
+
network. Build the rule program with `feeProgram()` — rules are tried in order,
|
|
306
|
+
first match wins, otherwise the default applies:
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
import { Delopay, feeProgram } from '@delopay/sdk';
|
|
310
|
+
|
|
311
|
+
const delopay = new Delopay(process.env.DELOPAY_API_KEY ?? '');
|
|
312
|
+
|
|
313
|
+
const algorithm = feeProgram()
|
|
314
|
+
.rule({ name: 'crypto', when: { paymentMethod: 'crypto' }, fee: { percentage: 1.0 } })
|
|
315
|
+
.rule({
|
|
316
|
+
name: 'card_on_cryptomus',
|
|
317
|
+
when: { paymentMethod: 'card', connector: 'cryptomus' },
|
|
318
|
+
fee: { percentage: 2.0 },
|
|
319
|
+
})
|
|
320
|
+
.otherwise({ percentage: 3.0 })
|
|
321
|
+
.build();
|
|
322
|
+
|
|
323
|
+
await delopay.fees.rules.upsert({ algorithm }, 'merchant_abc123');
|
|
324
|
+
|
|
325
|
+
const program = await delopay.fees.rules.retrieve('merchant_abc123'); // or null
|
|
326
|
+
await delopay.fees.rules.delete('merchant_abc123'); // revert to flat schedules
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Merchants without a rule program keep their existing flat fee schedules / volume
|
|
330
|
+
tier unchanged.
|
|
331
|
+
|
|
332
|
+
### Webhook verification
|
|
333
|
+
|
|
334
|
+
Delopay signs each outgoing webhook with HMAC-SHA512 over the raw request body and delivers the hex-encoded digest in the `X-Webhook-Signature-512` header. Use `express.raw()` (not `express.json()`) so the bytes reach the verifier unchanged.
|
|
335
|
+
|
|
336
|
+
The verified event matches the wire body: `{ merchant_id, event_id, event_type, content: { type, object }, timestamp }`. `event_type` says what happened (e.g. `'payment_succeeded'`); `content.type` tags the payload kind (e.g. `'payment_details'`) — narrow on it to get a typed `content.object` (the payment/refund/dispute, with `payment_id` etc.).
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
import express from 'express';
|
|
340
|
+
import { Delopay } from '@delopay/sdk';
|
|
341
|
+
|
|
342
|
+
app.post('/webhooks/delopay', express.raw({ type: 'application/json' }), async (req, res) => {
|
|
343
|
+
const signature = req.header('x-webhook-signature-512') ?? '';
|
|
344
|
+
const secret = process.env.DELOPAY_WEBHOOK_SECRET!;
|
|
345
|
+
|
|
346
|
+
let event;
|
|
347
|
+
try {
|
|
348
|
+
event = await Delopay.webhooks.verify(req.body, signature, secret);
|
|
349
|
+
} catch {
|
|
350
|
+
return res.status(400).send('Invalid signature');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (event.content.type === 'payment_details') {
|
|
354
|
+
const payment = event.content.object; // typed: PaymentResponse
|
|
355
|
+
switch (event.event_type) {
|
|
356
|
+
case 'payment_succeeded':
|
|
357
|
+
// fulfil order — payment.payment_id, payment.amount, payment.currency
|
|
358
|
+
break;
|
|
359
|
+
case 'payment_failed':
|
|
360
|
+
// notify customer — payment.error_message
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
res.json({ received: true });
|
|
366
|
+
});
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## Error Handling
|
|
370
|
+
|
|
371
|
+
All errors are instances of `DelopayError`:
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
import { Delopay, DelopayError, DelopayAuthenticationError } from '@delopay/sdk';
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
const payment = await delopay.payments.retrieve('pay_does_not_exist');
|
|
378
|
+
} catch (err) {
|
|
379
|
+
if (err instanceof DelopayAuthenticationError) {
|
|
380
|
+
// status: 401 — invalid or missing API key
|
|
381
|
+
console.error('Check your API key');
|
|
382
|
+
} else if (err instanceof DelopayError) {
|
|
383
|
+
console.error(err.message); // human-readable message
|
|
384
|
+
console.error(err.status); // HTTP status code
|
|
385
|
+
console.error(err.code); // machine-readable error code
|
|
386
|
+
console.error(err.type); // error category (e.g. 'not_found')
|
|
387
|
+
console.error(err.data); // structured context for select codes (see below)
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**Error classes:**
|
|
393
|
+
|
|
394
|
+
| Class | When |
|
|
395
|
+
| ---------------------------- | ---------------------------------- |
|
|
396
|
+
| `DelopayError` | Base class for all API errors |
|
|
397
|
+
| `DelopayAuthenticationError` | `401` — invalid or missing API key |
|
|
398
|
+
|
|
399
|
+
Network timeouts throw `DelopayError` with `code: 'TIMEOUT'`. Network failures throw with `code: 'NETWORK'`.
|
|
400
|
+
|
|
401
|
+
**Structured error context (`err.data`):** populated for a small set of codes that benefit from a machine-readable hint. Currently:
|
|
402
|
+
|
|
403
|
+
| `err.code` | `err.data` shape | Meaning |
|
|
404
|
+
| ---------- | ------------------------------ | -------------------------------------------------------------------------- |
|
|
405
|
+
| `UR_48` | `{ retry_after_secs: number }` | TOTP attempt counter locked out — wait this many seconds before retrying. |
|
|
406
|
+
| `UR_63` | `{ retry_after_secs: number }` | Auth-endpoint rate limit tripped — wait this many seconds before retrying. |
|
|
407
|
+
|
|
408
|
+
## TypeScript
|
|
409
|
+
|
|
410
|
+
The SDK is written in strict TypeScript. All request and response shapes are fully typed. Import types directly when needed:
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
import type { PaymentResponse, PaymentCreateRequest, Currency } from '@delopay/sdk';
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Environments
|
|
417
|
+
|
|
418
|
+
| Environment | Base URL | API key prefix |
|
|
419
|
+
| ----------- | ----------------------------- | -------------------- |
|
|
420
|
+
| Production | `https://api.delopay.net` | `prd_…` / `pk_prd_…` |
|
|
421
|
+
| Sandbox | `https://sandbox.delopay.net` | `snd_…` / `pk_snd_…` |
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
// Sandbox
|
|
425
|
+
const delopay = new Delopay(process.env.DELOPAY_API_KEY!, { sandbox: true });
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
## License
|
|
429
|
+
|
|
430
|
+
MIT
|