@odla-ai/chapter 0.39.2 → 0.40.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.
@@ -714,6 +714,9 @@ interface ChapterEnv {
714
714
  * reads fail closed when it is absent or stale. */
715
715
  ODLA_APP_INCARNATION?: string;
716
716
  ODLA_ENV: string;
717
+ /** Immutable developer-runtime identity. Omitted by legacy deployments whose
718
+ * runtime and data environment are the same. */
719
+ ODLA_RUNTIME?: string;
717
720
  ODLA_API_KEY: string;
718
721
  SEND_EMAIL?: {
719
722
  send(payload: EmailPayload): Promise<{
@@ -714,6 +714,9 @@ interface ChapterEnv {
714
714
  * reads fail closed when it is absent or stale. */
715
715
  ODLA_APP_INCARNATION?: string;
716
716
  ODLA_ENV: string;
717
+ /** Immutable developer-runtime identity. Omitted by legacy deployments whose
718
+ * runtime and data environment are the same. */
719
+ ODLA_RUNTIME?: string;
717
720
  ODLA_API_KEY: string;
718
721
  SEND_EMAIL?: {
719
722
  send(payload: EmailPayload): Promise<{
@@ -3354,12 +3354,16 @@ function findApplicationRef(obj) {
3354
3354
  const applicationId = candidates.map(pick).find(Boolean);
3355
3355
  const appId = candidates.map((metadata) => typeof metadata.odlaAppId === "string" ? metadata.odlaAppId : void 0).find(Boolean);
3356
3356
  const environment = candidates.map((metadata) => typeof metadata.odlaEnvironment === "string" ? metadata.odlaEnvironment : void 0).find(Boolean);
3357
+ const dataEnvironment = candidates.map((metadata) => typeof metadata.odlaDataEnvironment === "string" ? metadata.odlaDataEnvironment : void 0).find(Boolean);
3358
+ const runtime = candidates.map((metadata) => typeof metadata.odlaRuntime === "string" ? metadata.odlaRuntime : void 0).find(Boolean);
3357
3359
  const customerId = typeof obj.customer === "string" ? obj.customer : void 0;
3358
3360
  return {
3359
3361
  ...applicationId ? { applicationId } : {},
3360
3362
  ...customerId ? { customerId } : {},
3361
3363
  ...appId ? { appId } : {},
3362
- ...environment ? { environment } : {}
3364
+ ...environment ? { environment } : {},
3365
+ ...dataEnvironment ? { dataEnvironment } : {},
3366
+ ...runtime ? { runtime } : {}
3363
3367
  };
3364
3368
  }
3365
3369
  function findGiftRef(obj) {
@@ -3369,17 +3373,31 @@ function findGiftRef(obj) {
3369
3373
  const subscription = obj.subscription_details ?? parent?.subscription_details;
3370
3374
  const giftIntentId = pick(metaOf(obj)) ?? pick(metaOf(subscription));
3371
3375
  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 ?? "") };
3376
+ const candidates = [metaOf(obj), metaOf(subscription)];
3377
+ const pickIdentity = (name) => candidates.map((metadata) => typeof metadata[name] === "string" ? String(metadata[name]) : void 0).find(Boolean);
3378
+ const appId = pickIdentity("odlaAppId");
3379
+ const environment = pickIdentity("odlaEnvironment");
3380
+ const dataEnvironment = pickIdentity("odlaDataEnvironment");
3381
+ const runtime = pickIdentity("odlaRuntime");
3382
+ return {
3383
+ ...giftIntentId ? { giftIntentId } : {},
3384
+ providerObjectId: subscriptionId ?? String(obj.id ?? ""),
3385
+ ...appId ? { appId } : {},
3386
+ ...environment ? { environment } : {},
3387
+ ...dataEnvironment ? { dataEnvironment } : {},
3388
+ ...runtime ? { runtime } : {}
3389
+ };
3373
3390
  }
3374
3391
  function normalizeWebhookEvent(event) {
3375
3392
  const obj = event.data?.object ?? {};
3376
3393
  const gift = findGiftRef(obj);
3377
3394
  if (gift.giftIntentId) {
3395
+ const { giftIntentId, providerObjectId, ...identity } = gift;
3378
3396
  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 };
3397
+ return { kind: "gift_paid", giftIntentId, providerObjectId, ...identity };
3380
3398
  }
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" };
3399
+ if (event.type === "charge.refunded") return { kind: "gift_revoked", giftIntentId, ...identity, reason: "refunded" };
3400
+ if (event.type === "customer.subscription.deleted") return { kind: "gift_revoked", giftIntentId, ...identity, reason: "canceled" };
3383
3401
  return { kind: "ignored", type: event.type };
3384
3402
  }
3385
3403
  const ref = findApplicationRef(obj);
@@ -3456,7 +3474,9 @@ function createStripeProvider(config) {
3456
3474
  groupId: input.groupId,
3457
3475
  email: input.email,
3458
3476
  odlaAppId: input.appId,
3459
- odlaEnvironment: input.environment
3477
+ odlaEnvironment: input.environment,
3478
+ odlaDataEnvironment: input.environment,
3479
+ ...input.runtime ? { odlaRuntime: input.runtime } : {}
3460
3480
  };
3461
3481
  let customerId = input.existingCustomerId;
3462
3482
  if (!customerId) {
@@ -3744,12 +3764,16 @@ var clientSecret = (value) => {
3744
3764
  const nested = confirmation?.client_secret ?? intent?.client_secret;
3745
3765
  return typeof nested === "string" && nested ? nested : null;
3746
3766
  };
3747
- async function startGiftProviderCheckout(secretKey, intent, email, existingCustomerId) {
3767
+ async function startGiftProviderCheckout(secretKey, intent, email, existingCustomerId, runtime) {
3748
3768
  const offer = intent.offerSnapshot;
3749
3769
  const metadata = {
3750
3770
  giftIntentId: intent.id,
3751
3771
  chapterId: intent.chapterId,
3752
3772
  environment: intent.environment,
3773
+ odlaAppId: intent.chapterId,
3774
+ odlaEnvironment: intent.environment,
3775
+ odlaDataEnvironment: intent.environment,
3776
+ ...runtime ? { odlaRuntime: runtime } : {},
3753
3777
  offerId: intent.offerId
3754
3778
  };
3755
3779
  let customerId = existingCustomerId;
@@ -3799,6 +3823,7 @@ async function startGiftIntent(db, input) {
3799
3823
  id: input.id,
3800
3824
  chapterId: input.chapterId,
3801
3825
  environment: input.environment,
3826
+ ...input.runtime ? { runtime: input.runtime } : {},
3802
3827
  purchaserType: input.purchaserType,
3803
3828
  purchaserEmailNormalized: input.purchaserEmailNormalized,
3804
3829
  ...input.purchaserApplicationId ? { purchaserApplicationId: input.purchaserApplicationId } : {},
@@ -3818,7 +3843,7 @@ async function startGiftIntent(db, input) {
3818
3843
  stored = write.duplicate ? first2((await db.query({ giftPurchaseIntents: { $: { where: { id: input.id }, limit: 1 } } })).giftPurchaseIntents) : attrs;
3819
3844
  if (!write.duplicate) giftCode = prepared.code;
3820
3845
  }
3821
- if (!stored || stored.chapterId !== input.chapterId || stored.environment !== input.environment || stored.offerId !== input.offer.id || stored.purchaserEmailNormalized !== input.purchaserEmailNormalized) {
3846
+ if (!stored || stored.chapterId !== input.chapterId || stored.environment !== input.environment || stored.runtime !== input.runtime || stored.offerId !== input.offer.id || stored.purchaserEmailNormalized !== input.purchaserEmailNormalized) {
3822
3847
  throw new Error("gift purchase intent does not match this checkout");
3823
3848
  }
3824
3849
  if (stored.status === "paid") throw new Error("gift purchase is already paid");
@@ -3826,7 +3851,8 @@ async function startGiftIntent(db, input) {
3826
3851
  input.stripeSecret,
3827
3852
  stored,
3828
3853
  input.purchaserEmail,
3829
- typeof stored.providerCustomerId === "string" ? stored.providerCustomerId : input.existingCustomerId
3854
+ typeof stored.providerCustomerId === "string" ? stored.providerCustomerId : input.existingCustomerId,
3855
+ input.runtime
3830
3856
  );
3831
3857
  await db.transact([{ t: "update", ns: "giftPurchaseIntents", id: input.id, attrs: {
3832
3858
  status: "checkout_ready",
@@ -3845,18 +3871,29 @@ async function startGiftIntent(db, input) {
3845
3871
  };
3846
3872
  }
3847
3873
 
3874
+ // src/stripe-runtime.ts
3875
+ function stripeWebhookSecretName(runtime) {
3876
+ if (!runtime) return "stripe_webhook_secret";
3877
+ if (!/^[a-z0-9]{2,12}$/.test(runtime) || runtime === "prod" || runtime === "production") {
3878
+ throw new Error("ODLA_RUNTIME is invalid");
3879
+ }
3880
+ return `stripe_webhook_secret__${runtime}`;
3881
+ }
3882
+
3883
+ // src/payments-route-support.ts
3884
+ var paymentErrorCode = (error) => error && typeof error === "object" && typeof error.code === "string" ? error.code : "unknown";
3885
+ function paymentLineItems(group) {
3886
+ const standard = Number(group.standardPriceCents ?? 0);
3887
+ const discount = Number(group.foundingDiscountCents ?? 0);
3888
+ return { standardCents: standard, discountCents: discount, dueTodayCents: standard - discount };
3889
+ }
3890
+
3848
3891
  // src/worker-routes-payments.ts
3849
- var codeOf = (err) => err && typeof err === "object" && typeof err.code === "string" ? err.code : "unknown";
3850
3892
  async function firstRow2(db, ns, q) {
3851
3893
  const res = await db.query({ [ns]: { $: q } });
3852
3894
  const rows4 = res[ns];
3853
3895
  return Array.isArray(rows4) ? rows4[0] : void 0;
3854
3896
  }
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
3897
  async function findApplication(db, event) {
3861
3898
  if ("applicationId" in event && event.applicationId) {
3862
3899
  return firstRow2(db, "applications", { where: { id: event.applicationId }, limit: 1 });
@@ -3947,6 +3984,7 @@ async function startSubscription(req, env, ctx) {
3947
3984
  chapterId: ctx.chapter.id,
3948
3985
  environment: env.ODLA_ENV,
3949
3986
  offer,
3987
+ ...env.ODLA_RUNTIME ? { runtime: env.ODLA_RUNTIME } : {},
3950
3988
  purchaserType: "application",
3951
3989
  purchaserEmail: String(app.email ?? ""),
3952
3990
  purchaserEmailNormalized: normalizeChapterEmail(String(app.email ?? "")),
@@ -3979,6 +4017,7 @@ async function startSubscription(req, env, ctx) {
3979
4017
  result = await provider.createSubscription({
3980
4018
  appId: env.ODLA_APP_ID,
3981
4019
  environment: env.ODLA_ENV,
4020
+ ...env.ODLA_RUNTIME ? { runtime: env.ODLA_RUNTIME } : {},
3982
4021
  applicationId,
3983
4022
  groupId: String(group.id),
3984
4023
  email: String(app.email ?? ""),
@@ -3987,29 +4026,35 @@ async function startSubscription(req, env, ctx) {
3987
4026
  existingCustomerId: typeof app.stripeCustomerId === "string" ? app.stripeCustomerId : void 0
3988
4027
  });
3989
4028
  } catch (err) {
3990
- return json({ error: "payment setup failed", code: codeOf(err) }, 502);
4029
+ return json({ error: "payment setup failed", code: paymentErrorCode(err) }, 502);
3991
4030
  }
3992
4031
  await db.transact([
3993
4032
  {
3994
4033
  t: "update",
3995
4034
  ns: "applications",
3996
4035
  id: applicationId,
3997
- attrs: { stripeCustomerId: result.customerId, stripeSubscriptionId: result.subscriptionId, refundPolicyAckAt: Date.now() }
4036
+ attrs: {
4037
+ stripeCustomerId: result.customerId,
4038
+ stripeSubscriptionId: result.subscriptionId,
4039
+ ...env.ODLA_RUNTIME ? { stripeRuntime: env.ODLA_RUNTIME } : {},
4040
+ refundPolicyAckAt: Date.now()
4041
+ }
3998
4042
  }
3999
4043
  ]);
4000
- return json({ clientSecret: result.clientSecret, publishableKey: group.stripePublishableKey ?? null, lineItems: lineItems(group) });
4044
+ return json({ clientSecret: result.clientSecret, publishableKey: group.stripePublishableKey ?? null, lineItems: paymentLineItems(group) });
4001
4045
  }
4002
4046
  async function ingestWebhook(req, env, ctx) {
4003
4047
  const db = ctx.makeDb(env);
4004
- const webhookSecret = await getVaultSecret(db, "stripe_webhook_secret");
4048
+ const webhookSecret = await getVaultSecret(db, stripeWebhookSecretName(env.ODLA_RUNTIME));
4005
4049
  if (!webhookSecret) return json({ error: "webhook not configured" }, 503);
4006
4050
  const rawBody = await req.text();
4007
4051
  const ingest = await createStripeProvider({ webhookSecret }).ingestWebhook(rawBody, req.headers.get("stripe-signature") ?? "");
4008
4052
  if (!ingest.ok) return json({ error: "invalid signature" }, 400);
4009
4053
  const { eventId, event } = ingest;
4010
4054
  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)) {
4055
+ const identifiedEnvironment = event.appId !== void 0 || event.environment !== void 0 || event.dataEnvironment !== void 0 || event.runtime !== void 0;
4056
+ const eventDataEnvironment = event.dataEnvironment ?? event.environment;
4057
+ 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
4058
  return json({ ok: true, matched: false });
4014
4059
  }
4015
4060
  if (event.kind === "gift_paid" || event.kind === "gift_revoked") {
@@ -4017,6 +4062,9 @@ async function ingestWebhook(req, env, ctx) {
4017
4062
  if (!intent || intent.chapterId !== ctx.chapter.id || intent.environment !== env.ODLA_ENV) {
4018
4063
  return json({ ok: true, matched: false });
4019
4064
  }
4065
+ if (event.runtime === void 0 && typeof intent.runtime === "string" && intent.runtime !== env.ODLA_RUNTIME) {
4066
+ return json({ ok: true, matched: false });
4067
+ }
4020
4068
  if (event.kind === "gift_paid") {
4021
4069
  if (intent.providerObjectId !== event.providerObjectId) return json({ ok: true, matched: false });
4022
4070
  if (intent.status === "refunded" || intent.status === "canceled") return json({ ok: true, matched: false });
@@ -4040,6 +4088,9 @@ async function ingestWebhook(req, env, ctx) {
4040
4088
  }
4041
4089
  const app = await findApplication(db, event);
4042
4090
  if (!app) return json({ ok: true, matched: false });
4091
+ if (event.runtime === void 0 && typeof app.stripeRuntime === "string" && app.stripeRuntime !== env.ODLA_RUNTIME) {
4092
+ return json({ ok: true, matched: false });
4093
+ }
4043
4094
  const patch = webhookPatch(event, String(app.status ?? ""));
4044
4095
  let next = app;
4045
4096
  if (Object.keys(patch).length) {
@@ -4142,6 +4193,7 @@ var handleGifts = async (req, url, env, ctx) => {
4142
4193
  offer,
4143
4194
  purchaserType: "member",
4144
4195
  purchaserEmail: user.email,
4196
+ ...env.ODLA_RUNTIME ? { runtime: env.ODLA_RUNTIME } : {},
4145
4197
  purchaserEmailNormalized: emailNormalized,
4146
4198
  purchaserUserId: user.userId,
4147
4199
  ...typeof app?.stripeCustomerId === "string" ? { existingCustomerId: app.stripeCustomerId } : {},