@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/index.cjs CHANGED
@@ -83,6 +83,8 @@ __export(index_exports, {
83
83
  InvoiceSettingsResource: () => InvoiceSettingsResource,
84
84
  Invoices: () => Invoices,
85
85
  LOCALIZABLE_COPY_FIELDS: () => LOCALIZABLE_COPY_FIELDS,
86
+ Live: () => Live,
87
+ LiveEventStream: () => LiveEventStream,
86
88
  MAX_SECTION_LABEL_LENGTH: () => MAX_SECTION_LABEL_LENGTH,
87
89
  MAX_SECTION_ORDER: () => MAX_SECTION_ORDER,
88
90
  METHOD_SECTIONS: () => METHOD_SECTIONS,
@@ -308,6 +310,100 @@ var DelopayAuthenticationError = class extends DelopayError {
308
310
  }
309
311
  };
310
312
 
313
+ // src/eventStream.ts
314
+ var LF = 10;
315
+ var CR = 13;
316
+ var EventStreamParser = class {
317
+ /**
318
+ * @param lastEventId - The id the stream resumes after, as `EventSource`
319
+ * carries it into a reconnection: a dispatch before the server names an
320
+ * id keeps this one.
321
+ */
322
+ constructor(lastEventId = "") {
323
+ this.line = "";
324
+ /** The previous chunk ended in CR: a LF opening the next one belongs to it. */
325
+ this.skipLf = false;
326
+ this.data = "";
327
+ this.hasData = false;
328
+ this.eventType = "";
329
+ this.retry = null;
330
+ this.idBuffer = lastEventId;
331
+ }
332
+ /** Feed the next piece of decoded text; returns the dispatches it completed. */
333
+ push(text) {
334
+ const out = [];
335
+ if (text.length === 0) return out;
336
+ let start = 0;
337
+ if (this.skipLf) {
338
+ this.skipLf = false;
339
+ if (text.charCodeAt(0) === LF) start = 1;
340
+ }
341
+ for (let i = start; i < text.length; i++) {
342
+ const c = text.charCodeAt(i);
343
+ if (c !== LF && c !== CR) continue;
344
+ const line = this.line + text.slice(start, i);
345
+ this.line = "";
346
+ this.processLine(line, out);
347
+ if (c === CR) {
348
+ if (i + 1 < text.length) {
349
+ if (text.charCodeAt(i + 1) === LF) i++;
350
+ } else {
351
+ this.skipLf = true;
352
+ }
353
+ }
354
+ start = i + 1;
355
+ }
356
+ this.line += text.slice(start);
357
+ return out;
358
+ }
359
+ processLine(line, out) {
360
+ if (line === "") {
361
+ this.dispatch(out);
362
+ return;
363
+ }
364
+ if (line.charCodeAt(0) === 58) return;
365
+ const colon = line.indexOf(":");
366
+ let field;
367
+ let value;
368
+ if (colon === -1) {
369
+ field = line;
370
+ value = "";
371
+ } else {
372
+ field = line.slice(0, colon);
373
+ value = line.slice(colon + 1);
374
+ if (value.charCodeAt(0) === 32) value = value.slice(1);
375
+ }
376
+ switch (field) {
377
+ case "event":
378
+ this.eventType = value;
379
+ break;
380
+ case "data":
381
+ this.data = this.hasData ? `${this.data}
382
+ ${value}` : value;
383
+ this.hasData = true;
384
+ break;
385
+ case "id":
386
+ if (!value.includes("\0")) this.idBuffer = value;
387
+ break;
388
+ case "retry":
389
+ if (/^[0-9]+$/.test(value)) {
390
+ const ms = Number(value);
391
+ if (Number.isSafeInteger(ms)) this.retry = ms;
392
+ }
393
+ break;
394
+ default:
395
+ break;
396
+ }
397
+ }
398
+ dispatch(out) {
399
+ const event = this.hasData ? { type: this.eventType || "message", data: this.data } : null;
400
+ out.push({ lastEventId: this.idBuffer, retry: this.retry, event });
401
+ this.data = "";
402
+ this.hasData = false;
403
+ this.eventType = "";
404
+ }
405
+ };
406
+
311
407
  // src/resources/apiKeys.ts
312
408
  var ApiKeys = class {
313
409
  constructor(request) {
@@ -1553,8 +1649,8 @@ var Customers = class {
1553
1649
  };
1554
1650
 
1555
1651
  // src/recordCounter.ts
1556
- var LF = 10;
1557
- var CR = 13;
1652
+ var LF2 = 10;
1653
+ var CR2 = 13;
1558
1654
  var QUOTE = 34;
1559
1655
  var BACKSLASH = 92;
1560
1656
  var OPEN_BRACE = 123;
@@ -1578,10 +1674,10 @@ var CsvRecordCounter = class {
1578
1674
  lineHasContent = true;
1579
1675
  } else if (inQuotes) {
1580
1676
  lineHasContent = true;
1581
- } else if (byte === LF) {
1677
+ } else if (byte === LF2) {
1582
1678
  newlines++;
1583
1679
  lineHasContent = false;
1584
- } else if (byte !== CR) {
1680
+ } else if (byte !== CR2) {
1585
1681
  lineHasContent = true;
1586
1682
  }
1587
1683
  }
@@ -9115,6 +9211,133 @@ var Risk = class {
9115
9211
  }
9116
9212
  };
9117
9213
 
9214
+ // src/resources/live.ts
9215
+ function readField(data, key) {
9216
+ let value;
9217
+ try {
9218
+ value = JSON.parse(data);
9219
+ } catch {
9220
+ return void 0;
9221
+ }
9222
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
9223
+ return Object.getOwnPropertyDescriptor(value, key)?.value;
9224
+ }
9225
+ function toLiveStreamEvent(event, id, retry) {
9226
+ switch (event.type) {
9227
+ case "ready": {
9228
+ const resumed = readField(event.data, "resumed");
9229
+ return { type: "ready", id, retry, resumed: typeof resumed === "boolean" ? resumed : null };
9230
+ }
9231
+ case "change": {
9232
+ const changes = readField(event.data, "changes");
9233
+ if (!Array.isArray(changes)) return { type: "resync", id };
9234
+ return { type: "change", id, changes };
9235
+ }
9236
+ case "resync":
9237
+ return { type: "resync", id };
9238
+ default:
9239
+ return null;
9240
+ }
9241
+ }
9242
+ var LiveEventStream = class {
9243
+ constructor(connection, lastEventId = null) {
9244
+ this.connection = connection;
9245
+ this._retry = null;
9246
+ this._lastEventId = lastEventId || null;
9247
+ }
9248
+ /**
9249
+ * The position to resume after: the id of the last event read, advanced by
9250
+ * keepalives too. `null` before the server named one. Send it as
9251
+ * `lastEventId` when reconnecting.
9252
+ */
9253
+ get lastEventId() {
9254
+ return this._lastEventId;
9255
+ }
9256
+ /** The wait before reconnecting the server asked for, in ms; `null` until it says. */
9257
+ get retry() {
9258
+ return this._retry;
9259
+ }
9260
+ /** Close the stream; the iteration ends without an error. */
9261
+ close() {
9262
+ this.connection.close();
9263
+ }
9264
+ [Symbol.asyncIterator]() {
9265
+ this.iterator ?? (this.iterator = this.events());
9266
+ return this.iterator;
9267
+ }
9268
+ async *events() {
9269
+ for await (const dispatch of this.connection.dispatches) {
9270
+ const event = this.read(dispatch);
9271
+ if (event) yield event;
9272
+ }
9273
+ }
9274
+ read(dispatch) {
9275
+ this._lastEventId = dispatch.lastEventId || null;
9276
+ if (dispatch.retry !== null) this._retry = dispatch.retry;
9277
+ if (!dispatch.event) return null;
9278
+ return toLiveStreamEvent(dispatch.event, this._lastEventId, this._retry);
9279
+ }
9280
+ };
9281
+ async function openLiveStream(openStream, path, options = {}) {
9282
+ const connection = await openStream(path, {
9283
+ ...options.lastEventId ? { lastEventId: options.lastEventId } : {},
9284
+ ...options.signal ? { signal: options.signal } : {},
9285
+ ...options.idleTimeoutMs !== void 0 ? { idleTimeoutMs: options.idleTimeoutMs } : {},
9286
+ ...options.headers ? { headers: options.headers } : {}
9287
+ });
9288
+ return new LiveEventStream(connection, options.lastEventId ?? null);
9289
+ }
9290
+ var Live = class {
9291
+ constructor(request, openStream) {
9292
+ this.request = request;
9293
+ this.openStream = openStream;
9294
+ }
9295
+ /**
9296
+ * Open the stream of what changed in the caller's merchant, as their role
9297
+ * may read it. Resolves once the server answered; a `401` rejects with
9298
+ * `DelopayAuthenticationError`.
9299
+ *
9300
+ * The first event is `ready`. `change` names the kinds of record and, when
9301
+ * few, the ids that changed: read them again through the usual endpoints.
9302
+ * `resync`, and `ready` with `resumed: false`, mean read everything again.
9303
+ * The SDK does not reconnect — see {@link LiveEventStream}.
9304
+ *
9305
+ * `GET /live/stream`
9306
+ *
9307
+ * @example
9308
+ * ```typescript
9309
+ * const stream = await delopay.live.stream({ lastEventId, signal });
9310
+ * for await (const event of stream) {
9311
+ * if (event.type === 'change') refresh(event.changes);
9312
+ * else if (event.type === 'resync' || event.resumed === false) refreshAll();
9313
+ * }
9314
+ * lastEventId = stream.lastEventId; // reconnect after stream.retry
9315
+ * ```
9316
+ */
9317
+ async stream(options) {
9318
+ return openLiveStream(this.openStream, "/live/stream", options);
9319
+ }
9320
+ /**
9321
+ * The current version of each kind of record named, in the caller's
9322
+ * merchant and shop. Read it before the record and send the entry's
9323
+ * `if_match` in the `If-Match` header of the save: a save over a newer
9324
+ * version is refused with `IR_16` (400), `error.data` a
9325
+ * `LiveChangedElsewhere` naming who made the change.
9326
+ *
9327
+ * `GET /live/versions`
9328
+ */
9329
+ async versions(params, options) {
9330
+ return this.request("GET", "/live/versions", {
9331
+ query: {
9332
+ entities: params.entities.join(","),
9333
+ profile_id: params.profile_id,
9334
+ record_id: params.record_id
9335
+ },
9336
+ ...options
9337
+ });
9338
+ }
9339
+ };
9340
+
9118
9341
  // src/client.ts
9119
9342
  var PRODUCTION_URL = "https://api.delopay.net";
9120
9343
  var SANDBOX_URL = "https://sandbox.delopay.net";
@@ -9138,6 +9361,40 @@ function truncateRawBody(raw) {
9138
9361
  if (!raw) return void 0;
9139
9362
  return raw.length > MAX_RAW_BODY_BYTES ? raw.slice(0, MAX_RAW_BODY_BYTES) + "\u2026" : raw;
9140
9363
  }
9364
+ async function errorFromResponse(response, requestId) {
9365
+ const rawBody = await response.text().catch(() => "");
9366
+ let parsed = {};
9367
+ if (rawBody) {
9368
+ try {
9369
+ parsed = JSON.parse(rawBody);
9370
+ } catch {
9371
+ }
9372
+ }
9373
+ const err = parsed.error ?? parsed;
9374
+ const message = err.message ?? `Request failed with status ${response.status}`;
9375
+ const code = err.code ?? "";
9376
+ const type = err.error_type ?? err.type ?? "";
9377
+ const data = err.data && typeof err.data === "object" && !Array.isArray(err.data) ? err.data : void 0;
9378
+ const truncatedRaw = truncateRawBody(rawBody);
9379
+ const isConnectorPassthrough = type === "connector" || code.startsWith("CE_");
9380
+ if (response.status === 401 && !isConnectorPassthrough) {
9381
+ return new DelopayAuthenticationError(message, {
9382
+ code,
9383
+ type,
9384
+ requestId,
9385
+ rawBody: truncatedRaw,
9386
+ data
9387
+ });
9388
+ }
9389
+ return new DelopayError(message, {
9390
+ status: response.status,
9391
+ code,
9392
+ type,
9393
+ requestId,
9394
+ rawBody: truncatedRaw,
9395
+ data
9396
+ });
9397
+ }
9141
9398
  function findIdempotencyKey(headers) {
9142
9399
  for (const [k, v] of Object.entries(headers)) {
9143
9400
  if (k.toLowerCase() === "idempotency-key") return v;
@@ -9298,6 +9555,7 @@ function redactUrlForLogging(url) {
9298
9555
  });
9299
9556
  return `${base}?${parts.join("&")}`;
9300
9557
  }
9558
+ var DEFAULT_STREAM_IDLE_TIMEOUT_MS = 45e3;
9301
9559
  var Delopay = class {
9302
9560
  /**
9303
9561
  * Create a new Delopay client.
@@ -9320,6 +9578,7 @@ var Delopay = class {
9320
9578
  this.baseUrl = PRODUCTION_URL;
9321
9579
  }
9322
9580
  const request = this.request.bind(this);
9581
+ const openStream = this.openEventStream.bind(this);
9323
9582
  this.payments = new Payments(request);
9324
9583
  this.refunds = new Refunds(request);
9325
9584
  this.customers = new Customers(request);
@@ -9356,6 +9615,7 @@ var Delopay = class {
9356
9615
  this.settings = new Settings(request);
9357
9616
  this.messageTemplates = new MessageTemplates(request);
9358
9617
  this.risk = new Risk(request);
9618
+ this.live = new Live(request, openStream);
9359
9619
  this.subscriptions = new Subscriptions(request);
9360
9620
  this.buyerPortal = new BuyerPortal(request);
9361
9621
  this.buyerAccount = new BuyerAccount(request);
@@ -9432,24 +9692,12 @@ var Delopay = class {
9432
9692
  this.setJwtToken(response.token);
9433
9693
  return response;
9434
9694
  }
9435
- /**
9436
- * Make a raw HTTP request to the Delopay API.
9437
- *
9438
- * You rarely need to call this directly — prefer the typed resource methods.
9439
- * Use it only for endpoints not yet covered by a resource class.
9440
- *
9441
- * @param method - HTTP method (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`).
9442
- * @param path - API path starting with `/` (e.g. `/payments`).
9443
- * @param options - Optional body, query parameters, and headers.
9444
- * @returns Parsed JSON response body typed as `T`.
9445
- * @throws {DelopayAuthenticationError} On 401 responses.
9446
- * @throws {DelopayError} On all other non-2xx responses, timeouts, and network errors.
9447
- */
9448
- async request(method, path, options) {
9695
+ /** `path` on the base URL, with the query string `query` makes. */
9696
+ buildUrl(path, query) {
9449
9697
  let url = `${this.baseUrl}${path}`;
9450
- if (options?.query) {
9698
+ if (query) {
9451
9699
  const params = new URLSearchParams();
9452
- for (const [key, value] of Object.entries(options.query)) {
9700
+ for (const [key, value] of Object.entries(query)) {
9453
9701
  if (value === void 0 || value === null) continue;
9454
9702
  if (Array.isArray(value)) {
9455
9703
  for (const v of value) {
@@ -9464,8 +9712,189 @@ var Delopay = class {
9464
9712
  url += `?${qs}`;
9465
9713
  }
9466
9714
  }
9715
+ return url;
9716
+ }
9717
+ /** The credential header: the JWT when one is set, else the API key. */
9718
+ authHeaders() {
9719
+ if (this.jwtToken) return { Authorization: `Bearer ${this.jwtToken}` };
9720
+ if (this.apiKey) return { "api-key": this.apiKey };
9721
+ return {};
9722
+ }
9723
+ /** One debug event, when `debug` is on. */
9724
+ log(event, data) {
9725
+ if (!this.debug) return;
9726
+ if (this.logger) {
9727
+ this.logger(event, data);
9728
+ return;
9729
+ }
9730
+ if (event === "request")
9731
+ console.log(`[delopay] ${data.method} ${data.url}`);
9732
+ else if (event === "response")
9733
+ console.log(
9734
+ `[delopay] ${data.status} ${data.method} ${data.path}`
9735
+ );
9736
+ else
9737
+ console.log(
9738
+ `[delopay] retry ${data.attempt}/${data.maxRetries} ${data.method} ${data.path}`
9739
+ );
9740
+ }
9741
+ /**
9742
+ * Open a `GET` `text/event-stream` and read it as it arrives.
9743
+ *
9744
+ * `request()` keeps every chunk of a body until the body ends, which a
9745
+ * stream that stays open for half an hour must not do; this reads the body
9746
+ * chunk by chunk through {@link EventStreamParser} and keeps nothing it has
9747
+ * dispatched. Credentials, the base URL, debug logging and the mapping of a
9748
+ * non-2xx answer to `DelopayError` / `DelopayAuthenticationError` are the
9749
+ * ones `request()` uses.
9750
+ *
9751
+ * Never retried, and never reconnected: the caller owns reconnection.
9752
+ * Resolves once the response headers arrive; rejects on a non-2xx answer,
9753
+ * on a 2xx that is not `text/event-stream` (code `NOT_EVENT_STREAM`), and
9754
+ * with `TIMEOUT`, `NETWORK` or `ABORTED` like `request()`.
9755
+ */
9756
+ async openEventStream(path, options = {}) {
9757
+ const url = this.buildUrl(path, options.query);
9758
+ const lastEventId = options.lastEventId ?? "";
9759
+ const headers = {
9760
+ ...this.authHeaders(),
9761
+ Accept: "text/event-stream",
9762
+ ...lastEventId ? { "Last-Event-ID": lastEventId } : {},
9763
+ ...options.headers
9764
+ };
9765
+ const idleMs = options.idleTimeoutMs ?? DEFAULT_STREAM_IDLE_TIMEOUT_MS;
9766
+ const callerSignal = options.signal;
9767
+ const aborted = () => new DelopayError("Request aborted", { status: 0, code: "ABORTED", type: "abort_error" });
9768
+ if (callerSignal?.aborted) throw aborted();
9769
+ const idleCtrl = new AbortController();
9770
+ const closeCtrl = new AbortController();
9771
+ let timer;
9772
+ const stopIdleTimer = () => {
9773
+ if (timer !== void 0) clearTimeout(timer);
9774
+ timer = void 0;
9775
+ };
9776
+ const startIdleTimer = () => {
9777
+ stopIdleTimer();
9778
+ if (!idleCtrl.signal.aborted) timer = setTimeout(() => idleCtrl.abort(), idleMs);
9779
+ };
9780
+ const combined = combineSignals(
9781
+ callerSignal ? [idleCtrl.signal, closeCtrl.signal, callerSignal] : [idleCtrl.signal, closeCtrl.signal]
9782
+ );
9783
+ const release = () => {
9784
+ stopIdleTimer();
9785
+ combined.dispose();
9786
+ };
9787
+ const timedOut = () => new DelopayError(`Event stream silent for ${idleMs} ms`, {
9788
+ status: 0,
9789
+ code: "TIMEOUT",
9790
+ type: "timeout_error"
9791
+ });
9792
+ const network = (cause) => new DelopayError(
9793
+ `Network error: ${cause instanceof Error ? cause.message : String(cause ?? "stream failed")}`,
9794
+ { status: 0, code: "NETWORK", type: "network_error" }
9795
+ );
9796
+ this.log("request", { method: "GET", url: redactUrlForLogging(url), path });
9797
+ startIdleTimer();
9798
+ let response;
9799
+ try {
9800
+ response = await fetch(url, { method: "GET", headers, signal: combined.signal });
9801
+ } catch (err) {
9802
+ release();
9803
+ if (callerSignal?.aborted) throw aborted();
9804
+ if (idleCtrl.signal.aborted) throw timedOut();
9805
+ throw network(err);
9806
+ }
9807
+ const requestId = response.headers?.get("x-request-id") ?? response.headers?.get("x-trace-id") ?? void 0;
9808
+ this.log("response", { status: response.status, method: "GET", path, requestId });
9809
+ if (!response.ok) {
9810
+ const error = await errorFromResponse(response, requestId);
9811
+ release();
9812
+ throw error;
9813
+ }
9814
+ const body = response.body;
9815
+ const contentType = response.headers?.get("content-type") ?? "";
9816
+ const mediaType = (contentType.split(";")[0] ?? "").trim().toLowerCase();
9817
+ if (mediaType !== "text/event-stream" || !body || typeof body.getReader !== "function") {
9818
+ release();
9819
+ body?.cancel().catch(noop);
9820
+ throw new DelopayError(
9821
+ mediaType === "text/event-stream" ? "The event stream has no readable body" : `Expected a text/event-stream response, got ${contentType ? `"${contentType}"` : "no content type"}`,
9822
+ {
9823
+ status: response.status,
9824
+ code: "NOT_EVENT_STREAM",
9825
+ type: "invalid_response",
9826
+ requestId
9827
+ }
9828
+ );
9829
+ }
9830
+ stopIdleTimer();
9831
+ const reader = body.getReader();
9832
+ const parser = new EventStreamParser(lastEventId);
9833
+ const decoder = new TextDecoder();
9834
+ const stopped = () => Boolean(callerSignal?.aborted) || closeCtrl.signal.aborted;
9835
+ let finished = false;
9836
+ const finish = () => {
9837
+ if (finished) return;
9838
+ finished = true;
9839
+ release();
9840
+ reader.cancel().catch(noop);
9841
+ };
9842
+ async function* dispatches() {
9843
+ try {
9844
+ for (; ; ) {
9845
+ let result;
9846
+ startIdleTimer();
9847
+ try {
9848
+ result = await readChunk(reader, combined.signal);
9849
+ } catch (err) {
9850
+ if (callerSignal?.aborted || closeCtrl.signal.aborted) return;
9851
+ if (idleCtrl.signal.aborted) throw timedOut();
9852
+ throw network(err instanceof BodyStreamFailure ? err.cause : err);
9853
+ } finally {
9854
+ stopIdleTimer();
9855
+ }
9856
+ if (result.done) {
9857
+ for (const dispatch of parser.push(decoder.decode())) {
9858
+ if (stopped()) return;
9859
+ yield dispatch;
9860
+ }
9861
+ return;
9862
+ }
9863
+ if (!result.value || result.value.byteLength === 0) continue;
9864
+ for (const dispatch of parser.push(decoder.decode(result.value, { stream: true }))) {
9865
+ if (stopped()) return;
9866
+ yield dispatch;
9867
+ }
9868
+ }
9869
+ } finally {
9870
+ finish();
9871
+ }
9872
+ }
9873
+ return {
9874
+ dispatches: dispatches(),
9875
+ close: () => {
9876
+ closeCtrl.abort();
9877
+ finish();
9878
+ }
9879
+ };
9880
+ }
9881
+ /**
9882
+ * Make a raw HTTP request to the Delopay API.
9883
+ *
9884
+ * You rarely need to call this directly — prefer the typed resource methods.
9885
+ * Use it only for endpoints not yet covered by a resource class.
9886
+ *
9887
+ * @param method - HTTP method (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`).
9888
+ * @param path - API path starting with `/` (e.g. `/payments`).
9889
+ * @param options - Optional body, query parameters, and headers.
9890
+ * @returns Parsed JSON response body typed as `T`.
9891
+ * @throws {DelopayAuthenticationError} On 401 responses.
9892
+ * @throws {DelopayError} On all other non-2xx responses, timeouts, and network errors.
9893
+ */
9894
+ async request(method, path, options) {
9895
+ const url = this.buildUrl(path, options?.query);
9467
9896
  let headers = {
9468
- ...this.jwtToken ? { Authorization: `Bearer ${this.jwtToken}` } : this.apiKey ? { "api-key": this.apiKey } : {},
9897
+ ...this.authHeaders(),
9469
9898
  ...options?.headers
9470
9899
  };
9471
9900
  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);
@@ -9518,23 +9947,7 @@ var Delopay = class {
9518
9947
  let lastError;
9519
9948
  let retryAfterOverrideMs = null;
9520
9949
  const safeUrl = () => redactUrlForLogging(url);
9521
- const emit = (event, data) => {
9522
- if (!this.debug) return;
9523
- if (this.logger) {
9524
- this.logger(event, data);
9525
- return;
9526
- }
9527
- if (event === "request")
9528
- console.log(`[delopay] ${data.method} ${data.url}`);
9529
- else if (event === "response")
9530
- console.log(
9531
- `[delopay] ${data.status} ${data.method} ${data.path}`
9532
- );
9533
- else
9534
- console.log(
9535
- `[delopay] retry ${data.attempt}/${data.maxRetries} ${data.method} ${data.path}`
9536
- );
9537
- };
9950
+ const emit = (event, data) => this.log(event, data);
9538
9951
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
9539
9952
  if (attempt > 0) {
9540
9953
  const base = Math.min(500 * 2 ** (attempt - 1), 5e3);
@@ -9566,38 +9979,7 @@ var Delopay = class {
9566
9979
  const requestId = response.headers?.get("x-request-id") ?? response.headers?.get("x-trace-id") ?? void 0;
9567
9980
  emit("response", { status: response.status, method, path, requestId });
9568
9981
  if (!response.ok) {
9569
- const rawBody = await response.text().catch(() => "");
9570
- let parsed = {};
9571
- if (rawBody) {
9572
- try {
9573
- parsed = JSON.parse(rawBody);
9574
- } catch {
9575
- }
9576
- }
9577
- const err = parsed.error ?? parsed;
9578
- const message = err.message ?? `Request failed with status ${response.status}`;
9579
- const code = err.code ?? "";
9580
- const type = err.error_type ?? err.type ?? "";
9581
- const data = err.data && typeof err.data === "object" && !Array.isArray(err.data) ? err.data : void 0;
9582
- const truncatedRaw = truncateRawBody(rawBody);
9583
- const isConnectorPassthrough = type === "connector" || code.startsWith("CE_");
9584
- if (response.status === 401 && !isConnectorPassthrough) {
9585
- throw new DelopayAuthenticationError(message, {
9586
- code,
9587
- type,
9588
- requestId,
9589
- rawBody: truncatedRaw,
9590
- data
9591
- });
9592
- }
9593
- const error = new DelopayError(message, {
9594
- status: response.status,
9595
- code,
9596
- type,
9597
- requestId,
9598
- rawBody: truncatedRaw,
9599
- data
9600
- });
9982
+ const error = await errorFromResponse(response, requestId);
9601
9983
  const isTransientStatus = response.status >= 500 || response.status === 429;
9602
9984
  if (isTransientStatus && isRetryable && attempt < this.maxRetries) {
9603
9985
  if (response.status === 429) {
@@ -11065,6 +11447,13 @@ var DEFAULT_BRANDING_BASE = {
11065
11447
  paypalFocusRing: "strong",
11066
11448
  mollieFocusRing: "strong",
11067
11449
  paypalValidTint: false,
11450
+ epayoutsCardLayout: "split",
11451
+ epayoutsLabelStyle: "above",
11452
+ epayoutsFieldChrome: "outlined",
11453
+ epayoutsFieldFontSize: "md",
11454
+ epayoutsFieldWeight: "regular",
11455
+ epayoutsFocusRing: "strong",
11456
+ epayoutsValidTint: false,
11068
11457
  nomupayFormStyle: "plain",
11069
11458
  nomupayShowLabels: true,
11070
11459
  nomupayShowPlaceholders: true,
@@ -12095,6 +12484,66 @@ function decodeBranding(source) {
12095
12484
  DEFAULT_BRANDING.whopSelection
12096
12485
  ),
12097
12486
  paypalValidTint: parseBool(extras["paypalValidTint"], DEFAULT_BRANDING.paypalValidTint),
12487
+ // A look stored before todos#412 has no e-Payouts keys: the form keeps
12488
+ // the PayPal values it was drawn with until then.
12489
+ epayoutsCardLayout: pickEnum(
12490
+ extras["epayoutsCardLayout"],
12491
+ PAYPAL_CARD_LAYOUTS,
12492
+ pickEnum(
12493
+ extras["paypalCardLayout"],
12494
+ PAYPAL_CARD_LAYOUTS,
12495
+ DEFAULT_BRANDING.epayoutsCardLayout
12496
+ )
12497
+ ),
12498
+ epayoutsLabelStyle: pickEnum(
12499
+ extras["epayoutsLabelStyle"],
12500
+ PAYPAL_LABEL_STYLES,
12501
+ pickEnum(
12502
+ extras["paypalLabelStyle"],
12503
+ PAYPAL_LABEL_STYLES,
12504
+ DEFAULT_BRANDING.epayoutsLabelStyle
12505
+ )
12506
+ ),
12507
+ epayoutsFieldChrome: pickEnum(
12508
+ extras["epayoutsFieldChrome"],
12509
+ CARD_FIELD_CHROMES,
12510
+ pickEnum(
12511
+ extras["paypalFieldChrome"],
12512
+ CARD_FIELD_CHROMES,
12513
+ DEFAULT_BRANDING.epayoutsFieldChrome
12514
+ )
12515
+ ),
12516
+ epayoutsFieldFontSize: pickEnum(
12517
+ extras["epayoutsFieldFontSize"],
12518
+ CARD_FIELD_FONT_SIZES,
12519
+ pickEnum(
12520
+ extras["paypalFieldFontSize"],
12521
+ CARD_FIELD_FONT_SIZES,
12522
+ DEFAULT_BRANDING.epayoutsFieldFontSize
12523
+ )
12524
+ ),
12525
+ epayoutsFieldWeight: pickEnum(
12526
+ extras["epayoutsFieldWeight"],
12527
+ CARD_FIELD_WEIGHTS,
12528
+ pickEnum(
12529
+ extras["paypalFieldWeight"],
12530
+ CARD_FIELD_WEIGHTS,
12531
+ DEFAULT_BRANDING.epayoutsFieldWeight
12532
+ )
12533
+ ),
12534
+ epayoutsFocusRing: pickEnum(
12535
+ extras["epayoutsFocusRing"],
12536
+ CARD_FOCUS_RINGS,
12537
+ pickEnum(
12538
+ extras["paypalFocusRing"],
12539
+ CARD_FOCUS_RINGS,
12540
+ DEFAULT_BRANDING.epayoutsFocusRing
12541
+ )
12542
+ ),
12543
+ epayoutsValidTint: parseBool(
12544
+ extras["epayoutsValidTint"],
12545
+ parseBool(extras["paypalValidTint"], DEFAULT_BRANDING.epayoutsValidTint)
12546
+ ),
12098
12547
  nomupayFormStyle: pickEnum(
12099
12548
  extras["nomupayFormStyle"],
12100
12549
  NOMUPAY_FORM_STYLES,
@@ -12256,6 +12705,13 @@ function encodeBranding(branding, base) {
12256
12705
  whopMethodIcons: String(branding.whopMethodIcons),
12257
12706
  whopSelection: branding.whopSelection,
12258
12707
  paypalValidTint: String(branding.paypalValidTint),
12708
+ epayoutsCardLayout: branding.epayoutsCardLayout,
12709
+ epayoutsLabelStyle: branding.epayoutsLabelStyle,
12710
+ epayoutsFieldChrome: branding.epayoutsFieldChrome,
12711
+ epayoutsFieldFontSize: branding.epayoutsFieldFontSize,
12712
+ epayoutsFieldWeight: branding.epayoutsFieldWeight,
12713
+ epayoutsFocusRing: branding.epayoutsFocusRing,
12714
+ epayoutsValidTint: String(branding.epayoutsValidTint),
12259
12715
  nomupayFormStyle: branding.nomupayFormStyle,
12260
12716
  nomupayShowLabels: String(branding.nomupayShowLabels),
12261
12717
  nomupayShowPlaceholders: String(branding.nomupayShowPlaceholders),
@@ -12717,6 +13173,62 @@ function parseImportedBranding(raw) {
12717
13173
  dflt.whopSelection
12718
13174
  ),
12719
13175
  paypalValidTint: parseBool(root["paypalValidTint"], dflt.paypalValidTint),
13176
+ // A look stored before todos#412 has no e-Payouts keys: the form keeps
13177
+ // the PayPal values it was drawn with until then.
13178
+ epayoutsCardLayout: pickEnum(
13179
+ root["epayoutsCardLayout"],
13180
+ PAYPAL_CARD_LAYOUTS,
13181
+ pickEnum(
13182
+ root["paypalCardLayout"],
13183
+ PAYPAL_CARD_LAYOUTS,
13184
+ dflt.epayoutsCardLayout
13185
+ )
13186
+ ),
13187
+ epayoutsLabelStyle: pickEnum(
13188
+ root["epayoutsLabelStyle"],
13189
+ PAYPAL_LABEL_STYLES,
13190
+ pickEnum(
13191
+ root["paypalLabelStyle"],
13192
+ PAYPAL_LABEL_STYLES,
13193
+ dflt.epayoutsLabelStyle
13194
+ )
13195
+ ),
13196
+ epayoutsFieldChrome: pickEnum(
13197
+ root["epayoutsFieldChrome"],
13198
+ CARD_FIELD_CHROMES,
13199
+ pickEnum(
13200
+ root["paypalFieldChrome"],
13201
+ CARD_FIELD_CHROMES,
13202
+ dflt.epayoutsFieldChrome
13203
+ )
13204
+ ),
13205
+ epayoutsFieldFontSize: pickEnum(
13206
+ root["epayoutsFieldFontSize"],
13207
+ CARD_FIELD_FONT_SIZES,
13208
+ pickEnum(
13209
+ root["paypalFieldFontSize"],
13210
+ CARD_FIELD_FONT_SIZES,
13211
+ dflt.epayoutsFieldFontSize
13212
+ )
13213
+ ),
13214
+ epayoutsFieldWeight: pickEnum(
13215
+ root["epayoutsFieldWeight"],
13216
+ CARD_FIELD_WEIGHTS,
13217
+ pickEnum(
13218
+ root["paypalFieldWeight"],
13219
+ CARD_FIELD_WEIGHTS,
13220
+ dflt.epayoutsFieldWeight
13221
+ )
13222
+ ),
13223
+ epayoutsFocusRing: pickEnum(
13224
+ root["epayoutsFocusRing"],
13225
+ CARD_FOCUS_RINGS,
13226
+ pickEnum(root["paypalFocusRing"], CARD_FOCUS_RINGS, dflt.epayoutsFocusRing)
13227
+ ),
13228
+ epayoutsValidTint: parseBool(
13229
+ root["epayoutsValidTint"],
13230
+ parseBool(root["paypalValidTint"], dflt.epayoutsValidTint)
13231
+ ),
12720
13232
  nomupayFormStyle: pickEnum(
12721
13233
  root["nomupayFormStyle"],
12722
13234
  NOMUPAY_FORM_STYLES,
@@ -14255,6 +14767,8 @@ var encodeNativePanes = encodePanes;
14255
14767
  InvoiceSettingsResource,
14256
14768
  Invoices,
14257
14769
  LOCALIZABLE_COPY_FIELDS,
14770
+ Live,
14771
+ LiveEventStream,
14258
14772
  MAX_SECTION_LABEL_LENGTH,
14259
14773
  MAX_SECTION_ORDER,
14260
14774
  METHOD_SECTIONS,