@delopay/sdk 0.60.0 → 0.62.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,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 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 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