@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.js
CHANGED
|
@@ -1495,12 +1495,12 @@ function validateControlledApplication(chapter, document) {
|
|
|
1495
1495
|
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`);
|
|
1496
1496
|
}
|
|
1497
1497
|
function groupAttrs(document) {
|
|
1498
|
-
const
|
|
1498
|
+
const first5 = [...document.tiers].filter((tier) => tier.available).sort((a, b) => a.sortOrder - b.sortOrder)[0];
|
|
1499
1499
|
const firstPaid = [...document.tiers].find((tier) => tier.available && tier.priceCents > 0);
|
|
1500
1500
|
return {
|
|
1501
|
-
...
|
|
1502
|
-
currency:
|
|
1503
|
-
interval:
|
|
1501
|
+
...first5 ? { standardPriceCents: first5.priceCents } : {},
|
|
1502
|
+
currency: first5?.currency ?? "usd",
|
|
1503
|
+
interval: first5?.interval ?? "year",
|
|
1504
1504
|
foundingDiscountCents: 0,
|
|
1505
1505
|
...firstPaid?.stripePriceId ? { stripePriceId: firstPaid.stripePriceId } : {},
|
|
1506
1506
|
...document.policy ?? {},
|
|
@@ -1847,6 +1847,22 @@ async function notifyAdminOfApplication(db, env, chapterId, applicationId, field
|
|
|
1847
1847
|
}
|
|
1848
1848
|
}
|
|
1849
1849
|
|
|
1850
|
+
// src/worker-routes-health.ts
|
|
1851
|
+
var handleHealth = async (_req, url, env, ctx) => {
|
|
1852
|
+
if (url.pathname !== "/api/health") return null;
|
|
1853
|
+
const response = json({ ok: true });
|
|
1854
|
+
response.headers.set("cache-control", "no-store");
|
|
1855
|
+
for (const [name, value] of Object.entries({
|
|
1856
|
+
"x-odla-app-id": env.ODLA_APP_ID,
|
|
1857
|
+
"x-odla-chapter-id": ctx.chapter.id,
|
|
1858
|
+
"x-odla-data-environment": env.ODLA_ENV,
|
|
1859
|
+
"x-odla-runtime": env.ODLA_RUNTIME || "default"
|
|
1860
|
+
})) {
|
|
1861
|
+
if (typeof value === "string" && /^[a-z0-9][a-z0-9-]{0,62}$/.test(value)) response.headers.set(name, value);
|
|
1862
|
+
}
|
|
1863
|
+
return response;
|
|
1864
|
+
};
|
|
1865
|
+
|
|
1850
1866
|
// src/worker-routes.ts
|
|
1851
1867
|
async function memberSessionApplication(db, chapterId, identity) {
|
|
1852
1868
|
let apps = (await db.query({ applications: { $: {
|
|
@@ -1870,7 +1886,6 @@ async function memberSessionApplication(db, chapterId, identity) {
|
|
|
1870
1886
|
source: app
|
|
1871
1887
|
};
|
|
1872
1888
|
}
|
|
1873
|
-
var handleHealth = async (_req, url) => url.pathname === "/api/health" ? json({ ok: true }) : null;
|
|
1874
1889
|
var handleConfig = async (_req, url, env, ctx) => {
|
|
1875
1890
|
if (url.pathname !== "/api/config") return null;
|
|
1876
1891
|
try {
|
|
@@ -4468,12 +4483,130 @@ async function resolveStripeWebhookSecret(db, runtime) {
|
|
|
4468
4483
|
|
|
4469
4484
|
// src/payments-route-support.ts
|
|
4470
4485
|
var paymentErrorCode = (error) => error && typeof error === "object" && typeof error.code === "string" ? error.code : "unknown";
|
|
4471
|
-
function paymentLineItems(group) {
|
|
4486
|
+
function paymentLineItems(group, tier) {
|
|
4472
4487
|
const standard = Number(group.standardPriceCents ?? 0);
|
|
4473
4488
|
const discount = Number(group.foundingDiscountCents ?? 0);
|
|
4489
|
+
const legacyTier = !tier || tier.id === "standard" && tier.priceCents === standard && tier.stripePriceId === group.stripePriceId;
|
|
4490
|
+
if (!legacyTier) {
|
|
4491
|
+
return { standardCents: tier.priceCents, discountCents: 0, dueTodayCents: tier.priceCents };
|
|
4492
|
+
}
|
|
4474
4493
|
return { standardCents: standard, discountCents: discount, dueTodayCents: standard - discount };
|
|
4475
4494
|
}
|
|
4476
4495
|
|
|
4496
|
+
// src/subscription-checkout.ts
|
|
4497
|
+
var first3 = (value) => Array.isArray(value) ? value[0] : void 0;
|
|
4498
|
+
function mismatch() {
|
|
4499
|
+
const error = new Error("subscription checkout intent does not match this checkout");
|
|
4500
|
+
error.code = "checkout_intent_mismatch";
|
|
4501
|
+
throw error;
|
|
4502
|
+
}
|
|
4503
|
+
function intentAttrs(input) {
|
|
4504
|
+
return {
|
|
4505
|
+
id: input.applicationId,
|
|
4506
|
+
applicationId: input.applicationId,
|
|
4507
|
+
appId: input.appId,
|
|
4508
|
+
chapterId: input.chapterId,
|
|
4509
|
+
environment: input.environment,
|
|
4510
|
+
...input.runtime ? { runtime: input.runtime } : {},
|
|
4511
|
+
groupId: input.groupId,
|
|
4512
|
+
tierId: input.tierId,
|
|
4513
|
+
priceId: input.priceId,
|
|
4514
|
+
priceCents: input.priceCents,
|
|
4515
|
+
currency: input.currency,
|
|
4516
|
+
interval: input.interval,
|
|
4517
|
+
purchaserEmailNormalized: input.emailNormalized,
|
|
4518
|
+
...input.existingCustomerId ? { requestedCustomerId: input.existingCustomerId } : {},
|
|
4519
|
+
status: "created",
|
|
4520
|
+
createdAt: input.now,
|
|
4521
|
+
updatedAt: input.now
|
|
4522
|
+
};
|
|
4523
|
+
}
|
|
4524
|
+
function matchesIntent(stored, expected) {
|
|
4525
|
+
return [
|
|
4526
|
+
"id",
|
|
4527
|
+
"applicationId",
|
|
4528
|
+
"appId",
|
|
4529
|
+
"chapterId",
|
|
4530
|
+
"environment",
|
|
4531
|
+
"runtime",
|
|
4532
|
+
"groupId",
|
|
4533
|
+
"tierId",
|
|
4534
|
+
"priceId",
|
|
4535
|
+
"priceCents",
|
|
4536
|
+
"currency",
|
|
4537
|
+
"interval",
|
|
4538
|
+
"purchaserEmailNormalized",
|
|
4539
|
+
"requestedCustomerId"
|
|
4540
|
+
].every((key) => stored[key] === expected[key]);
|
|
4541
|
+
}
|
|
4542
|
+
async function loadIntent(db, id) {
|
|
4543
|
+
return first3((await db.query({
|
|
4544
|
+
subscriptionCheckoutIntents: { $: { where: { id }, limit: 1 } }
|
|
4545
|
+
})).subscriptionCheckoutIntents);
|
|
4546
|
+
}
|
|
4547
|
+
async function startSubscriptionCheckout(db, provider, input) {
|
|
4548
|
+
const expected = intentAttrs(input);
|
|
4549
|
+
let stored = await loadIntent(db, input.applicationId);
|
|
4550
|
+
if (!stored) {
|
|
4551
|
+
const write = await db.transact([{
|
|
4552
|
+
t: "update",
|
|
4553
|
+
ns: "subscriptionCheckoutIntents",
|
|
4554
|
+
id: input.applicationId,
|
|
4555
|
+
attrs: expected
|
|
4556
|
+
}], {
|
|
4557
|
+
mutationId: `subscription-checkout-intent:${input.applicationId}`,
|
|
4558
|
+
guards: [{ ns: "subscriptionCheckoutIntents", id: input.applicationId, exists: false }]
|
|
4559
|
+
});
|
|
4560
|
+
stored = write.duplicate ? await loadIntent(db, input.applicationId) : expected;
|
|
4561
|
+
}
|
|
4562
|
+
if (!stored || !matchesIntent(stored, expected)) mismatch();
|
|
4563
|
+
if (stored.status !== "created" && stored.status !== "checkout_ready") mismatch();
|
|
4564
|
+
const result = await provider.createSubscription({
|
|
4565
|
+
appId: input.appId,
|
|
4566
|
+
chapterId: input.chapterId,
|
|
4567
|
+
environment: input.environment,
|
|
4568
|
+
...input.runtime ? { runtime: input.runtime } : {},
|
|
4569
|
+
applicationId: input.applicationId,
|
|
4570
|
+
groupId: input.groupId,
|
|
4571
|
+
email: input.email,
|
|
4572
|
+
name: input.name,
|
|
4573
|
+
priceId: input.priceId,
|
|
4574
|
+
existingCustomerId: typeof stored.providerCustomerId === "string" ? stored.providerCustomerId : input.existingCustomerId
|
|
4575
|
+
});
|
|
4576
|
+
if (stored.providerCustomerId && stored.providerCustomerId !== result.customerId || stored.providerSubscriptionId && stored.providerSubscriptionId !== result.subscriptionId) mismatch();
|
|
4577
|
+
await db.transact([
|
|
4578
|
+
{
|
|
4579
|
+
t: "update",
|
|
4580
|
+
ns: "subscriptionCheckoutIntents",
|
|
4581
|
+
id: input.applicationId,
|
|
4582
|
+
attrs: {
|
|
4583
|
+
status: "checkout_ready",
|
|
4584
|
+
providerCustomerId: result.customerId,
|
|
4585
|
+
providerSubscriptionId: result.subscriptionId,
|
|
4586
|
+
updatedAt: input.now
|
|
4587
|
+
}
|
|
4588
|
+
},
|
|
4589
|
+
{
|
|
4590
|
+
t: "update",
|
|
4591
|
+
ns: "applications",
|
|
4592
|
+
id: input.applicationId,
|
|
4593
|
+
attrs: {
|
|
4594
|
+
stripeCustomerId: result.customerId,
|
|
4595
|
+
stripeSubscriptionId: result.subscriptionId,
|
|
4596
|
+
...input.runtime ? { stripeRuntime: input.runtime } : {},
|
|
4597
|
+
refundPolicyAckAt: input.now
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4600
|
+
], {
|
|
4601
|
+
mutationId: `subscription-checkout-ready:${input.applicationId}:${result.subscriptionId}`,
|
|
4602
|
+
guards: [
|
|
4603
|
+
{ ns: "subscriptionCheckoutIntents", id: input.applicationId, exists: true, equals: { status: stored.status } },
|
|
4604
|
+
{ ns: "applications", id: input.applicationId, exists: true, equals: { status: "submitted" } }
|
|
4605
|
+
]
|
|
4606
|
+
});
|
|
4607
|
+
return result;
|
|
4608
|
+
}
|
|
4609
|
+
|
|
4477
4610
|
// src/member-system-contract.ts
|
|
4478
4611
|
function classifyMemberStripeEvent(identity, target, hasProvenLegacyLocalLink) {
|
|
4479
4612
|
const keys = ["appId", "environment", "dataEnvironment", "runtime", "chapterId"];
|
|
@@ -4642,35 +4775,29 @@ async function startSubscription(req, env, ctx) {
|
|
|
4642
4775
|
const provider = createStripeProvider({ secretKey });
|
|
4643
4776
|
let result;
|
|
4644
4777
|
try {
|
|
4645
|
-
result = await provider
|
|
4778
|
+
result = await startSubscriptionCheckout(db, provider, {
|
|
4646
4779
|
appId: env.ODLA_APP_ID,
|
|
4647
4780
|
chapterId: ctx.chapter.id,
|
|
4648
4781
|
environment: env.ODLA_ENV,
|
|
4649
4782
|
runtime: stripeRuntimeIdentity(env.ODLA_RUNTIME),
|
|
4650
4783
|
applicationId,
|
|
4651
4784
|
groupId: String(group.id),
|
|
4785
|
+
tierId: tier.id,
|
|
4786
|
+
priceCents: tier.priceCents,
|
|
4787
|
+
currency: tier.currency ?? String(group.currency ?? "usd"),
|
|
4788
|
+
interval: tier.interval ?? (group.interval === "month" ? "month" : "year"),
|
|
4652
4789
|
email: String(app.email ?? ""),
|
|
4790
|
+
emailNormalized: normalizeChapterEmail(String(app.email ?? "")),
|
|
4653
4791
|
name: `${app.firstName ?? ""} ${app.lastName ?? ""}`.trim(),
|
|
4654
4792
|
priceId: String(priceId),
|
|
4655
|
-
existingCustomerId: typeof app.stripeCustomerId === "string" ? app.stripeCustomerId : void 0
|
|
4793
|
+
existingCustomerId: typeof app.stripeCustomerId === "string" ? app.stripeCustomerId : void 0,
|
|
4794
|
+
now: Date.now()
|
|
4656
4795
|
});
|
|
4657
4796
|
} catch (err) {
|
|
4658
|
-
|
|
4797
|
+
const code = paymentErrorCode(err);
|
|
4798
|
+
return json({ error: "payment setup failed", code }, code === "checkout_intent_mismatch" ? 409 : 502);
|
|
4659
4799
|
}
|
|
4660
|
-
|
|
4661
|
-
{
|
|
4662
|
-
t: "update",
|
|
4663
|
-
ns: "applications",
|
|
4664
|
-
id: applicationId,
|
|
4665
|
-
attrs: {
|
|
4666
|
-
stripeCustomerId: result.customerId,
|
|
4667
|
-
stripeSubscriptionId: result.subscriptionId,
|
|
4668
|
-
stripeRuntime: stripeRuntimeIdentity(env.ODLA_RUNTIME),
|
|
4669
|
-
refundPolicyAckAt: Date.now()
|
|
4670
|
-
}
|
|
4671
|
-
}
|
|
4672
|
-
]);
|
|
4673
|
-
return json({ clientSecret: result.clientSecret, publishableKey: group.stripePublishableKey ?? null, lineItems: paymentLineItems(group) });
|
|
4800
|
+
return json({ clientSecret: result.clientSecret, publishableKey: group.stripePublishableKey ?? null, lineItems: paymentLineItems(group, tier) });
|
|
4674
4801
|
}
|
|
4675
4802
|
async function ingestWebhook(req, env, ctx) {
|
|
4676
4803
|
const db = ctx.makeDb(env);
|
|
@@ -4782,18 +4909,18 @@ var handlePayments = async (req, url, env, ctx) => {
|
|
|
4782
4909
|
};
|
|
4783
4910
|
|
|
4784
4911
|
// src/worker-routes-gifts.ts
|
|
4785
|
-
var
|
|
4912
|
+
var first4 = (value) => Array.isArray(value) ? value[0] : void 0;
|
|
4786
4913
|
var rows2 = (value) => Array.isArray(value) ? value : [];
|
|
4787
4914
|
var invalid = () => json({ error: "gift code is invalid or unavailable" }, 400);
|
|
4788
4915
|
var userFor = async (req, env, ctx) => {
|
|
4789
4916
|
const user = await ctx.verifyUser(req, env);
|
|
4790
4917
|
return user?.email ? user : null;
|
|
4791
4918
|
};
|
|
4792
|
-
var loadClaim = async (db, id) =>
|
|
4919
|
+
var loadClaim = async (db, id) => first4((await db.query({ giftClaims: { $: { where: { id }, limit: 1 } } })).giftClaims);
|
|
4793
4920
|
async function consumeRateLimit(db, chapterId, requester, now) {
|
|
4794
4921
|
const bucket = await giftRequesterBucket(chapterId, requester);
|
|
4795
4922
|
const id = `gift-rate:${chapterId}:${bucket}`;
|
|
4796
|
-
const current =
|
|
4923
|
+
const current = first4((await db.query({ giftRedemptionAttempts: { $: { where: { id }, limit: 1 } } })).giftRedemptionAttempts);
|
|
4797
4924
|
const within = Boolean(current && now - Number(current.windowStartedAt) < 15 * 6e4);
|
|
4798
4925
|
const attemptCount = within ? Number(current?.attemptCount ?? 0) + 1 : 1;
|
|
4799
4926
|
if (attemptCount > 10) return false;
|
|
@@ -4818,7 +4945,7 @@ var handleGifts = async (req, url, env, ctx) => {
|
|
|
4818
4945
|
purchaserEmailNormalized: normalizeChapterEmail(user.email),
|
|
4819
4946
|
chapterId: ctx.chapter.id
|
|
4820
4947
|
}, limit: 200 } } });
|
|
4821
|
-
const group =
|
|
4948
|
+
const group = first4((await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups);
|
|
4822
4949
|
return json({
|
|
4823
4950
|
offers: publicGiftOffers(control3?.document.giftOffers, Date.now()),
|
|
4824
4951
|
claims: buyerGiftClaims(rows2(found.giftClaims).sort((a, b) => Number(b.createdAt) - Number(a.createdAt)), Date.now()),
|
|
@@ -4840,7 +4967,7 @@ var handleGifts = async (req, url, env, ctx) => {
|
|
|
4840
4967
|
const control3 = await loadActiveSignupControl(db, ctx.chapter.id, env.ODLA_ENV);
|
|
4841
4968
|
const offer = findGiftOffer(control3?.document.giftOffers, typeof body.offerId === "string" ? body.offerId : "", Date.now());
|
|
4842
4969
|
if (!offer) return json({ error: "gift offer is unavailable" }, 409);
|
|
4843
|
-
const group =
|
|
4970
|
+
const group = first4((await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups);
|
|
4844
4971
|
const stripeSecret = await getVaultSecret(db, "stripe_secret_key");
|
|
4845
4972
|
if (!group || !stripeSecret || typeof group.stripePublishableKey !== "string") return json({ error: "payments not configured" }, 503);
|
|
4846
4973
|
const emailNormalized = normalizeChapterEmail(user.email);
|
|
@@ -4909,16 +5036,16 @@ var handleGifts = async (req, url, env, ctx) => {
|
|
|
4909
5036
|
return invalid();
|
|
4910
5037
|
}
|
|
4911
5038
|
if (!/^gift1_[A-Za-z0-9_-]{43}$/.test(code) || !applicationId) return invalid();
|
|
4912
|
-
const app =
|
|
5039
|
+
const app = first4((await db.query({ applications: { $: { where: { id: applicationId }, limit: 1 } } })).applications);
|
|
4913
5040
|
const emailNormalized = normalizeChapterEmail(user.email);
|
|
4914
5041
|
if (!app || String(app.groupId ?? "") !== ctx.chapter.id || String(app.emailNormalized ?? "") !== emailNormalized || typeof app.clerkUserId === "string" && app.clerkUserId !== user.userId) return invalid();
|
|
4915
5042
|
const codeHash = await hashGiftCode(ctx.chapter.id, code);
|
|
4916
|
-
const claim =
|
|
5043
|
+
const claim = first4((await db.query({ giftClaims: { $: { where: { codeHash }, limit: 1 } } })).giftClaims);
|
|
4917
5044
|
if (!claim || claim.chapterId !== ctx.chapter.id) return invalid();
|
|
4918
5045
|
const sameRedemption = (candidate) => candidate.status === "redeemed" && candidate.applicationId === applicationId && candidate.beneficiaryEmailNormalized === emailNormalized && candidate.beneficiaryUserId === user.userId;
|
|
4919
5046
|
const success = async () => {
|
|
4920
|
-
const current =
|
|
4921
|
-
const group2 =
|
|
5047
|
+
const current = first4((await db.query({ applications: { $: { where: { id: applicationId }, limit: 1 } } })).applications);
|
|
5048
|
+
const group2 = first4((await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups);
|
|
4922
5049
|
const state = current && group2 ? await continueJoin(db, ctx, env, url.origin, current, group2) : null;
|
|
4923
5050
|
return json({
|
|
4924
5051
|
ok: true,
|
|
@@ -4937,7 +5064,7 @@ var handleGifts = async (req, url, env, ctx) => {
|
|
|
4937
5064
|
return invalid();
|
|
4938
5065
|
}
|
|
4939
5066
|
const target = claim.target;
|
|
4940
|
-
const group =
|
|
5067
|
+
const group = first4((await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups);
|
|
4941
5068
|
const tier = group ? findTier(await loadTiers(db, group), typeof app.tierId === "string" ? app.tierId : null) : void 0;
|
|
4942
5069
|
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();
|
|
4943
5070
|
const redeemed = await redeemGiftClaim(db, claim, {
|