@peable.to/sdk 0.1.1 → 0.2.1

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.
Files changed (46) hide show
  1. package/README.md +14 -0
  2. package/dist/cjs/browser/mount.js +55 -8
  3. package/dist/cjs/browser/payerClient.js +58 -8
  4. package/dist/cjs/checkout.js +9 -1
  5. package/dist/cjs/core/client.js +25 -1
  6. package/dist/cjs/core/config.js +11 -0
  7. package/dist/cjs/core/errors.js +1 -1
  8. package/dist/cjs/core/serviceToken.js +11 -2
  9. package/dist/cjs/index.js +28 -1
  10. package/dist/cjs/resources/billing.js +25 -0
  11. package/dist/cjs/resources/checkoutSessions.js +2 -1
  12. package/dist/cjs/resources/connectedAccounts.js +63 -0
  13. package/dist/cjs/resources/merchants.js +51 -0
  14. package/dist/cjs/resources/paymentIntents.js +30 -2
  15. package/dist/cjs/resources/refunds.js +54 -0
  16. package/dist/cjs/resources/transfers.js +49 -0
  17. package/dist/esm/browser/mount.js +52 -7
  18. package/dist/esm/browser/payerClient.js +58 -8
  19. package/dist/esm/checkout.js +5 -0
  20. package/dist/esm/core/client.js +25 -1
  21. package/dist/esm/core/config.js +10 -0
  22. package/dist/esm/core/errors.js +1 -1
  23. package/dist/esm/core/serviceToken.js +11 -2
  24. package/dist/esm/index.js +28 -1
  25. package/dist/esm/resources/billing.js +21 -0
  26. package/dist/esm/resources/checkoutSessions.js +2 -1
  27. package/dist/esm/resources/connectedAccounts.js +59 -0
  28. package/dist/esm/resources/merchants.js +46 -0
  29. package/dist/esm/resources/paymentIntents.js +30 -2
  30. package/dist/esm/resources/refunds.js +50 -0
  31. package/dist/esm/resources/transfers.js +45 -0
  32. package/dist/types/browser/mount.d.ts +23 -1
  33. package/dist/types/browser/payerClient.d.ts +56 -2
  34. package/dist/types/checkout.d.ts +2 -1
  35. package/dist/types/core/client.d.ts +1 -0
  36. package/dist/types/core/config.d.ts +4 -0
  37. package/dist/types/core/errors.d.ts +2 -2
  38. package/dist/types/index.d.ts +40 -1
  39. package/dist/types/resources/billing.d.ts +15 -0
  40. package/dist/types/resources/checkoutSessions.d.ts +15 -1
  41. package/dist/types/resources/connectedAccounts.d.ts +84 -0
  42. package/dist/types/resources/merchants.d.ts +113 -0
  43. package/dist/types/resources/paymentIntents.d.ts +47 -1
  44. package/dist/types/resources/refunds.d.ts +77 -0
  45. package/dist/types/resources/transfers.d.ts +75 -0
  46. package/package.json +2 -2
package/README.md ADDED
@@ -0,0 +1,14 @@
1
+
2
+ ### HTTP deadlines and existing operation keys
3
+
4
+ SDK0.2.1 accepts `requestTimeoutMs` on `new Peable(...)`. It bounds each token
5
+ mint and gateway HTTP attempt, including reading its body. Omitting it preserves
6
+ the previous timeout behavior. A consumer replacing a20s HTTP client should
7
+ pass `requestTimeoutMs: 20_000`. A401 may still trigger the existing single token
8
+ refresh/retry; timeouts and interrupted responses never retry a payment POST.
9
+ The remote outcome may be unknown: recover explicitly with the same durable key.
10
+
11
+ `paymentIntents.reject`, `refunds.create`, and `transfers.create` now accept an
12
+ optional second `{ idempotencyKey }` argument, forwarding the existing header.
13
+ Refund/transfer `externalRef` remains their durable business identity; the option
14
+ neither replaces it nor changes gateway financial semantics.
@@ -3,8 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.PeableCheckout = void 0;
6
+ exports.PeableCheckout = exports.PeableRailUnsupportedError = void 0;
7
7
  exports.deriveIntentId = deriveIntentId;
8
+ exports.payDeepLinkFor = payDeepLinkFor;
8
9
  exports.buildPayDeepLink = buildPayDeepLink;
9
10
  exports.isMobileUserAgent = isMobileUserAgent;
10
11
  exports.createEmitter = createEmitter;
@@ -60,6 +61,47 @@ function deriveIntentId(clientSecret, intentId) {
60
61
  /** The `peable://pay` deep-link scheme + host — mirrors `PAYMENT_REQUEST_PREFIX`
61
62
  * in the wallet's `packages/frontend/src/pay/payment-request.ts`. */
62
63
  const PAYMENT_REQUEST_PREFIX = 'peable://pay';
64
+ /**
65
+ * Thrown when a deep link is asked for on an intent that has no chain payment
66
+ * behind it.
67
+ *
68
+ * `address` and `network` became nullable when the card rail landed (gateway
69
+ * ADR 0001 D6), and the tempting fix was a `?? ''` at the call site. That would
70
+ * mint `peable://pay?...&address=&network=` — a link the wallet's parser
71
+ * accepts structurally and which sends a payer to an empty address. Failing
72
+ * here is the only safe direction.
73
+ */
74
+ class PeableRailUnsupportedError extends errors_1.PeableError {
75
+ constructor(rail) {
76
+ // Extends `PeableError`, not `Error`: the widget reports everything through
77
+ // its `error` event, whose payload type is `PeableError`, and a bare `Error`
78
+ // would arrive there only after being flattened into a `PeableApiError`
79
+ // that a caller cannot tell apart from a network failure.
80
+ // `invalid_request_error`, because that is what it is: the caller asked
81
+ // this widget for a surface the intent does not have. Nothing went wrong
82
+ // upstream and retrying cannot help.
83
+ super('invalid_request_error', `the '${rail}' rail has no wallet deep link; it is not a chain payment`);
84
+ this.name = 'PeableRailUnsupportedError';
85
+ Object.setPrototypeOf(this, PeableRailUnsupportedError.prototype);
86
+ }
87
+ }
88
+ exports.PeableRailUnsupportedError = PeableRailUnsupportedError;
89
+ /**
90
+ * Build the deep link for an intent, refusing any intent that is not a chain
91
+ * payment. The narrowing is what lets `buildPayDeepLink` keep non-null fields.
92
+ */
93
+ function payDeepLinkFor(intent) {
94
+ if (intent.rail !== 'faircoin' || intent.address === null || intent.network === null) {
95
+ throw new PeableRailUnsupportedError(intent.rail);
96
+ }
97
+ return buildPayDeepLink({
98
+ intentId: intent.id,
99
+ clientSecret: intent.clientSecret,
100
+ address: intent.address,
101
+ amount: intent.amount,
102
+ network: intent.network,
103
+ });
104
+ }
63
105
  /**
64
106
  * Build an `peable://pay?...` deep link. Param names AND percent-encoding
65
107
  * must match the wallet's parser byte-for-byte
@@ -251,13 +293,18 @@ function mount(target, opts) {
251
293
  button.addEventListener('click', () => {
252
294
  if (!latestIntent)
253
295
  return; // initial snapshot hasn't loaded yet
254
- const deepLink = buildPayDeepLink({
255
- intentId,
256
- clientSecret,
257
- address: latestIntent.address,
258
- amount: latestIntent.amount,
259
- network: latestIntent.network,
260
- });
296
+ // A card intent has no wallet to deep-link into. `error` rather than a
297
+ // throw into an event handler nobody is listening to: this widget's whole
298
+ // contract is that a host page learns what happened through `on(...)`, and
299
+ // an uncaught exception here would leave the button silently dead.
300
+ let deepLink;
301
+ try {
302
+ deepLink = payDeepLinkFor(latestIntent);
303
+ }
304
+ catch (err) {
305
+ emitter.emit('error', toPeableError(err));
306
+ return;
307
+ }
261
308
  if (isMobileUserAgent(navigator.userAgent)) {
262
309
  window.location.href = deepLink;
263
310
  }
@@ -4,7 +4,7 @@ exports.createPeableCheckout = createPeableCheckout;
4
4
  // The payer-side REST+socket client core — the "./checkout" entry's payer
5
5
  // capability (Fase 2 SDK plan, Task 5). Mirrors the wallet's
6
6
  // `gateway-client.ts`/`gateway-socket.ts` but standalone: no
7
- // `@oxyhq/services`/`@oxyhq/core` — this is a third-party browser bundle, so
7
+ // `@oxy.so/services`/`@oxy.so/core` — this is a third-party browser bundle, so
8
8
  // it talks to the Gateway over bare `fetch` + `socket.io-client`.
9
9
  //
10
10
  // Holds NO service token and NO private key — only a public `client_secret`,
@@ -65,6 +65,25 @@ function createPeableCheckout(opts = {}) {
65
65
  throw (0, errors_1.errorFromResponse)(response.status, body);
66
66
  return body;
67
67
  }
68
+ async function getClientAction(id, clientSecret) {
69
+ let response;
70
+ try {
71
+ response = await fetch(`${baseUrl}/v1/payment_intents/${encodeURIComponent(id)}/client_action`, {
72
+ method: 'POST',
73
+ // The HEADER, never a query param, for the same reason the snapshot
74
+ // read uses one: a capability token in a URL lands in every access
75
+ // log between here and the gateway.
76
+ headers: { 'X-Peable-Client-Secret': clientSecret },
77
+ });
78
+ }
79
+ catch (cause) {
80
+ throw new errors_1.PeableApiError(`Failed to reach the Peable Gateway at ${baseUrl}: ${cause instanceof Error ? cause.message : String(cause)}`);
81
+ }
82
+ const body = await readJsonBody(response);
83
+ if (!response.ok)
84
+ throw (0, errors_1.errorFromResponse)(response.status, body);
85
+ return body;
86
+ }
68
87
  /**
69
88
  * Opens its OWN socket per call (never a shared/module-level connection —
70
89
  * this SDK has no session to key a singleton off) and connects with NO
@@ -74,7 +93,7 @@ function createPeableCheckout(opts = {}) {
74
93
  * the real authorization check — it requires this intent's `client_secret`
75
94
  * — so nothing here needs an Oxy session.
76
95
  */
77
- async function subscribe(id, clientSecret, onUpdate) {
96
+ async function subscribe(id, clientSecret, onUpdate, onConnectionChange) {
78
97
  const socket = (0, socket_io_client_1.io)(baseUrl, {
79
98
  transports: ['websocket'],
80
99
  });
@@ -105,6 +124,22 @@ function createPeableCheckout(opts = {}) {
105
124
  }
106
125
  };
107
126
  socket.on('intent.updated', listener);
127
+ // A drop AFTER this point used to be invisible to the caller. `subscribe`
128
+ // had already resolved, so a `.catch` on it could not fire again, and the
129
+ // reconnect below restores DELIVERY but replays nothing — socket.io has no
130
+ // buffer for frames emitted while the socket was gone. A payment that
131
+ // settled during a thirty-second blip therefore never reached the page, and
132
+ // the payer watched a snapshot that would never update again.
133
+ //
134
+ // Reported rather than handled here: the SDK cannot know what a host wants
135
+ // to do about it. The hosted checkout turns REST polling on and re-reads
136
+ // the intent before turning it off again.
137
+ let closedByUs = false;
138
+ const onDisconnect = () => {
139
+ if (!closedByUs)
140
+ onConnectionChange?.('lost');
141
+ };
142
+ socket.on('disconnect', onDisconnect);
108
143
  // socket.io-client's default reconnection restores the TRANSPORT, but the
109
144
  // server spins up a fresh Socket with no room membership — so every
110
145
  // reconnect (mobile network blip, backgrounded tab, LB cycle) must re-run
@@ -115,21 +150,36 @@ function createPeableCheckout(opts = {}) {
115
150
  // the user reopening the screen. `reconnect` is a Manager-level event
116
151
  // (`socket.io`, not `socket` itself) in socket.io-client.
117
152
  const onReconnect = () => {
118
- void socket.emitWithAck('subscribe', { intentId: id, clientSecret }).catch(() => {
119
- // Fire-and-forget: `onUpdate` has no error channel to surface a
120
- // failed resubscribe (e.g. the intent expired between drop and
121
- // reconnect) — socket.io keeps retrying the transport regardless, so
122
- // there is nothing actionable to do with this rejection here.
153
+ void socket
154
+ .emitWithAck('subscribe', { intentId: id, clientSecret })
155
+ .then((ack) => {
156
+ // `'live'` only once the ROOM is rejoined, never on the transport
157
+ // alone: a socket that is connected and in no room delivers nothing,
158
+ // and telling the host it is live there would have them stand their
159
+ // fallback down into permanent silence.
160
+ if (ack.ok)
161
+ onConnectionChange?.('live');
162
+ })
163
+ .catch(() => {
164
+ // The subscription is gone for good (the intent expired between drop
165
+ // and reconnect, say). Staying `lost` is the honest report: socket.io
166
+ // keeps retrying the transport, but nothing will arrive on it.
123
167
  });
124
168
  };
125
169
  socket.io.on('reconnect', onReconnect);
126
170
  return {
127
171
  unsubscribe() {
172
+ // Set BEFORE `disconnect()`: a deliberate close fires `disconnect` like
173
+ // any other, and reporting `'lost'` there would have a host spin up a
174
+ // fallback for a subscription it just tore down — on an unmounting
175
+ // component, at that.
176
+ closedByUs = true;
128
177
  socket.off('intent.updated', listener);
178
+ socket.off('disconnect', onDisconnect);
129
179
  socket.io.off('reconnect', onReconnect);
130
180
  socket.disconnect();
131
181
  },
132
182
  };
133
183
  }
134
- return { getPaymentIntent, subscribe, submitTx };
184
+ return { getPaymentIntent, subscribe, submitTx, getClientAction };
135
185
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PeableCheckout = exports.createPeableCheckout = void 0;
3
+ exports.PeableRailUnsupportedError = exports.buildPayDeepLink = exports.payDeepLinkFor = exports.PeableCheckout = exports.createPeableCheckout = void 0;
4
4
  // Browser entry (`@peable.to/sdk/checkout`) — the anonymous payer-side client
5
5
  // core (Task 5) plus the embeddable pay-button widget (Task 6). Deliberately
6
6
  // imports nothing from `./core/*` beyond `core/errors.ts` (a pure
@@ -15,3 +15,11 @@ var payerClient_1 = require("./browser/payerClient");
15
15
  Object.defineProperty(exports, "createPeableCheckout", { enumerable: true, get: function () { return payerClient_1.createPeableCheckout; } });
16
16
  var mount_1 = require("./browser/mount");
17
17
  Object.defineProperty(exports, "PeableCheckout", { enumerable: true, get: function () { return mount_1.PeableCheckout; } });
18
+ // The rail-aware deep-link builder and its refusal. Exported because
19
+ // `packages/checkout` renders its own pay button over the payer client core
20
+ // and must make the same decision this widget makes — one definition of "which
21
+ // intents have a wallet deep link", not two.
22
+ var mount_2 = require("./browser/mount");
23
+ Object.defineProperty(exports, "payDeepLinkFor", { enumerable: true, get: function () { return mount_2.payDeepLinkFor; } });
24
+ Object.defineProperty(exports, "buildPayDeepLink", { enumerable: true, get: function () { return mount_2.buildPayDeepLink; } });
25
+ Object.defineProperty(exports, "PeableRailUnsupportedError", { enumerable: true, get: function () { return mount_2.PeableRailUnsupportedError; } });
@@ -5,6 +5,7 @@
5
5
  // maps every non-2xx response via `errors.ts`.
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.createRestClient = createRestClient;
8
+ const config_1 = require("./config");
8
9
  const errors_1 = require("./errors");
9
10
  const HTTP_UNAUTHORIZED = 401;
10
11
  function buildUrl(baseURL, path, query) {
@@ -32,6 +33,7 @@ async function readJsonBody(response) {
32
33
  }
33
34
  function createRestClient(config, tokenProvider, deps = {}) {
34
35
  const fetchImpl = deps.fetch ?? fetch;
36
+ const timeout = (0, config_1.resolveRequestTimeout)(config.requestTimeoutMs);
35
37
  async function performRequest(method, path, opts, allowRetryOn401) {
36
38
  const token = await tokenProvider.getToken();
37
39
  const headers = { Authorization: `Bearer ${token}` };
@@ -46,6 +48,7 @@ function createRestClient(config, tokenProvider, deps = {}) {
46
48
  response = await fetchImpl(buildUrl(config.baseURL, path, opts.query), {
47
49
  method,
48
50
  headers,
51
+ ...(timeout === undefined ? {} : { signal: AbortSignal.timeout(timeout) }),
49
52
  body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
50
53
  });
51
54
  }
@@ -63,10 +66,31 @@ function createRestClient(config, tokenProvider, deps = {}) {
63
66
  // The Gateway echoes a date-based `Peable-Version` response header
64
67
  // (Stripe parity). No SDK behavior depends on it yet, so it is
65
68
  // deliberately not read here — nothing to do with it today.
66
- const body = await readJsonBody(response);
69
+ let body;
70
+ try {
71
+ body = await readJsonBody(response);
72
+ }
73
+ catch {
74
+ // Headers can arrive before the connection fails during body consumption.
75
+ // Preserve the known status, but do not infer whether a mutation committed
76
+ // or retry it automatically. Recovery keeps the caller's original key.
77
+ throw new errors_1.PeableApiError('The Peable Gateway response body could not be read; remote outcome is unknown', {
78
+ statusCode: response.status,
79
+ code: 'invalid_response',
80
+ });
81
+ }
67
82
  if (!response.ok) {
68
83
  throw (0, errors_1.errorFromResponse)(response.status, body);
69
84
  }
85
+ // Only explicit No Content can satisfy a void response. A missing or
86
+ // malformed JSON success is indeterminate, not a typed resource. Do not
87
+ // retry a mutation automatically: callers retain the original intent key.
88
+ if (body === undefined && response.status !== 204) {
89
+ throw new errors_1.PeableApiError('The Peable Gateway returned an invalid JSON response', {
90
+ statusCode: response.status,
91
+ code: 'invalid_response',
92
+ });
93
+ }
70
94
  return body;
71
95
  }
72
96
  return {
@@ -6,6 +6,7 @@
6
6
  // "Architecture / auth" section.
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.DEFAULT_OXY_API_URL = exports.DEFAULT_GATEWAY_BASE_URL = void 0;
9
+ exports.resolveRequestTimeout = resolveRequestTimeout;
9
10
  exports.resolveConfig = resolveConfig;
10
11
  /** Default Gateway host (`api.peable.to`) — the SDK never sends a `livemode`
11
12
  * flag; the credential's `environment` rides in the minted service JWT and
@@ -13,6 +14,15 @@ exports.resolveConfig = resolveConfig;
13
14
  exports.DEFAULT_GATEWAY_BASE_URL = 'https://api.peable.to';
14
15
  /** Default oxy-api host used to mint the short-lived service token. */
15
16
  exports.DEFAULT_OXY_API_URL = 'https://api.oxy.so';
17
+ function resolveRequestTimeout(value) {
18
+ if (value === undefined)
19
+ return undefined;
20
+ const timeout = value;
21
+ if (!Number.isSafeInteger(timeout) || timeout < 1 || timeout > 2147483647) {
22
+ throw new Error('Peable: requestTimeoutMs must be a positive32-bit integer');
23
+ }
24
+ return timeout;
25
+ }
16
26
  function stripTrailingSlash(url) {
17
27
  return url.endsWith('/') ? url.slice(0, -1) : url;
18
28
  }
@@ -30,6 +40,7 @@ function resolveConfig(config) {
30
40
  throw new Error('Peable: `secret` is required');
31
41
  }
32
42
  return {
43
+ requestTimeoutMs: resolveRequestTimeout(config.requestTimeoutMs),
33
44
  publicKey: config.publicKey,
34
45
  secret: config.secret,
35
46
  baseURL: stripTrailingSlash(config.baseURL ?? exports.DEFAULT_GATEWAY_BASE_URL),
@@ -78,7 +78,7 @@ function isFlatGatewayErrorBody(body) {
78
78
  * request was rejected:
79
79
  * - The Gateway's own route handlers use the Stripe-style nested envelope
80
80
  * `{ error: { type, message } }` (`lib/http.ts`'s `sendError`).
81
- * - `@oxyhq/core`'s Express auth middleware (`oxyClient.serviceAuth()` /
81
+ * - `@oxy.so/core`'s Express auth middleware (`oxy.middleware.service()` /
82
82
  * `requireScope()` — rejecting a missing/expired/insufficiently-scoped
83
83
  * service token BEFORE a route handler ever runs) and oxy-api's own
84
84
  * `ApiError.toJSON()` use a FLAT envelope: `{ error: <CODE_STRING>,
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  // Mints a short-lived Oxy service token from an `ApplicationCredential`
3
3
  // (`{publicKey, secret}`) by presenting it to oxy-api's `POST
4
- // /auth/service-token` — the exact mechanism `oxyClient.serviceAuth()`
4
+ // /auth/service-token` — the exact mechanism `oxy.middleware.service()`
5
5
  // validates at the Gateway. Caches the token until near expiry and re-mints
6
6
  // reactively when `RestClient` sees a 401 (see `client.ts`).
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -43,6 +43,7 @@ function createServiceTokenProvider(cfg, deps = {}) {
43
43
  try {
44
44
  response = await fetchImpl(`${resolved.oxyApiUrl}${SERVICE_TOKEN_PATH}`, {
45
45
  method: 'POST',
46
+ ...(resolved.requestTimeoutMs === undefined ? {} : { signal: AbortSignal.timeout(resolved.requestTimeoutMs) }),
46
47
  headers: { 'Content-Type': 'application/json' },
47
48
  body: JSON.stringify({ apiKey: resolved.publicKey, apiSecret: resolved.secret }),
48
49
  });
@@ -50,7 +51,15 @@ function createServiceTokenProvider(cfg, deps = {}) {
50
51
  catch (cause) {
51
52
  throw new errors_1.PeableApiError(`Failed to reach ${resolved.oxyApiUrl} to mint an Peable service token: ${cause instanceof Error ? cause.message : String(cause)}`);
52
53
  }
53
- const body = await readJsonBody(response);
54
+ let body;
55
+ try {
56
+ body = await readJsonBody(response);
57
+ }
58
+ catch {
59
+ throw new errors_1.PeableApiError('The Oxy service-token response body could not be read', {
60
+ statusCode: response.status, code: 'invalid_response',
61
+ });
62
+ }
54
63
  if (!response.ok) {
55
64
  throw (0, errors_1.errorFromResponse)(response.status, body);
56
65
  }
package/dist/cjs/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WEBHOOK_SIGNATURE_HEADER = exports.PeableSignatureVerificationError = exports.PeableApiError = exports.PeablePermissionError = exports.PeableInvalidRequestError = exports.PeableAuthenticationError = exports.PeableError = exports.DEFAULT_OXY_API_URL = exports.DEFAULT_GATEWAY_BASE_URL = exports.Peable = void 0;
4
+ const billing_1 = require("./resources/billing");
4
5
  // Server entry (`@peable.to/sdk`) — the merchant-authed SDK. Configured with a
5
6
  // confidential `ApplicationCredential` (`{publicKey, secret}`); mints and
6
7
  // caches an Oxy service token, and exposes Stripe-ergonomics resource
@@ -15,15 +16,41 @@ const paymentIntents_1 = require("./resources/paymentIntents");
15
16
  const paymentLinks_1 = require("./resources/paymentLinks");
16
17
  const checkoutSessions_1 = require("./resources/checkoutSessions");
17
18
  const webhooks_1 = require("./resources/webhooks");
19
+ const connectedAccounts_1 = require("./resources/connectedAccounts");
20
+ const merchants_1 = require("./resources/merchants");
21
+ const refunds_1 = require("./resources/refunds");
22
+ const transfers_1 = require("./resources/transfers");
23
+ /**
24
+ * The merchant-authed client.
25
+ *
26
+ * ## Why the namespaces below are not optional extras
27
+ *
28
+ * This class published four: intents, links, checkout and webhooks. The gateway
29
+ * has always served merchants, connected accounts, refunds, transfers and
30
+ * disputes as well — so every integrator reaching those wrote their own HTTP
31
+ * client and their own partial types for responses this package already knew.
32
+ * Mercaria did exactly that. Two descriptions of one wire format, in two
33
+ * repositories, with nothing comparing them: a field renamed in the gateway is
34
+ * a runtime failure over there, found by a settlement that does not happen.
35
+ *
36
+ * An SDK that covers less than the API it wraps does not reduce surface, it
37
+ * relocates it.
38
+ */
18
39
  class Peable {
19
40
  constructor(config) {
20
41
  const resolved = (0, config_1.resolveConfig)(config);
21
42
  const tokenProvider = (0, serviceToken_1.createServiceTokenProvider)(config);
22
- const client = (0, client_1.createRestClient)({ baseURL: resolved.baseURL }, tokenProvider);
43
+ const client = (0, client_1.createRestClient)({ baseURL: resolved.baseURL, requestTimeoutMs: resolved.requestTimeoutMs }, tokenProvider);
44
+ this.billing = new billing_1.BillingResource(client);
23
45
  this.paymentIntents = new paymentIntents_1.PaymentIntentsResource(client);
24
46
  this.paymentLinks = new paymentLinks_1.PaymentLinksResource(client);
25
47
  this.checkout = new checkoutSessions_1.CheckoutResource(client);
26
48
  this.webhooks = new webhooks_1.WebhooksResource();
49
+ this.merchants = new merchants_1.MerchantsResource(client);
50
+ this.connectedAccounts = new connectedAccounts_1.ConnectedAccountsResource(client);
51
+ this.refunds = new refunds_1.RefundsResource(client);
52
+ this.transfers = new transfers_1.TransfersResource(client);
53
+ this.disputes = new merchants_1.DisputesResource(client);
27
54
  }
28
55
  }
29
56
  exports.Peable = Peable;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BillingResource = void 0;
4
+ /** Recurring billing is separate from one-off checkout and marketplace transfers. */
5
+ class BillingResource {
6
+ constructor(client) {
7
+ this.client = client;
8
+ }
9
+ ensureCustomer(params, options) {
10
+ return this.client.request('POST', '/v1/billing/customers', { body: params, idempotencyKey: options.idempotencyKey });
11
+ }
12
+ createCheckoutSession(params, options) {
13
+ return this.client.request('POST', '/v1/billing/checkout_sessions', { body: params, idempotencyKey: options.idempotencyKey });
14
+ }
15
+ createPortalSession(params, options) {
16
+ return this.client.request('POST', '/v1/billing/portal_sessions', { body: params, idempotencyKey: options.idempotencyKey });
17
+ }
18
+ retrieveSubscription(ref) {
19
+ return this.client.request('GET', `/v1/billing/subscriptions/${encodeURIComponent(ref)}`);
20
+ }
21
+ cancelAtPeriodEnd(ref, options) {
22
+ return this.client.request('POST', `/v1/billing/subscriptions/${encodeURIComponent(ref)}/cancel_at_period_end`, { body: {}, idempotencyKey: options.idempotencyKey });
23
+ }
24
+ }
25
+ exports.BillingResource = BillingResource;
@@ -5,9 +5,10 @@ class CheckoutSessionsResource {
5
5
  constructor(client) {
6
6
  this.client = client;
7
7
  }
8
- create(params) {
8
+ create(params, options = {}) {
9
9
  return this.client.request('POST', '/v1/checkout_sessions', {
10
10
  body: params,
11
+ ...(options.idempotencyKey === undefined ? {} : { idempotencyKey: options.idempotencyKey }),
11
12
  });
12
13
  }
13
14
  retrieve(id) {
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConnectedAccountsResource = void 0;
4
+ class ConnectedAccountsResource {
5
+ constructor(client) {
6
+ this.client = client;
7
+ }
8
+ /**
9
+ * Open the account for a seller, or return the one they already have.
10
+ *
11
+ * Converges rather than erroring, because the underlying object CANNOT BE
12
+ * DELETED: opening two for one seller leaves them with two forever, one of
13
+ * which nobody uses and which keeps generating requirement emails.
14
+ */
15
+ create(params) {
16
+ return this.client.request('POST', '/v1/connected_accounts', {
17
+ body: params,
18
+ });
19
+ }
20
+ list(params = {}) {
21
+ return this.client.request('GET', '/v1/connected_accounts', {
22
+ query: { limit: params.limit, starting_after: params.starting_after },
23
+ });
24
+ }
25
+ retrieve(accountId) {
26
+ return this.client.request('GET', `/v1/connected_accounts/${encodeURIComponent(accountId)}`);
27
+ }
28
+ /**
29
+ * By the merchant's OWN reference.
30
+ *
31
+ * The recovery read: a merchant who lost the `ca_…` — a create whose response
32
+ * never arrived — still has the reference they chose, and this is what makes
33
+ * finding the account possible without a list scan.
34
+ */
35
+ retrieveByRef(externalRef) {
36
+ return this.client.request('GET', `/v1/connected_accounts/by_ref/${encodeURIComponent(externalRef)}`);
37
+ }
38
+ /**
39
+ * Re-read this account from the provider now.
40
+ *
41
+ * Rarely needed: readiness arrives on its own through the gateway's event
42
+ * drain and a backstop sweep. This is for a seller staring at a dashboard who
43
+ * has just finished onboarding.
44
+ */
45
+ refresh(accountId) {
46
+ return this.client.request('POST', `/v1/connected_accounts/${encodeURIComponent(accountId)}/refresh`);
47
+ }
48
+ /**
49
+ * A hosted onboarding link.
50
+ *
51
+ * SINGLE-USE and short-lived at the provider, so it is minted on demand and
52
+ * must never be stored, emailed or put in a chat message — a stored link is
53
+ * one that has already expired by the time anyone follows it, and the failure
54
+ * looks like the seller's fault.
55
+ *
56
+ * A visit to `returnUrl` proves NOTHING about verification. Read the account
57
+ * back, or wait for the readiness event.
58
+ */
59
+ createAccountLink(accountId, params) {
60
+ return this.client.request('POST', `/v1/connected_accounts/${encodeURIComponent(accountId)}/account_links`, { body: params });
61
+ }
62
+ }
63
+ exports.ConnectedAccountsResource = ConnectedAccountsResource;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DisputesResource = exports.MerchantsResource = void 0;
4
+ class MerchantsResource {
5
+ constructor(client) {
6
+ this.client = client;
7
+ }
8
+ register(params = {}) {
9
+ return this.client.request('POST', '/v1/merchants', { body: params });
10
+ }
11
+ retrieve() {
12
+ return this.client.request('GET', '/v1/merchants/me');
13
+ }
14
+ update(params) {
15
+ return this.client.request('PATCH', '/v1/merchants/me', { body: params });
16
+ }
17
+ }
18
+ exports.MerchantsResource = MerchantsResource;
19
+ /**
20
+ * Disputes: read them, and answer one.
21
+ *
22
+ * A dispute is initiated by the card NETWORK, not by you, so there is nothing
23
+ * to create — the only write is a response. This surface was read-only for a
24
+ * while, because the gateway had no way to deliver evidence and a route that
25
+ * accepted it and did nothing would have been worse than none: you would have
26
+ * believed you had responded.
27
+ */
28
+ class DisputesResource {
29
+ constructor(client) {
30
+ this.client = client;
31
+ }
32
+ listForPaymentIntent(paymentIntentId) {
33
+ return this.client.request('GET', `/v1/payment_intents/${encodeURIComponent(paymentIntentId)}/disputes`);
34
+ }
35
+ /**
36
+ * Answer a dispute. **One shot.**
37
+ *
38
+ * Submitting is one-way at the card network, so this cannot be revised and a
39
+ * second call does not send a second response — it answers 200 with the
40
+ * dispute as it stands. Send everything you have the first time.
41
+ *
42
+ * Refused (409) when the dispute is already answered, already decided, or
43
+ * past `evidenceDueAt`. That last one is checked by the gateway rather than
44
+ * by the acquirer, so you learn it was the clock and not your request — a
45
+ * late response is the one failure here that no retry fixes.
46
+ */
47
+ submitEvidence(disputeId, evidence) {
48
+ return this.client.request('POST', `/v1/disputes/${encodeURIComponent(disputeId)}/evidence`, { body: evidence });
49
+ }
50
+ }
51
+ exports.DisputesResource = DisputesResource;
@@ -23,8 +23,36 @@ class PaymentIntentsResource {
23
23
  },
24
24
  });
25
25
  }
26
- reject(id) {
27
- return this.client.request('POST', `/v1/payment_intents/${encodeURIComponent(id)}/reject`);
26
+ /**
27
+ * Reject a payment the payer has not completed.
28
+ *
29
+ * The gateway CANCELS it at the provider before it announces anything, so
30
+ * this can lose a race and say so: a 409 means the payer confirmed first and
31
+ * the payment is settled, and a 502 means the provider could not be reached
32
+ * and nothing was decided. Neither is a failure of the request — both are
33
+ * facts about a payment that is still live.
34
+ */
35
+ reject(id, options = {}) {
36
+ return this.client.request('POST', `/v1/payment_intents/${encodeURIComponent(id)}/reject`, { idempotencyKey: options.idempotencyKey });
37
+ }
38
+ /**
39
+ * What the PAYER's client has to do next — the resume operation.
40
+ *
41
+ * The payment-intent shape carries no confirmation credential, because a
42
+ * credential on that shape would be handed out by every list and every
43
+ * re-read. This is the bounded way to get one: a single payment, named
44
+ * explicitly, for a caller who has proven they may pay it.
45
+ *
46
+ * Call it to resume an unpaid checkout rather than creating a second payment
47
+ * — which is what a buyer returning the next day would otherwise produce, and
48
+ * an idempotency key that has aged out at the provider will not prevent.
49
+ *
50
+ * @throws {PeableInvalidRequestError} (409) when the payment can no longer be
51
+ * paid, so a surface handed this can never render a card form over a
52
+ * settled payment.
53
+ */
54
+ clientAction(id) {
55
+ return this.client.request('POST', `/v1/payment_intents/${encodeURIComponent(id)}/client_action`);
28
56
  }
29
57
  }
30
58
  exports.PaymentIntentsResource = PaymentIntentsResource;