@dpayglobal/dpay-node-sdk 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1709 @@
1
+ import { isScalar, phpStrval, phpJsonEncode, phpRound, ApiResponse, isNumeric, phpInt, isPhpBool, isPhpInt } from './chunk-SPQCX4N4.js';
2
+ export { ApiResponse } from './chunk-SPQCX4N4.js';
3
+ import { timingSafeEqual, createHash, createPublicKey, publicEncrypt, constants } from 'crypto';
4
+ import { inspect } from 'util';
5
+
6
+ // src/errors.ts
7
+ var DPayError = class extends Error {
8
+ /** Stable discriminator, usable in `switch` when `instanceof` is unreliable. */
9
+ type = "value_error";
10
+ constructor(message, options) {
11
+ super(message, options);
12
+ this.name = new.target.name;
13
+ Error.captureStackTrace?.(this, new.target);
14
+ }
15
+ };
16
+ var DPayValueError = class extends DPayError {
17
+ type = "value_error";
18
+ };
19
+ var TransportError = class extends DPayError {
20
+ type = "transport_error";
21
+ };
22
+ var SignatureVerificationError = class extends DPayError {
23
+ type = "signature_verification_error";
24
+ };
25
+ var CardEncryptionError = class extends DPayError {
26
+ type = "card_encryption_error";
27
+ };
28
+ var ApiError = class extends DPayError {
29
+ type = "api_error";
30
+ /** HTTP status of the response, or 200 for rejections carried in a success body. */
31
+ httpStatus;
32
+ /** Business error code from the API, when present. */
33
+ errorCode;
34
+ /** Per-field validation messages, normalized across the 400 and 422 formats. */
35
+ fieldErrors;
36
+ /** Unparsed response body. */
37
+ rawBody;
38
+ constructor(message, httpStatus, errorCode = null, fieldErrors = {}, rawBody = "") {
39
+ super(message);
40
+ this.httpStatus = httpStatus;
41
+ this.errorCode = errorCode;
42
+ this.fieldErrors = fieldErrors;
43
+ this.rawBody = rawBody;
44
+ }
45
+ };
46
+ var AuthenticationError = class extends ApiError {
47
+ type = "authentication_error";
48
+ };
49
+ var InvalidRequestError = class extends ApiError {
50
+ type = "invalid_request_error";
51
+ };
52
+ var AccessDeniedError = class extends ApiError {
53
+ type = "access_denied_error";
54
+ };
55
+ var NotFoundError = class extends ApiError {
56
+ type = "not_found_error";
57
+ };
58
+ var ApiServerError = class extends ApiError {
59
+ type = "api_server_error";
60
+ };
61
+ var RateLimitError = class extends ApiError {
62
+ type = "rate_limit_error";
63
+ /** Seconds from the `Retry-After` header. */
64
+ retryAfter;
65
+ /** Value of `X-RateLimit-Limit`. */
66
+ limit;
67
+ /** Value of `X-RateLimit-Remaining`. */
68
+ remaining;
69
+ constructor(message, httpStatus, retryAfter = null, limit = null, remaining = null, rawBody = "") {
70
+ super(message, httpStatus, null, {}, rawBody);
71
+ this.retryAfter = retryAfter;
72
+ this.limit = limit;
73
+ this.remaining = remaining;
74
+ }
75
+ };
76
+ var PaymentRejectedError = class _PaymentRejectedError extends ApiError {
77
+ type = "payment_rejected_error";
78
+ /** Transaction identifier returned alongside the rejection, when present. */
79
+ transactionId;
80
+ constructor(message, httpStatus, errorCode = null, fieldErrors = {}, rawBody = "", transactionId = null) {
81
+ super(message, httpStatus, errorCode, fieldErrors, rawBody);
82
+ this.transactionId = transactionId;
83
+ }
84
+ static fromApi(data) {
85
+ const message = typeof data.msg === "string" ? data.msg : "Payment rejected";
86
+ const additional = isRecord(data.additionalInfo) ? data.additionalInfo : {};
87
+ const errorCode = typeof additional.error === "string" ? additional.error : null;
88
+ const transactionId = isScalar(data.transactionId) ? phpStrval(data.transactionId) : null;
89
+ return new _PaymentRejectedError(message, 200, errorCode, {}, phpJsonEncode(data), transactionId);
90
+ }
91
+ };
92
+ var CardPaymentError = class _CardPaymentError extends ApiError {
93
+ type = "card_payment_error";
94
+ static fromApi(data) {
95
+ const message = typeof data.message === "string" ? data.message : "Card payment failed";
96
+ return new _CardPaymentError(message, 200, message, {}, phpJsonEncode(data));
97
+ }
98
+ };
99
+ function isRecord(value) {
100
+ return typeof value === "object" && value !== null && !Array.isArray(value);
101
+ }
102
+
103
+ // src/internal/base-urls.ts
104
+ var API_PAYMENTS = "apiPayments";
105
+ var PANEL = "panel";
106
+ var DEFAULT_BASE_URLS = {
107
+ apiPayments: "https://api-payments.dpay.pl",
108
+ panel: "https://panel.dpay.pl",
109
+ gateway: "https://secure.dpay.pl"
110
+ };
111
+ var BaseUrls = class {
112
+ urls;
113
+ constructor(overrides = {}) {
114
+ for (const key of Object.keys(overrides)) {
115
+ if (!Object.hasOwn(DEFAULT_BASE_URLS, key)) throw new DPayValueError(`Unknown base URL key "${key}"`);
116
+ }
117
+ this.urls = { ...DEFAULT_BASE_URLS };
118
+ for (const [key, url] of Object.entries(overrides)) {
119
+ this.urls[key] = url.replace(/\/+$/, "");
120
+ }
121
+ }
122
+ resolve(host) {
123
+ const url = this.urls[host];
124
+ if (!Object.hasOwn(this.urls, host) || url === void 0)
125
+ throw new DPayValueError(`Unknown API host "${host}"`);
126
+ return url;
127
+ }
128
+ };
129
+
130
+ // src/internal/operation.ts
131
+ function decodeJsonOrFail(response) {
132
+ const data = response.decodeJson();
133
+ if (data === null) {
134
+ throw new ApiServerError("Invalid JSON in API response", response.status, null, {}, response.body);
135
+ }
136
+ return data;
137
+ }
138
+ function decodeRecordOrFail(response) {
139
+ const data = decodeJsonOrFail(response);
140
+ return typeof data === "object" && data !== null && !Array.isArray(data) ? data : {};
141
+ }
142
+
143
+ // src/currency.ts
144
+ var PATTERN = /^[A-Z]{3}$/;
145
+ var Currency = { PLN: "PLN", EUR: "EUR", CZK: "CZK" };
146
+ function isValidCurrency(code) {
147
+ return PATTERN.test(code);
148
+ }
149
+ function assertCurrency(code) {
150
+ if (!isValidCurrency(code)) throw new DPayValueError(`Invalid currency code "${code}"`);
151
+ }
152
+
153
+ // src/money.ts
154
+ var DECIMAL = /^(-?)(\d+)(?:\.(\d{1,2}))?$/;
155
+ var Money = class _Money {
156
+ /** Amount in minor units, for example 1050 for 10.50 PLN. */
157
+ minor;
158
+ /** Three-letter currency code. */
159
+ currency;
160
+ constructor(minor, currency) {
161
+ if (!Number.isSafeInteger(minor)) {
162
+ throw new DPayValueError("Money amount must be a safe integer number of minor units");
163
+ }
164
+ this.minor = minor;
165
+ this.currency = currency;
166
+ Object.freeze(this);
167
+ }
168
+ /** Amount in Polish grosz, for example `Money.pln(1050)` is 10.50 PLN. */
169
+ static pln(minor) {
170
+ return new _Money(minor, Currency.PLN);
171
+ }
172
+ /** Amount in minor units of an arbitrary currency. */
173
+ static of(minor, currency) {
174
+ assertCurrency(currency);
175
+ return new _Money(minor, currency);
176
+ }
177
+ /** Parses a decimal string with at most two fraction digits. */
178
+ static fromDecimal(decimal, currency) {
179
+ assertCurrency(currency);
180
+ const parsed = parseDecimal(decimal);
181
+ if (parsed === null) throw new DPayValueError(`Invalid money amount "${decimal}"`);
182
+ return new _Money(parsed, currency);
183
+ }
184
+ /** Parses whatever shape the API used for this field. Throws on anything unparsable. */
185
+ static fromApiNumber(value, currency) {
186
+ const money = _Money.tryFromApiNumber(value, currency);
187
+ if (money === null) throw new DPayValueError("Money value must be a number or a decimal string");
188
+ return money;
189
+ }
190
+ /** Same as `fromApiNumber`, but returns null instead of throwing. */
191
+ static tryFromApiNumber(value, currency) {
192
+ if (!isValidCurrency(currency)) return null;
193
+ if (typeof value === "number") {
194
+ if (!Number.isFinite(value)) return null;
195
+ const minor = phpRound(value * 100);
196
+ return Number.isSafeInteger(minor) ? new _Money(minor, currency) : null;
197
+ }
198
+ if (typeof value === "string") {
199
+ const parsed = parseDecimal(value);
200
+ if (parsed === null || !Number.isSafeInteger(parsed)) return null;
201
+ return new _Money(parsed, currency);
202
+ }
203
+ return null;
204
+ }
205
+ /** Renders the amount as a decimal string with exactly two fraction digits. */
206
+ toDecimal() {
207
+ const absolute = Math.abs(this.minor);
208
+ const sign = this.minor < 0 ? "-" : "";
209
+ const fraction = String(absolute % 100).padStart(2, "0");
210
+ return `${sign}${Math.floor(absolute / 100)}.${fraction}`;
211
+ }
212
+ get isNegative() {
213
+ return this.minor < 0;
214
+ }
215
+ equals(other) {
216
+ return this.minor === other.minor && this.currency === other.currency;
217
+ }
218
+ toString() {
219
+ return `${this.toDecimal()} ${this.currency}`;
220
+ }
221
+ toJSON() {
222
+ return { minor: this.minor, currency: this.currency };
223
+ }
224
+ };
225
+ function parseDecimal(decimal) {
226
+ const match = DECIMAL.exec(decimal);
227
+ if (match === null) return null;
228
+ const fraction = (match[3] ?? "").padEnd(2, "0");
229
+ const minor = Number(match[2]) * 100 + Number(fraction);
230
+ return match[1] === "-" ? -minor : minor;
231
+ }
232
+
233
+ // src/payment/enums.ts
234
+ var TransactionType = {
235
+ TRANSFERS: "transfers",
236
+ DCB_GATEWAY: "dcb_gateway",
237
+ CARD_AUTH: "card_auth",
238
+ MB_WAY_DIRECT: "mb_way_direct",
239
+ BIZUM_DIRECT: "bizum_direct",
240
+ BLIK_RECURRING: "blik_recurring",
241
+ CARD_RECURRING: "card_recurring"
242
+ };
243
+ function assertTransactionType(value) {
244
+ if (!Object.values(TransactionType).includes(value)) {
245
+ throw new DPayValueError(`Invalid transaction type "${value}"`);
246
+ }
247
+ }
248
+ var TransactionStatus = {
249
+ PAID: "paid",
250
+ CREATED: "created",
251
+ PROCESSING: "processing",
252
+ EXPIRED: "expired",
253
+ CAPTURED: "captured"
254
+ };
255
+ var PayoutFeeMode = { NET: "net", GROSS: "gross" };
256
+ function assertPayoutFeeMode(value) {
257
+ if (!Object.values(PayoutFeeMode).includes(value)) {
258
+ throw new DPayValueError(`Invalid payout fee mode "${value}"`);
259
+ }
260
+ }
261
+
262
+ // src/payment/models.ts
263
+ var HTTP_URL = /^https?:\/\//;
264
+ function parseRegisteredPayment(data) {
265
+ const message = scalarString(data, "msg");
266
+ const additional = record(data.additionalInfo);
267
+ return Object.freeze({
268
+ transactionId: scalarString(data, "transactionId"),
269
+ message,
270
+ ipksef: strictString(data, "ipksef"),
271
+ redirectUrl: HTTP_URL.test(message) ? message : null,
272
+ isPaid: message === "Transaction paid",
273
+ isInternalProcessing: message === "Internal processing",
274
+ cardRecurringAlias: additional === null ? null : strictString(additional, "card_recurring_alias"),
275
+ raw: data
276
+ });
277
+ }
278
+ function parseTransaction(data) {
279
+ const inner = record(data.transaction) ?? {};
280
+ const status = scalarString(inner, "status");
281
+ const refunds = Array.isArray(data.refunds) ? data.refunds : [];
282
+ return Object.freeze({
283
+ id: scalarString(inner, "id"),
284
+ value: moneyOrZero(inner, "value"),
285
+ status,
286
+ paymentMethod: scalarStringOrNull(inner, "payment_method"),
287
+ creationDate: scalarStringOrNull(inner, "creation_date"),
288
+ paymentDate: strictString(inner, "payment_date"),
289
+ isPaid: status === TransactionStatus.PAID || status === TransactionStatus.CAPTURED,
290
+ isSettled: Boolean(inner.settled),
291
+ isRefunded: Boolean(inner.refunded),
292
+ refundedAmount: moneyOrZero(inner, "refunded_amount"),
293
+ availableRefundAmount: moneyOrZero(inner, "available_refund_amount"),
294
+ isFullyRefunded: Boolean(inner.fully_refunded),
295
+ isDirect: Boolean(inner.direct),
296
+ gatewayId: strictString(inner, "gateway_id"),
297
+ payer: record(data.payer) ?? {},
298
+ refunds: Object.freeze(
299
+ refunds.filter((item) => record(item) !== null).map(parseTransactionRefund)
300
+ ),
301
+ raw: data
302
+ });
303
+ }
304
+ function parseTransactionRefund(data) {
305
+ const status = scalarStringOrNull(data, "status");
306
+ return Object.freeze({
307
+ paymentId: scalarString(data, "payment_id"),
308
+ value: moneyOrZero(data, "value"),
309
+ status: status ?? TransactionStatus.PAID,
310
+ creationDate: scalarStringOrNull(data, "creation_date"),
311
+ paymentDate: scalarStringOrNull(data, "payment_date"),
312
+ raw: data
313
+ });
314
+ }
315
+ function scalarString(data, key) {
316
+ const value = data[key];
317
+ return isScalar(value) ? phpStrval(value) : "";
318
+ }
319
+ function scalarStringOrNull(data, key) {
320
+ const value = data[key];
321
+ return isScalar(value) ? phpStrval(value) : null;
322
+ }
323
+ function strictString(data, key) {
324
+ const value = data[key];
325
+ return typeof value === "string" ? value : null;
326
+ }
327
+ function record(value) {
328
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
329
+ }
330
+ function moneyOrZero(data, key) {
331
+ return Money.tryFromApiNumber(data[key] ?? 0, Currency.PLN) ?? Money.pln(0);
332
+ }
333
+
334
+ // src/bank/models.ts
335
+ function parseBank(data) {
336
+ return Object.freeze({
337
+ id: scalarString(data, "id"),
338
+ name: scalarString(data, "name"),
339
+ image: strictString(data, "image"),
340
+ onFrom: isScalar(data.on_from) ? phpInt(data.on_from) : 0,
341
+ onTo: isScalar(data.on_to) ? phpInt(data.on_to) : 0,
342
+ iterator: isScalar(data.iterator) ? phpInt(data.iterator) : null,
343
+ isTest: isScalar(data.test) ? phpBool(data.test) : false,
344
+ type: strictString(data, "type"),
345
+ raw: data
346
+ });
347
+ }
348
+ function phpBool(value) {
349
+ if (typeof value === "string") return value !== "" && value !== "0";
350
+ return Boolean(value);
351
+ }
352
+
353
+ // src/bank/ops.ts
354
+ function allBanksOperation() {
355
+ return { method: "GET", host: PANEL, path: "/api/v1/pbl/banks", parse: parseBanks };
356
+ }
357
+ function forServiceOperation(service, checksum, timestamp) {
358
+ const body = {
359
+ service,
360
+ timestamp: timestamp ?? Math.floor(Date.now() / 1e3)
361
+ };
362
+ body.checksum = checksum.orderedBody(Object.values(body));
363
+ return { method: "POST", host: PANEL, path: "/api/v1/pbl/banks", body, parse: parseBanks };
364
+ }
365
+ function parseBanks(response) {
366
+ const data = decodeJsonOrFail(response);
367
+ const items = Array.isArray(data) ? data : Object.values(data);
368
+ return items.filter(
369
+ (item) => typeof item === "object" && item !== null && !Array.isArray(item)
370
+ ).map(parseBank);
371
+ }
372
+
373
+ // src/bank/service.ts
374
+ var BankService = class {
375
+ api;
376
+ constructor(api) {
377
+ this.api = api;
378
+ }
379
+ /** Every bank dpay supports. */
380
+ async all(options) {
381
+ return this.api.execute(allBanksOperation(), options);
382
+ }
383
+ /** Banks enabled for this payment point. Defaults `timestamp` to the current clock. */
384
+ async forService(params, options) {
385
+ return this.api.execute(
386
+ forServiceOperation(this.api.service, this.api.checksum, params?.timestamp),
387
+ options
388
+ );
389
+ }
390
+ };
391
+
392
+ // src/blik/enums.ts
393
+ var BlikAliasType = { UID: "UID", PAYID: "PAYID" };
394
+ function assertBlikAliasType(value) {
395
+ if (!Object.values(BlikAliasType).includes(value)) {
396
+ throw new DPayValueError(`Invalid BLIK alias type "${value}"`);
397
+ }
398
+ }
399
+
400
+ // src/blik/models.ts
401
+ function parseBlikAlias(data) {
402
+ const status = strictString(data, "status");
403
+ const apps = Array.isArray(data.apps) ? data.apps : [];
404
+ return Object.freeze({
405
+ aliasValue: scalarStringOr(data, "alias_value", ""),
406
+ aliasType: scalarStringOr(data, "alias_type", BlikAliasType.UID),
407
+ status,
408
+ expirationDate: strictString(data, "expiration_date"),
409
+ apps: Object.freeze(
410
+ apps.filter((app) => record(app) !== null).map((app) => Object.freeze({ key: strictString(app, "key"), label: strictString(app, "label") }))
411
+ ),
412
+ isActive: status === "ACTIVE",
413
+ raw: data
414
+ });
415
+ }
416
+ function parseBlikRecurringStatus(data) {
417
+ const status = strictString(data, "status");
418
+ const registration = record(data.registration);
419
+ return Object.freeze({
420
+ aliasValue: scalarStringOr(data, "alias_value", ""),
421
+ aliasType: scalarStringOr(data, "alias_type", BlikAliasType.PAYID),
422
+ status,
423
+ expirationDate: strictString(data, "expiration_date"),
424
+ registration: registration === null ? null : parseRegistrationInfo(registration),
425
+ isActive: status === "ACTIVE",
426
+ raw: data
427
+ });
428
+ }
429
+ function parseRegistrationInfo(data) {
430
+ return Object.freeze({
431
+ model: strictString(data, "model"),
432
+ frequency: strictString(data, "frequency"),
433
+ limitAmt: isPhpInt(data.limit_amt) ? data.limit_amt : null,
434
+ totLimitAmt: isPhpInt(data.tot_limit_amt) ? data.tot_limit_amt : null,
435
+ isLimitAmtFixed: isPhpBool(data.is_limit_amt_fixed) ? data.is_limit_amt_fixed : null,
436
+ initDate: strictString(data, "init_date"),
437
+ label: strictString(data, "label"),
438
+ registeredAt: strictString(data, "registered_at"),
439
+ raw: data
440
+ });
441
+ }
442
+ function scalarStringOr(data, key, fallback) {
443
+ const value = data[key];
444
+ return isScalar(value) ? phpStrval(value) : fallback;
445
+ }
446
+
447
+ // src/blik/ops.ts
448
+ function aliasBody(service, params) {
449
+ const aliasType = params.aliasType ?? "UID";
450
+ assertBlikAliasType(aliasType);
451
+ return { service, alias_value: params.aliasValue, alias_type: aliasType };
452
+ }
453
+ function envelope(response) {
454
+ return record(decodeRecordOrFail(response).data) ?? {};
455
+ }
456
+ function aliasOperation(service, checksum, params) {
457
+ const body = aliasBody(service, params);
458
+ body.checksum = checksum.secretSecond(service, [params.aliasValue]);
459
+ return {
460
+ method: "POST",
461
+ host: API_PAYMENTS,
462
+ path: "/api/v1_0/payments/blik/aliases",
463
+ body,
464
+ parse: (response) => parseBlikAlias(envelope(response))
465
+ };
466
+ }
467
+ function unregisterAliasOperation(service, checksum, params) {
468
+ const body = aliasBody(service, params);
469
+ if (params.reason !== void 0) body.reason = params.reason;
470
+ body.checksum = checksum.secretSecond(service, [params.aliasValue]);
471
+ return {
472
+ method: "POST",
473
+ host: API_PAYMENTS,
474
+ path: "/api/v1_0/payments/blik/aliases/unregister",
475
+ body,
476
+ parse: (response) => {
477
+ decodeRecordOrFail(response);
478
+ }
479
+ };
480
+ }
481
+ function recurringStatusOperation(service, checksum, params) {
482
+ const body = { service, alias_value: params.aliasValue };
483
+ body.checksum = checksum.secretSecond(service, [params.aliasValue]);
484
+ return {
485
+ method: "POST",
486
+ host: API_PAYMENTS,
487
+ path: "/api/v1_0/payments/blik/recurring/status",
488
+ body,
489
+ parse: (response) => parseBlikRecurringStatus(envelope(response))
490
+ };
491
+ }
492
+
493
+ // src/blik/service.ts
494
+ var BlikService = class {
495
+ api;
496
+ constructor(api) {
497
+ this.api = api;
498
+ }
499
+ /** Registers a BLIK alias so the payer can pay without a code next time. */
500
+ async alias(params, options) {
501
+ return this.api.execute(aliasOperation(this.api.service, this.api.checksum, params), options);
502
+ }
503
+ /** Removes a BLIK alias. */
504
+ async unregisterAlias(params, options) {
505
+ return this.api.execute(unregisterAliasOperation(this.api.service, this.api.checksum, params), options);
506
+ }
507
+ /** Reads the state of a recurring BLIK mandate. */
508
+ async recurringStatus(params, options) {
509
+ return this.api.execute(recurringStatusOperation(this.api.service, this.api.checksum, params), options);
510
+ }
511
+ };
512
+
513
+ // src/card/enums.ts
514
+ var RedirectType = {
515
+ SUCCESS: "SUCCESS",
516
+ FORM: "FORM",
517
+ URL: "URL",
518
+ DCC_OFFER: "DCC_OFFER"
519
+ };
520
+ var DccDecision = { ACCEPT: "accept", REJECT: "reject" };
521
+ function assertDccDecision(value) {
522
+ if (!Object.values(DccDecision).includes(value)) {
523
+ throw new DPayValueError(`Invalid DCC decision "${value}"`);
524
+ }
525
+ }
526
+ var CardRecurringOperation = {
527
+ ADD_CARD: "add_card",
528
+ COF_INITIAL: "cof_initial",
529
+ CHARGE: "charge"
530
+ };
531
+ function assertCardRecurringOperation(value) {
532
+ if (!Object.values(CardRecurringOperation).includes(value)) {
533
+ throw new DPayValueError(`Invalid card recurring operation "${value}"`);
534
+ }
535
+ }
536
+ var CardRecurringFrequency = {
537
+ DAILY: "DAILY",
538
+ WEEKLY: "WEEKLY",
539
+ BIWEEKLY: "BIWEEKLY",
540
+ MONTHLY: "MONTHLY",
541
+ QUARTERLY: "QUARTERLY",
542
+ SEMIANNUAL: "SEMIANNUAL",
543
+ ANNUAL: "ANNUAL"
544
+ };
545
+ function assertCardRecurringFrequency(value) {
546
+ if (!Object.values(CardRecurringFrequency).includes(value)) {
547
+ throw new DPayValueError(`Invalid card recurring frequency "${value}"`);
548
+ }
549
+ }
550
+
551
+ // src/card/models.ts
552
+ function parseCardPaymentResult(data) {
553
+ const message = record(data.message) ?? {};
554
+ const redirectType = strictString(message, "redirectType");
555
+ const redirectTextRaw = strictString(message, "redirectText");
556
+ const redirectText = redirectTextRaw === null || redirectTextRaw === "" ? null : redirectTextRaw;
557
+ const offer = record(message.dccOffer);
558
+ const requiresThreeDsForm = redirectType === RedirectType.FORM;
559
+ const requiresRedirect = redirectType === RedirectType.URL;
560
+ return Object.freeze({
561
+ redirectType,
562
+ redirectText,
563
+ isSuccess: redirectType === RedirectType.SUCCESS,
564
+ requiresThreeDsForm,
565
+ requiresRedirect,
566
+ hasDccOffer: redirectType === RedirectType.DCC_OFFER,
567
+ threeDsFormHtml: requiresThreeDsForm && redirectText !== null ? decodeBase64(redirectText) : null,
568
+ redirectUrl: requiresRedirect && redirectText !== null ? decodeBase64(redirectText) : null,
569
+ dccOffer: offer === null ? null : parseDccOffer(offer),
570
+ raw: data
571
+ });
572
+ }
573
+ function parseDccOffer(data) {
574
+ const markup = Array.isArray(data.markup) ? data.markup : [];
575
+ return Object.freeze({
576
+ currencyConversionId: scalarString(data, "currencyConversionId"),
577
+ originalAmount: amount(data, "originalAmount", "originalCurrency"),
578
+ convertedAmount: amount(data, "convertedAmount", "convertedCurrency"),
579
+ exchangeRate: floatOrZero(data, "exchangeRate"),
580
+ validUntil: scalarString(data, "validUntil"),
581
+ declarationText: scalarString(data, "declarationText"),
582
+ isEuropeanEconomicArea: Boolean(data.europeanEconomicArea),
583
+ markup: Object.freeze(
584
+ markup.filter((item) => record(item) !== null).map(
585
+ (item) => Object.freeze({
586
+ rate: floatOrZero(item, "rate"),
587
+ additionalInfo: strictString(item, "additionalInfo")
588
+ })
589
+ )
590
+ ),
591
+ raw: data
592
+ });
593
+ }
594
+ function amount(data, amountKey, currencyKey) {
595
+ const currency = data[currencyKey];
596
+ return Money.tryFromApiNumber(data[amountKey] ?? 0, typeof currency === "string" ? currency : Currency.PLN) ?? Money.pln(0);
597
+ }
598
+ function floatOrZero(data, key) {
599
+ const value = data[key] ?? 0;
600
+ return isNumeric(value) ? Number(value) : 0;
601
+ }
602
+ function decodeBase64(text) {
603
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(text) || text.length % 4 !== 0) return null;
604
+ return Buffer.from(text, "base64").toString("utf8");
605
+ }
606
+
607
+ // src/internal/validation.ts
608
+ var EMAIL = /^[^@\s]+@[^@\s.]+(\.[^@\s.]+)+$/;
609
+ var DATE = /^\d{4}-\d{2}-\d{2}$/;
610
+ function isValidUrl(url) {
611
+ if (url === "") return false;
612
+ for (const char of url) {
613
+ const code = char.charCodeAt(0);
614
+ if (code <= 32 || code === 127) return false;
615
+ }
616
+ let parsed;
617
+ try {
618
+ parsed = new URL(url);
619
+ } catch {
620
+ return false;
621
+ }
622
+ return parsed.protocol !== "" && parsed.host !== "";
623
+ }
624
+ function isValidEmail(email) {
625
+ return EMAIL.test(email);
626
+ }
627
+ function isValidDate(date) {
628
+ return DATE.test(date);
629
+ }
630
+ function assertDate(date) {
631
+ if (!isValidDate(date)) throw new DPayValueError(`Date "${date}" must be in YYYY-MM-DD format`);
632
+ return date;
633
+ }
634
+
635
+ // src/payment/params.ts
636
+ function serializeDeviceInfo(info) {
637
+ if (info.deviceId === "" || info.deviceId.length > 64) {
638
+ throw new DPayValueError("Device ID must be 1-64 characters");
639
+ }
640
+ if (info.applicationName === "" || info.applicationName.length > 64) {
641
+ throw new DPayValueError("Application name must be 1-64 characters");
642
+ }
643
+ const data = {
644
+ browserAcceptHeader: info.browserAcceptHeader,
645
+ browserLanguage: info.browserLanguage,
646
+ browserColorDepth: info.browserColorDepth,
647
+ browserScreenHeight: info.browserScreenHeight,
648
+ browserScreenWidth: info.browserScreenWidth,
649
+ browserTZ: info.browserTz,
650
+ browserUserAgent: info.browserUserAgent,
651
+ systemFamily: info.systemFamily,
652
+ geoLocalization: info.geoLocalization,
653
+ deviceID: info.deviceId,
654
+ applicationName: info.applicationName
655
+ };
656
+ if (info.browserJavaEnabled !== void 0) {
657
+ data.browserJavaEnabled = info.browserJavaEnabled ? "true" : "false";
658
+ }
659
+ return data;
660
+ }
661
+ function serializeInvoice(invoice) {
662
+ const data = {};
663
+ if (invoice.payerNip !== void 0) data.payer_nip = invoice.payerNip;
664
+ if (invoice.payerName !== void 0) data.payer_name = invoice.payerName;
665
+ if (invoice.invoiceNumber !== void 0) data.invoice_number = invoice.invoiceNumber;
666
+ if (invoice.paymentDueDate !== void 0) {
667
+ if (!isValidDate(invoice.paymentDueDate)) {
668
+ throw new DPayValueError("Payment due date must be in YYYY-MM-DD format");
669
+ }
670
+ data.payment_due_date = invoice.paymentDueDate;
671
+ }
672
+ if (invoice.vatAmount !== void 0) data.vat_amount = invoice.vatAmount.minor;
673
+ return data;
674
+ }
675
+ function serializePayoutInstruction(payout) {
676
+ if (payout.positions.length === 0) {
677
+ throw new DPayValueError("Payout instruction requires at least one position");
678
+ }
679
+ const feeMode = payout.feeMode ?? PayoutFeeMode.NET;
680
+ assertPayoutFeeMode(feeMode);
681
+ return {
682
+ fee_mode: feeMode,
683
+ positions: payout.positions.map((position) => {
684
+ if (position.iban === "") throw new DPayValueError("Payout IBAN must not be empty");
685
+ if (position.title === "" || position.title.length > 255) {
686
+ throw new DPayValueError("Payout title must be 1-255 characters");
687
+ }
688
+ return { iban: position.iban, title: position.title, amount: Number(position.amount.toDecimal()) };
689
+ })
690
+ };
691
+ }
692
+
693
+ // src/card/params.ts
694
+ function serializeCardPayment(params) {
695
+ const body = {};
696
+ if (params.email !== void 0) body.email = params.email;
697
+ if (params.channelId !== void 0) body.channelId = params.channelId;
698
+ if (params.cardHolderFirstName !== void 0) body.cardHolderFirstName = params.cardHolderFirstName;
699
+ if (params.cardHolderLastName !== void 0) body.cardHolderLastName = params.cardHolderLastName;
700
+ if (params.encryptedCardData !== void 0) body.encryptedCardData = params.encryptedCardData;
701
+ body.deviceInfo = serializeDeviceInfo(params.deviceInfo);
702
+ if (params.threeDsConfirmed !== void 0) body.threeDsConfirmed = params.threeDsConfirmed;
703
+ if (params.dccDecision !== void 0) {
704
+ assertDccDecision(params.dccDecision);
705
+ body.dccDecision = params.dccDecision;
706
+ }
707
+ return body;
708
+ }
709
+ function serializeGooglePay(params) {
710
+ const body = {};
711
+ if (params.email !== void 0) body.email = params.email;
712
+ if (params.channelId !== void 0) body.channelId = params.channelId;
713
+ body.xPayType = "GOOGLE_PAY";
714
+ body.xPayToken = params.token;
715
+ body.deviceInfo = serializeDeviceInfo(params.deviceInfo);
716
+ return body;
717
+ }
718
+ function serializeApplePay(params) {
719
+ const body = {};
720
+ if (params.channelId !== void 0) body.channelId = params.channelId;
721
+ body.xPayType = params.token === void 0 ? "APPLE_PAY_INIT" : "APPLE_PAY";
722
+ if (params.token !== void 0) body.xPayToken = params.token;
723
+ body.deviceInfo = serializeDeviceInfo(params.deviceInfo);
724
+ return body;
725
+ }
726
+
727
+ // src/card/ops.ts
728
+ function publicKeyOperation() {
729
+ return {
730
+ method: "GET",
731
+ host: API_PAYMENTS,
732
+ path: "/api/v1_0/cards/public-key",
733
+ parse: (response) => response.body.trim()
734
+ };
735
+ }
736
+ function paymentOperation(transactionId, suffix, body) {
737
+ return {
738
+ method: "POST",
739
+ host: API_PAYMENTS,
740
+ path: `/api/v1_0/cards/payment/${encodeURIComponent(transactionId)}${suffix}`,
741
+ body,
742
+ parse: parseResult
743
+ };
744
+ }
745
+ function parseResult(response) {
746
+ const data = decodeRecordOrFail(response);
747
+ if (data.success !== true) throw CardPaymentError.fromApi(data);
748
+ return parseCardPaymentResult(data);
749
+ }
750
+ function payOtpOperation(id, params) {
751
+ return paymentOperation(id, "/pay/card-otp", serializeCardPayment(params));
752
+ }
753
+ function preAuthOperation(id, params) {
754
+ return paymentOperation(id, "/pay/card-pre-auth", serializeCardPayment(params));
755
+ }
756
+ function captureOperation(id, amount2) {
757
+ return paymentOperation(id, "/capture", amountBody(amount2));
758
+ }
759
+ function cancelOperation(id, amount2) {
760
+ return paymentOperation(id, "/cancellation", amountBody(amount2));
761
+ }
762
+ function googlePayOperation(id, params) {
763
+ return paymentOperation(id, "/pay/google-pay", serializeGooglePay(params));
764
+ }
765
+ function applePayOperation(id, params) {
766
+ return paymentOperation(id, "/pay/apple-pay", serializeApplePay(params));
767
+ }
768
+ function amountBody(amount2) {
769
+ return amount2 === void 0 ? {} : { amount: Number(amount2.toDecimal()) };
770
+ }
771
+
772
+ // src/card/service.ts
773
+ var CardService = class {
774
+ api;
775
+ constructor(api) {
776
+ this.api = api;
777
+ }
778
+ /** Current RSA public key. dpay rotates it, so fetch it before every attempt. */
779
+ async publicKey(options) {
780
+ return this.api.execute(publicKeyOperation(), options);
781
+ }
782
+ /** Charges a card immediately. */
783
+ async payOtp(transactionId, params, options) {
784
+ return this.api.execute(payOtpOperation(transactionId, params), options);
785
+ }
786
+ /** Authorizes a card without capturing the funds. */
787
+ async preAuth(transactionId, params, options) {
788
+ return this.api.execute(preAuthOperation(transactionId, params), options);
789
+ }
790
+ /** Captures a pre-authorization. Omit `amount` to capture in full. */
791
+ async capture(transactionId, params, options) {
792
+ return this.api.execute(captureOperation(transactionId, params?.amount), options);
793
+ }
794
+ /** Cancels a pre-authorization. Omit `amount` to cancel in full. */
795
+ async cancel(transactionId, params, options) {
796
+ return this.api.execute(cancelOperation(transactionId, params?.amount), options);
797
+ }
798
+ /** Pays with a Google Pay token. */
799
+ async googlePay(transactionId, params, options) {
800
+ return this.api.execute(googlePayOperation(transactionId, params), options);
801
+ }
802
+ /** Initialises an Apple Pay session, or pays when `token` is given. */
803
+ async applePay(transactionId, params, options) {
804
+ return this.api.execute(applePayOperation(transactionId, params), options);
805
+ }
806
+ };
807
+
808
+ // src/config.ts
809
+ var KNOWN_OPTIONS = [
810
+ "service",
811
+ "secretHash",
812
+ "timeout",
813
+ "httpClient",
814
+ "baseUrls",
815
+ "onRequest",
816
+ "onResponse"
817
+ ];
818
+ function ownOption(options, key) {
819
+ return Object.hasOwn(options, key) ? options[key] : void 0;
820
+ }
821
+ var DEFAULT_TIMEOUT = 3e4;
822
+ var TIMEOUT_MESSAGE = 'Option "timeout" must be a positive integer number of milliseconds';
823
+ function validateTimeoutMs(timeout) {
824
+ if (!Number.isInteger(timeout) || timeout < 1) {
825
+ throw new DPayValueError(TIMEOUT_MESSAGE);
826
+ }
827
+ return timeout;
828
+ }
829
+ var Config = class _Config {
830
+ service;
831
+ secretHash;
832
+ timeout;
833
+ httpClient;
834
+ baseUrls;
835
+ onRequest;
836
+ onResponse;
837
+ constructor(init) {
838
+ this.service = init.service;
839
+ this.secretHash = init.secretHash;
840
+ this.timeout = init.timeout;
841
+ this.httpClient = init.httpClient;
842
+ this.baseUrls = init.baseUrls;
843
+ this.onRequest = init.onRequest;
844
+ this.onResponse = init.onResponse;
845
+ Object.freeze(this);
846
+ }
847
+ static fromOptions(options) {
848
+ const snapshot = Object.fromEntries(Object.entries(options));
849
+ for (const key of Object.keys(snapshot)) {
850
+ if (!KNOWN_OPTIONS.includes(key)) {
851
+ throw new DPayValueError(`Unknown option "${key}"`);
852
+ }
853
+ }
854
+ const service = ownOption(snapshot, "service");
855
+ if (typeof service !== "string" || service === "") {
856
+ throw new DPayValueError('Option "service" is required and must be a non-empty string');
857
+ }
858
+ const secretHash = ownOption(snapshot, "secretHash");
859
+ if (typeof secretHash !== "string" || secretHash === "") {
860
+ throw new DPayValueError('Option "secretHash" is required and must be a non-empty string');
861
+ }
862
+ const timeout = validateTimeoutMs(ownOption(snapshot, "timeout") ?? DEFAULT_TIMEOUT);
863
+ const httpClient = ownOption(snapshot, "httpClient") ?? null;
864
+ if (httpClient !== null && typeof httpClient.request !== "function") {
865
+ throw new DPayValueError('Option "httpClient" must implement HttpClient');
866
+ }
867
+ const overrides = ownOption(snapshot, "baseUrls") ?? {};
868
+ if (typeof overrides !== "object" || overrides === null || Array.isArray(overrides)) {
869
+ throw new DPayValueError('Option "baseUrls" must be an object');
870
+ }
871
+ for (const url of Object.values(overrides)) {
872
+ if (typeof url !== "string" || url === "") {
873
+ throw new DPayValueError("Base URLs must be non-empty strings");
874
+ }
875
+ }
876
+ for (const name of ["onRequest", "onResponse"]) {
877
+ const hook = ownOption(snapshot, name);
878
+ if (hook !== void 0 && typeof hook !== "function") {
879
+ throw new DPayValueError(`Option "${name}" must be a function`);
880
+ }
881
+ }
882
+ return new _Config({
883
+ service,
884
+ secretHash,
885
+ timeout,
886
+ httpClient,
887
+ baseUrls: new BaseUrls(overrides),
888
+ onRequest: ownOption(snapshot, "onRequest") ?? null,
889
+ onResponse: ownOption(snapshot, "onResponse") ?? null
890
+ });
891
+ }
892
+ };
893
+
894
+ // src/http/fetch-client.ts
895
+ var FetchHttpClient = class {
896
+ fetchImpl;
897
+ constructor(fetchImpl = globalThis.fetch) {
898
+ this.fetchImpl = fetchImpl;
899
+ }
900
+ async request(request) {
901
+ let response;
902
+ try {
903
+ response = await this.fetchImpl(request.url, {
904
+ method: request.method,
905
+ headers: { ...request.headers },
906
+ ...request.body !== null ? { body: request.body } : {},
907
+ signal: request.signal ?? null,
908
+ redirect: "manual"
909
+ });
910
+ } catch (error) {
911
+ throw new TransportError("HTTP request failed", { cause: error });
912
+ }
913
+ let body;
914
+ try {
915
+ body = await response.text();
916
+ } catch (error) {
917
+ throw new TransportError("Unable to read the API response body", { cause: error });
918
+ }
919
+ const headers = {};
920
+ response.headers.forEach((value, name) => {
921
+ headers[name] = value;
922
+ });
923
+ return new ApiResponse(response.status, headers, body);
924
+ }
925
+ };
926
+
927
+ // src/version.ts
928
+ var SDK_VERSION = "0.1.0";
929
+ var ChecksumCalculator = class {
930
+ secretHash;
931
+ constructor(secretHash) {
932
+ this.secretHash = secretHash;
933
+ }
934
+ secretSecond(service, fields) {
935
+ const parts = [service, this.secretHash, ...fields.map(phpStrval)];
936
+ return sha256(parts.join("|"));
937
+ }
938
+ orderedBody(values) {
939
+ const joined = values.map(phpStrval).join("|");
940
+ return sha256(`${joined}|${this.secretHash}`);
941
+ }
942
+ };
943
+ function sha256(text) {
944
+ return createHash("sha256").update(text, "utf8").digest("hex");
945
+ }
946
+
947
+ // src/internal/error-mapper.ts
948
+ function mapError(response) {
949
+ const status = response.status;
950
+ const rawBody = response.body;
951
+ const decoded = response.decodeJson();
952
+ const data = typeof decoded === "object" && decoded !== null && !Array.isArray(decoded) ? decoded : {};
953
+ let message = "Unexpected API error";
954
+ if (typeof data.message === "string") message = data.message;
955
+ else if (typeof data.msg === "string") message = data.msg;
956
+ const errorCode = typeof data.errorcode === "string" ? data.errorcode : null;
957
+ const fieldErrors = normalizeFieldErrors(data.errors);
958
+ if (status === 429) {
959
+ return new RateLimitError(
960
+ message,
961
+ status,
962
+ intHeader(response, "Retry-After"),
963
+ intHeader(response, "X-RateLimit-Limit"),
964
+ intHeader(response, "X-RateLimit-Remaining"),
965
+ rawBody
966
+ );
967
+ }
968
+ if (status === 401) return new AuthenticationError(message, status, errorCode, fieldErrors, rawBody);
969
+ if (status === 403) return new AccessDeniedError(message, status, errorCode, fieldErrors, rawBody);
970
+ if (status === 404) return new NotFoundError(message, status, errorCode, fieldErrors, rawBody);
971
+ if (status === 400 || status === 422) {
972
+ return new InvalidRequestError(message, status, errorCode, fieldErrors, rawBody);
973
+ }
974
+ if (status >= 500) return new ApiServerError(message, status, errorCode, fieldErrors, rawBody);
975
+ return new ApiError(message, status, errorCode, fieldErrors, rawBody);
976
+ }
977
+ function normalizeFieldErrors(errors) {
978
+ let entries;
979
+ if (Array.isArray(errors)) {
980
+ entries = errors.map((value, index) => [String(index), value]);
981
+ } else if (typeof errors === "object" && errors !== null) {
982
+ entries = Object.entries(errors);
983
+ } else {
984
+ return {};
985
+ }
986
+ const normalized = {};
987
+ for (const [field, messages] of entries) {
988
+ if (typeof messages === "string") {
989
+ normalized[phpStrval(field)] = [messages];
990
+ } else if (Array.isArray(messages)) {
991
+ normalized[phpStrval(field)] = messages.filter(isScalar).map(phpStrval);
992
+ }
993
+ }
994
+ return normalized;
995
+ }
996
+ function intHeader(response, name) {
997
+ const value = response.getHeader(name);
998
+ return value === null || !isNumeric(value) ? null : phpInt(value);
999
+ }
1000
+
1001
+ // src/internal/requestor.ts
1002
+ var USER_AGENT = `dpay-node-sdk/${SDK_VERSION} node/${process.versions.node}`;
1003
+ var REDACTED_KEYS = ["checksum", "encryptedCardData", "xPayToken"];
1004
+ var ApiRequestor = class {
1005
+ service;
1006
+ checksum;
1007
+ config;
1008
+ httpClient;
1009
+ constructor(config, httpClient) {
1010
+ this.config = config;
1011
+ this.httpClient = httpClient;
1012
+ this.service = config.service;
1013
+ this.checksum = new ChecksumCalculator(config.secretHash);
1014
+ }
1015
+ async execute(operation, options) {
1016
+ const request = this.buildRequest(operation, options);
1017
+ this.notify(() => this.config.onRequest?.(redactRequest(request)));
1018
+ let response;
1019
+ try {
1020
+ response = await this.httpClient.request(request);
1021
+ } catch (error) {
1022
+ if (error instanceof DPayError) throw error;
1023
+ throw new TransportError("HTTP request failed", { cause: error });
1024
+ }
1025
+ this.notify(
1026
+ () => this.config.onResponse?.({ status: response.status, url: request.url, body: response.body })
1027
+ );
1028
+ if (operation.raiseForStatus !== false && response.status >= 400) throw mapError(response);
1029
+ return operation.parse(response);
1030
+ }
1031
+ buildRequest(operation, options) {
1032
+ const url = this.config.baseUrls.resolve(operation.host) + operation.path;
1033
+ const headers = { Accept: "application/json", "User-Agent": USER_AGENT };
1034
+ let body = null;
1035
+ if (operation.body !== void 0) {
1036
+ headers["Content-Type"] = "application/json";
1037
+ try {
1038
+ body = phpJsonEncode(operation.body);
1039
+ } catch (error) {
1040
+ throw new TransportError("Unable to encode request body as JSON", {
1041
+ cause: error
1042
+ });
1043
+ }
1044
+ }
1045
+ return { method: operation.method, url, headers, body, signal: this.resolveSignal(options) };
1046
+ }
1047
+ resolveSignal(options) {
1048
+ const timeout = AbortSignal.timeout(validateTimeoutMs(options?.timeout ?? this.config.timeout));
1049
+ return options?.signal === void 0 ? timeout : AbortSignal.any([options.signal, timeout]);
1050
+ }
1051
+ notify(emit) {
1052
+ let outcome;
1053
+ try {
1054
+ outcome = emit();
1055
+ } catch {
1056
+ return;
1057
+ }
1058
+ if (isThenable(outcome)) outcome.then(noop, noop);
1059
+ }
1060
+ };
1061
+ function noop() {
1062
+ }
1063
+ function isThenable(value) {
1064
+ return typeof value === "object" && value !== null && typeof value.then === "function";
1065
+ }
1066
+ function redactRequest(request) {
1067
+ return {
1068
+ method: request.method,
1069
+ url: request.url,
1070
+ headers: { ...request.headers },
1071
+ body: redactBody(request.body)
1072
+ };
1073
+ }
1074
+ function redactBody(body) {
1075
+ if (body === null) return null;
1076
+ let decoded;
1077
+ try {
1078
+ decoded = JSON.parse(body);
1079
+ } catch {
1080
+ return body;
1081
+ }
1082
+ if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return body;
1083
+ try {
1084
+ return phpJsonEncode(redactValue(decoded));
1085
+ } catch {
1086
+ return body;
1087
+ }
1088
+ }
1089
+ function redactValue(value) {
1090
+ if (Array.isArray(value)) return value.map(redactValue);
1091
+ if (typeof value !== "object" || value === null) return value;
1092
+ const redacted = {};
1093
+ for (const [key, nested] of Object.entries(value)) {
1094
+ redacted[key] = REDACTED_KEYS.includes(key) ? "[redacted]" : redactValue(nested);
1095
+ }
1096
+ return redacted;
1097
+ }
1098
+
1099
+ // src/ipn/request.ts
1100
+ async function readRawBody(request) {
1101
+ if (typeof request === "string") return request;
1102
+ if (request instanceof Uint8Array) return Buffer.from(request).toString("utf8");
1103
+ if (typeof request !== "object" || request === null) {
1104
+ throw new DPayValueError("Unsupported request object passed to the IPN verifier");
1105
+ }
1106
+ const candidate = request;
1107
+ if (typeof candidate.text === "function") {
1108
+ try {
1109
+ return await candidate.text();
1110
+ } catch (error) {
1111
+ if (error instanceof DPayValueError) {
1112
+ throw error;
1113
+ }
1114
+ throw new TransportError("Unable to read the request body", { cause: error });
1115
+ }
1116
+ }
1117
+ if (typeof candidate[Symbol.asyncIterator] === "function") {
1118
+ const chunks = [];
1119
+ try {
1120
+ for await (const chunk of request) {
1121
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk);
1122
+ }
1123
+ } catch (error) {
1124
+ if (error instanceof DPayValueError) {
1125
+ throw error;
1126
+ }
1127
+ throw new TransportError("Unable to read the request body", { cause: error });
1128
+ }
1129
+ const text = Buffer.concat(chunks).toString("utf8");
1130
+ if (text === "" && candidate.body !== void 0) {
1131
+ throw new DPayValueError(
1132
+ "The request body was already consumed by a body parser. Mount the IPN route before express.json(), for example with express.raw({ type: '*/*' }), and pass the raw Buffer instead."
1133
+ );
1134
+ }
1135
+ return text;
1136
+ }
1137
+ throw new DPayValueError("Unsupported request object passed to the IPN verifier");
1138
+ }
1139
+
1140
+ // src/ipn/event.ts
1141
+ var IpnType = { TRANSFER: "transfer", CAPTURE: "capture", DCB: "dcb" };
1142
+ var IPN_ACK = "OK";
1143
+ function buildIpnEvent(raw) {
1144
+ const type = scalar(raw, "type") ?? "";
1145
+ return Object.freeze({
1146
+ id: scalar(raw, "id") ?? "",
1147
+ amount: scalar(raw, "amount") ?? "",
1148
+ email: scalar(raw, "email"),
1149
+ type,
1150
+ attempt: isNumeric(raw.attempt) ? phpInt(raw.attempt) : 0,
1151
+ version: isNumeric(raw.version) ? phpInt(raw.version) : 0,
1152
+ custom: scalar(raw, "custom"),
1153
+ capturePaymentId: scalar(raw, "capture_payment_id"),
1154
+ signature: scalar(raw, "signature") ?? "",
1155
+ isTransfer: type === IpnType.TRANSFER,
1156
+ isCapture: type === IpnType.CAPTURE,
1157
+ isDcb: type === IpnType.DCB,
1158
+ raw
1159
+ });
1160
+ }
1161
+ function scalar(raw, key) {
1162
+ const value = raw[key];
1163
+ return isScalar(value) ? phpStrval(value) : null;
1164
+ }
1165
+
1166
+ // src/ipn/verifier.ts
1167
+ var REQUIRED = ["id", "amount", "type", "attempt", "version", "signature"];
1168
+ function constructIpnEvent(rawBody, secretHash) {
1169
+ const text = typeof rawBody === "string" ? rawBody : Buffer.from(rawBody).toString("utf8");
1170
+ let payload;
1171
+ try {
1172
+ payload = JSON.parse(text);
1173
+ } catch (error) {
1174
+ throw new SignatureVerificationError("Invalid IPN payload", { cause: error });
1175
+ }
1176
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
1177
+ throw new SignatureVerificationError("Invalid IPN payload");
1178
+ }
1179
+ const raw = payload;
1180
+ for (const field of REQUIRED) {
1181
+ if (raw[field] === void 0 || raw[field] === null) {
1182
+ throw new SignatureVerificationError("Invalid IPN payload");
1183
+ }
1184
+ }
1185
+ if (typeof raw.signature !== "string") throw new SignatureVerificationError("Invalid IPN payload");
1186
+ const expected = expectedSignature(raw, secretHash);
1187
+ const provided = Buffer.from(raw.signature, "utf8");
1188
+ const wanted = Buffer.from(expected, "utf8");
1189
+ if (provided.length !== wanted.length || !timingSafeEqual(provided, wanted)) {
1190
+ throw new SignatureVerificationError("Invalid IPN signature");
1191
+ }
1192
+ return buildIpnEvent(raw);
1193
+ }
1194
+ function expectedSignature(raw, secretHash) {
1195
+ const type = isScalar(raw.type) ? phpStrval(raw.type) : "";
1196
+ const parts = [
1197
+ isScalar(raw.id) ? phpStrval(raw.id) : "",
1198
+ secretHash,
1199
+ isScalar(raw.amount) ? phpStrval(raw.amount) : ""
1200
+ ];
1201
+ if (type !== IpnType.DCB) parts.push(isScalar(raw.email) ? phpStrval(raw.email) : "");
1202
+ parts.push(type);
1203
+ parts.push(isNumeric(raw.attempt) ? phpStrval(raw.attempt) : "0");
1204
+ parts.push(isNumeric(raw.version) ? phpStrval(raw.version) : "0");
1205
+ parts.push(isScalar(raw.custom) ? phpStrval(raw.custom) : "");
1206
+ return createHash("sha256").update(parts.join(""), "utf8").digest("hex");
1207
+ }
1208
+
1209
+ // src/ipn/service.ts
1210
+ var IpnService = class {
1211
+ config;
1212
+ constructor(config) {
1213
+ this.config = config;
1214
+ }
1215
+ /** Verifies an IPN body. Uses the client's `secretHash` unless one is given. */
1216
+ constructEvent(rawBody, secretHash) {
1217
+ return constructIpnEvent(rawBody, secretHash ?? this.config.secretHash);
1218
+ }
1219
+ /** Reads and verifies an IPN straight from an incoming HTTP request. */
1220
+ async constructEventFromRequest(request, secretHash) {
1221
+ return constructIpnEvent(await readRawBody(request), secretHash ?? this.config.secretHash);
1222
+ }
1223
+ };
1224
+
1225
+ // src/blik/params.ts
1226
+ var FREQUENCY = /^[1-9][0-9]{0,2}[DWMQY]$/;
1227
+ var MODELS = ["A", "M", "O"];
1228
+ function serializeBlikAliasRegistration(params) {
1229
+ assertLabel(params.label, "Alias label must be 1-50 characters");
1230
+ const type = params.type ?? BlikAliasType.UID;
1231
+ assertBlikAliasType(type);
1232
+ return { label: params.label, type };
1233
+ }
1234
+ function serializeBlikRecurringRegistration(params) {
1235
+ assertLabel(params.label, "Alias label must be 1-50 characters");
1236
+ if (!MODELS.includes(params.model)) {
1237
+ throw new DPayValueError(`Invalid recurring model "${params.model}"`);
1238
+ }
1239
+ if (!FREQUENCY.test(params.frequency)) {
1240
+ throw new DPayValueError(`Invalid recurring frequency "${params.frequency}"`);
1241
+ }
1242
+ const data = {
1243
+ label: params.label,
1244
+ type: BlikAliasType.PAYID,
1245
+ model: params.model,
1246
+ frequency: params.frequency
1247
+ };
1248
+ if (params.value !== void 0) data.value = params.value.toDecimal();
1249
+ if (params.limitAmt !== void 0) data.limit_amt = params.limitAmt;
1250
+ if (params.totLimitAmt !== void 0) data.tot_limit_amt = params.totLimitAmt;
1251
+ if (params.limitAmtFixed !== void 0) data.is_limit_amt_fixed = params.limitAmtFixed;
1252
+ if (params.expirationDate !== void 0) data.expiration_date = assertDate(params.expirationDate);
1253
+ if (params.initDate !== void 0) data.init_date = assertDate(params.initDate);
1254
+ return data;
1255
+ }
1256
+ function assertLabel(label, message) {
1257
+ if (label === "" || label.length > 50) throw new DPayValueError(message);
1258
+ }
1259
+
1260
+ // src/card/recurring-params.ts
1261
+ function serializeCardRecurringRegistration(params) {
1262
+ if (params.label === "" || params.label.length > 50) {
1263
+ throw new DPayValueError("Mandate label must be 1-50 characters");
1264
+ }
1265
+ const data = { label: params.label };
1266
+ if (params.frequency !== void 0) {
1267
+ assertCardRecurringFrequency(params.frequency);
1268
+ data.frequency = params.frequency;
1269
+ }
1270
+ if (params.limitAmt !== void 0) data.limit_amt = params.limitAmt.minor;
1271
+ if (params.totLimitAmt !== void 0) data.tot_limit_amt = params.totLimitAmt.minor;
1272
+ if (params.limitAmtFixed !== void 0) data.is_limit_amt_fixed = params.limitAmtFixed;
1273
+ if (params.expirationDate !== void 0) data.expiration_date = assertDate(params.expirationDate);
1274
+ if (params.initDate !== void 0) data.init_date = assertDate(params.initDate);
1275
+ return data;
1276
+ }
1277
+
1278
+ // src/payment/register-body.ts
1279
+ var PARTNER_PLATFORM = /^[A-Z0-9]{1,64}$/;
1280
+ var BLIK_CODE = /^\d{6}$/;
1281
+ function buildRegisterBody(service, params) {
1282
+ assertTransactionType(params.transactionType);
1283
+ assertExclusions(params);
1284
+ const body = {
1285
+ service,
1286
+ value: params.amount.toDecimal(),
1287
+ transactionType: params.transactionType,
1288
+ url_success: assertUrl(params.urls.success, "success"),
1289
+ url_fail: assertUrl(params.urls.fail, "fail"),
1290
+ url_ipn: assertUrl(params.urls.ipn, "ipn")
1291
+ };
1292
+ if (params.description !== void 0) body.description = params.description;
1293
+ if (params.custom !== void 0) body.custom = params.custom;
1294
+ if (params.payer?.email !== void 0) {
1295
+ if (!isValidEmail(params.payer.email)) throw new DPayValueError(`Invalid email "${params.payer.email}"`);
1296
+ body.email = params.payer.email;
1297
+ }
1298
+ if (params.payer?.firstName !== void 0) body.client_name = params.payer.firstName;
1299
+ if (params.payer?.lastName !== void 0) body.client_surname = params.payer.lastName;
1300
+ if (params.acceptTos !== void 0) body.accept_tos = params.acceptTos;
1301
+ if (params.channel !== void 0) body.channel = params.channel;
1302
+ const toggles = [
1303
+ ["creditcard", params.creditCard],
1304
+ ["paysafecard", params.paysafecard],
1305
+ ["blik", params.blik],
1306
+ ["installment", params.installment],
1307
+ ["paypal", params.paypal],
1308
+ ["nobanks", params.noBanks]
1309
+ ];
1310
+ for (const [key, flag2] of toggles) {
1311
+ if (flag2 !== void 0) body[key] = flag2 ? 1 : 0;
1312
+ }
1313
+ if (params.phoneNumber !== void 0) body.phone_number = params.phoneNumber;
1314
+ if (params.currencyCode !== void 0) {
1315
+ assertCurrency(params.currencyCode);
1316
+ body.currency_code = params.currencyCode;
1317
+ }
1318
+ if (params.partnerPlatform !== void 0) {
1319
+ if (!PARTNER_PLATFORM.test(params.partnerPlatform)) {
1320
+ throw new DPayValueError("Partner platform must match ^[A-Z0-9]{1,64}$");
1321
+ }
1322
+ body.partner_platform = params.partnerPlatform;
1323
+ }
1324
+ if (params.userAgent !== void 0) body.user_agent = params.userAgent;
1325
+ if (params.userIp !== void 0) body.user_ip = params.userIp;
1326
+ if (params.blikCode !== void 0) {
1327
+ if (!BLIK_CODE.test(params.blikCode)) throw new DPayValueError("BLIK code must be exactly 6 digits");
1328
+ body.blik_code = params.blikCode;
1329
+ }
1330
+ if (params.blikAlias !== void 0) body.blik_alias = params.blikAlias;
1331
+ if (params.registerBlikAlias !== void 0) {
1332
+ body.register_blik_alias = serializeBlikAliasRegistration(params.registerBlikAlias);
1333
+ }
1334
+ if (params.registerBlikRecurringAlias !== void 0) {
1335
+ body.register_blik_recurring_alias = serializeBlikRecurringRegistration(params.registerBlikRecurringAlias);
1336
+ }
1337
+ if (params.aliasIpnUrl !== void 0) {
1338
+ if (!isValidUrl(params.aliasIpnUrl)) {
1339
+ throw new DPayValueError(`Invalid alias IPN URL "${params.aliasIpnUrl}"`);
1340
+ }
1341
+ body.alias_ipn_url = params.aliasIpnUrl;
1342
+ }
1343
+ if (params.noDelay !== void 0) body.no_delay = params.noDelay;
1344
+ if (params.registerCardRecurring !== void 0) {
1345
+ body.register_card_recurring = serializeCardRecurringRegistration(params.registerCardRecurring);
1346
+ }
1347
+ if (params.cardRecurringAlias !== void 0) body.card_recurring_alias = params.cardRecurringAlias;
1348
+ if (params.authorizeOnly !== void 0) body.authorize_only = params.authorizeOnly;
1349
+ if (params.cardRecurringOperation !== void 0) {
1350
+ assertCardRecurringOperation(params.cardRecurringOperation);
1351
+ body.card_recurring_operation = params.cardRecurringOperation;
1352
+ }
1353
+ if (params.payout !== void 0) body.payout = serializePayoutInstruction(params.payout);
1354
+ if (params.billingAddress !== void 0) body.billing_address = { ...params.billingAddress };
1355
+ if (params.shippingAddress !== void 0) body.shipping_address = { ...params.shippingAddress };
1356
+ if (params.deviceInfo !== void 0) body.device_info = serializeDeviceInfo(params.deviceInfo);
1357
+ if (params.products !== void 0) body.products = params.products.map((product) => ({ ...product }));
1358
+ if (params.efaktura !== void 0) body.efaktura = params.efaktura;
1359
+ if (params.invoice !== void 0) body.invoice = serializeInvoice(params.invoice);
1360
+ return body;
1361
+ }
1362
+ function assertExclusions(params) {
1363
+ if (params.blikCode !== void 0 && params.blikAlias !== void 0) {
1364
+ throw new DPayValueError("blik_code cannot be combined with blik_alias");
1365
+ }
1366
+ if (params.blikAlias !== void 0 && (params.registerBlikAlias !== void 0 || params.registerBlikRecurringAlias !== void 0)) {
1367
+ throw new DPayValueError("blik_alias cannot be combined with blik_code or alias registration");
1368
+ }
1369
+ if (params.registerCardRecurring !== void 0 && params.cardRecurringAlias !== void 0) {
1370
+ throw new DPayValueError("register_card_recurring cannot be combined with card_recurring_alias");
1371
+ }
1372
+ if (params.efaktura === true && params.transactionType !== "transfers") {
1373
+ throw new DPayValueError('efaktura is allowed only for transactionType "transfers"');
1374
+ }
1375
+ if ((params.blikCode !== void 0 || params.blikAlias !== void 0) && (params.userAgent === void 0 || params.userIp === void 0)) {
1376
+ throw new DPayValueError("blik_code and blik_alias require user_agent and user_ip");
1377
+ }
1378
+ if (params.phoneNumber !== void 0 && params.currencyCode === void 0) {
1379
+ throw new DPayValueError("phone_number requires currency_code");
1380
+ }
1381
+ }
1382
+ function assertUrl(url, name) {
1383
+ if (!isValidUrl(url)) throw new DPayValueError(`Invalid ${name} URL "${url}"`);
1384
+ return url;
1385
+ }
1386
+
1387
+ // src/payment/ops.ts
1388
+ function registerOperation(service, checksum, params) {
1389
+ const body = buildRegisterBody(service, params);
1390
+ body.checksum = checksum.secretSecond(service, [
1391
+ stringField(body, "value"),
1392
+ stringField(body, "url_success"),
1393
+ stringField(body, "url_fail"),
1394
+ stringField(body, "url_ipn")
1395
+ ]);
1396
+ return {
1397
+ method: "POST",
1398
+ host: API_PAYMENTS,
1399
+ path: "/api/v1_0/payments/register",
1400
+ body,
1401
+ parse: parseRegisterResponse
1402
+ };
1403
+ }
1404
+ function parseRegisterResponse(response) {
1405
+ const data = decodeRecordOrFail(response);
1406
+ if (data.error === true || data.status === false) throw PaymentRejectedError.fromApi(data);
1407
+ return parseRegisteredPayment(data);
1408
+ }
1409
+ function detailsOperation(service, checksum, transactionId) {
1410
+ const body = { service, transaction_id: transactionId };
1411
+ body.checksum = checksum.orderedBody(Object.values(body));
1412
+ return {
1413
+ method: "POST",
1414
+ host: PANEL,
1415
+ path: "/api/v1/pbl/details",
1416
+ body,
1417
+ parse: (response) => parseTransaction(decodeRecordOrFail(response))
1418
+ };
1419
+ }
1420
+ function stringField(body, key) {
1421
+ const value = body[key];
1422
+ return isScalar(value) ? phpStrval(value) : "";
1423
+ }
1424
+
1425
+ // src/payment/service.ts
1426
+ var PaymentService = class {
1427
+ api;
1428
+ constructor(api) {
1429
+ this.api = api;
1430
+ }
1431
+ /**
1432
+ * Registers a payment and returns the redirect target.
1433
+ * Throws `PaymentRejectedError` when dpay rejects it with HTTP 200.
1434
+ */
1435
+ async register(params, options) {
1436
+ return this.api.execute(registerOperation(this.api.service, this.api.checksum, params), options);
1437
+ }
1438
+ /** Reads the current state of a transaction, including its refunds. */
1439
+ async details(transactionId, options) {
1440
+ return this.api.execute(detailsOperation(this.api.service, this.api.checksum, transactionId), options);
1441
+ }
1442
+ };
1443
+
1444
+ // src/payout/models.ts
1445
+ function parsePayoutDetails(data) {
1446
+ const state = isScalar(data.state) ? phpInt(data.state) : 0;
1447
+ const receiver = record(data.receiver);
1448
+ return Object.freeze({
1449
+ id: isScalar(data.id) ? phpInt(data.id) : 0,
1450
+ state,
1451
+ net: moneyOrZero(data, "net"),
1452
+ fee: moneyOrZero(data, "fee"),
1453
+ gross: moneyOrZero(data, "gross"),
1454
+ creationDate: strictString(data, "creation_date"),
1455
+ isDirectSettlement: flag(data, "direct_settlement"),
1456
+ nrb: strictString(data, "nrb"),
1457
+ isDeclined: flag(data, "declined"),
1458
+ declineReason: strictString(data, "decline_reason"),
1459
+ declineStatus: strictString(data, "decline_status"),
1460
+ receiver: receiver === null ? null : parsePayoutReceiver(receiver),
1461
+ isWaiting: state === 0,
1462
+ isProcessed: state === 1,
1463
+ isFailed: state === -1,
1464
+ raw: data
1465
+ });
1466
+ }
1467
+ function parsePayoutReceiver(data) {
1468
+ return Object.freeze({
1469
+ nrb: strictString(data, "nrb"),
1470
+ title: strictString(data, "title"),
1471
+ amount: Money.tryFromApiNumber(data.amount, Currency.PLN),
1472
+ service: strictString(data, "service"),
1473
+ receiverName: strictString(data, "receiverName"),
1474
+ receiverAddress: strictString(data, "receiverAddress"),
1475
+ raw: data
1476
+ });
1477
+ }
1478
+ function flag(data, key) {
1479
+ const value = data[key];
1480
+ return isScalar(value) ? phpInt(value) === 1 : false;
1481
+ }
1482
+
1483
+ // src/payout/ops.ts
1484
+ function payoutDetailsOperation(service, checksum, params) {
1485
+ const body = { service };
1486
+ if (params.timestamp !== void 0) body.timestamp = params.timestamp;
1487
+ body.withdraw_id = params.withdrawId;
1488
+ body.checksum = checksum.orderedBody(Object.values(body));
1489
+ return {
1490
+ method: "POST",
1491
+ host: PANEL,
1492
+ path: "/api/v1/pbl/withdraws/details",
1493
+ body,
1494
+ parse: (response) => parsePayoutDetails(decodeRecordOrFail(response))
1495
+ };
1496
+ }
1497
+
1498
+ // src/payout/service.ts
1499
+ var PayoutService = class {
1500
+ api;
1501
+ constructor(api) {
1502
+ this.api = api;
1503
+ }
1504
+ /** Reads the state of a single payout. */
1505
+ async details(params, options) {
1506
+ return this.api.execute(payoutDetailsOperation(this.api.service, this.api.checksum, params), options);
1507
+ }
1508
+ };
1509
+
1510
+ // src/refund/models.ts
1511
+ function parseRefund(data) {
1512
+ return Object.freeze({
1513
+ isAccepted: data.status === "success" && data.refund === true,
1514
+ message: scalarStringOrNull(data, "message"),
1515
+ raw: data
1516
+ });
1517
+ }
1518
+ function parseRefundAvailability(data, httpStatus) {
1519
+ return Object.freeze({
1520
+ isAvailable: data.refund === true,
1521
+ message: scalarStringOrNull(data, "message"),
1522
+ httpStatus,
1523
+ raw: data
1524
+ });
1525
+ }
1526
+
1527
+ // src/refund/ops.ts
1528
+ var AVAILABILITY_OUTCOMES = [200, 400, 402, 406, 409, 410, 411];
1529
+ function signedBody(service, checksum, params) {
1530
+ const body = { service, transaction_id: params.transactionId };
1531
+ if (params.amount !== void 0) body.value = params.amount.toDecimal();
1532
+ if (params.reason !== void 0) body.reason = params.reason;
1533
+ body.checksum = checksum.orderedBody(Object.values(body));
1534
+ return body;
1535
+ }
1536
+ function createRefundOperation(service, checksum, params) {
1537
+ return {
1538
+ method: "POST",
1539
+ host: PANEL,
1540
+ path: "/api/v1/pbl/refund",
1541
+ body: signedBody(service, checksum, params),
1542
+ parse: (response) => parseRefund(decodeRecordOrFail(response))
1543
+ };
1544
+ }
1545
+ function checkAvailabilityOperation(service, checksum, params) {
1546
+ return {
1547
+ method: "POST",
1548
+ host: PANEL,
1549
+ path: "/api/v1/pbl/check-refund-availability",
1550
+ body: signedBody(service, checksum, params),
1551
+ raiseForStatus: false,
1552
+ parse: parseAvailability
1553
+ };
1554
+ }
1555
+ function parseAvailability(response) {
1556
+ const json = response.decodeJson();
1557
+ if (typeof json === "object" && json !== null && !Array.isArray(json)) {
1558
+ const data = json;
1559
+ if (Object.hasOwn(data, "refund") && isOutcome(response.status, data)) {
1560
+ return parseRefundAvailability(data, response.status);
1561
+ }
1562
+ }
1563
+ throw mapError(response);
1564
+ }
1565
+ function isOutcome(status, data) {
1566
+ if (AVAILABILITY_OUTCOMES.includes(status)) return true;
1567
+ return status === 401 && data.message !== "Unauthorized request";
1568
+ }
1569
+
1570
+ // src/refund/service.ts
1571
+ var RefundService = class {
1572
+ api;
1573
+ constructor(api) {
1574
+ this.api = api;
1575
+ }
1576
+ /** Refunds a transaction in full, or partially when `amount` is given. */
1577
+ async create(params, options) {
1578
+ return this.api.execute(createRefundOperation(this.api.service, this.api.checksum, params), options);
1579
+ }
1580
+ /**
1581
+ * Checks whether a refund would be accepted, without performing it.
1582
+ * Refusal arrives as a 4xx status carrying an outcome body, not as an error.
1583
+ */
1584
+ async checkAvailability(params, options) {
1585
+ return this.api.execute(checkAvailabilityOperation(this.api.service, this.api.checksum, params), options);
1586
+ }
1587
+ };
1588
+
1589
+ // src/client.ts
1590
+ var DPayClient = class {
1591
+ /** Version of this SDK. */
1592
+ VERSION = SDK_VERSION;
1593
+ /** Register payments and read transaction state. */
1594
+ payments;
1595
+ /** Refund transactions and check refund availability. */
1596
+ refunds;
1597
+ /** List banks available on the payment page. */
1598
+ banks;
1599
+ /** Register, unregister and inspect BLIK aliases. */
1600
+ blik;
1601
+ /** Server-to-server card payments, wallets and pre-authorizations. */
1602
+ cards;
1603
+ /** Read the state of payouts. */
1604
+ payouts;
1605
+ /** Verify incoming IPN notifications. */
1606
+ ipn;
1607
+ /**
1608
+ * Validates `options` eagerly - before any network call - and mounts every service on
1609
+ * top of a shared transport. Defaults to `FetchHttpClient` when `options.httpClient` is
1610
+ * not given.
1611
+ */
1612
+ constructor(options) {
1613
+ const config = Config.fromOptions(options);
1614
+ const transport = config.httpClient ?? new FetchHttpClient();
1615
+ const api = new ApiRequestor(config, transport);
1616
+ this.payments = new PaymentService(api);
1617
+ this.refunds = new RefundService(api);
1618
+ this.banks = new BankService(api);
1619
+ this.blik = new BlikService(api);
1620
+ this.cards = new CardService(api);
1621
+ this.payouts = new PayoutService(api);
1622
+ this.ipn = new IpnService(config);
1623
+ }
1624
+ };
1625
+ var PAN = /^\d{12,19}$/;
1626
+ var CVV = /^\d{3,4}$/;
1627
+ var EXPIRY = /^(0[1-9]|1[0-2])\/\d{2}$/;
1628
+ var CardData = class {
1629
+ /** Card number, normalized to digits only (spaces stripped). */
1630
+ #pan;
1631
+ /** Card verification value, 3-4 digits. */
1632
+ #cvv;
1633
+ /** Expiry in `MM/YY` format. */
1634
+ #expiry;
1635
+ constructor(params) {
1636
+ const pan = params.pan.replaceAll(" ", "");
1637
+ if (!PAN.test(pan)) throw new DPayValueError("Card number must be 12-19 digits");
1638
+ if (!CVV.test(params.cvv)) throw new DPayValueError("CVV must be 3-4 digits");
1639
+ if (!EXPIRY.test(params.expiry)) throw new DPayValueError("Expiry must be in MM/YY format");
1640
+ this.#pan = pan;
1641
+ this.#cvv = params.cvv;
1642
+ this.#expiry = params.expiry;
1643
+ Object.freeze(this);
1644
+ }
1645
+ /** Card number, normalized to digits only (spaces stripped). */
1646
+ get pan() {
1647
+ return this.#pan;
1648
+ }
1649
+ /** Card verification value, 3-4 digits. */
1650
+ get cvv() {
1651
+ return this.#cvv;
1652
+ }
1653
+ /** Expiry in `MM/YY` format. */
1654
+ get expiry() {
1655
+ return this.#expiry;
1656
+ }
1657
+ redacted() {
1658
+ return { pan: `****${this.#pan.slice(-4)}`, cvv: "***", expiry: this.#expiry };
1659
+ }
1660
+ /** Redacted representation - `pan` masked to its last 4 digits, `cvv` fully masked. */
1661
+ toJSON() {
1662
+ return this.redacted();
1663
+ }
1664
+ /** Redacted representation - `pan` masked to its last 4 digits, `cvv` fully masked. */
1665
+ toString() {
1666
+ const { pan, cvv, expiry } = this.redacted();
1667
+ return `CardData(pan='${pan}', cvv='${cvv}', expiry='${expiry}')`;
1668
+ }
1669
+ /** Node `util.inspect` hook - delegates to the same redacted `toString()`. */
1670
+ [inspect.custom]() {
1671
+ return this.toString();
1672
+ }
1673
+ };
1674
+ var CardEncryptor = class {
1675
+ /**
1676
+ * Returns the Base64 payload to put in `encryptedCardData`.
1677
+ * Fetch the public key immediately before every attempt - dpay rotates it.
1678
+ */
1679
+ encrypt(card, transactionId, publicKeyPem) {
1680
+ let key;
1681
+ try {
1682
+ key = createPublicKey(publicKeyPem);
1683
+ } catch (error) {
1684
+ throw new CardEncryptionError("Invalid RSA public key", { cause: error });
1685
+ }
1686
+ const payload = phpJsonEncode(
1687
+ {
1688
+ PN: card.pan,
1689
+ SC: card.cvv,
1690
+ DT: card.expiry,
1691
+ ID: transactionId,
1692
+ TX: Math.floor(Date.now() / 1e3)
1693
+ },
1694
+ { escapeSlashes: true }
1695
+ );
1696
+ try {
1697
+ return publicEncrypt(
1698
+ { key, padding: constants.RSA_PKCS1_PADDING },
1699
+ Buffer.from(payload, "utf8")
1700
+ ).toString("base64");
1701
+ } catch (error) {
1702
+ throw new CardEncryptionError("Card data encryption failed", { cause: error });
1703
+ }
1704
+ }
1705
+ };
1706
+
1707
+ export { AccessDeniedError, ApiError, ApiServerError, AuthenticationError, BlikAliasType, CardData, CardEncryptionError, CardEncryptor, CardPaymentError, CardRecurringFrequency, CardRecurringOperation, Currency, DEFAULT_TIMEOUT, DPayClient, DPayError, DPayValueError, DccDecision, FetchHttpClient, IPN_ACK, InvalidRequestError, IpnType, Money, NotFoundError, PaymentRejectedError, PayoutFeeMode, RateLimitError, RedirectType, SDK_VERSION, SignatureVerificationError, TransactionStatus, TransactionType, TransportError, assertCurrency, constructIpnEvent, isValidCurrency, readRawBody };
1708
+ //# sourceMappingURL=index.js.map
1709
+ //# sourceMappingURL=index.js.map