@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.
- package/dist/index.cjs +25 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +25 -5
- package/dist/index.js.map +1 -1
- package/dist/worker/index.cjs +73 -21
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +3 -0
- package/dist/worker/index.d.ts +3 -0
- package/dist/worker/index.js +73 -21
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/runbooks/greenfield.md +9 -4
package/dist/worker/index.d.cts
CHANGED
|
@@ -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<{
|
package/dist/worker/index.d.ts
CHANGED
|
@@ -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<{
|
package/dist/worker/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
3397
|
+
return { kind: "gift_paid", giftIntentId, providerObjectId, ...identity };
|
|
3380
3398
|
}
|
|
3381
|
-
if (event.type === "charge.refunded") return { kind: "gift_revoked", giftIntentId
|
|
3382
|
-
if (event.type === "customer.subscription.deleted") return { kind: "gift_revoked", giftIntentId
|
|
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:
|
|
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: {
|
|
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:
|
|
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,
|
|
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
|
-
|
|
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 } : {},
|