@delopay/sdk 0.277.0 → 0.279.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/internal.cjs CHANGED
@@ -36,6 +36,7 @@ __export(internal_exports, {
36
36
  APPLE_PAY_BUTTON_STYLES: () => APPLE_PAY_BUTTON_STYLES,
37
37
  APPLE_PAY_BUTTON_TYPES: () => APPLE_PAY_BUTTON_TYPES,
38
38
  Admin: () => Admin,
39
+ AdminLive: () => AdminLive,
39
40
  AdminPortal: () => AdminPortal,
40
41
  Analytics: () => Analytics,
41
42
  AnalyticsDashboard: () => AnalyticsDashboard,
@@ -94,6 +95,8 @@ __export(internal_exports, {
94
95
  InvoiceSettingsResource: () => InvoiceSettingsResource,
95
96
  Invoices: () => Invoices,
96
97
  LOCALIZABLE_COPY_FIELDS: () => LOCALIZABLE_COPY_FIELDS,
98
+ Live: () => Live,
99
+ LiveEventStream: () => LiveEventStream,
97
100
  MAX_SECTION_LABEL_LENGTH: () => MAX_SECTION_LABEL_LENGTH,
98
101
  MAX_SECTION_ORDER: () => MAX_SECTION_ORDER,
99
102
  METHOD_SECTIONS: () => METHOD_SECTIONS,
@@ -324,6 +327,100 @@ var DelopayAuthenticationError = class extends DelopayError {
324
327
  }
325
328
  };
326
329
 
330
+ // src/eventStream.ts
331
+ var LF = 10;
332
+ var CR = 13;
333
+ var EventStreamParser = class {
334
+ /**
335
+ * @param lastEventId - The id the stream resumes after, as `EventSource`
336
+ * carries it into a reconnection: a dispatch before the server names an
337
+ * id keeps this one.
338
+ */
339
+ constructor(lastEventId = "") {
340
+ this.line = "";
341
+ /** The previous chunk ended in CR: a LF opening the next one belongs to it. */
342
+ this.skipLf = false;
343
+ this.data = "";
344
+ this.hasData = false;
345
+ this.eventType = "";
346
+ this.retry = null;
347
+ this.idBuffer = lastEventId;
348
+ }
349
+ /** Feed the next piece of decoded text; returns the dispatches it completed. */
350
+ push(text) {
351
+ const out = [];
352
+ if (text.length === 0) return out;
353
+ let start = 0;
354
+ if (this.skipLf) {
355
+ this.skipLf = false;
356
+ if (text.charCodeAt(0) === LF) start = 1;
357
+ }
358
+ for (let i = start; i < text.length; i++) {
359
+ const c = text.charCodeAt(i);
360
+ if (c !== LF && c !== CR) continue;
361
+ const line = this.line + text.slice(start, i);
362
+ this.line = "";
363
+ this.processLine(line, out);
364
+ if (c === CR) {
365
+ if (i + 1 < text.length) {
366
+ if (text.charCodeAt(i + 1) === LF) i++;
367
+ } else {
368
+ this.skipLf = true;
369
+ }
370
+ }
371
+ start = i + 1;
372
+ }
373
+ this.line += text.slice(start);
374
+ return out;
375
+ }
376
+ processLine(line, out) {
377
+ if (line === "") {
378
+ this.dispatch(out);
379
+ return;
380
+ }
381
+ if (line.charCodeAt(0) === 58) return;
382
+ const colon = line.indexOf(":");
383
+ let field;
384
+ let value;
385
+ if (colon === -1) {
386
+ field = line;
387
+ value = "";
388
+ } else {
389
+ field = line.slice(0, colon);
390
+ value = line.slice(colon + 1);
391
+ if (value.charCodeAt(0) === 32) value = value.slice(1);
392
+ }
393
+ switch (field) {
394
+ case "event":
395
+ this.eventType = value;
396
+ break;
397
+ case "data":
398
+ this.data = this.hasData ? `${this.data}
399
+ ${value}` : value;
400
+ this.hasData = true;
401
+ break;
402
+ case "id":
403
+ if (!value.includes("\0")) this.idBuffer = value;
404
+ break;
405
+ case "retry":
406
+ if (/^[0-9]+$/.test(value)) {
407
+ const ms = Number(value);
408
+ if (Number.isSafeInteger(ms)) this.retry = ms;
409
+ }
410
+ break;
411
+ default:
412
+ break;
413
+ }
414
+ }
415
+ dispatch(out) {
416
+ const event = this.hasData ? { type: this.eventType || "message", data: this.data } : null;
417
+ out.push({ lastEventId: this.idBuffer, retry: this.retry, event });
418
+ this.data = "";
419
+ this.hasData = false;
420
+ this.eventType = "";
421
+ }
422
+ };
423
+
327
424
  // src/resources/apiKeys.ts
328
425
  var ApiKeys = class {
329
426
  constructor(request) {
@@ -1569,8 +1666,8 @@ var Customers = class {
1569
1666
  };
1570
1667
 
1571
1668
  // src/recordCounter.ts
1572
- var LF = 10;
1573
- var CR = 13;
1669
+ var LF2 = 10;
1670
+ var CR2 = 13;
1574
1671
  var QUOTE = 34;
1575
1672
  var BACKSLASH = 92;
1576
1673
  var OPEN_BRACE = 123;
@@ -1594,10 +1691,10 @@ var CsvRecordCounter = class {
1594
1691
  lineHasContent = true;
1595
1692
  } else if (inQuotes) {
1596
1693
  lineHasContent = true;
1597
- } else if (byte === LF) {
1694
+ } else if (byte === LF2) {
1598
1695
  newlines++;
1599
1696
  lineHasContent = false;
1600
- } else if (byte !== CR) {
1697
+ } else if (byte !== CR2) {
1601
1698
  lineHasContent = true;
1602
1699
  }
1603
1700
  }
@@ -9131,6 +9228,133 @@ var Risk = class {
9131
9228
  }
9132
9229
  };
9133
9230
 
9231
+ // src/resources/live.ts
9232
+ function readField(data, key) {
9233
+ let value;
9234
+ try {
9235
+ value = JSON.parse(data);
9236
+ } catch {
9237
+ return void 0;
9238
+ }
9239
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
9240
+ return Object.getOwnPropertyDescriptor(value, key)?.value;
9241
+ }
9242
+ function toLiveStreamEvent(event, id, retry) {
9243
+ switch (event.type) {
9244
+ case "ready": {
9245
+ const resumed = readField(event.data, "resumed");
9246
+ return { type: "ready", id, retry, resumed: typeof resumed === "boolean" ? resumed : null };
9247
+ }
9248
+ case "change": {
9249
+ const changes = readField(event.data, "changes");
9250
+ if (!Array.isArray(changes)) return { type: "resync", id };
9251
+ return { type: "change", id, changes };
9252
+ }
9253
+ case "resync":
9254
+ return { type: "resync", id };
9255
+ default:
9256
+ return null;
9257
+ }
9258
+ }
9259
+ var LiveEventStream = class {
9260
+ constructor(connection, lastEventId = null) {
9261
+ this.connection = connection;
9262
+ this._retry = null;
9263
+ this._lastEventId = lastEventId || null;
9264
+ }
9265
+ /**
9266
+ * The position to resume after: the id of the last event read, advanced by
9267
+ * keepalives too. `null` before the server named one. Send it as
9268
+ * `lastEventId` when reconnecting.
9269
+ */
9270
+ get lastEventId() {
9271
+ return this._lastEventId;
9272
+ }
9273
+ /** The wait before reconnecting the server asked for, in ms; `null` until it says. */
9274
+ get retry() {
9275
+ return this._retry;
9276
+ }
9277
+ /** Close the stream; the iteration ends without an error. */
9278
+ close() {
9279
+ this.connection.close();
9280
+ }
9281
+ [Symbol.asyncIterator]() {
9282
+ this.iterator ?? (this.iterator = this.events());
9283
+ return this.iterator;
9284
+ }
9285
+ async *events() {
9286
+ for await (const dispatch of this.connection.dispatches) {
9287
+ const event = this.read(dispatch);
9288
+ if (event) yield event;
9289
+ }
9290
+ }
9291
+ read(dispatch) {
9292
+ this._lastEventId = dispatch.lastEventId || null;
9293
+ if (dispatch.retry !== null) this._retry = dispatch.retry;
9294
+ if (!dispatch.event) return null;
9295
+ return toLiveStreamEvent(dispatch.event, this._lastEventId, this._retry);
9296
+ }
9297
+ };
9298
+ async function openLiveStream(openStream, path, options = {}) {
9299
+ const connection = await openStream(path, {
9300
+ ...options.lastEventId ? { lastEventId: options.lastEventId } : {},
9301
+ ...options.signal ? { signal: options.signal } : {},
9302
+ ...options.idleTimeoutMs !== void 0 ? { idleTimeoutMs: options.idleTimeoutMs } : {},
9303
+ ...options.headers ? { headers: options.headers } : {}
9304
+ });
9305
+ return new LiveEventStream(connection, options.lastEventId ?? null);
9306
+ }
9307
+ var Live = class {
9308
+ constructor(request, openStream) {
9309
+ this.request = request;
9310
+ this.openStream = openStream;
9311
+ }
9312
+ /**
9313
+ * Open the stream of what changed in the caller's merchant, as their role
9314
+ * may read it. Resolves once the server answered; a `401` rejects with
9315
+ * `DelopayAuthenticationError`.
9316
+ *
9317
+ * The first event is `ready`. `change` names the kinds of record and, when
9318
+ * few, the ids that changed: read them again through the usual endpoints.
9319
+ * `resync`, and `ready` with `resumed: false`, mean read everything again.
9320
+ * The SDK does not reconnect — see {@link LiveEventStream}.
9321
+ *
9322
+ * `GET /live/stream`
9323
+ *
9324
+ * @example
9325
+ * ```typescript
9326
+ * const stream = await delopay.live.stream({ lastEventId, signal });
9327
+ * for await (const event of stream) {
9328
+ * if (event.type === 'change') refresh(event.changes);
9329
+ * else if (event.type === 'resync' || event.resumed === false) refreshAll();
9330
+ * }
9331
+ * lastEventId = stream.lastEventId; // reconnect after stream.retry
9332
+ * ```
9333
+ */
9334
+ async stream(options) {
9335
+ return openLiveStream(this.openStream, "/live/stream", options);
9336
+ }
9337
+ /**
9338
+ * The current version of each kind of record named, in the caller's
9339
+ * merchant and shop. Read it before the record and send the entry's
9340
+ * `if_match` in the `If-Match` header of the save: a save over a newer
9341
+ * version is refused with `IR_16` (400), `error.data` a
9342
+ * `LiveChangedElsewhere` naming who made the change.
9343
+ *
9344
+ * `GET /live/versions`
9345
+ */
9346
+ async versions(params, options) {
9347
+ return this.request("GET", "/live/versions", {
9348
+ query: {
9349
+ entities: params.entities.join(","),
9350
+ profile_id: params.profile_id,
9351
+ record_id: params.record_id
9352
+ },
9353
+ ...options
9354
+ });
9355
+ }
9356
+ };
9357
+
9134
9358
  // src/client.ts
9135
9359
  var PRODUCTION_URL = "https://api.delopay.net";
9136
9360
  var SANDBOX_URL = "https://sandbox.delopay.net";
@@ -9154,6 +9378,40 @@ function truncateRawBody(raw) {
9154
9378
  if (!raw) return void 0;
9155
9379
  return raw.length > MAX_RAW_BODY_BYTES ? raw.slice(0, MAX_RAW_BODY_BYTES) + "\u2026" : raw;
9156
9380
  }
9381
+ async function errorFromResponse(response, requestId) {
9382
+ const rawBody = await response.text().catch(() => "");
9383
+ let parsed = {};
9384
+ if (rawBody) {
9385
+ try {
9386
+ parsed = JSON.parse(rawBody);
9387
+ } catch {
9388
+ }
9389
+ }
9390
+ const err = parsed.error ?? parsed;
9391
+ const message = err.message ?? `Request failed with status ${response.status}`;
9392
+ const code = err.code ?? "";
9393
+ const type = err.error_type ?? err.type ?? "";
9394
+ const data = err.data && typeof err.data === "object" && !Array.isArray(err.data) ? err.data : void 0;
9395
+ const truncatedRaw = truncateRawBody(rawBody);
9396
+ const isConnectorPassthrough = type === "connector" || code.startsWith("CE_");
9397
+ if (response.status === 401 && !isConnectorPassthrough) {
9398
+ return new DelopayAuthenticationError(message, {
9399
+ code,
9400
+ type,
9401
+ requestId,
9402
+ rawBody: truncatedRaw,
9403
+ data
9404
+ });
9405
+ }
9406
+ return new DelopayError(message, {
9407
+ status: response.status,
9408
+ code,
9409
+ type,
9410
+ requestId,
9411
+ rawBody: truncatedRaw,
9412
+ data
9413
+ });
9414
+ }
9157
9415
  function findIdempotencyKey(headers) {
9158
9416
  for (const [k, v] of Object.entries(headers)) {
9159
9417
  if (k.toLowerCase() === "idempotency-key") return v;
@@ -9314,6 +9572,7 @@ function redactUrlForLogging(url) {
9314
9572
  });
9315
9573
  return `${base}?${parts.join("&")}`;
9316
9574
  }
9575
+ var DEFAULT_STREAM_IDLE_TIMEOUT_MS = 45e3;
9317
9576
  var Delopay = class {
9318
9577
  /**
9319
9578
  * Create a new Delopay client.
@@ -9336,6 +9595,7 @@ var Delopay = class {
9336
9595
  this.baseUrl = PRODUCTION_URL;
9337
9596
  }
9338
9597
  const request = this.request.bind(this);
9598
+ const openStream = this.openEventStream.bind(this);
9339
9599
  this.payments = new Payments(request);
9340
9600
  this.refunds = new Refunds(request);
9341
9601
  this.customers = new Customers(request);
@@ -9372,6 +9632,7 @@ var Delopay = class {
9372
9632
  this.settings = new Settings(request);
9373
9633
  this.messageTemplates = new MessageTemplates(request);
9374
9634
  this.risk = new Risk(request);
9635
+ this.live = new Live(request, openStream);
9375
9636
  this.subscriptions = new Subscriptions(request);
9376
9637
  this.buyerPortal = new BuyerPortal(request);
9377
9638
  this.buyerAccount = new BuyerAccount(request);
@@ -9448,24 +9709,12 @@ var Delopay = class {
9448
9709
  this.setJwtToken(response.token);
9449
9710
  return response;
9450
9711
  }
9451
- /**
9452
- * Make a raw HTTP request to the Delopay API.
9453
- *
9454
- * You rarely need to call this directly — prefer the typed resource methods.
9455
- * Use it only for endpoints not yet covered by a resource class.
9456
- *
9457
- * @param method - HTTP method (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`).
9458
- * @param path - API path starting with `/` (e.g. `/payments`).
9459
- * @param options - Optional body, query parameters, and headers.
9460
- * @returns Parsed JSON response body typed as `T`.
9461
- * @throws {DelopayAuthenticationError} On 401 responses.
9462
- * @throws {DelopayError} On all other non-2xx responses, timeouts, and network errors.
9463
- */
9464
- async request(method, path, options) {
9712
+ /** `path` on the base URL, with the query string `query` makes. */
9713
+ buildUrl(path, query) {
9465
9714
  let url = `${this.baseUrl}${path}`;
9466
- if (options?.query) {
9715
+ if (query) {
9467
9716
  const params = new URLSearchParams();
9468
- for (const [key, value] of Object.entries(options.query)) {
9717
+ for (const [key, value] of Object.entries(query)) {
9469
9718
  if (value === void 0 || value === null) continue;
9470
9719
  if (Array.isArray(value)) {
9471
9720
  for (const v of value) {
@@ -9480,8 +9729,189 @@ var Delopay = class {
9480
9729
  url += `?${qs}`;
9481
9730
  }
9482
9731
  }
9732
+ return url;
9733
+ }
9734
+ /** The credential header: the JWT when one is set, else the API key. */
9735
+ authHeaders() {
9736
+ if (this.jwtToken) return { Authorization: `Bearer ${this.jwtToken}` };
9737
+ if (this.apiKey) return { "api-key": this.apiKey };
9738
+ return {};
9739
+ }
9740
+ /** One debug event, when `debug` is on. */
9741
+ log(event, data) {
9742
+ if (!this.debug) return;
9743
+ if (this.logger) {
9744
+ this.logger(event, data);
9745
+ return;
9746
+ }
9747
+ if (event === "request")
9748
+ console.log(`[delopay] ${data.method} ${data.url}`);
9749
+ else if (event === "response")
9750
+ console.log(
9751
+ `[delopay] ${data.status} ${data.method} ${data.path}`
9752
+ );
9753
+ else
9754
+ console.log(
9755
+ `[delopay] retry ${data.attempt}/${data.maxRetries} ${data.method} ${data.path}`
9756
+ );
9757
+ }
9758
+ /**
9759
+ * Open a `GET` `text/event-stream` and read it as it arrives.
9760
+ *
9761
+ * `request()` keeps every chunk of a body until the body ends, which a
9762
+ * stream that stays open for half an hour must not do; this reads the body
9763
+ * chunk by chunk through {@link EventStreamParser} and keeps nothing it has
9764
+ * dispatched. Credentials, the base URL, debug logging and the mapping of a
9765
+ * non-2xx answer to `DelopayError` / `DelopayAuthenticationError` are the
9766
+ * ones `request()` uses.
9767
+ *
9768
+ * Never retried, and never reconnected: the caller owns reconnection.
9769
+ * Resolves once the response headers arrive; rejects on a non-2xx answer,
9770
+ * on a 2xx that is not `text/event-stream` (code `NOT_EVENT_STREAM`), and
9771
+ * with `TIMEOUT`, `NETWORK` or `ABORTED` like `request()`.
9772
+ */
9773
+ async openEventStream(path, options = {}) {
9774
+ const url = this.buildUrl(path, options.query);
9775
+ const lastEventId = options.lastEventId ?? "";
9776
+ const headers = {
9777
+ ...this.authHeaders(),
9778
+ Accept: "text/event-stream",
9779
+ ...lastEventId ? { "Last-Event-ID": lastEventId } : {},
9780
+ ...options.headers
9781
+ };
9782
+ const idleMs = options.idleTimeoutMs ?? DEFAULT_STREAM_IDLE_TIMEOUT_MS;
9783
+ const callerSignal = options.signal;
9784
+ const aborted = () => new DelopayError("Request aborted", { status: 0, code: "ABORTED", type: "abort_error" });
9785
+ if (callerSignal?.aborted) throw aborted();
9786
+ const idleCtrl = new AbortController();
9787
+ const closeCtrl = new AbortController();
9788
+ let timer;
9789
+ const stopIdleTimer = () => {
9790
+ if (timer !== void 0) clearTimeout(timer);
9791
+ timer = void 0;
9792
+ };
9793
+ const startIdleTimer = () => {
9794
+ stopIdleTimer();
9795
+ if (!idleCtrl.signal.aborted) timer = setTimeout(() => idleCtrl.abort(), idleMs);
9796
+ };
9797
+ const combined = combineSignals(
9798
+ callerSignal ? [idleCtrl.signal, closeCtrl.signal, callerSignal] : [idleCtrl.signal, closeCtrl.signal]
9799
+ );
9800
+ const release = () => {
9801
+ stopIdleTimer();
9802
+ combined.dispose();
9803
+ };
9804
+ const timedOut = () => new DelopayError(`Event stream silent for ${idleMs} ms`, {
9805
+ status: 0,
9806
+ code: "TIMEOUT",
9807
+ type: "timeout_error"
9808
+ });
9809
+ const network = (cause) => new DelopayError(
9810
+ `Network error: ${cause instanceof Error ? cause.message : String(cause ?? "stream failed")}`,
9811
+ { status: 0, code: "NETWORK", type: "network_error" }
9812
+ );
9813
+ this.log("request", { method: "GET", url: redactUrlForLogging(url), path });
9814
+ startIdleTimer();
9815
+ let response;
9816
+ try {
9817
+ response = await fetch(url, { method: "GET", headers, signal: combined.signal });
9818
+ } catch (err) {
9819
+ release();
9820
+ if (callerSignal?.aborted) throw aborted();
9821
+ if (idleCtrl.signal.aborted) throw timedOut();
9822
+ throw network(err);
9823
+ }
9824
+ const requestId = response.headers?.get("x-request-id") ?? response.headers?.get("x-trace-id") ?? void 0;
9825
+ this.log("response", { status: response.status, method: "GET", path, requestId });
9826
+ if (!response.ok) {
9827
+ const error = await errorFromResponse(response, requestId);
9828
+ release();
9829
+ throw error;
9830
+ }
9831
+ const body = response.body;
9832
+ const contentType = response.headers?.get("content-type") ?? "";
9833
+ const mediaType = (contentType.split(";")[0] ?? "").trim().toLowerCase();
9834
+ if (mediaType !== "text/event-stream" || !body || typeof body.getReader !== "function") {
9835
+ release();
9836
+ body?.cancel().catch(noop);
9837
+ throw new DelopayError(
9838
+ mediaType === "text/event-stream" ? "The event stream has no readable body" : `Expected a text/event-stream response, got ${contentType ? `"${contentType}"` : "no content type"}`,
9839
+ {
9840
+ status: response.status,
9841
+ code: "NOT_EVENT_STREAM",
9842
+ type: "invalid_response",
9843
+ requestId
9844
+ }
9845
+ );
9846
+ }
9847
+ stopIdleTimer();
9848
+ const reader = body.getReader();
9849
+ const parser = new EventStreamParser(lastEventId);
9850
+ const decoder = new TextDecoder();
9851
+ const stopped = () => Boolean(callerSignal?.aborted) || closeCtrl.signal.aborted;
9852
+ let finished = false;
9853
+ const finish = () => {
9854
+ if (finished) return;
9855
+ finished = true;
9856
+ release();
9857
+ reader.cancel().catch(noop);
9858
+ };
9859
+ async function* dispatches() {
9860
+ try {
9861
+ for (; ; ) {
9862
+ let result;
9863
+ startIdleTimer();
9864
+ try {
9865
+ result = await readChunk(reader, combined.signal);
9866
+ } catch (err) {
9867
+ if (callerSignal?.aborted || closeCtrl.signal.aborted) return;
9868
+ if (idleCtrl.signal.aborted) throw timedOut();
9869
+ throw network(err instanceof BodyStreamFailure ? err.cause : err);
9870
+ } finally {
9871
+ stopIdleTimer();
9872
+ }
9873
+ if (result.done) {
9874
+ for (const dispatch of parser.push(decoder.decode())) {
9875
+ if (stopped()) return;
9876
+ yield dispatch;
9877
+ }
9878
+ return;
9879
+ }
9880
+ if (!result.value || result.value.byteLength === 0) continue;
9881
+ for (const dispatch of parser.push(decoder.decode(result.value, { stream: true }))) {
9882
+ if (stopped()) return;
9883
+ yield dispatch;
9884
+ }
9885
+ }
9886
+ } finally {
9887
+ finish();
9888
+ }
9889
+ }
9890
+ return {
9891
+ dispatches: dispatches(),
9892
+ close: () => {
9893
+ closeCtrl.abort();
9894
+ finish();
9895
+ }
9896
+ };
9897
+ }
9898
+ /**
9899
+ * Make a raw HTTP request to the Delopay API.
9900
+ *
9901
+ * You rarely need to call this directly — prefer the typed resource methods.
9902
+ * Use it only for endpoints not yet covered by a resource class.
9903
+ *
9904
+ * @param method - HTTP method (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`).
9905
+ * @param path - API path starting with `/` (e.g. `/payments`).
9906
+ * @param options - Optional body, query parameters, and headers.
9907
+ * @returns Parsed JSON response body typed as `T`.
9908
+ * @throws {DelopayAuthenticationError} On 401 responses.
9909
+ * @throws {DelopayError} On all other non-2xx responses, timeouts, and network errors.
9910
+ */
9911
+ async request(method, path, options) {
9912
+ const url = this.buildUrl(path, options?.query);
9483
9913
  let headers = {
9484
- ...this.jwtToken ? { Authorization: `Bearer ${this.jwtToken}` } : this.apiKey ? { "api-key": this.apiKey } : {},
9914
+ ...this.authHeaders(),
9485
9915
  ...options?.headers
9486
9916
  };
9487
9917
  const isRawBody = options?.body !== void 0 && options?.body !== null && (options.body instanceof FormData || options.body instanceof Blob || options.body instanceof ArrayBuffer || options.body instanceof URLSearchParams);
@@ -9534,23 +9964,7 @@ var Delopay = class {
9534
9964
  let lastError;
9535
9965
  let retryAfterOverrideMs = null;
9536
9966
  const safeUrl = () => redactUrlForLogging(url);
9537
- const emit = (event, data) => {
9538
- if (!this.debug) return;
9539
- if (this.logger) {
9540
- this.logger(event, data);
9541
- return;
9542
- }
9543
- if (event === "request")
9544
- console.log(`[delopay] ${data.method} ${data.url}`);
9545
- else if (event === "response")
9546
- console.log(
9547
- `[delopay] ${data.status} ${data.method} ${data.path}`
9548
- );
9549
- else
9550
- console.log(
9551
- `[delopay] retry ${data.attempt}/${data.maxRetries} ${data.method} ${data.path}`
9552
- );
9553
- };
9967
+ const emit = (event, data) => this.log(event, data);
9554
9968
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
9555
9969
  if (attempt > 0) {
9556
9970
  const base = Math.min(500 * 2 ** (attempt - 1), 5e3);
@@ -9582,38 +9996,7 @@ var Delopay = class {
9582
9996
  const requestId = response.headers?.get("x-request-id") ?? response.headers?.get("x-trace-id") ?? void 0;
9583
9997
  emit("response", { status: response.status, method, path, requestId });
9584
9998
  if (!response.ok) {
9585
- const rawBody = await response.text().catch(() => "");
9586
- let parsed = {};
9587
- if (rawBody) {
9588
- try {
9589
- parsed = JSON.parse(rawBody);
9590
- } catch {
9591
- }
9592
- }
9593
- const err = parsed.error ?? parsed;
9594
- const message = err.message ?? `Request failed with status ${response.status}`;
9595
- const code = err.code ?? "";
9596
- const type = err.error_type ?? err.type ?? "";
9597
- const data = err.data && typeof err.data === "object" && !Array.isArray(err.data) ? err.data : void 0;
9598
- const truncatedRaw = truncateRawBody(rawBody);
9599
- const isConnectorPassthrough = type === "connector" || code.startsWith("CE_");
9600
- if (response.status === 401 && !isConnectorPassthrough) {
9601
- throw new DelopayAuthenticationError(message, {
9602
- code,
9603
- type,
9604
- requestId,
9605
- rawBody: truncatedRaw,
9606
- data
9607
- });
9608
- }
9609
- const error = new DelopayError(message, {
9610
- status: response.status,
9611
- code,
9612
- type,
9613
- requestId,
9614
- rawBody: truncatedRaw,
9615
- data
9616
- });
9999
+ const error = await errorFromResponse(response, requestId);
9617
10000
  const isTransientStatus = response.status >= 500 || response.status === 429;
9618
10001
  if (isTransientStatus && isRetryable && attempt < this.maxRetries) {
9619
10002
  if (response.status === 429) {
@@ -11081,6 +11464,13 @@ var DEFAULT_BRANDING_BASE = {
11081
11464
  paypalFocusRing: "strong",
11082
11465
  mollieFocusRing: "strong",
11083
11466
  paypalValidTint: false,
11467
+ epayoutsCardLayout: "split",
11468
+ epayoutsLabelStyle: "above",
11469
+ epayoutsFieldChrome: "outlined",
11470
+ epayoutsFieldFontSize: "md",
11471
+ epayoutsFieldWeight: "regular",
11472
+ epayoutsFocusRing: "strong",
11473
+ epayoutsValidTint: false,
11084
11474
  nomupayFormStyle: "plain",
11085
11475
  nomupayShowLabels: true,
11086
11476
  nomupayShowPlaceholders: true,
@@ -12111,6 +12501,66 @@ function decodeBranding(source) {
12111
12501
  DEFAULT_BRANDING.whopSelection
12112
12502
  ),
12113
12503
  paypalValidTint: parseBool(extras["paypalValidTint"], DEFAULT_BRANDING.paypalValidTint),
12504
+ // A look stored before todos#412 has no e-Payouts keys: the form keeps
12505
+ // the PayPal values it was drawn with until then.
12506
+ epayoutsCardLayout: pickEnum(
12507
+ extras["epayoutsCardLayout"],
12508
+ PAYPAL_CARD_LAYOUTS,
12509
+ pickEnum(
12510
+ extras["paypalCardLayout"],
12511
+ PAYPAL_CARD_LAYOUTS,
12512
+ DEFAULT_BRANDING.epayoutsCardLayout
12513
+ )
12514
+ ),
12515
+ epayoutsLabelStyle: pickEnum(
12516
+ extras["epayoutsLabelStyle"],
12517
+ PAYPAL_LABEL_STYLES,
12518
+ pickEnum(
12519
+ extras["paypalLabelStyle"],
12520
+ PAYPAL_LABEL_STYLES,
12521
+ DEFAULT_BRANDING.epayoutsLabelStyle
12522
+ )
12523
+ ),
12524
+ epayoutsFieldChrome: pickEnum(
12525
+ extras["epayoutsFieldChrome"],
12526
+ CARD_FIELD_CHROMES,
12527
+ pickEnum(
12528
+ extras["paypalFieldChrome"],
12529
+ CARD_FIELD_CHROMES,
12530
+ DEFAULT_BRANDING.epayoutsFieldChrome
12531
+ )
12532
+ ),
12533
+ epayoutsFieldFontSize: pickEnum(
12534
+ extras["epayoutsFieldFontSize"],
12535
+ CARD_FIELD_FONT_SIZES,
12536
+ pickEnum(
12537
+ extras["paypalFieldFontSize"],
12538
+ CARD_FIELD_FONT_SIZES,
12539
+ DEFAULT_BRANDING.epayoutsFieldFontSize
12540
+ )
12541
+ ),
12542
+ epayoutsFieldWeight: pickEnum(
12543
+ extras["epayoutsFieldWeight"],
12544
+ CARD_FIELD_WEIGHTS,
12545
+ pickEnum(
12546
+ extras["paypalFieldWeight"],
12547
+ CARD_FIELD_WEIGHTS,
12548
+ DEFAULT_BRANDING.epayoutsFieldWeight
12549
+ )
12550
+ ),
12551
+ epayoutsFocusRing: pickEnum(
12552
+ extras["epayoutsFocusRing"],
12553
+ CARD_FOCUS_RINGS,
12554
+ pickEnum(
12555
+ extras["paypalFocusRing"],
12556
+ CARD_FOCUS_RINGS,
12557
+ DEFAULT_BRANDING.epayoutsFocusRing
12558
+ )
12559
+ ),
12560
+ epayoutsValidTint: parseBool(
12561
+ extras["epayoutsValidTint"],
12562
+ parseBool(extras["paypalValidTint"], DEFAULT_BRANDING.epayoutsValidTint)
12563
+ ),
12114
12564
  nomupayFormStyle: pickEnum(
12115
12565
  extras["nomupayFormStyle"],
12116
12566
  NOMUPAY_FORM_STYLES,
@@ -12272,6 +12722,13 @@ function encodeBranding(branding, base) {
12272
12722
  whopMethodIcons: String(branding.whopMethodIcons),
12273
12723
  whopSelection: branding.whopSelection,
12274
12724
  paypalValidTint: String(branding.paypalValidTint),
12725
+ epayoutsCardLayout: branding.epayoutsCardLayout,
12726
+ epayoutsLabelStyle: branding.epayoutsLabelStyle,
12727
+ epayoutsFieldChrome: branding.epayoutsFieldChrome,
12728
+ epayoutsFieldFontSize: branding.epayoutsFieldFontSize,
12729
+ epayoutsFieldWeight: branding.epayoutsFieldWeight,
12730
+ epayoutsFocusRing: branding.epayoutsFocusRing,
12731
+ epayoutsValidTint: String(branding.epayoutsValidTint),
12275
12732
  nomupayFormStyle: branding.nomupayFormStyle,
12276
12733
  nomupayShowLabels: String(branding.nomupayShowLabels),
12277
12734
  nomupayShowPlaceholders: String(branding.nomupayShowPlaceholders),
@@ -12733,6 +13190,62 @@ function parseImportedBranding(raw) {
12733
13190
  dflt.whopSelection
12734
13191
  ),
12735
13192
  paypalValidTint: parseBool(root["paypalValidTint"], dflt.paypalValidTint),
13193
+ // A look stored before todos#412 has no e-Payouts keys: the form keeps
13194
+ // the PayPal values it was drawn with until then.
13195
+ epayoutsCardLayout: pickEnum(
13196
+ root["epayoutsCardLayout"],
13197
+ PAYPAL_CARD_LAYOUTS,
13198
+ pickEnum(
13199
+ root["paypalCardLayout"],
13200
+ PAYPAL_CARD_LAYOUTS,
13201
+ dflt.epayoutsCardLayout
13202
+ )
13203
+ ),
13204
+ epayoutsLabelStyle: pickEnum(
13205
+ root["epayoutsLabelStyle"],
13206
+ PAYPAL_LABEL_STYLES,
13207
+ pickEnum(
13208
+ root["paypalLabelStyle"],
13209
+ PAYPAL_LABEL_STYLES,
13210
+ dflt.epayoutsLabelStyle
13211
+ )
13212
+ ),
13213
+ epayoutsFieldChrome: pickEnum(
13214
+ root["epayoutsFieldChrome"],
13215
+ CARD_FIELD_CHROMES,
13216
+ pickEnum(
13217
+ root["paypalFieldChrome"],
13218
+ CARD_FIELD_CHROMES,
13219
+ dflt.epayoutsFieldChrome
13220
+ )
13221
+ ),
13222
+ epayoutsFieldFontSize: pickEnum(
13223
+ root["epayoutsFieldFontSize"],
13224
+ CARD_FIELD_FONT_SIZES,
13225
+ pickEnum(
13226
+ root["paypalFieldFontSize"],
13227
+ CARD_FIELD_FONT_SIZES,
13228
+ dflt.epayoutsFieldFontSize
13229
+ )
13230
+ ),
13231
+ epayoutsFieldWeight: pickEnum(
13232
+ root["epayoutsFieldWeight"],
13233
+ CARD_FIELD_WEIGHTS,
13234
+ pickEnum(
13235
+ root["paypalFieldWeight"],
13236
+ CARD_FIELD_WEIGHTS,
13237
+ dflt.epayoutsFieldWeight
13238
+ )
13239
+ ),
13240
+ epayoutsFocusRing: pickEnum(
13241
+ root["epayoutsFocusRing"],
13242
+ CARD_FOCUS_RINGS,
13243
+ pickEnum(root["paypalFocusRing"], CARD_FOCUS_RINGS, dflt.epayoutsFocusRing)
13244
+ ),
13245
+ epayoutsValidTint: parseBool(
13246
+ root["epayoutsValidTint"],
13247
+ parseBool(root["paypalValidTint"], dflt.epayoutsValidTint)
13248
+ ),
12736
13249
  nomupayFormStyle: pickEnum(
12737
13250
  root["nomupayFormStyle"],
12738
13251
  NOMUPAY_FORM_STYLES,
@@ -14247,6 +14760,41 @@ var Admin = class {
14247
14760
  }
14248
14761
  };
14249
14762
 
14763
+ // src/internal/resources/adminLive.ts
14764
+ var AdminLive = class {
14765
+ constructor(request, openStream) {
14766
+ this.request = request;
14767
+ this.openStream = openStream;
14768
+ }
14769
+ /**
14770
+ * Open the stream of what changed in every merchant. Each change names its
14771
+ * `merchant_id`. The SDK does not reconnect — see `LiveEventStream`.
14772
+ *
14773
+ * `GET /admin-portal/live/stream`
14774
+ */
14775
+ async stream(options) {
14776
+ return openLiveStream(this.openStream, "/admin-portal/live/stream", options);
14777
+ }
14778
+ /**
14779
+ * The versions an admin editor of `merchantId` names in `If-Match`. A save
14780
+ * over a newer version is refused with `IR_16` (400), `error.data` a
14781
+ * `LiveChangedElsewhere`.
14782
+ *
14783
+ * `GET /admin-portal/live/versions`
14784
+ */
14785
+ async versions(merchantId, params, options) {
14786
+ return this.request("GET", "/admin-portal/live/versions", {
14787
+ query: {
14788
+ merchant_id: merchantId,
14789
+ entities: params.entities.join(","),
14790
+ profile_id: params.profile_id,
14791
+ record_id: params.record_id
14792
+ },
14793
+ ...options
14794
+ });
14795
+ }
14796
+ };
14797
+
14250
14798
  // src/internal/resources/adminPortal.ts
14251
14799
  var AdminPortal = class {
14252
14800
  constructor(request) {
@@ -15912,6 +16460,8 @@ var DelopayInternal = class extends Delopay {
15912
16460
  const request = this.request.bind(this);
15913
16461
  this.admin = new Admin(request);
15914
16462
  this.adminPortal = new AdminPortal(request);
16463
+ const openStream = this.openEventStream.bind(this);
16464
+ this.adminLive = new AdminLive(request, openStream);
15915
16465
  this.auditLogs = new AuditLogs(request);
15916
16466
  this.cache = new Cache(request);
15917
16467
  this.cardIssuers = new CardIssuers(request);
@@ -15945,6 +16495,7 @@ var DelopayInternal = class extends Delopay {
15945
16495
  APPLE_PAY_BUTTON_STYLES,
15946
16496
  APPLE_PAY_BUTTON_TYPES,
15947
16497
  Admin,
16498
+ AdminLive,
15948
16499
  AdminPortal,
15949
16500
  Analytics,
15950
16501
  AnalyticsDashboard,
@@ -16003,6 +16554,8 @@ var DelopayInternal = class extends Delopay {
16003
16554
  InvoiceSettingsResource,
16004
16555
  Invoices,
16005
16556
  LOCALIZABLE_COPY_FIELDS,
16557
+ Live,
16558
+ LiveEventStream,
16006
16559
  MAX_SECTION_LABEL_LENGTH,
16007
16560
  MAX_SECTION_ORDER,
16008
16561
  METHOD_SECTIONS,