@odla-ai/chapter 0.47.8 → 0.47.10
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 +28 -0
- package/dist/index.cjs +24 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +164 -46
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +10 -2
- package/dist/testing.d.ts +10 -2
- package/dist/testing.js +164 -46
- package/dist/testing.js.map +1 -1
- package/dist/worker/index.cjs +160 -33
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.js +160 -33
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
package/dist/worker/index.cjs
CHANGED
|
@@ -1525,12 +1525,12 @@ function validateControlledApplication(chapter, document) {
|
|
|
1525
1525
|
return [...document.application.required, ...document.application.optional].filter((field) => !declared.has(field)).map((field) => `document.application field "${field}" is not declared by this chapter schema`);
|
|
1526
1526
|
}
|
|
1527
1527
|
function groupAttrs(document) {
|
|
1528
|
-
const
|
|
1528
|
+
const first5 = [...document.tiers].filter((tier) => tier.available).sort((a, b) => a.sortOrder - b.sortOrder)[0];
|
|
1529
1529
|
const firstPaid = [...document.tiers].find((tier) => tier.available && tier.priceCents > 0);
|
|
1530
1530
|
return {
|
|
1531
|
-
...
|
|
1532
|
-
currency:
|
|
1533
|
-
interval:
|
|
1531
|
+
...first5 ? { standardPriceCents: first5.priceCents } : {},
|
|
1532
|
+
currency: first5?.currency ?? "usd",
|
|
1533
|
+
interval: first5?.interval ?? "year",
|
|
1534
1534
|
foundingDiscountCents: 0,
|
|
1535
1535
|
...firstPaid?.stripePriceId ? { stripePriceId: firstPaid.stripePriceId } : {},
|
|
1536
1536
|
...document.policy ?? {},
|
|
@@ -1877,6 +1877,22 @@ async function notifyAdminOfApplication(db, env, chapterId, applicationId, field
|
|
|
1877
1877
|
}
|
|
1878
1878
|
}
|
|
1879
1879
|
|
|
1880
|
+
// src/worker-routes-health.ts
|
|
1881
|
+
var handleHealth = async (_req, url, env, ctx) => {
|
|
1882
|
+
if (url.pathname !== "/api/health") return null;
|
|
1883
|
+
const response = json({ ok: true });
|
|
1884
|
+
response.headers.set("cache-control", "no-store");
|
|
1885
|
+
for (const [name, value] of Object.entries({
|
|
1886
|
+
"x-odla-app-id": env.ODLA_APP_ID,
|
|
1887
|
+
"x-odla-chapter-id": ctx.chapter.id,
|
|
1888
|
+
"x-odla-data-environment": env.ODLA_ENV,
|
|
1889
|
+
"x-odla-runtime": env.ODLA_RUNTIME || "default"
|
|
1890
|
+
})) {
|
|
1891
|
+
if (typeof value === "string" && /^[a-z0-9][a-z0-9-]{0,62}$/.test(value)) response.headers.set(name, value);
|
|
1892
|
+
}
|
|
1893
|
+
return response;
|
|
1894
|
+
};
|
|
1895
|
+
|
|
1880
1896
|
// src/worker-routes.ts
|
|
1881
1897
|
async function memberSessionApplication(db, chapterId, identity) {
|
|
1882
1898
|
let apps = (await db.query({ applications: { $: {
|
|
@@ -1900,7 +1916,6 @@ async function memberSessionApplication(db, chapterId, identity) {
|
|
|
1900
1916
|
source: app
|
|
1901
1917
|
};
|
|
1902
1918
|
}
|
|
1903
|
-
var handleHealth = async (_req, url) => url.pathname === "/api/health" ? json({ ok: true }) : null;
|
|
1904
1919
|
var handleConfig = async (_req, url, env, ctx) => {
|
|
1905
1920
|
if (url.pathname !== "/api/config") return null;
|
|
1906
1921
|
try {
|
|
@@ -4498,12 +4513,130 @@ async function resolveStripeWebhookSecret(db, runtime) {
|
|
|
4498
4513
|
|
|
4499
4514
|
// src/payments-route-support.ts
|
|
4500
4515
|
var paymentErrorCode = (error) => error && typeof error === "object" && typeof error.code === "string" ? error.code : "unknown";
|
|
4501
|
-
function paymentLineItems(group) {
|
|
4516
|
+
function paymentLineItems(group, tier) {
|
|
4502
4517
|
const standard = Number(group.standardPriceCents ?? 0);
|
|
4503
4518
|
const discount = Number(group.foundingDiscountCents ?? 0);
|
|
4519
|
+
const legacyTier = !tier || tier.id === "standard" && tier.priceCents === standard && tier.stripePriceId === group.stripePriceId;
|
|
4520
|
+
if (!legacyTier) {
|
|
4521
|
+
return { standardCents: tier.priceCents, discountCents: 0, dueTodayCents: tier.priceCents };
|
|
4522
|
+
}
|
|
4504
4523
|
return { standardCents: standard, discountCents: discount, dueTodayCents: standard - discount };
|
|
4505
4524
|
}
|
|
4506
4525
|
|
|
4526
|
+
// src/subscription-checkout.ts
|
|
4527
|
+
var first3 = (value) => Array.isArray(value) ? value[0] : void 0;
|
|
4528
|
+
function mismatch() {
|
|
4529
|
+
const error = new Error("subscription checkout intent does not match this checkout");
|
|
4530
|
+
error.code = "checkout_intent_mismatch";
|
|
4531
|
+
throw error;
|
|
4532
|
+
}
|
|
4533
|
+
function intentAttrs(input) {
|
|
4534
|
+
return {
|
|
4535
|
+
id: input.applicationId,
|
|
4536
|
+
applicationId: input.applicationId,
|
|
4537
|
+
appId: input.appId,
|
|
4538
|
+
chapterId: input.chapterId,
|
|
4539
|
+
environment: input.environment,
|
|
4540
|
+
...input.runtime ? { runtime: input.runtime } : {},
|
|
4541
|
+
groupId: input.groupId,
|
|
4542
|
+
tierId: input.tierId,
|
|
4543
|
+
priceId: input.priceId,
|
|
4544
|
+
priceCents: input.priceCents,
|
|
4545
|
+
currency: input.currency,
|
|
4546
|
+
interval: input.interval,
|
|
4547
|
+
purchaserEmailNormalized: input.emailNormalized,
|
|
4548
|
+
...input.existingCustomerId ? { requestedCustomerId: input.existingCustomerId } : {},
|
|
4549
|
+
status: "created",
|
|
4550
|
+
createdAt: input.now,
|
|
4551
|
+
updatedAt: input.now
|
|
4552
|
+
};
|
|
4553
|
+
}
|
|
4554
|
+
function matchesIntent(stored, expected) {
|
|
4555
|
+
return [
|
|
4556
|
+
"id",
|
|
4557
|
+
"applicationId",
|
|
4558
|
+
"appId",
|
|
4559
|
+
"chapterId",
|
|
4560
|
+
"environment",
|
|
4561
|
+
"runtime",
|
|
4562
|
+
"groupId",
|
|
4563
|
+
"tierId",
|
|
4564
|
+
"priceId",
|
|
4565
|
+
"priceCents",
|
|
4566
|
+
"currency",
|
|
4567
|
+
"interval",
|
|
4568
|
+
"purchaserEmailNormalized",
|
|
4569
|
+
"requestedCustomerId"
|
|
4570
|
+
].every((key) => stored[key] === expected[key]);
|
|
4571
|
+
}
|
|
4572
|
+
async function loadIntent(db, id) {
|
|
4573
|
+
return first3((await db.query({
|
|
4574
|
+
subscriptionCheckoutIntents: { $: { where: { id }, limit: 1 } }
|
|
4575
|
+
})).subscriptionCheckoutIntents);
|
|
4576
|
+
}
|
|
4577
|
+
async function startSubscriptionCheckout(db, provider, input) {
|
|
4578
|
+
const expected = intentAttrs(input);
|
|
4579
|
+
let stored = await loadIntent(db, input.applicationId);
|
|
4580
|
+
if (!stored) {
|
|
4581
|
+
const write = await db.transact([{
|
|
4582
|
+
t: "update",
|
|
4583
|
+
ns: "subscriptionCheckoutIntents",
|
|
4584
|
+
id: input.applicationId,
|
|
4585
|
+
attrs: expected
|
|
4586
|
+
}], {
|
|
4587
|
+
mutationId: `subscription-checkout-intent:${input.applicationId}`,
|
|
4588
|
+
guards: [{ ns: "subscriptionCheckoutIntents", id: input.applicationId, exists: false }]
|
|
4589
|
+
});
|
|
4590
|
+
stored = write.duplicate ? await loadIntent(db, input.applicationId) : expected;
|
|
4591
|
+
}
|
|
4592
|
+
if (!stored || !matchesIntent(stored, expected)) mismatch();
|
|
4593
|
+
if (stored.status !== "created" && stored.status !== "checkout_ready") mismatch();
|
|
4594
|
+
const result = await provider.createSubscription({
|
|
4595
|
+
appId: input.appId,
|
|
4596
|
+
chapterId: input.chapterId,
|
|
4597
|
+
environment: input.environment,
|
|
4598
|
+
...input.runtime ? { runtime: input.runtime } : {},
|
|
4599
|
+
applicationId: input.applicationId,
|
|
4600
|
+
groupId: input.groupId,
|
|
4601
|
+
email: input.email,
|
|
4602
|
+
name: input.name,
|
|
4603
|
+
priceId: input.priceId,
|
|
4604
|
+
existingCustomerId: typeof stored.providerCustomerId === "string" ? stored.providerCustomerId : input.existingCustomerId
|
|
4605
|
+
});
|
|
4606
|
+
if (stored.providerCustomerId && stored.providerCustomerId !== result.customerId || stored.providerSubscriptionId && stored.providerSubscriptionId !== result.subscriptionId) mismatch();
|
|
4607
|
+
await db.transact([
|
|
4608
|
+
{
|
|
4609
|
+
t: "update",
|
|
4610
|
+
ns: "subscriptionCheckoutIntents",
|
|
4611
|
+
id: input.applicationId,
|
|
4612
|
+
attrs: {
|
|
4613
|
+
status: "checkout_ready",
|
|
4614
|
+
providerCustomerId: result.customerId,
|
|
4615
|
+
providerSubscriptionId: result.subscriptionId,
|
|
4616
|
+
updatedAt: input.now
|
|
4617
|
+
}
|
|
4618
|
+
},
|
|
4619
|
+
{
|
|
4620
|
+
t: "update",
|
|
4621
|
+
ns: "applications",
|
|
4622
|
+
id: input.applicationId,
|
|
4623
|
+
attrs: {
|
|
4624
|
+
stripeCustomerId: result.customerId,
|
|
4625
|
+
stripeSubscriptionId: result.subscriptionId,
|
|
4626
|
+
...input.runtime ? { stripeRuntime: input.runtime } : {},
|
|
4627
|
+
refundPolicyAckAt: input.now
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4630
|
+
], {
|
|
4631
|
+
mutationId: `subscription-checkout-ready:${input.applicationId}:${result.subscriptionId}`,
|
|
4632
|
+
guards: [
|
|
4633
|
+
{ ns: "subscriptionCheckoutIntents", id: input.applicationId, exists: true, equals: { status: stored.status } },
|
|
4634
|
+
{ ns: "applications", id: input.applicationId, exists: true, equals: { status: "submitted" } }
|
|
4635
|
+
]
|
|
4636
|
+
});
|
|
4637
|
+
return result;
|
|
4638
|
+
}
|
|
4639
|
+
|
|
4507
4640
|
// src/member-system-contract.ts
|
|
4508
4641
|
function classifyMemberStripeEvent(identity, target, hasProvenLegacyLocalLink) {
|
|
4509
4642
|
const keys = ["appId", "environment", "dataEnvironment", "runtime", "chapterId"];
|
|
@@ -4672,35 +4805,29 @@ async function startSubscription(req, env, ctx) {
|
|
|
4672
4805
|
const provider = createStripeProvider({ secretKey });
|
|
4673
4806
|
let result;
|
|
4674
4807
|
try {
|
|
4675
|
-
result = await provider
|
|
4808
|
+
result = await startSubscriptionCheckout(db, provider, {
|
|
4676
4809
|
appId: env.ODLA_APP_ID,
|
|
4677
4810
|
chapterId: ctx.chapter.id,
|
|
4678
4811
|
environment: env.ODLA_ENV,
|
|
4679
4812
|
runtime: stripeRuntimeIdentity(env.ODLA_RUNTIME),
|
|
4680
4813
|
applicationId,
|
|
4681
4814
|
groupId: String(group.id),
|
|
4815
|
+
tierId: tier.id,
|
|
4816
|
+
priceCents: tier.priceCents,
|
|
4817
|
+
currency: tier.currency ?? String(group.currency ?? "usd"),
|
|
4818
|
+
interval: tier.interval ?? (group.interval === "month" ? "month" : "year"),
|
|
4682
4819
|
email: String(app.email ?? ""),
|
|
4820
|
+
emailNormalized: normalizeChapterEmail(String(app.email ?? "")),
|
|
4683
4821
|
name: `${app.firstName ?? ""} ${app.lastName ?? ""}`.trim(),
|
|
4684
4822
|
priceId: String(priceId),
|
|
4685
|
-
existingCustomerId: typeof app.stripeCustomerId === "string" ? app.stripeCustomerId : void 0
|
|
4823
|
+
existingCustomerId: typeof app.stripeCustomerId === "string" ? app.stripeCustomerId : void 0,
|
|
4824
|
+
now: Date.now()
|
|
4686
4825
|
});
|
|
4687
4826
|
} catch (err) {
|
|
4688
|
-
|
|
4827
|
+
const code = paymentErrorCode(err);
|
|
4828
|
+
return json({ error: "payment setup failed", code }, code === "checkout_intent_mismatch" ? 409 : 502);
|
|
4689
4829
|
}
|
|
4690
|
-
|
|
4691
|
-
{
|
|
4692
|
-
t: "update",
|
|
4693
|
-
ns: "applications",
|
|
4694
|
-
id: applicationId,
|
|
4695
|
-
attrs: {
|
|
4696
|
-
stripeCustomerId: result.customerId,
|
|
4697
|
-
stripeSubscriptionId: result.subscriptionId,
|
|
4698
|
-
stripeRuntime: stripeRuntimeIdentity(env.ODLA_RUNTIME),
|
|
4699
|
-
refundPolicyAckAt: Date.now()
|
|
4700
|
-
}
|
|
4701
|
-
}
|
|
4702
|
-
]);
|
|
4703
|
-
return json({ clientSecret: result.clientSecret, publishableKey: group.stripePublishableKey ?? null, lineItems: paymentLineItems(group) });
|
|
4830
|
+
return json({ clientSecret: result.clientSecret, publishableKey: group.stripePublishableKey ?? null, lineItems: paymentLineItems(group, tier) });
|
|
4704
4831
|
}
|
|
4705
4832
|
async function ingestWebhook(req, env, ctx) {
|
|
4706
4833
|
const db = ctx.makeDb(env);
|
|
@@ -4812,18 +4939,18 @@ var handlePayments = async (req, url, env, ctx) => {
|
|
|
4812
4939
|
};
|
|
4813
4940
|
|
|
4814
4941
|
// src/worker-routes-gifts.ts
|
|
4815
|
-
var
|
|
4942
|
+
var first4 = (value) => Array.isArray(value) ? value[0] : void 0;
|
|
4816
4943
|
var rows2 = (value) => Array.isArray(value) ? value : [];
|
|
4817
4944
|
var invalid = () => json({ error: "gift code is invalid or unavailable" }, 400);
|
|
4818
4945
|
var userFor = async (req, env, ctx) => {
|
|
4819
4946
|
const user = await ctx.verifyUser(req, env);
|
|
4820
4947
|
return user?.email ? user : null;
|
|
4821
4948
|
};
|
|
4822
|
-
var loadClaim = async (db, id) =>
|
|
4949
|
+
var loadClaim = async (db, id) => first4((await db.query({ giftClaims: { $: { where: { id }, limit: 1 } } })).giftClaims);
|
|
4823
4950
|
async function consumeRateLimit(db, chapterId, requester, now) {
|
|
4824
4951
|
const bucket = await giftRequesterBucket(chapterId, requester);
|
|
4825
4952
|
const id = `gift-rate:${chapterId}:${bucket}`;
|
|
4826
|
-
const current =
|
|
4953
|
+
const current = first4((await db.query({ giftRedemptionAttempts: { $: { where: { id }, limit: 1 } } })).giftRedemptionAttempts);
|
|
4827
4954
|
const within = Boolean(current && now - Number(current.windowStartedAt) < 15 * 6e4);
|
|
4828
4955
|
const attemptCount = within ? Number(current?.attemptCount ?? 0) + 1 : 1;
|
|
4829
4956
|
if (attemptCount > 10) return false;
|
|
@@ -4848,7 +4975,7 @@ var handleGifts = async (req, url, env, ctx) => {
|
|
|
4848
4975
|
purchaserEmailNormalized: normalizeChapterEmail(user.email),
|
|
4849
4976
|
chapterId: ctx.chapter.id
|
|
4850
4977
|
}, limit: 200 } } });
|
|
4851
|
-
const group =
|
|
4978
|
+
const group = first4((await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups);
|
|
4852
4979
|
return json({
|
|
4853
4980
|
offers: publicGiftOffers(control3?.document.giftOffers, Date.now()),
|
|
4854
4981
|
claims: buyerGiftClaims(rows2(found.giftClaims).sort((a, b) => Number(b.createdAt) - Number(a.createdAt)), Date.now()),
|
|
@@ -4870,7 +4997,7 @@ var handleGifts = async (req, url, env, ctx) => {
|
|
|
4870
4997
|
const control3 = await loadActiveSignupControl(db, ctx.chapter.id, env.ODLA_ENV);
|
|
4871
4998
|
const offer = findGiftOffer(control3?.document.giftOffers, typeof body.offerId === "string" ? body.offerId : "", Date.now());
|
|
4872
4999
|
if (!offer) return json({ error: "gift offer is unavailable" }, 409);
|
|
4873
|
-
const group =
|
|
5000
|
+
const group = first4((await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups);
|
|
4874
5001
|
const stripeSecret = await getVaultSecret(db, "stripe_secret_key");
|
|
4875
5002
|
if (!group || !stripeSecret || typeof group.stripePublishableKey !== "string") return json({ error: "payments not configured" }, 503);
|
|
4876
5003
|
const emailNormalized = normalizeChapterEmail(user.email);
|
|
@@ -4939,16 +5066,16 @@ var handleGifts = async (req, url, env, ctx) => {
|
|
|
4939
5066
|
return invalid();
|
|
4940
5067
|
}
|
|
4941
5068
|
if (!/^gift1_[A-Za-z0-9_-]{43}$/.test(code) || !applicationId) return invalid();
|
|
4942
|
-
const app =
|
|
5069
|
+
const app = first4((await db.query({ applications: { $: { where: { id: applicationId }, limit: 1 } } })).applications);
|
|
4943
5070
|
const emailNormalized = normalizeChapterEmail(user.email);
|
|
4944
5071
|
if (!app || String(app.groupId ?? "") !== ctx.chapter.id || String(app.emailNormalized ?? "") !== emailNormalized || typeof app.clerkUserId === "string" && app.clerkUserId !== user.userId) return invalid();
|
|
4945
5072
|
const codeHash = await hashGiftCode(ctx.chapter.id, code);
|
|
4946
|
-
const claim =
|
|
5073
|
+
const claim = first4((await db.query({ giftClaims: { $: { where: { codeHash }, limit: 1 } } })).giftClaims);
|
|
4947
5074
|
if (!claim || claim.chapterId !== ctx.chapter.id) return invalid();
|
|
4948
5075
|
const sameRedemption = (candidate) => candidate.status === "redeemed" && candidate.applicationId === applicationId && candidate.beneficiaryEmailNormalized === emailNormalized && candidate.beneficiaryUserId === user.userId;
|
|
4949
5076
|
const success = async () => {
|
|
4950
|
-
const current =
|
|
4951
|
-
const group2 =
|
|
5077
|
+
const current = first4((await db.query({ applications: { $: { where: { id: applicationId }, limit: 1 } } })).applications);
|
|
5078
|
+
const group2 = first4((await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups);
|
|
4952
5079
|
const state = current && group2 ? await continueJoin(db, ctx, env, url.origin, current, group2) : null;
|
|
4953
5080
|
return json({
|
|
4954
5081
|
ok: true,
|
|
@@ -4967,7 +5094,7 @@ var handleGifts = async (req, url, env, ctx) => {
|
|
|
4967
5094
|
return invalid();
|
|
4968
5095
|
}
|
|
4969
5096
|
const target = claim.target;
|
|
4970
|
-
const group =
|
|
5097
|
+
const group = first4((await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups);
|
|
4971
5098
|
const tier = group ? findTier(await loadTiers(db, group), typeof app.tierId === "string" ? app.tierId : null) : void 0;
|
|
4972
5099
|
if (String(app.status ?? "") !== "submitted" || typeof app.stripeSubscriptionId === "string" || typeof app.giftClaimId === "string" || !tier || tierIsFree(tier) || target?.type !== "tier" || target.tierId !== app.tierId) return invalid();
|
|
4973
5100
|
const redeemed = await redeemGiftClaim(db, claim, {
|