@odla-ai/chapter 0.31.2 → 0.31.4

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
@@ -841,6 +841,14 @@ worker to 6 lines and deleted ~2,500 lines. The order that worked:
841
841
 
842
842
  These bite silently — a smoke test won't catch them:
843
843
 
844
+ - **Application fields generate the application schema.** The resolved
845
+ `application.required` and `application.optional` lists now control both
846
+ submit validation and the `applications` entity emitted by
847
+ `defineChapter()`. Built-in fields retain their types and indexes;
848
+ site-defined fields become string attrs. A field removed from the reference
849
+ form no longer remains accidentally required at `db.transact`, and a field
850
+ listed in both arrays is rejected at definition time. Keep package-owned
851
+ operational attrs such as `status` out of both lists.
844
852
  - **Per-field caps.** `application.defaultMaxLen` is 2000. If your form accepts
845
853
  longer input, pass `maxLen` explicitly or the default starts rejecting it.
846
854
  - **`services` default** is `["db","calendar","o11y"]`; `smoke` compares config
@@ -371,9 +371,9 @@ interface ResolvedPipeline {
371
371
  }
372
372
  /** The application (join form) validation surface — which string fields are
373
373
  * required vs accepted, their max lengths, and the request body cap. Drives
374
- * submit validation + the CRM slot projection; defaults to the reference form.
375
- * The `applications` schema attrs stay fixed; this is
376
- * validation config, not schema generation. */
374
+ * submit validation, the generated `applications` schema, and the CRM slot
375
+ * projection; defaults to the reference form. Built-in fields retain their
376
+ * declared schema types/indexes, and site-defined fields become string attrs. */
377
377
  interface ChapterApplication {
378
378
  required?: readonly string[];
379
379
  optional?: readonly string[];
package/dist/index.cjs CHANGED
@@ -106,7 +106,7 @@ __export(index_exports, {
106
106
  slotWindow: () => slotWindow,
107
107
  stageIndex: () => stageIndex,
108
108
  stripeCall: () => stripeCall,
109
- stripeForm: () => stripeForm,
109
+ stripeForm: () => import_stripe.stripeForm,
110
110
  subAnnualCents: () => subAnnualCents,
111
111
  submitApplication: () => submitApplication,
112
112
  subscriptionIdempotencyKey: () => subscriptionIdempotencyKey,
@@ -116,7 +116,7 @@ __export(index_exports, {
116
116
  updateClerkUserMetadata: () => updateClerkUserMetadata,
117
117
  updateClerkUserMetadataByEmail: () => updateClerkUserMetadataByEmail,
118
118
  validateScheduling: () => validateScheduling,
119
- verifyStripeSignature: () => verifyStripeSignature,
119
+ verifyStripeSignature: () => import_stripe.verifyStripeSignature,
120
120
  webhookMutationId: () => webhookMutationId
121
121
  });
122
122
  module.exports = __toCommonJS(index_exports);
@@ -124,6 +124,154 @@ module.exports = __toCommonJS(index_exports);
124
124
  // src/config.ts
125
125
  var import_crm2 = require("@odla-ai/crm");
126
126
 
127
+ // src/member.ts
128
+ var import_crm = require("@odla-ai/crm");
129
+
130
+ // src/application-id.ts
131
+ var APPLICATION_ID_DOMAIN = "odla-ai/chapter/application/v1:";
132
+ async function applicationIdForSubmission(submissionId) {
133
+ const input = new TextEncoder().encode(`${APPLICATION_ID_DOMAIN}${submissionId}`);
134
+ const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", input)).slice(0, 16);
135
+ bytes[6] = bytes[6] & 15 | 128;
136
+ bytes[8] = bytes[8] & 63 | 128;
137
+ const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
138
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
139
+ }
140
+
141
+ // src/member.ts
142
+ var DEFAULT_REQUIRED = ["firstName", "lastName", "email", "referral", "whoYouAre", "message"];
143
+ var DEFAULT_OPTIONAL = ["referralName", "linkedin", "phone", "state"];
144
+ var SCHEMA_FIELD = /^[A-Za-z_][A-Za-z0-9_-]*$/;
145
+ function resolveApplication(a) {
146
+ const required = a?.required ?? DEFAULT_REQUIRED;
147
+ const optional = a?.optional ?? DEFAULT_OPTIONAL;
148
+ for (const [name, arr] of [["required", required], ["optional", optional]]) {
149
+ if (!Array.isArray(arr) || !arr.every((f) => typeof f === "string" && f !== "")) {
150
+ throw new Error(`defineChapter.application.${name}: must be an array of field-name strings`);
151
+ }
152
+ const unsafe = arr.find((field) => !SCHEMA_FIELD.test(field));
153
+ if (unsafe) {
154
+ throw new Error(`defineChapter.application.${name}: field "${unsafe}" must match ${SCHEMA_FIELD.source}`);
155
+ }
156
+ }
157
+ const duplicate = [...required, ...optional].find((field, index, fields) => fields.indexOf(field) !== index);
158
+ if (duplicate) {
159
+ throw new Error(`defineChapter.application: duplicate field "${duplicate}" across required/optional lists`);
160
+ }
161
+ if (a?.profileFields !== void 0 && (!Array.isArray(a.profileFields) || !a.profileFields.every((f) => typeof f === "string" && f !== ""))) {
162
+ throw new Error("defineChapter.application.profileFields: must be an array of field-name strings");
163
+ }
164
+ if (a?.crmFields !== void 0 && (!Array.isArray(a.crmFields) || !a.crmFields.every((f) => typeof f === "string" && f !== ""))) {
165
+ throw new Error("defineChapter.application.crmFields: must be an array of field-name strings");
166
+ }
167
+ const conditions = a?.conditions ?? {};
168
+ for (const [field, declared] of Object.entries(conditions)) {
169
+ for (const key of ["visibleWhen", "requiredWhen"]) {
170
+ const expression = declared?.[key];
171
+ if (expression === void 0) continue;
172
+ if (typeof expression !== "string" || !expression.trim()) {
173
+ throw new Error(`defineChapter.application.conditions.${field}.${key}: must be a non-empty CEL condition`);
174
+ }
175
+ (0, import_crm.assertFieldCondition)(expression, `defineChapter.application.conditions.${field}.${key}`);
176
+ }
177
+ }
178
+ return {
179
+ required,
180
+ optional,
181
+ conditions,
182
+ maxLen: a?.maxLen ?? {},
183
+ defaultMaxLen: a?.defaultMaxLen ?? 2e3,
184
+ bodyCap: a?.bodyCap ?? 32768,
185
+ requireDisclaimerAck: a?.requireDisclaimerAck ?? true,
186
+ profileFields: a?.profileFields ?? [],
187
+ crmFields: a?.crmFields ?? [],
188
+ maxArrayLen: a?.maxArrayLen ?? 100,
189
+ validateEmail: a?.validateEmail ?? true
190
+ };
191
+ }
192
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
193
+ function isValidEmail(value) {
194
+ return typeof value === "string" && EMAIL_RE.test(value);
195
+ }
196
+ function clampArray(value, max) {
197
+ if (!Array.isArray(value)) return value;
198
+ return value.filter((x) => typeof x === "string" || typeof x === "number" || typeof x === "boolean").slice(0, max);
199
+ }
200
+ function hasDisclaimerAck(fields) {
201
+ return fields.disclaimerAck === true || fields.disclaimerAck === "true";
202
+ }
203
+ var IDENTITY_FIELDS = /* @__PURE__ */ new Set(["email", "firstName", "lastName"]);
204
+ function applicantProfile(chapter, fields) {
205
+ const app = chapter.application;
206
+ const allowed = (f) => app.profileFields.includes(f);
207
+ const profile = {};
208
+ for (const f of [...app.required, ...app.optional]) {
209
+ if (IDENTITY_FIELDS.has(f) || !allowed(f)) continue;
210
+ const v = fields[f];
211
+ if (typeof v === "string" && v.trim() !== "") profile[f] = v.trim();
212
+ }
213
+ if (fields.focus !== void 0 && allowed("focus")) profile.focus = clampArray(fields.focus, app.maxArrayLen);
214
+ return Object.keys(profile).length > 0 ? profile : void 0;
215
+ }
216
+ async function submitApplication(db, chapter, fields, opts) {
217
+ const app = chapter.application;
218
+ const fieldStates = (0, import_crm.resolveFieldStates)(app.conditions ?? {}, fields, app.required);
219
+ for (const [f, state] of Object.entries(fieldStates)) {
220
+ if (!state.required) continue;
221
+ const v = fields[f];
222
+ if (typeof v !== "string" || v.trim() === "") return { ok: false, error: `${f} is required` };
223
+ }
224
+ for (const f of [...app.required, ...app.optional]) {
225
+ const v = fields[f];
226
+ const cap = app.maxLen[f] ?? app.defaultMaxLen;
227
+ if (typeof v === "string" && v.length > cap) return { ok: false, error: `${f} exceeds ${cap} characters` };
228
+ }
229
+ if (app.validateEmail && typeof fields.email === "string" && !isValidEmail(fields.email)) {
230
+ return { ok: false, error: "email must be a valid email address" };
231
+ }
232
+ const acked = hasDisclaimerAck(fields);
233
+ if (app.requireDisclaimerAck && !acked) {
234
+ return { ok: false, error: "disclaimerAck is required" };
235
+ }
236
+ const id2 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
237
+ const row = { id: id2, status: chapter.pipeline.initial, createdAt: opts.now };
238
+ for (const f of [...app.required, ...app.optional]) {
239
+ if (fieldStates[f]?.visible === false) continue;
240
+ if (typeof fields[f] === "string") row[f] = fields[f].trim();
241
+ }
242
+ if (fields.focus !== void 0) row.focus = clampArray(fields.focus, app.maxArrayLen);
243
+ if (opts.groupId) row.groupId = opts.groupId;
244
+ if (typeof fields.tierId === "string" && fields.tierId) {
245
+ if (opts.tierIds && !opts.tierIds.includes(fields.tierId)) {
246
+ return { ok: false, error: "tierId is not an offered tier" };
247
+ }
248
+ row.tierId = fields.tierId;
249
+ }
250
+ if (acked) row.disclaimerAckAt = opts.now;
251
+ const { duplicate } = await db.transact(
252
+ [{ t: "update", ns: "applications", id: id2, attrs: row }],
253
+ opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
254
+ );
255
+ return { ok: true, id: id2, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
256
+ }
257
+ function joinConfig(group, paymentsReady2, tiers2 = [], conditions = {}) {
258
+ return {
259
+ id: group.id,
260
+ name: group.name,
261
+ standardPriceCents: group.standardPriceCents ?? 0,
262
+ foundingDiscountCents: group.foundingDiscountCents ?? 0,
263
+ tiers: [...tiers2],
264
+ // The browser evaluates the same conditions the server enforces.
265
+ conditions,
266
+ disclaimerText: group.disclaimerText ?? "",
267
+ refundPolicyText: group.refundPolicyText ?? "",
268
+ trustCopy: group.trustCopy ?? "",
269
+ commitmentText: group.commitmentText ?? "",
270
+ normsText: group.normsText ?? "",
271
+ paymentsReady: paymentsReady2
272
+ };
273
+ }
274
+
127
275
  // src/schema.ts
128
276
  function attr(type, flags = {}) {
129
277
  return {
@@ -198,6 +346,47 @@ var applications = {
198
346
  canceled: attr("boolean", { optional: true })
199
347
  }
200
348
  };
349
+ var APPLICATION_INPUT_FIELDS = /* @__PURE__ */ new Set([
350
+ "firstName",
351
+ "lastName",
352
+ "email",
353
+ "referral",
354
+ "referralName",
355
+ "whoYouAre",
356
+ "focus",
357
+ "linkedin",
358
+ "message",
359
+ "phone",
360
+ "state"
361
+ ]);
362
+ function applicationsFor(application) {
363
+ const attrs = {};
364
+ for (const [name, spec] of Object.entries(applications.attrs)) {
365
+ attrs[name] = {
366
+ ...spec,
367
+ optional: APPLICATION_INPUT_FIELDS.has(name) ? true : spec.optional
368
+ };
369
+ }
370
+ const apply = (field, optional) => {
371
+ const existing = Object.hasOwn(attrs, field) ? attrs[field] : void 0;
372
+ if (existing && !APPLICATION_INPUT_FIELDS.has(field)) {
373
+ throw new Error(`defineChapter.application: field "${field}" is reserved by the applications schema`);
374
+ }
375
+ if (existing && existing.type !== "string") {
376
+ throw new Error(`defineChapter.application: field "${field}" is not a configurable string field`);
377
+ }
378
+ const spec = existing ? { ...existing, optional } : attr("string", { optional });
379
+ Object.defineProperty(attrs, field, {
380
+ value: spec,
381
+ enumerable: true,
382
+ configurable: true,
383
+ writable: true
384
+ });
385
+ };
386
+ for (const field of application.required) apply(field, false);
387
+ for (const field of application.optional) apply(field, true);
388
+ return { attrs };
389
+ }
201
390
  var groups = {
202
391
  attrs: {
203
392
  id: id(),
@@ -269,10 +458,10 @@ var emailLog = {
269
458
  sentAt: attr("number", { indexed: true })
270
459
  }
271
460
  };
272
- function chapterDb(mode, auth, includeNetworkNotes = false) {
461
+ function chapterDb(mode, auth, includeNetworkNotes = false, application = resolveApplication(void 0)) {
273
462
  const entities = {};
274
463
  if (mode === "chapter") {
275
- entities.applications = applications;
464
+ entities.applications = applicationsFor(application);
276
465
  entities.groups = groups;
277
466
  entities.tiers = tiers;
278
467
  entities.meetings = meetings;
@@ -535,145 +724,6 @@ function canApprove(status, p) {
535
724
  return p.approvableFrom.includes(status);
536
725
  }
537
726
 
538
- // src/member.ts
539
- var import_crm = require("@odla-ai/crm");
540
-
541
- // src/application-id.ts
542
- var APPLICATION_ID_DOMAIN = "odla-ai/chapter/application/v1:";
543
- async function applicationIdForSubmission(submissionId) {
544
- const input = new TextEncoder().encode(`${APPLICATION_ID_DOMAIN}${submissionId}`);
545
- const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", input)).slice(0, 16);
546
- bytes[6] = bytes[6] & 15 | 128;
547
- bytes[8] = bytes[8] & 63 | 128;
548
- const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
549
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
550
- }
551
-
552
- // src/member.ts
553
- var DEFAULT_REQUIRED = ["firstName", "lastName", "email", "referral", "whoYouAre", "message"];
554
- var DEFAULT_OPTIONAL = ["referralName", "linkedin", "phone", "state"];
555
- function resolveApplication(a) {
556
- const required = a?.required ?? DEFAULT_REQUIRED;
557
- const optional = a?.optional ?? DEFAULT_OPTIONAL;
558
- for (const [name, arr] of [["required", required], ["optional", optional]]) {
559
- if (!Array.isArray(arr) || !arr.every((f) => typeof f === "string" && f !== "")) {
560
- throw new Error(`defineChapter.application.${name}: must be an array of field-name strings`);
561
- }
562
- }
563
- if (a?.profileFields !== void 0 && (!Array.isArray(a.profileFields) || !a.profileFields.every((f) => typeof f === "string" && f !== ""))) {
564
- throw new Error("defineChapter.application.profileFields: must be an array of field-name strings");
565
- }
566
- if (a?.crmFields !== void 0 && (!Array.isArray(a.crmFields) || !a.crmFields.every((f) => typeof f === "string" && f !== ""))) {
567
- throw new Error("defineChapter.application.crmFields: must be an array of field-name strings");
568
- }
569
- const conditions = a?.conditions ?? {};
570
- for (const [field, declared] of Object.entries(conditions)) {
571
- for (const key of ["visibleWhen", "requiredWhen"]) {
572
- const expression = declared?.[key];
573
- if (expression === void 0) continue;
574
- if (typeof expression !== "string" || !expression.trim()) {
575
- throw new Error(`defineChapter.application.conditions.${field}.${key}: must be a non-empty CEL condition`);
576
- }
577
- (0, import_crm.assertFieldCondition)(expression, `defineChapter.application.conditions.${field}.${key}`);
578
- }
579
- }
580
- return {
581
- required,
582
- optional,
583
- conditions,
584
- maxLen: a?.maxLen ?? {},
585
- defaultMaxLen: a?.defaultMaxLen ?? 2e3,
586
- bodyCap: a?.bodyCap ?? 32768,
587
- requireDisclaimerAck: a?.requireDisclaimerAck ?? true,
588
- profileFields: a?.profileFields ?? [],
589
- crmFields: a?.crmFields ?? [],
590
- maxArrayLen: a?.maxArrayLen ?? 100,
591
- validateEmail: a?.validateEmail ?? true
592
- };
593
- }
594
- var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
595
- function isValidEmail(value) {
596
- return typeof value === "string" && EMAIL_RE.test(value);
597
- }
598
- function clampArray(value, max) {
599
- if (!Array.isArray(value)) return value;
600
- return value.filter((x) => typeof x === "string" || typeof x === "number" || typeof x === "boolean").slice(0, max);
601
- }
602
- function hasDisclaimerAck(fields) {
603
- return fields.disclaimerAck === true || fields.disclaimerAck === "true";
604
- }
605
- var IDENTITY_FIELDS = /* @__PURE__ */ new Set(["email", "firstName", "lastName"]);
606
- function applicantProfile(chapter, fields) {
607
- const app = chapter.application;
608
- const allowed = (f) => app.profileFields.includes(f);
609
- const profile = {};
610
- for (const f of [...app.required, ...app.optional]) {
611
- if (IDENTITY_FIELDS.has(f) || !allowed(f)) continue;
612
- const v = fields[f];
613
- if (typeof v === "string" && v.trim() !== "") profile[f] = v.trim();
614
- }
615
- if (fields.focus !== void 0 && allowed("focus")) profile.focus = clampArray(fields.focus, app.maxArrayLen);
616
- return Object.keys(profile).length > 0 ? profile : void 0;
617
- }
618
- async function submitApplication(db, chapter, fields, opts) {
619
- const app = chapter.application;
620
- const fieldStates = (0, import_crm.resolveFieldStates)(app.conditions ?? {}, fields, app.required);
621
- for (const [f, state] of Object.entries(fieldStates)) {
622
- if (!state.required) continue;
623
- const v = fields[f];
624
- if (typeof v !== "string" || v.trim() === "") return { ok: false, error: `${f} is required` };
625
- }
626
- for (const f of [...app.required, ...app.optional]) {
627
- const v = fields[f];
628
- const cap = app.maxLen[f] ?? app.defaultMaxLen;
629
- if (typeof v === "string" && v.length > cap) return { ok: false, error: `${f} exceeds ${cap} characters` };
630
- }
631
- if (app.validateEmail && typeof fields.email === "string" && !isValidEmail(fields.email)) {
632
- return { ok: false, error: "email must be a valid email address" };
633
- }
634
- const acked = hasDisclaimerAck(fields);
635
- if (app.requireDisclaimerAck && !acked) {
636
- return { ok: false, error: "disclaimerAck is required" };
637
- }
638
- const id2 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
639
- const row = { id: id2, status: chapter.pipeline.initial, createdAt: opts.now };
640
- for (const f of [...app.required, ...app.optional]) {
641
- if (fieldStates[f]?.visible === false) continue;
642
- if (typeof fields[f] === "string") row[f] = fields[f].trim();
643
- }
644
- if (fields.focus !== void 0) row.focus = clampArray(fields.focus, app.maxArrayLen);
645
- if (opts.groupId) row.groupId = opts.groupId;
646
- if (typeof fields.tierId === "string" && fields.tierId) {
647
- if (opts.tierIds && !opts.tierIds.includes(fields.tierId)) {
648
- return { ok: false, error: "tierId is not an offered tier" };
649
- }
650
- row.tierId = fields.tierId;
651
- }
652
- if (acked) row.disclaimerAckAt = opts.now;
653
- const { duplicate } = await db.transact(
654
- [{ t: "update", ns: "applications", id: id2, attrs: row }],
655
- opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
656
- );
657
- return { ok: true, id: id2, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
658
- }
659
- function joinConfig(group, paymentsReady2, tiers2 = [], conditions = {}) {
660
- return {
661
- id: group.id,
662
- name: group.name,
663
- standardPriceCents: group.standardPriceCents ?? 0,
664
- foundingDiscountCents: group.foundingDiscountCents ?? 0,
665
- tiers: [...tiers2],
666
- // The browser evaluates the same conditions the server enforces.
667
- conditions,
668
- disclaimerText: group.disclaimerText ?? "",
669
- refundPolicyText: group.refundPolicyText ?? "",
670
- trustCopy: group.trustCopy ?? "",
671
- commitmentText: group.commitmentText ?? "",
672
- normsText: group.normsText ?? "",
673
- paymentsReady: paymentsReady2
674
- };
675
- }
676
-
677
727
  // src/copy-defaults-admin.ts
678
728
  var DEFAULT_ADMIN_COPY = {
679
729
  auth: {
@@ -1222,7 +1272,7 @@ function defineChapter(config) {
1222
1272
  const copy = resolveChapterCopy(config.copy);
1223
1273
  const network = resolveNetwork(config, crm);
1224
1274
  const formation = resolveLeaderFormation(config.formation, crm);
1225
- const { schema, rules } = chapterDb(mode, auth, network.targets.length > 0);
1275
+ const { schema, rules } = chapterDb(mode, auth, network.targets.length > 0, application);
1226
1276
  const services = config.services ?? ["db", "calendar", "o11y"];
1227
1277
  const account = config.account ?? "none";
1228
1278
  if (account !== "invite" && account !== "create" && account !== "none") {
@@ -1680,53 +1730,10 @@ function emailGroupFrom(row) {
1680
1730
  }
1681
1731
 
1682
1732
  // src/payments.ts
1683
- function parseSigHeader(header) {
1684
- const parts = {};
1685
- for (const p of header.split(",")) {
1686
- const [k, v] = p.split("=", 2);
1687
- if (k && v !== void 0) parts[k] = v;
1688
- }
1689
- return { t: parts.t, v1: parts.v1 };
1690
- }
1691
- function toHex(buf) {
1692
- return [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
1693
- }
1694
- function timingSafeEqual(a, b) {
1695
- if (a.length !== b.length) return false;
1696
- let diff = 0;
1697
- for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
1698
- return diff === 0;
1699
- }
1700
- async function verifyStripeSignature(payload, header, secret, opts = {}) {
1701
- const { t, v1 } = parseSigHeader(header);
1702
- if (!t || !v1) return false;
1703
- const ts = Number(t);
1704
- if (!Number.isFinite(ts)) return false;
1705
- const nowSec = (opts.now ?? Date.now()) / 1e3;
1706
- const tolerance = opts.toleranceSec ?? 300;
1707
- if (Math.abs(nowSec - ts) > tolerance) return false;
1708
- const enc = new TextEncoder();
1709
- const key = await crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
1710
- const mac = await crypto.subtle.sign("HMAC", key, enc.encode(`${t}.${payload}`));
1711
- return timingSafeEqual(toHex(mac), v1);
1712
- }
1733
+ var import_stripe = require("@odla-ai/stripe");
1713
1734
  function paymentsReady(group, hasSecretKey) {
1714
1735
  return Boolean(group.stripePublishableKey && group.stripePriceId && hasSecretKey);
1715
1736
  }
1716
- function stripeForm(params) {
1717
- const out = new URLSearchParams();
1718
- for (const [k, v] of Object.entries(params)) {
1719
- if (v === void 0 || v === null) continue;
1720
- if (typeof v === "object") {
1721
- for (const [k2, v2] of Object.entries(v)) {
1722
- if (v2 !== void 0 && v2 !== null) out.append(`${k}[${k2}]`, String(v2));
1723
- }
1724
- } else {
1725
- out.append(k, String(v));
1726
- }
1727
- }
1728
- return out.toString();
1729
- }
1730
1737
  function subscriptionIdempotencyKey(applicationId) {
1731
1738
  return `sub:${applicationId}`;
1732
1739
  }
@@ -2110,18 +2117,15 @@ function subAnnualCents(sub) {
2110
2117
  }
2111
2118
 
2112
2119
  // src/payments-stripe.ts
2120
+ var import_stripe2 = require("@odla-ai/stripe");
2113
2121
  async function stripeCall(sk, method, path, params, idempotencyKey) {
2114
- const qs = method === "GET" && params ? `?${stripeForm(params)}` : "";
2115
- const headers = { authorization: `Bearer ${sk}` };
2116
- if (idempotencyKey) headers["idempotency-key"] = idempotencyKey;
2117
- const init = { method, headers };
2118
- if (method === "POST" && params) {
2119
- headers["content-type"] = "application/x-www-form-urlencoded";
2120
- init.body = stripeForm(params);
2121
- }
2122
- const res = await fetch(`https://api.stripe.com${path}${qs}`, init);
2123
- const body = await res.json().catch(() => ({}));
2124
- return { ok: res.ok, status: res.status, body };
2122
+ return (0, import_stripe2.stripeRequest)(
2123
+ { secretKey: sk },
2124
+ method,
2125
+ path,
2126
+ params,
2127
+ idempotencyKey ? { idempotencyKey } : {}
2128
+ );
2125
2129
  }
2126
2130
 
2127
2131
  // src/clerk.ts