framepayments 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -83,6 +83,42 @@ await frame.configuration.getEvervaultConfiguration({ usePublishableKey: true })
83
83
 
84
84
  The SDK defaults match the native Frame iOS / Android SDKs exactly — endpoints that those SDKs invoke with `usePublishableKey: true` are publishable-by-default here as well (`paymentMethods.createApplePayPaymentMethod`, `paymentMethods.createGooglePayPaymentMethod`, `deviceAttestation.*`, `wallet.getGooglePayConfiguration`). Everything else defaults to secret.
85
85
 
86
+ ## 🪪 Onboarding sessions & per-object client secrets
87
+
88
+ Publishable-key-only mobile clients can authenticate **onboarding sessions** and **per-object client secrets**, matching the three-tier auth model of the native Frame iOS / Android SDKs. The bearer for any request is resolved in this precedence order:
89
+
90
+ 1. a per-request `authToken` (an object `client_secret`, e.g. `ci_..._secret_...`), else
91
+ 2. the active onboarding-session token (e.g. `onb_sess_...`), else
92
+ 3. the publishable key (when `usePublishableKey: true`) or the secret key.
93
+
94
+ ### Onboarding sessions
95
+
96
+ Begin a session to route **every** request through the session token, overriding the configured keys (and ignoring `usePublishableKey`) while it is active. Mirrors iOS `beginOnboardingSession` / `endOnboardingSession`.
97
+
98
+ ```ts
99
+ const frame = new FrameSDK({ publishableKey: 'pk_...' });
100
+
101
+ frame.setOnboardingSession('onb_sess_...');
102
+ // Every call now sends `Authorization: Bearer onb_sess_...`.
103
+ await frame.onboardingSessions.create({ /* ... */ });
104
+ await frame.termsOfService.createToken();
105
+
106
+ // End the session, reverting to the publishable key.
107
+ frame.clearOnboardingSession();
108
+ ```
109
+
110
+ `clearOnboardingSession(token?)` is **safe-clear**: passing a `token` only clears the session when it matches the active one (so a stale unmount can't wipe a newer session), and returns `true` if it cleared / `false` if the guard prevented it. Omit the argument to force-clear.
111
+
112
+ ### Per-object client secrets
113
+
114
+ For charge-intent confirm/show and 3-D Secure, pass the object's `client_secret` as a per-request `authToken`. It wins over both an active session and the configured keys:
115
+
116
+ ```ts
117
+ await frame.chargeIntents.confirm('ci_123', { authToken: 'ci_123_secret_...' });
118
+ await frame.chargeIntents.get('ci_123', { authToken: 'ci_123_secret_...' });
119
+ await frame.threeDS.get('tds_123', { authToken: 'tds_123_secret_...' });
120
+ ```
121
+
86
122
  ## 📚 Available APIs
87
123
 
88
124
  | Namespace | Purpose |
package/dist/index.cjs CHANGED
@@ -35,12 +35,14 @@ var FrameAPIError = class extends Error {
35
35
  };
36
36
  //#endregion
37
37
  //#region src/client.ts
38
- const PUBLISHABLE_FLAG_KEY = "X-Frame-Use-Publishable-Key";
38
+ const FRAME_USE_PUBLISHABLE_KEY = "frameUsePublishableKey";
39
+ const FRAME_AUTH_TOKEN = "frameAuthToken";
40
+ const createOnboardingSessionStore = () => ({ token: null });
39
41
  function safeRawFromAxiosError(err) {
40
42
  if (!err || typeof err !== "object") return err;
41
43
  const cleanedConfig = err.config ? (() => {
42
- const { headers, ...restConfig } = err.config;
43
- const cleanedHeaders = headers && typeof headers === "object" ? Object.fromEntries(Object.entries(headers).filter(([k]) => k.toLowerCase() !== "authorization" && k !== PUBLISHABLE_FLAG_KEY)) : headers;
44
+ const { headers, [FRAME_AUTH_TOKEN]: _authToken, ...restConfig } = err.config;
45
+ const cleanedHeaders = headers && typeof headers === "object" ? Object.fromEntries(Object.entries(headers).filter(([k]) => k.toLowerCase() !== "authorization")) : headers;
44
46
  return {
45
47
  ...restConfig,
46
48
  headers: cleanedHeaders
@@ -53,7 +55,7 @@ function safeRawFromAxiosError(err) {
53
55
  config: cleanedConfig
54
56
  };
55
57
  }
56
- const createApiClient = (config) => {
58
+ const createApiClient = (config, sessionStore = createOnboardingSessionStore()) => {
57
59
  const { apiKey, publishableKey, defaultHeaders } = config;
58
60
  if (!apiKey && !publishableKey) throw new Error("FrameSDK config requires at least one of: apiKey, publishableKey. Mobile clients should ship publishableKey only; server code should use apiKey.");
59
61
  const baseURL = config.baseURL ?? "https://api.framepayments.com";
@@ -63,9 +65,11 @@ const createApiClient = (config) => {
63
65
  });
64
66
  client.interceptors.request.use((requestConfig) => {
65
67
  const headers = requestConfig.headers;
66
- const wantsPublishable = headers instanceof axios.AxiosHeaders ? headers.has(PUBLISHABLE_FLAG_KEY) : Boolean(headers?.[PUBLISHABLE_FLAG_KEY]);
67
- if (headers instanceof axios.AxiosHeaders) headers.delete(PUBLISHABLE_FLAG_KEY);
68
- else if (headers && PUBLISHABLE_FLAG_KEY in headers) delete headers[PUBLISHABLE_FLAG_KEY];
68
+ const cfg = requestConfig;
69
+ const wantsPublishable = cfg.frameUsePublishableKey === true;
70
+ const rawAuthToken = cfg.frameAuthToken;
71
+ if (rawAuthToken === "") throw new FrameAPIError("Frame authToken was provided but empty. Pass a non-empty client_secret (e.g. ci_..._secret_...), or omit authToken to use the configured key.", "invalid_auth_token", 0, null);
72
+ const perRequestAuthToken = typeof rawAuthToken === "string" && rawAuthToken.length > 0 ? rawAuthToken : void 0;
69
73
  if (defaultHeaders) for (const [name, value] of Object.entries(defaultHeaders)) {
70
74
  if (value == null) continue;
71
75
  if (headers instanceof axios.AxiosHeaders) {
@@ -75,10 +79,17 @@ const createApiClient = (config) => {
75
79
  if (h[name] === void 0) h[name] = value;
76
80
  }
77
81
  }
78
- const keyToUse = wantsPublishable ? publishableKey : apiKey;
79
- if (!keyToUse) throw new FrameAPIError(wantsPublishable ? "Frame publishable key is not configured. Pass { publishableKey } to new FrameSDK(...) before calling endpoints with { usePublishableKey: true }." : "Frame API key is not configured. Pass { apiKey } to new FrameSDK(...) before calling secret-keyed endpoints.", wantsPublishable ? "missing_publishable_key" : "missing_api_key", 0, null);
80
- if (headers instanceof axios.AxiosHeaders) headers.set("Authorization", `Bearer ${keyToUse}`);
81
- else if (headers) headers["Authorization"] = `Bearer ${keyToUse}`;
82
+ const sessionToken = sessionStore.token;
83
+ let bearer;
84
+ if (perRequestAuthToken) bearer = perRequestAuthToken;
85
+ else if (sessionToken) bearer = sessionToken;
86
+ else {
87
+ const keyToUse = wantsPublishable ? publishableKey : apiKey;
88
+ if (!keyToUse) throw new FrameAPIError(wantsPublishable ? "Frame publishable key is not configured. Pass { publishableKey } to new FrameSDK(...) before calling endpoints with { usePublishableKey: true }." : "Frame API key is not configured. Pass { apiKey } to new FrameSDK(...) before calling secret-keyed endpoints.", wantsPublishable ? "missing_publishable_key" : "missing_api_key", 0, null);
89
+ bearer = keyToUse;
90
+ }
91
+ if (headers instanceof axios.AxiosHeaders) headers.set("Authorization", `Bearer ${bearer}`);
92
+ else if (headers) headers["Authorization"] = `Bearer ${bearer}`;
82
93
  return requestConfig;
83
94
  });
84
95
  client.interceptors.response.use((response) => response, (error) => {
@@ -92,33 +103,18 @@ const createApiClient = (config) => {
92
103
  });
93
104
  return client;
94
105
  };
106
+ function buildRequestConfig(publishableDefault, opts) {
107
+ const { usePublishableKey = publishableDefault, authToken, ...rest } = opts ?? {};
108
+ const cfg = { ...rest };
109
+ if (usePublishableKey) cfg[FRAME_USE_PUBLISHABLE_KEY] = true;
110
+ if (authToken !== void 0) cfg[FRAME_AUTH_TOKEN] = authToken;
111
+ return cfg;
112
+ }
95
113
  function withPublishableKey(opts) {
96
- const { usePublishableKey = true, headers, ...rest } = opts ?? {};
97
- if (!usePublishableKey) return headers ? {
98
- ...rest,
99
- headers
100
- } : { ...rest };
101
- return {
102
- ...rest,
103
- headers: {
104
- ...headers ?? {},
105
- [PUBLISHABLE_FLAG_KEY]: "1"
106
- }
107
- };
114
+ return buildRequestConfig(true, opts);
108
115
  }
109
116
  function maybePublishableKey(opts) {
110
- const { usePublishableKey = false, headers, ...rest } = opts ?? {};
111
- if (!usePublishableKey) return headers ? {
112
- ...rest,
113
- headers
114
- } : { ...rest };
115
- return {
116
- ...rest,
117
- headers: {
118
- ...headers ?? {},
119
- [PUBLISHABLE_FLAG_KEY]: "1"
120
- }
121
- };
117
+ return buildRequestConfig(false, opts);
122
118
  }
123
119
  //#endregion
124
120
  //#region src/utils/paginator.ts
@@ -186,11 +182,11 @@ var CapabilitiesAPI = class {
186
182
  constructor(client) {
187
183
  this.client = client;
188
184
  }
189
- async list(accountId) {
190
- return (await this.client.get(`/v1/accounts/${accountId}/capabilities`)).data;
185
+ async list(accountId, opts) {
186
+ return (await this.client.get(`/v1/accounts/${accountId}/capabilities`, maybePublishableKey(opts))).data;
191
187
  }
192
- async request(accountId, params) {
193
- return (await this.client.post(`/v1/accounts/${accountId}/capabilities`, params)).data;
188
+ async request(accountId, params, opts) {
189
+ return (await this.client.post(`/v1/accounts/${accountId}/capabilities`, params, maybePublishableKey(opts))).data;
194
190
  }
195
191
  async get(accountId, name) {
196
192
  return (await this.client.get(`/v1/accounts/${accountId}/capabilities/${name}`)).data;
@@ -205,11 +201,14 @@ var OnboardingSessionsAPI = class {
205
201
  constructor(client) {
206
202
  this.client = client;
207
203
  }
208
- async create(params) {
209
- return (await this.client.post("/v1/onboarding_sessions", params)).data;
204
+ async create(params, opts) {
205
+ return (await this.client.post("/v1/onboarding_sessions", params, maybePublishableKey(opts))).data;
210
206
  }
211
- async getByAccount(accountId) {
212
- return (await this.client.get("/v1/onboarding_sessions", { params: { account_id: accountId } })).data;
207
+ async getByAccount(accountId, opts) {
208
+ return (await this.client.get("/v1/onboarding_sessions", {
209
+ ...maybePublishableKey(opts),
210
+ params: { account_id: accountId }
211
+ })).data;
213
212
  }
214
213
  };
215
214
  //#endregion
@@ -338,8 +337,8 @@ var ChargeIntentsAPI = class {
338
337
  async update(id, params) {
339
338
  return (await this.client.patch(`/v1/charge_intents/${id}`, params)).data;
340
339
  }
341
- async get(id) {
342
- return (await this.client.get(`/v1/charge_intents/${id}`)).data;
340
+ async get(id, opts) {
341
+ return (await this.client.get(`/v1/charge_intents/${id}`, maybePublishableKey(opts))).data;
343
342
  }
344
343
  async list(per_page, page) {
345
344
  return (await this.client.get("/v1/charge_intents", { params: {
@@ -996,14 +995,14 @@ var ThreeDSAPI = class {
996
995
  constructor(client) {
997
996
  this.client = client;
998
997
  }
999
- async create(params) {
1000
- return (await this.client.post("/v1/3ds/intents", params)).data;
998
+ async create(params, opts) {
999
+ return (await this.client.post("/v1/3ds/intents", params, maybePublishableKey(opts))).data;
1001
1000
  }
1002
- async get(id) {
1003
- return (await this.client.get(`/v1/3ds/intents/${id}`)).data;
1001
+ async get(id, opts) {
1002
+ return (await this.client.get(`/v1/3ds/intents/${id}`, maybePublishableKey(opts))).data;
1004
1003
  }
1005
- async resend(id) {
1006
- return (await this.client.post(`/v1/3ds/intents/${id}/resend`)).data;
1004
+ async resend(id, opts) {
1005
+ return (await this.client.post(`/v1/3ds/intents/${id}/resend`, void 0, maybePublishableKey(opts))).data;
1007
1006
  }
1008
1007
  };
1009
1008
  //#endregion
@@ -1048,8 +1047,8 @@ var TermsOfServiceAPI = class {
1048
1047
  constructor(client) {
1049
1048
  this.client = client;
1050
1049
  }
1051
- async createToken() {
1052
- return (await this.client.post("/v1/terms_of_service", {})).data;
1050
+ async createToken(opts) {
1051
+ return (await this.client.post("/v1/terms_of_service", {}, maybePublishableKey(opts))).data;
1053
1052
  }
1054
1053
  async update(params) {
1055
1054
  return (await this.client.patch("/v1/terms_of_service", params)).data;
@@ -1127,7 +1126,8 @@ var GeoComplianceAPI = class {
1127
1126
  //#region src/index.ts
1128
1127
  var FrameSDK = class {
1129
1128
  constructor(config) {
1130
- const client = createApiClient(config);
1129
+ this.onboardingSessionStore = createOnboardingSessionStore();
1130
+ const client = createApiClient(config, this.onboardingSessionStore);
1131
1131
  this.accounts = new AccountsAPI(client);
1132
1132
  this.capabilities = new CapabilitiesAPI(client);
1133
1133
  this.onboardingSessions = new OnboardingSessionsAPI(client);
@@ -1167,6 +1167,33 @@ var FrameSDK = class {
1167
1167
  this.wallet = new WalletAPI(client);
1168
1168
  this.geoCompliance = new GeoComplianceAPI(client);
1169
1169
  }
1170
+ /**
1171
+ * Begin an onboarding session. While a session is active, every request sends
1172
+ * `Authorization: Bearer <token>` (e.g. an `onb_sess_...` token), overriding
1173
+ * the configured publishable/secret keys regardless of `usePublishableKey`.
1174
+ * A per-request `authToken` (object client_secret) still takes precedence.
1175
+ *
1176
+ * Mirrors the native iOS `beginOnboardingSession`.
1177
+ */
1178
+ setOnboardingSession(token) {
1179
+ this.onboardingSessionStore.token = token;
1180
+ }
1181
+ /**
1182
+ * End the active onboarding session, reverting auth to the configured
1183
+ * publishable/secret keys.
1184
+ *
1185
+ * Safe-clear: when `token` is provided, the session is cleared only if it
1186
+ * matches the currently active token. This mirrors Android's guarded
1187
+ * teardown so a stale unmount cannot wipe a newer session. Omit `token` to
1188
+ * force-clear unconditionally.
1189
+ *
1190
+ * @returns true if a session was cleared, false if the guard prevented it.
1191
+ */
1192
+ clearOnboardingSession(token) {
1193
+ if (token !== void 0 && this.onboardingSessionStore.token !== token) return false;
1194
+ this.onboardingSessionStore.token = null;
1195
+ return true;
1196
+ }
1170
1197
  };
1171
1198
  //#endregion
1172
1199
  exports.FrameAPIError = FrameAPIError;