@odla-ai/chapter 0.39.2 → 0.40.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.
@@ -273,6 +273,8 @@ interface ChapterPrices {
273
273
  interface ChapterPolicy {
274
274
  disclaimerText?: string;
275
275
  refundPolicyText?: string;
276
+ /** Plain-language merchant identity shown immediately before payment consent. */
277
+ merchantDisclosureText?: string;
276
278
  trustCopy?: string;
277
279
  commitmentText?: string;
278
280
  normsText?: string;
@@ -714,6 +716,9 @@ interface ChapterEnv {
714
716
  * reads fail closed when it is absent or stale. */
715
717
  ODLA_APP_INCARNATION?: string;
716
718
  ODLA_ENV: string;
719
+ /** Immutable developer-runtime identity. Omitted by legacy deployments whose
720
+ * runtime and data environment are the same. */
721
+ ODLA_RUNTIME?: string;
717
722
  ODLA_API_KEY: string;
718
723
  SEND_EMAIL?: {
719
724
  send(payload: EmailPayload): Promise<{
@@ -874,6 +879,8 @@ interface SignupControlApplication {
874
879
  interface SignupControlPolicy {
875
880
  disclaimerText: string;
876
881
  refundPolicyText: string;
882
+ /** Plain-language merchant identity displayed before payment consent. */
883
+ merchantDisclosureText?: string;
877
884
  trustCopy?: string;
878
885
  commitmentText?: string;
879
886
  normsText?: string;
@@ -273,6 +273,8 @@ interface ChapterPrices {
273
273
  interface ChapterPolicy {
274
274
  disclaimerText?: string;
275
275
  refundPolicyText?: string;
276
+ /** Plain-language merchant identity shown immediately before payment consent. */
277
+ merchantDisclosureText?: string;
276
278
  trustCopy?: string;
277
279
  commitmentText?: string;
278
280
  normsText?: string;
@@ -714,6 +716,9 @@ interface ChapterEnv {
714
716
  * reads fail closed when it is absent or stale. */
715
717
  ODLA_APP_INCARNATION?: string;
716
718
  ODLA_ENV: string;
719
+ /** Immutable developer-runtime identity. Omitted by legacy deployments whose
720
+ * runtime and data environment are the same. */
721
+ ODLA_RUNTIME?: string;
717
722
  ODLA_API_KEY: string;
718
723
  SEND_EMAIL?: {
719
724
  send(payload: EmailPayload): Promise<{
@@ -874,6 +879,8 @@ interface SignupControlApplication {
874
879
  interface SignupControlPolicy {
875
880
  disclaimerText: string;
876
881
  refundPolicyText: string;
882
+ /** Plain-language merchant identity displayed before payment consent. */
883
+ merchantDisclosureText?: string;
877
884
  trustCopy?: string;
878
885
  commitmentText?: string;
879
886
  normsText?: string;
@@ -317,6 +317,7 @@ function joinConfig(group, paymentsReady, tiers = [], conditions = {}) {
317
317
  conditions,
318
318
  disclaimerText: group.disclaimerText ?? "",
319
319
  refundPolicyText: group.refundPolicyText ?? "",
320
+ merchantDisclosureText: group.merchantDisclosureText ?? "",
320
321
  trustCopy: group.trustCopy ?? "",
321
322
  commitmentText: group.commitmentText ?? "",
322
323
  normsText: group.normsText ?? "",
@@ -2325,7 +2326,14 @@ function policyOf(value, errors) {
2325
2326
  errors.push("document.policy must be an object");
2326
2327
  return void 0;
2327
2328
  }
2328
- const allowed2 = /* @__PURE__ */ new Set(["disclaimerText", "refundPolicyText", "trustCopy", "commitmentText", "normsText"]);
2329
+ const allowed2 = /* @__PURE__ */ new Set([
2330
+ "disclaimerText",
2331
+ "refundPolicyText",
2332
+ "merchantDisclosureText",
2333
+ "trustCopy",
2334
+ "commitmentText",
2335
+ "normsText"
2336
+ ]);
2329
2337
  unknownKeys(input, allowed2, "document.policy", errors);
2330
2338
  const result = {};
2331
2339
  for (const key of allowed2) {
@@ -3354,12 +3362,16 @@ function findApplicationRef(obj) {
3354
3362
  const applicationId = candidates.map(pick).find(Boolean);
3355
3363
  const appId = candidates.map((metadata) => typeof metadata.odlaAppId === "string" ? metadata.odlaAppId : void 0).find(Boolean);
3356
3364
  const environment = candidates.map((metadata) => typeof metadata.odlaEnvironment === "string" ? metadata.odlaEnvironment : void 0).find(Boolean);
3365
+ const dataEnvironment = candidates.map((metadata) => typeof metadata.odlaDataEnvironment === "string" ? metadata.odlaDataEnvironment : void 0).find(Boolean);
3366
+ const runtime = candidates.map((metadata) => typeof metadata.odlaRuntime === "string" ? metadata.odlaRuntime : void 0).find(Boolean);
3357
3367
  const customerId = typeof obj.customer === "string" ? obj.customer : void 0;
3358
3368
  return {
3359
3369
  ...applicationId ? { applicationId } : {},
3360
3370
  ...customerId ? { customerId } : {},
3361
3371
  ...appId ? { appId } : {},
3362
- ...environment ? { environment } : {}
3372
+ ...environment ? { environment } : {},
3373
+ ...dataEnvironment ? { dataEnvironment } : {},
3374
+ ...runtime ? { runtime } : {}
3363
3375
  };
3364
3376
  }
3365
3377
  function findGiftRef(obj) {
@@ -3369,17 +3381,31 @@ function findGiftRef(obj) {
3369
3381
  const subscription = obj.subscription_details ?? parent?.subscription_details;
3370
3382
  const giftIntentId = pick(metaOf(obj)) ?? pick(metaOf(subscription));
3371
3383
  const subscriptionId = typeof obj.subscription === "string" ? obj.subscription : typeof subscription?.subscription === "string" ? String(subscription.subscription) : void 0;
3372
- return { ...giftIntentId ? { giftIntentId } : {}, providerObjectId: subscriptionId ?? String(obj.id ?? "") };
3384
+ const candidates = [metaOf(obj), metaOf(subscription)];
3385
+ const pickIdentity = (name) => candidates.map((metadata) => typeof metadata[name] === "string" ? String(metadata[name]) : void 0).find(Boolean);
3386
+ const appId = pickIdentity("odlaAppId");
3387
+ const environment = pickIdentity("odlaEnvironment");
3388
+ const dataEnvironment = pickIdentity("odlaDataEnvironment");
3389
+ const runtime = pickIdentity("odlaRuntime");
3390
+ return {
3391
+ ...giftIntentId ? { giftIntentId } : {},
3392
+ providerObjectId: subscriptionId ?? String(obj.id ?? ""),
3393
+ ...appId ? { appId } : {},
3394
+ ...environment ? { environment } : {},
3395
+ ...dataEnvironment ? { dataEnvironment } : {},
3396
+ ...runtime ? { runtime } : {}
3397
+ };
3373
3398
  }
3374
3399
  function normalizeWebhookEvent(event) {
3375
3400
  const obj = event.data?.object ?? {};
3376
3401
  const gift = findGiftRef(obj);
3377
3402
  if (gift.giftIntentId) {
3403
+ const { giftIntentId, providerObjectId, ...identity } = gift;
3378
3404
  if (event.type === "payment_intent.succeeded" || event.type === "invoice.paid" && obj.billing_reason === "subscription_create") {
3379
- return { kind: "gift_paid", giftIntentId: gift.giftIntentId, providerObjectId: gift.providerObjectId };
3405
+ return { kind: "gift_paid", giftIntentId, providerObjectId, ...identity };
3380
3406
  }
3381
- if (event.type === "charge.refunded") return { kind: "gift_revoked", giftIntentId: gift.giftIntentId, reason: "refunded" };
3382
- if (event.type === "customer.subscription.deleted") return { kind: "gift_revoked", giftIntentId: gift.giftIntentId, reason: "canceled" };
3407
+ if (event.type === "charge.refunded") return { kind: "gift_revoked", giftIntentId, ...identity, reason: "refunded" };
3408
+ if (event.type === "customer.subscription.deleted") return { kind: "gift_revoked", giftIntentId, ...identity, reason: "canceled" };
3383
3409
  return { kind: "ignored", type: event.type };
3384
3410
  }
3385
3411
  const ref = findApplicationRef(obj);
@@ -3456,7 +3482,9 @@ function createStripeProvider(config) {
3456
3482
  groupId: input.groupId,
3457
3483
  email: input.email,
3458
3484
  odlaAppId: input.appId,
3459
- odlaEnvironment: input.environment
3485
+ odlaEnvironment: input.environment,
3486
+ odlaDataEnvironment: input.environment,
3487
+ ...input.runtime ? { odlaRuntime: input.runtime } : {}
3460
3488
  };
3461
3489
  let customerId = input.existingCustomerId;
3462
3490
  if (!customerId) {
@@ -3744,12 +3772,16 @@ var clientSecret = (value) => {
3744
3772
  const nested = confirmation?.client_secret ?? intent?.client_secret;
3745
3773
  return typeof nested === "string" && nested ? nested : null;
3746
3774
  };
3747
- async function startGiftProviderCheckout(secretKey, intent, email, existingCustomerId) {
3775
+ async function startGiftProviderCheckout(secretKey, intent, email, existingCustomerId, runtime) {
3748
3776
  const offer = intent.offerSnapshot;
3749
3777
  const metadata = {
3750
3778
  giftIntentId: intent.id,
3751
3779
  chapterId: intent.chapterId,
3752
3780
  environment: intent.environment,
3781
+ odlaAppId: intent.chapterId,
3782
+ odlaEnvironment: intent.environment,
3783
+ odlaDataEnvironment: intent.environment,
3784
+ ...runtime ? { odlaRuntime: runtime } : {},
3753
3785
  offerId: intent.offerId
3754
3786
  };
3755
3787
  let customerId = existingCustomerId;
@@ -3799,6 +3831,7 @@ async function startGiftIntent(db, input) {
3799
3831
  id: input.id,
3800
3832
  chapterId: input.chapterId,
3801
3833
  environment: input.environment,
3834
+ ...input.runtime ? { runtime: input.runtime } : {},
3802
3835
  purchaserType: input.purchaserType,
3803
3836
  purchaserEmailNormalized: input.purchaserEmailNormalized,
3804
3837
  ...input.purchaserApplicationId ? { purchaserApplicationId: input.purchaserApplicationId } : {},
@@ -3818,7 +3851,7 @@ async function startGiftIntent(db, input) {
3818
3851
  stored = write.duplicate ? first2((await db.query({ giftPurchaseIntents: { $: { where: { id: input.id }, limit: 1 } } })).giftPurchaseIntents) : attrs;
3819
3852
  if (!write.duplicate) giftCode = prepared.code;
3820
3853
  }
3821
- if (!stored || stored.chapterId !== input.chapterId || stored.environment !== input.environment || stored.offerId !== input.offer.id || stored.purchaserEmailNormalized !== input.purchaserEmailNormalized) {
3854
+ if (!stored || stored.chapterId !== input.chapterId || stored.environment !== input.environment || stored.runtime !== input.runtime || stored.offerId !== input.offer.id || stored.purchaserEmailNormalized !== input.purchaserEmailNormalized) {
3822
3855
  throw new Error("gift purchase intent does not match this checkout");
3823
3856
  }
3824
3857
  if (stored.status === "paid") throw new Error("gift purchase is already paid");
@@ -3826,7 +3859,8 @@ async function startGiftIntent(db, input) {
3826
3859
  input.stripeSecret,
3827
3860
  stored,
3828
3861
  input.purchaserEmail,
3829
- typeof stored.providerCustomerId === "string" ? stored.providerCustomerId : input.existingCustomerId
3862
+ typeof stored.providerCustomerId === "string" ? stored.providerCustomerId : input.existingCustomerId,
3863
+ input.runtime
3830
3864
  );
3831
3865
  await db.transact([{ t: "update", ns: "giftPurchaseIntents", id: input.id, attrs: {
3832
3866
  status: "checkout_ready",
@@ -3845,18 +3879,29 @@ async function startGiftIntent(db, input) {
3845
3879
  };
3846
3880
  }
3847
3881
 
3882
+ // src/stripe-runtime.ts
3883
+ function stripeWebhookSecretName(runtime) {
3884
+ if (!runtime) return "stripe_webhook_secret";
3885
+ if (!/^[a-z0-9]{2,12}$/.test(runtime) || runtime === "prod" || runtime === "production") {
3886
+ throw new Error("ODLA_RUNTIME is invalid");
3887
+ }
3888
+ return `stripe_webhook_secret__${runtime}`;
3889
+ }
3890
+
3891
+ // src/payments-route-support.ts
3892
+ var paymentErrorCode = (error) => error && typeof error === "object" && typeof error.code === "string" ? error.code : "unknown";
3893
+ function paymentLineItems(group) {
3894
+ const standard = Number(group.standardPriceCents ?? 0);
3895
+ const discount = Number(group.foundingDiscountCents ?? 0);
3896
+ return { standardCents: standard, discountCents: discount, dueTodayCents: standard - discount };
3897
+ }
3898
+
3848
3899
  // src/worker-routes-payments.ts
3849
- var codeOf = (err) => err && typeof err === "object" && typeof err.code === "string" ? err.code : "unknown";
3850
3900
  async function firstRow2(db, ns, q) {
3851
3901
  const res = await db.query({ [ns]: { $: q } });
3852
3902
  const rows4 = res[ns];
3853
3903
  return Array.isArray(rows4) ? rows4[0] : void 0;
3854
3904
  }
3855
- function lineItems(group) {
3856
- const standard = Number(group.standardPriceCents ?? 0);
3857
- const discount = Number(group.foundingDiscountCents ?? 0);
3858
- return { standardCents: standard, discountCents: discount, dueTodayCents: standard - discount };
3859
- }
3860
3905
  async function findApplication(db, event) {
3861
3906
  if ("applicationId" in event && event.applicationId) {
3862
3907
  return firstRow2(db, "applications", { where: { id: event.applicationId }, limit: 1 });
@@ -3947,6 +3992,7 @@ async function startSubscription(req, env, ctx) {
3947
3992
  chapterId: ctx.chapter.id,
3948
3993
  environment: env.ODLA_ENV,
3949
3994
  offer,
3995
+ ...env.ODLA_RUNTIME ? { runtime: env.ODLA_RUNTIME } : {},
3950
3996
  purchaserType: "application",
3951
3997
  purchaserEmail: String(app.email ?? ""),
3952
3998
  purchaserEmailNormalized: normalizeChapterEmail(String(app.email ?? "")),
@@ -3979,6 +4025,7 @@ async function startSubscription(req, env, ctx) {
3979
4025
  result = await provider.createSubscription({
3980
4026
  appId: env.ODLA_APP_ID,
3981
4027
  environment: env.ODLA_ENV,
4028
+ ...env.ODLA_RUNTIME ? { runtime: env.ODLA_RUNTIME } : {},
3982
4029
  applicationId,
3983
4030
  groupId: String(group.id),
3984
4031
  email: String(app.email ?? ""),
@@ -3987,29 +4034,35 @@ async function startSubscription(req, env, ctx) {
3987
4034
  existingCustomerId: typeof app.stripeCustomerId === "string" ? app.stripeCustomerId : void 0
3988
4035
  });
3989
4036
  } catch (err) {
3990
- return json({ error: "payment setup failed", code: codeOf(err) }, 502);
4037
+ return json({ error: "payment setup failed", code: paymentErrorCode(err) }, 502);
3991
4038
  }
3992
4039
  await db.transact([
3993
4040
  {
3994
4041
  t: "update",
3995
4042
  ns: "applications",
3996
4043
  id: applicationId,
3997
- attrs: { stripeCustomerId: result.customerId, stripeSubscriptionId: result.subscriptionId, refundPolicyAckAt: Date.now() }
4044
+ attrs: {
4045
+ stripeCustomerId: result.customerId,
4046
+ stripeSubscriptionId: result.subscriptionId,
4047
+ ...env.ODLA_RUNTIME ? { stripeRuntime: env.ODLA_RUNTIME } : {},
4048
+ refundPolicyAckAt: Date.now()
4049
+ }
3998
4050
  }
3999
4051
  ]);
4000
- return json({ clientSecret: result.clientSecret, publishableKey: group.stripePublishableKey ?? null, lineItems: lineItems(group) });
4052
+ return json({ clientSecret: result.clientSecret, publishableKey: group.stripePublishableKey ?? null, lineItems: paymentLineItems(group) });
4001
4053
  }
4002
4054
  async function ingestWebhook(req, env, ctx) {
4003
4055
  const db = ctx.makeDb(env);
4004
- const webhookSecret = await getVaultSecret(db, "stripe_webhook_secret");
4056
+ const webhookSecret = await getVaultSecret(db, stripeWebhookSecretName(env.ODLA_RUNTIME));
4005
4057
  if (!webhookSecret) return json({ error: "webhook not configured" }, 503);
4006
4058
  const rawBody = await req.text();
4007
4059
  const ingest = await createStripeProvider({ webhookSecret }).ingestWebhook(rawBody, req.headers.get("stripe-signature") ?? "");
4008
4060
  if (!ingest.ok) return json({ error: "invalid signature" }, 400);
4009
4061
  const { eventId, event } = ingest;
4010
4062
  if (event.kind === "ignored") return json({ ok: true, ignored: event.type });
4011
- const identifiedEnvironment = event.appId !== void 0 || event.environment !== void 0;
4012
- if (env.ODLA_APP_ID && identifiedEnvironment && (event.appId !== env.ODLA_APP_ID || event.environment !== env.ODLA_ENV)) {
4063
+ const identifiedEnvironment = event.appId !== void 0 || event.environment !== void 0 || event.dataEnvironment !== void 0 || event.runtime !== void 0;
4064
+ const eventDataEnvironment = event.dataEnvironment ?? event.environment;
4065
+ if (env.ODLA_APP_ID && identifiedEnvironment && (event.appId !== env.ODLA_APP_ID || eventDataEnvironment !== env.ODLA_ENV || event.runtime !== void 0 && event.runtime !== env.ODLA_RUNTIME)) {
4013
4066
  return json({ ok: true, matched: false });
4014
4067
  }
4015
4068
  if (event.kind === "gift_paid" || event.kind === "gift_revoked") {
@@ -4017,6 +4070,9 @@ async function ingestWebhook(req, env, ctx) {
4017
4070
  if (!intent || intent.chapterId !== ctx.chapter.id || intent.environment !== env.ODLA_ENV) {
4018
4071
  return json({ ok: true, matched: false });
4019
4072
  }
4073
+ if (event.runtime === void 0 && typeof intent.runtime === "string" && intent.runtime !== env.ODLA_RUNTIME) {
4074
+ return json({ ok: true, matched: false });
4075
+ }
4020
4076
  if (event.kind === "gift_paid") {
4021
4077
  if (intent.providerObjectId !== event.providerObjectId) return json({ ok: true, matched: false });
4022
4078
  if (intent.status === "refunded" || intent.status === "canceled") return json({ ok: true, matched: false });
@@ -4040,6 +4096,9 @@ async function ingestWebhook(req, env, ctx) {
4040
4096
  }
4041
4097
  const app = await findApplication(db, event);
4042
4098
  if (!app) return json({ ok: true, matched: false });
4099
+ if (event.runtime === void 0 && typeof app.stripeRuntime === "string" && app.stripeRuntime !== env.ODLA_RUNTIME) {
4100
+ return json({ ok: true, matched: false });
4101
+ }
4043
4102
  const patch = webhookPatch(event, String(app.status ?? ""));
4044
4103
  let next = app;
4045
4104
  if (Object.keys(patch).length) {
@@ -4111,7 +4170,8 @@ var handleGifts = async (req, url, env, ctx) => {
4111
4170
  return json({
4112
4171
  offers: publicGiftOffers(control3?.document.giftOffers, Date.now()),
4113
4172
  claims: buyerGiftClaims(rows2(found.giftClaims).sort((a, b) => Number(b.createdAt) - Number(a.createdAt)), Date.now()),
4114
- refundPolicyText: typeof group?.refundPolicyText === "string" ? group.refundPolicyText : ""
4173
+ refundPolicyText: typeof group?.refundPolicyText === "string" ? group.refundPolicyText : "",
4174
+ merchantDisclosureText: typeof group?.merchantDisclosureText === "string" ? group.merchantDisclosureText : ""
4115
4175
  });
4116
4176
  }
4117
4177
  if (req.method === "POST" && url.pathname === "/api/gifts/checkout") {
@@ -4142,6 +4202,7 @@ var handleGifts = async (req, url, env, ctx) => {
4142
4202
  offer,
4143
4203
  purchaserType: "member",
4144
4204
  purchaserEmail: user.email,
4205
+ ...env.ODLA_RUNTIME ? { runtime: env.ODLA_RUNTIME } : {},
4145
4206
  purchaserEmailNormalized: emailNormalized,
4146
4207
  purchaserUserId: user.userId,
4147
4208
  ...typeof app?.stripeCustomerId === "string" ? { existingCustomerId: app.stripeCustomerId } : {},