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