@odla-ai/chapter 0.53.0 → 0.54.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/README.md +28 -1
- package/dist/{chunk-PTC43J7F.js → chunk-NDZCR3OW.js} +5 -5
- package/dist/chunk-NDZCR3OW.js.map +1 -0
- package/dist/{chunk-TEHCORNC.js → chunk-SPEL4OGE.js} +57 -23
- package/dist/chunk-SPEL4OGE.js.map +1 -0
- package/dist/index.cjs +219 -106
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -10
- package/dist/index.d.ts +30 -10
- package/dist/index.js +219 -106
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.js +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +8 -2
- package/dist/ui/member/index.d.ts +32 -1
- package/dist/ui/member/index.js +7 -1
- package/dist/worker/index.cjs +296 -128
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +84 -9
- package/dist/worker/index.d.ts +84 -9
- package/dist/worker/index.js +296 -128
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-PTC43J7F.js.map +0 -1
- package/dist/chunk-TEHCORNC.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -3565,8 +3565,8 @@ var object = (value) => {
|
|
|
3565
3565
|
var id6 = (value) => typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9_.:-]{2,159}$/.test(value) ? value : fail2("id");
|
|
3566
3566
|
var integer2 = (value, min, max) => typeof value === "number" && Number.isSafeInteger(value) && value >= min && value <= max ? value : fail2("integer");
|
|
3567
3567
|
var time = (value) => {
|
|
3568
|
-
const
|
|
3569
|
-
return integer2(
|
|
3568
|
+
const at2 = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : NaN;
|
|
3569
|
+
return integer2(at2, 0, 864e13);
|
|
3570
3570
|
};
|
|
3571
3571
|
var ids = (value, allowEmpty = false) => {
|
|
3572
3572
|
if (!Array.isArray(value) || value.length > 100 || !allowEmpty && value.length === 0) return fail2("ids");
|
|
@@ -3610,7 +3610,7 @@ function membershipOfferingVersion(value) {
|
|
|
3610
3610
|
term: normalizedTerm,
|
|
3611
3611
|
autoRenew: true,
|
|
3612
3612
|
paymentGraceDays: integer2(input.paymentGraceDays, 0, 60),
|
|
3613
|
-
seatPriceCents: integer2(input.seatPriceCents,
|
|
3613
|
+
seatPriceCents: integer2(input.seatPriceCents, 0, Number.MAX_SAFE_INTEGER),
|
|
3614
3614
|
...input.seatPurchaseWindowCalendarMonths === void 0 || input.seatPurchaseWindowCalendarMonths === null ? {} : { seatPurchaseWindowCalendarMonths: integer2(input.seatPurchaseWindowCalendarMonths, 1, 120) },
|
|
3615
3615
|
requiredDisclosures: ids(input.requiredDisclosures)
|
|
3616
3616
|
};
|
|
@@ -4382,6 +4382,181 @@ async function refundNetworkMembership(db, input, secretKey, now) {
|
|
|
4382
4382
|
// src/membership-provider-lifecycle.ts
|
|
4383
4383
|
var import_stripe4 = require("@odla-ai/stripe");
|
|
4384
4384
|
|
|
4385
|
+
// src/membership-seat-reservation.ts
|
|
4386
|
+
async function recipientKey(input, email) {
|
|
4387
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([input.appId, input.chapterId, input.environment, input.runtime, email])));
|
|
4388
|
+
return `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
4389
|
+
}
|
|
4390
|
+
async function reserveNamedMembershipSeat(db, input, now) {
|
|
4391
|
+
membershipClock(now);
|
|
4392
|
+
const name = typeof input.recipientName === "string" ? input.recipientName.trim().replace(/\s+/g, " ") : "";
|
|
4393
|
+
const email = normalizeChapterEmail(typeof input.recipientEmail === "string" ? input.recipientEmail : "");
|
|
4394
|
+
const purchaser = normalizeChapterEmail(typeof input.purchaserEmail === "string" ? input.purchaserEmail : "");
|
|
4395
|
+
if (name.length < 2 || name.length > 160 || /[\u0000-\u001f\u007f]/.test(input.recipientName) || email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) || !purchaser || email === purchaser || !/^price_[A-Za-z0-9_]+$/.test(input.priceId) || !/^user_[A-Za-z0-9_]+$/.test(input.purchaserUserId)) membershipFail("invalid_named_seat_recipient");
|
|
4396
|
+
const state = await loadMembershipAuthorityState(db, input);
|
|
4397
|
+
const memberId = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", memberId);
|
|
4398
|
+
requireMembershipScope(member, input);
|
|
4399
|
+
if (!Number.isSafeInteger(member.revision) || !/^price_[A-Za-z0-9_]+$/.test(String(member.providerPriceId)) || member.providerPriceId === input.priceId) membershipFail("named_seat_purchase_unavailable");
|
|
4400
|
+
const id10 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
|
|
4401
|
+
let seat = await membershipLoad(db, "networkSeat", id10);
|
|
4402
|
+
if (!seat) {
|
|
4403
|
+
const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
|
|
4404
|
+
const purchaseEndsAt = seatPurchaseDeadline(offering, member);
|
|
4405
|
+
if (!["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) || Number(member.refundedCents) !== 0 || now < Number(member.initialTermStartedAt) || now >= Number(member.termEndsAt) || now > purchaseEndsAt || !Number.isSafeInteger(purchaseEndsAt) || member.namedSeatId !== void 0) {
|
|
4406
|
+
membershipFail("named_seat_purchase_unavailable");
|
|
4407
|
+
}
|
|
4408
|
+
const attrs = {
|
|
4409
|
+
id: id10,
|
|
4410
|
+
...membershipScope(input),
|
|
4411
|
+
primaryMembershipId: memberId,
|
|
4412
|
+
recipientName: name,
|
|
4413
|
+
recipientEmailNormalized: email,
|
|
4414
|
+
purchaserUserId: input.purchaserUserId,
|
|
4415
|
+
recipientClaimId: claimId,
|
|
4416
|
+
priceId: input.priceId,
|
|
4417
|
+
primaryPriceId: member.providerPriceId,
|
|
4418
|
+
amountCents: offering.seatPriceCents,
|
|
4419
|
+
currency: offering.currency,
|
|
4420
|
+
interval: offering.interval,
|
|
4421
|
+
providerCustomerId: member.providerCustomerId,
|
|
4422
|
+
providerSubscriptionId: member.providerSubscriptionId,
|
|
4423
|
+
status: "payment_pending",
|
|
4424
|
+
preparedAt: now,
|
|
4425
|
+
termEndsAt: member.termEndsAt,
|
|
4426
|
+
seatPurchaseEndsAt: purchaseEndsAt,
|
|
4427
|
+
offeringSnapshot: offering,
|
|
4428
|
+
revision: 1,
|
|
4429
|
+
createdAt: now,
|
|
4430
|
+
updatedAt: now
|
|
4431
|
+
};
|
|
4432
|
+
try {
|
|
4433
|
+
await db.transact([
|
|
4434
|
+
membershipUpdate("networkSeat", id10, attrs),
|
|
4435
|
+
membershipUpdate("networkSeatRecipient", claimId, {
|
|
4436
|
+
id: claimId,
|
|
4437
|
+
appId: input.appId,
|
|
4438
|
+
chapterId: input.chapterId,
|
|
4439
|
+
environment: input.environment,
|
|
4440
|
+
runtime: input.runtime,
|
|
4441
|
+
seatId: id10,
|
|
4442
|
+
recipientEmailNormalized: email,
|
|
4443
|
+
createdAt: now
|
|
4444
|
+
}),
|
|
4445
|
+
membershipUpdate("networkMembership", memberId, { namedSeatId: id10, revision: Number(member.revision) + 1, updatedAt: now })
|
|
4446
|
+
], { mutationId: `named-seat-reserve:${id10}`, guards: [
|
|
4447
|
+
{ ns: "networkSeat", id: id10, exists: false },
|
|
4448
|
+
{ ns: "networkSeatRecipient", id: claimId, exists: false },
|
|
4449
|
+
{ ns: "networkMembership", id: memberId, exists: true, equals: { revision: member.revision }, absent: ["namedSeatId"] },
|
|
4450
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
4451
|
+
] });
|
|
4452
|
+
} catch (error) {
|
|
4453
|
+
if (error.code !== "transact_guard_failed") throw error;
|
|
4454
|
+
}
|
|
4455
|
+
seat = await membershipLoad(db, "networkSeat", id10);
|
|
4456
|
+
if (!seat && await membershipLoad(db, "networkSeatRecipient", claimId)) membershipFail("named_seat_recipient_unavailable");
|
|
4457
|
+
}
|
|
4458
|
+
requireMembershipScope(seat, input);
|
|
4459
|
+
if (seat.primaryMembershipId !== memberId || seat.recipientName !== name || seat.recipientEmailNormalized !== email || seat.priceId !== input.priceId || seat.purchaserUserId !== input.purchaserUserId) membershipFail("named_seat_purchase_conflict");
|
|
4460
|
+
return seat;
|
|
4461
|
+
}
|
|
4462
|
+
|
|
4463
|
+
// src/membership-seat-included.ts
|
|
4464
|
+
var namedSeatIncluded = (seat) => Number(seat.amountCents) === 0;
|
|
4465
|
+
var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean(seat.providerPaymentIntentId && seat.providerSubscriptionItemId);
|
|
4466
|
+
function namedSeatRecipientState(seat) {
|
|
4467
|
+
switch (String(seat.status)) {
|
|
4468
|
+
case "renewal_pending":
|
|
4469
|
+
case "pending_acceptance":
|
|
4470
|
+
return "invited";
|
|
4471
|
+
case "pending_approval":
|
|
4472
|
+
case "pending_primary":
|
|
4473
|
+
return "accepted";
|
|
4474
|
+
case "active":
|
|
4475
|
+
return "joined";
|
|
4476
|
+
case "refund_pending":
|
|
4477
|
+
case "refunded":
|
|
4478
|
+
return seat.refundReason === "declined" ? "declined" : "refunded";
|
|
4479
|
+
case "canceled":
|
|
4480
|
+
return "canceled";
|
|
4481
|
+
default:
|
|
4482
|
+
return "payment_pending";
|
|
4483
|
+
}
|
|
4484
|
+
}
|
|
4485
|
+
var OPEN = ["pending_acceptance", "pending_approval", "pending_primary", "active"];
|
|
4486
|
+
async function grantIncludedNamedSeat(db, input, now) {
|
|
4487
|
+
membershipClock(now);
|
|
4488
|
+
const seat = await reserveNamedMembershipSeat(db, input, now);
|
|
4489
|
+
if (!namedSeatIncluded(seat)) membershipFail("named_seat_not_included");
|
|
4490
|
+
const state = await loadMembershipAuthorityState(db, input);
|
|
4491
|
+
const member = await membershipLoad(db, "networkMembership", seat.primaryMembershipId);
|
|
4492
|
+
requireMembershipScope(member, input);
|
|
4493
|
+
if (!["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) || Number(member.refundedCents) !== 0 || member.namedSeatId !== seat.id || now >= Number(member.termEndsAt)) {
|
|
4494
|
+
membershipFail("named_seat_purchase_unavailable");
|
|
4495
|
+
}
|
|
4496
|
+
if (seat.status === "payment_pending") {
|
|
4497
|
+
const entitlementId = `${seat.id}:entitlement`, inviteId = `${seat.id}:invite`, source = `included:${seat.id}`;
|
|
4498
|
+
try {
|
|
4499
|
+
await db.transact([
|
|
4500
|
+
membershipUpdate("networkSeat", seat.id, {
|
|
4501
|
+
status: "pending_acceptance",
|
|
4502
|
+
purchasedAt: now,
|
|
4503
|
+
termStartedAt: now,
|
|
4504
|
+
termEndsAt: member.termEndsAt,
|
|
4505
|
+
acceptanceEndsAt: Number(member.termEndsAt),
|
|
4506
|
+
entitlementId,
|
|
4507
|
+
revision: Number(seat.revision) + 1,
|
|
4508
|
+
updatedAt: now
|
|
4509
|
+
}),
|
|
4510
|
+
membershipUpdate("networkEntitlement", entitlementId, {
|
|
4511
|
+
id: entitlementId,
|
|
4512
|
+
...membershipScope(input),
|
|
4513
|
+
membershipId: member.id,
|
|
4514
|
+
seatId: seat.id,
|
|
4515
|
+
kind: "named_seat",
|
|
4516
|
+
status: "pending_acceptance",
|
|
4517
|
+
termStartedAt: now,
|
|
4518
|
+
termEndsAt: member.termEndsAt,
|
|
4519
|
+
offeringSnapshot: member.offeringSnapshot,
|
|
4520
|
+
sourceEventId: source,
|
|
4521
|
+
revision: 1,
|
|
4522
|
+
createdAt: now,
|
|
4523
|
+
updatedAt: now
|
|
4524
|
+
}),
|
|
4525
|
+
membershipUpdate("membershipEffect", inviteId, {
|
|
4526
|
+
id: inviteId,
|
|
4527
|
+
chapterId: input.chapterId,
|
|
4528
|
+
environment: input.environment,
|
|
4529
|
+
runtime: input.runtime,
|
|
4530
|
+
membershipId: member.id,
|
|
4531
|
+
seatId: seat.id,
|
|
4532
|
+
kind: "invite_named_recipient",
|
|
4533
|
+
status: "pending",
|
|
4534
|
+
attempts: 0,
|
|
4535
|
+
createdAt: now,
|
|
4536
|
+
updatedAt: now
|
|
4537
|
+
})
|
|
4538
|
+
], { mutationId: `named-seat-included:${seat.id}`, guards: [
|
|
4539
|
+
{
|
|
4540
|
+
ns: "networkSeat",
|
|
4541
|
+
id: seat.id,
|
|
4542
|
+
exists: true,
|
|
4543
|
+
equals: { status: "payment_pending", revision: seat.revision, amountCents: 0 },
|
|
4544
|
+
absent: ["purchasedAt", "providerPaymentIntentId"]
|
|
4545
|
+
},
|
|
4546
|
+
{ ns: "networkEntitlement", id: entitlementId, exists: false },
|
|
4547
|
+
{ ns: "networkMembership", id: String(member.id), exists: true, equals: { revision: member.revision, namedSeatId: seat.id } },
|
|
4548
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
4549
|
+
] });
|
|
4550
|
+
} catch (error) {
|
|
4551
|
+
if (error.code !== "transact_guard_failed") throw error;
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4554
|
+
const current = await membershipLoad(db, "networkSeat", seat.id);
|
|
4555
|
+
requireMembershipScope(current, input);
|
|
4556
|
+
if (!namedSeatIncluded(current) || current.purchasedAt === void 0 || !OPEN.includes(String(current.status))) membershipFail("named_seat_purchase_unavailable");
|
|
4557
|
+
return { seatId: seat.id, amountCents: 0, currency: seat.currency, termEndsAt: Number(current.termEndsAt), status: String(current.status), included: true };
|
|
4558
|
+
}
|
|
4559
|
+
|
|
4385
4560
|
// src/membership-renewal-authority.ts
|
|
4386
4561
|
async function reconcileMembershipRenewal(db, input, now) {
|
|
4387
4562
|
membershipClock(now);
|
|
@@ -4417,7 +4592,9 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
4417
4592
|
if (start !== member.termEndsAt || start > now + 3e4) membershipFail("membership_renewal_period_gap");
|
|
4418
4593
|
const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
|
|
4419
4594
|
if (seat) requireMembershipScope(seat, input);
|
|
4420
|
-
const
|
|
4595
|
+
const openSeat = seat && !["refunded", "refund_pending", "canceled"].includes(String(seat.status)) ? seat : void 0;
|
|
4596
|
+
const billedSeat = openSeat && openSeat.providerSubscriptionId === member.providerSubscriptionId && openSeat.providerSubscriptionItemId ? openSeat : void 0;
|
|
4597
|
+
const carriedSeat = billedSeat ?? (openSeat && namedSeatIncluded(openSeat) && openSeat.purchasedAt !== void 0 ? openSeat : void 0);
|
|
4421
4598
|
const prices = [String(member.providerPriceId), ...billedSeat ? [String(billedSeat.priceId)] : []].sort();
|
|
4422
4599
|
const amount = Number(member.renewalAmountCents) + Number(billedSeat?.amountCents ?? 0);
|
|
4423
4600
|
if (!Number.isSafeInteger(amount) || amount <= 0 || fact.invoiceTotalCents !== amount || JSON.stringify(lines.map((line) => line.priceId).sort()) !== JSON.stringify(prices)) membershipFail("membership_renewal_amount_mismatch");
|
|
@@ -4457,19 +4634,19 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
4457
4634
|
evidenceGuard
|
|
4458
4635
|
];
|
|
4459
4636
|
if (seat) guards.push({ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision } });
|
|
4460
|
-
if (
|
|
4461
|
-
const seatEntitlement = await membershipLoad(db, "networkEntitlement", String(
|
|
4637
|
+
if (carriedSeat?.entitlementId) {
|
|
4638
|
+
const seatEntitlement = await membershipLoad(db, "networkEntitlement", String(carriedSeat.entitlementId));
|
|
4462
4639
|
requireMembershipScope(seatEntitlement, input);
|
|
4463
4640
|
guards.push({ ns: "networkEntitlement", id: String(seatEntitlement.id), exists: true, equals: { revision: seatEntitlement.revision } });
|
|
4464
4641
|
ops.push(
|
|
4465
|
-
membershipUpdate("networkSeat", String(
|
|
4642
|
+
membershipUpdate("networkSeat", String(carriedSeat.id), {
|
|
4466
4643
|
...paid ? { termStartedAt: start, termEndsAt: end } : {},
|
|
4467
|
-
revision: Number(
|
|
4644
|
+
revision: Number(carriedSeat.revision) + 1,
|
|
4468
4645
|
updatedAt: now
|
|
4469
4646
|
}),
|
|
4470
4647
|
membershipUpdate("networkEntitlement", String(seatEntitlement.id), {
|
|
4471
4648
|
...paid ? { termStartedAt: start, termEndsAt: end } : {},
|
|
4472
|
-
status:
|
|
4649
|
+
status: carriedSeat.journeyApprovedAt ? entitlementStatus : seatEntitlement.status,
|
|
4473
4650
|
revision: Number(seatEntitlement.revision) + 1,
|
|
4474
4651
|
updatedAt: now
|
|
4475
4652
|
})
|
|
@@ -4721,84 +4898,6 @@ async function verifyMembershipAccessReceipt(value, input, now) {
|
|
|
4721
4898
|
return { membershipId: row2.membershipId, allowed: row2.allowed, status: row2.status, accessEndsAt: row2.accessEndsAt, revision: row2.revision, ...row2.restartEligible === true && !row2.allowed ? { restartEligible: true } : {} };
|
|
4722
4899
|
}
|
|
4723
4900
|
|
|
4724
|
-
// src/membership-seat-reservation.ts
|
|
4725
|
-
async function recipientKey(input, email) {
|
|
4726
|
-
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([input.appId, input.chapterId, input.environment, input.runtime, email])));
|
|
4727
|
-
return `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
4728
|
-
}
|
|
4729
|
-
async function reserveNamedMembershipSeat(db, input, now) {
|
|
4730
|
-
membershipClock(now);
|
|
4731
|
-
const name = typeof input.recipientName === "string" ? input.recipientName.trim().replace(/\s+/g, " ") : "";
|
|
4732
|
-
const email = normalizeChapterEmail(typeof input.recipientEmail === "string" ? input.recipientEmail : "");
|
|
4733
|
-
const purchaser = normalizeChapterEmail(typeof input.purchaserEmail === "string" ? input.purchaserEmail : "");
|
|
4734
|
-
if (name.length < 2 || name.length > 160 || /[\u0000-\u001f\u007f]/.test(input.recipientName) || email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) || !purchaser || email === purchaser || !/^price_[A-Za-z0-9_]+$/.test(input.priceId) || !/^user_[A-Za-z0-9_]+$/.test(input.purchaserUserId)) membershipFail("invalid_named_seat_recipient");
|
|
4735
|
-
const state = await loadMembershipAuthorityState(db, input);
|
|
4736
|
-
const memberId = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", memberId);
|
|
4737
|
-
requireMembershipScope(member, input);
|
|
4738
|
-
if (!Number.isSafeInteger(member.revision) || !/^price_[A-Za-z0-9_]+$/.test(String(member.providerPriceId)) || member.providerPriceId === input.priceId) membershipFail("named_seat_purchase_unavailable");
|
|
4739
|
-
const id10 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
|
|
4740
|
-
let seat = await membershipLoad(db, "networkSeat", id10);
|
|
4741
|
-
if (!seat) {
|
|
4742
|
-
const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
|
|
4743
|
-
const purchaseEndsAt = seatPurchaseDeadline(offering, member);
|
|
4744
|
-
if (!["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) || Number(member.refundedCents) !== 0 || now < Number(member.initialTermStartedAt) || now >= Number(member.termEndsAt) || now > purchaseEndsAt || !Number.isSafeInteger(purchaseEndsAt) || member.namedSeatId !== void 0) {
|
|
4745
|
-
membershipFail("named_seat_purchase_unavailable");
|
|
4746
|
-
}
|
|
4747
|
-
const attrs = {
|
|
4748
|
-
id: id10,
|
|
4749
|
-
...membershipScope(input),
|
|
4750
|
-
primaryMembershipId: memberId,
|
|
4751
|
-
recipientName: name,
|
|
4752
|
-
recipientEmailNormalized: email,
|
|
4753
|
-
purchaserUserId: input.purchaserUserId,
|
|
4754
|
-
recipientClaimId: claimId,
|
|
4755
|
-
priceId: input.priceId,
|
|
4756
|
-
primaryPriceId: member.providerPriceId,
|
|
4757
|
-
amountCents: offering.seatPriceCents,
|
|
4758
|
-
currency: offering.currency,
|
|
4759
|
-
interval: offering.interval,
|
|
4760
|
-
providerCustomerId: member.providerCustomerId,
|
|
4761
|
-
providerSubscriptionId: member.providerSubscriptionId,
|
|
4762
|
-
status: "payment_pending",
|
|
4763
|
-
preparedAt: now,
|
|
4764
|
-
termEndsAt: member.termEndsAt,
|
|
4765
|
-
seatPurchaseEndsAt: purchaseEndsAt,
|
|
4766
|
-
offeringSnapshot: offering,
|
|
4767
|
-
revision: 1,
|
|
4768
|
-
createdAt: now,
|
|
4769
|
-
updatedAt: now
|
|
4770
|
-
};
|
|
4771
|
-
try {
|
|
4772
|
-
await db.transact([
|
|
4773
|
-
membershipUpdate("networkSeat", id10, attrs),
|
|
4774
|
-
membershipUpdate("networkSeatRecipient", claimId, {
|
|
4775
|
-
id: claimId,
|
|
4776
|
-
appId: input.appId,
|
|
4777
|
-
chapterId: input.chapterId,
|
|
4778
|
-
environment: input.environment,
|
|
4779
|
-
runtime: input.runtime,
|
|
4780
|
-
seatId: id10,
|
|
4781
|
-
recipientEmailNormalized: email,
|
|
4782
|
-
createdAt: now
|
|
4783
|
-
}),
|
|
4784
|
-
membershipUpdate("networkMembership", memberId, { namedSeatId: id10, revision: Number(member.revision) + 1, updatedAt: now })
|
|
4785
|
-
], { mutationId: `named-seat-reserve:${id10}`, guards: [
|
|
4786
|
-
{ ns: "networkSeat", id: id10, exists: false },
|
|
4787
|
-
{ ns: "networkSeatRecipient", id: claimId, exists: false },
|
|
4788
|
-
{ ns: "networkMembership", id: memberId, exists: true, equals: { revision: member.revision }, absent: ["namedSeatId"] },
|
|
4789
|
-
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
4790
|
-
] });
|
|
4791
|
-
} catch (error) {
|
|
4792
|
-
if (error.code !== "transact_guard_failed") throw error;
|
|
4793
|
-
}
|
|
4794
|
-
seat = await membershipLoad(db, "networkSeat", id10);
|
|
4795
|
-
if (!seat && await membershipLoad(db, "networkSeatRecipient", claimId)) membershipFail("named_seat_recipient_unavailable");
|
|
4796
|
-
}
|
|
4797
|
-
requireMembershipScope(seat, input);
|
|
4798
|
-
if (seat.primaryMembershipId !== memberId || seat.recipientName !== name || seat.recipientEmailNormalized !== email || seat.priceId !== input.priceId || seat.purchaserUserId !== input.purchaserUserId) membershipFail("named_seat_purchase_conflict");
|
|
4799
|
-
return seat;
|
|
4800
|
-
}
|
|
4801
|
-
|
|
4802
4901
|
// src/membership-seat-stripe.ts
|
|
4803
4902
|
var import_stripe7 = require("@odla-ai/stripe");
|
|
4804
4903
|
|
|
@@ -4901,6 +5000,7 @@ async function attachNamedSeatStripeRenewal(sk, seat) {
|
|
|
4901
5000
|
// src/membership-seat-checkout.ts
|
|
4902
5001
|
async function prepareNamedMembershipSeatPayment(db, input, secretKey, now) {
|
|
4903
5002
|
let seat = await reserveNamedMembershipSeat(db, input, now);
|
|
5003
|
+
if (namedSeatIncluded(seat)) membershipFail("named_seat_included");
|
|
4904
5004
|
const state = await loadMembershipAuthorityState(db, input);
|
|
4905
5005
|
const member = await membershipLoad(db, "networkMembership", seat.primaryMembershipId);
|
|
4906
5006
|
requireMembershipScope(member, input);
|
|
@@ -5107,7 +5207,7 @@ async function recordRecipient(db, input, now, approve) {
|
|
|
5107
5207
|
requireMembershipScope(member, input);
|
|
5108
5208
|
const entitlement = await membershipLoad(db, "networkEntitlement", String(seat.entitlementId));
|
|
5109
5209
|
requireMembershipScope(entitlement, input);
|
|
5110
|
-
if (!["pending_acceptance", "pending_approval", "pending_primary", "active"].includes(String(seat.status)) || !["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) || Number(member.refundedCents) !== 0 || now >= Number(member.termEndsAt) || !seat
|
|
5210
|
+
if (!["pending_acceptance", "pending_approval", "pending_primary", "active"].includes(String(seat.status)) || !["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) || Number(member.refundedCents) !== 0 || now >= Number(member.termEndsAt) || !namedSeatSettled(seat)) membershipFail("named_seat_acceptance_unavailable");
|
|
5111
5211
|
if (seat.acceptedAt === void 0) {
|
|
5112
5212
|
if (approve || normalizeChapterEmail(input.verifiedRecipientEmail) !== seat.recipientEmailNormalized) {
|
|
5113
5213
|
membershipFail("named_seat_recipient_verification_required");
|
|
@@ -5222,7 +5322,7 @@ async function requestNamedSeatRefund(db, input, now) {
|
|
|
5222
5322
|
const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
|
|
5223
5323
|
requireMembershipScope(member, input);
|
|
5224
5324
|
if (seat.status === "refunded" || seat.status === "refund_pending") return;
|
|
5225
|
-
if (!seat.purchasedAt || !seat.providerPaymentIntentId || input.reason === "declined" && seat.journeyApprovedAt !== void 0 || input.reason === "primary_closed" && !["refunded", "declined"].includes(String(member.status))) membershipFail("named_seat_refund_not_eligible");
|
|
5325
|
+
if (!seat.purchasedAt || !seat.providerPaymentIntentId && !namedSeatIncluded(seat) || input.reason === "declined" && seat.journeyApprovedAt !== void 0 || input.reason === "primary_closed" && !["refunded", "declined"].includes(String(member.status))) membershipFail("named_seat_refund_not_eligible");
|
|
5226
5326
|
const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
|
|
5227
5327
|
await db.transact([
|
|
5228
5328
|
membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
|
|
@@ -5267,7 +5367,7 @@ async function completeNamedSeatRefund(db, seatId, secretKey, now) {
|
|
|
5267
5367
|
await loadMembershipAuthorityState(db, seat);
|
|
5268
5368
|
if (seat.status === "refunded") return;
|
|
5269
5369
|
if (seat.status !== "refund_pending") membershipFail("named_seat_refund_not_ready");
|
|
5270
|
-
const result = await refundNamedSeatStripePayment(secretKey, seat);
|
|
5370
|
+
const result = namedSeatIncluded(seat) ? { refundedCents: 0, refundIds: [] } : await refundNamedSeatStripePayment(secretKey, seat);
|
|
5271
5371
|
await db.transact([
|
|
5272
5372
|
membershipUpdate("networkSeat", seatId, {
|
|
5273
5373
|
status: "refunded",
|
|
@@ -5301,7 +5401,7 @@ async function refundNamedSeatByOperator(db, input, stripeSecretKey, now) {
|
|
|
5301
5401
|
await requestNamedSeatRefund(db, { ...input, reason: "operator" }, now);
|
|
5302
5402
|
await completeNamedSeatRefund(db, input.seatId, stripeSecretKey, now);
|
|
5303
5403
|
const refunded = await membershipLoad(db, "networkSeat", input.seatId);
|
|
5304
|
-
if (refunded?.status !== "refunded" || !Number.isSafeInteger(refunded.refundedCents) || Number(refunded.refundedCents)
|
|
5404
|
+
if (refunded?.status !== "refunded" || !Number.isSafeInteger(refunded.refundedCents) || Number(refunded.refundedCents) !== Number(seat.amountCents)) {
|
|
5305
5405
|
throw new Error("named seat refund unconfirmed");
|
|
5306
5406
|
}
|
|
5307
5407
|
return { seatId: String(seat.id), status: "refunded", refundedCents: Number(refunded.refundedCents), subscriptionCanceled: false };
|
|
@@ -5323,6 +5423,7 @@ async function namedSeatOfferDigest(offer) {
|
|
|
5323
5423
|
])));
|
|
5324
5424
|
return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
5325
5425
|
}
|
|
5426
|
+
var at = (value) => Number.isSafeInteger(value) ? Number(value) : void 0;
|
|
5326
5427
|
async function readNamedSeatOffer(db, input, now) {
|
|
5327
5428
|
membershipClock(now);
|
|
5328
5429
|
await loadMembershipAuthorityState(db, input);
|
|
@@ -5345,10 +5446,19 @@ async function readNamedSeatOffer(db, input, now) {
|
|
|
5345
5446
|
seatId: String(seat.id),
|
|
5346
5447
|
status: String(seat.status),
|
|
5347
5448
|
recipientName: String(seat.recipientName),
|
|
5348
|
-
recipientEmail: String(seat.recipientEmailNormalized)
|
|
5449
|
+
recipientEmail: String(seat.recipientEmailNormalized),
|
|
5450
|
+
recipientState: namedSeatRecipientState(seat),
|
|
5451
|
+
...at(seat.purchasedAt) !== void 0 ? { purchasedAt: at(seat.purchasedAt) } : {},
|
|
5452
|
+
...at(seat.acceptedAt) !== void 0 ? { acceptedAt: at(seat.acceptedAt) } : {},
|
|
5453
|
+
...seat.status === "active" && at(seat.journeyApprovedAt) !== void 0 ? { joinedAt: at(seat.journeyApprovedAt) } : {}
|
|
5349
5454
|
} } : {}
|
|
5350
5455
|
};
|
|
5351
|
-
return {
|
|
5456
|
+
return {
|
|
5457
|
+
...offer,
|
|
5458
|
+
digest: await namedSeatOfferDigest(offer),
|
|
5459
|
+
included: offering.seatPriceCents === 0,
|
|
5460
|
+
purchaseWindowed: offering.seatPurchaseWindowCalendarMonths !== void 0
|
|
5461
|
+
};
|
|
5352
5462
|
}
|
|
5353
5463
|
|
|
5354
5464
|
// src/membership-seat-invitation.ts
|
|
@@ -5474,6 +5584,15 @@ async function handleMembershipSeatAction(action, db, peer, input, now, stripeSe
|
|
|
5474
5584
|
};
|
|
5475
5585
|
if (action === "seat-accept") return acceptNamedSeatRecipient(db, recipientInput, now);
|
|
5476
5586
|
if (action === "seat-approve") return approveNamedSeatRecipient(db, recipientInput, now);
|
|
5587
|
+
const checkout = {
|
|
5588
|
+
...target,
|
|
5589
|
+
priceId: peer.seatPriceId,
|
|
5590
|
+
recipientName: String(input.recipientName),
|
|
5591
|
+
recipientEmail: String(input.recipientEmail),
|
|
5592
|
+
purchaserEmail: String(input.purchaserEmail),
|
|
5593
|
+
purchaserUserId: String(input.purchaserUserId)
|
|
5594
|
+
};
|
|
5595
|
+
if (action === "seat-checkouts" && namedSeatIncluded(await reserveNamedMembershipSeat(db, checkout, now))) return grantIncludedNamedSeat(db, checkout, now);
|
|
5477
5596
|
const secret = await getVaultSecret(db, stripeSecretName);
|
|
5478
5597
|
if (!secret) throw new Error("network Stripe provider is unavailable");
|
|
5479
5598
|
if (action === "seat-decline") {
|
|
@@ -5489,14 +5608,7 @@ async function handleMembershipSeatAction(action, db, peer, input, now, stripeSe
|
|
|
5489
5608
|
return { seatId: seat.id, status: "refunded", refundedCents: refunded.refundedCents, subscriptionCanceled: false };
|
|
5490
5609
|
}
|
|
5491
5610
|
if (action === "seat-operator-refund") return refundNamedSeatByOperator(db, { ...target, seatId: String(input.seatId) }, secret, now);
|
|
5492
|
-
if (action === "seat-checkouts") return prepareNamedMembershipSeatPayment(db,
|
|
5493
|
-
...target,
|
|
5494
|
-
priceId: peer.seatPriceId,
|
|
5495
|
-
recipientName: String(input.recipientName),
|
|
5496
|
-
recipientEmail: String(input.recipientEmail),
|
|
5497
|
-
purchaserEmail: String(input.purchaserEmail),
|
|
5498
|
-
purchaserUserId: String(input.purchaserUserId)
|
|
5499
|
-
}, secret, now);
|
|
5611
|
+
if (action === "seat-checkouts") return prepareNamedMembershipSeatPayment(db, checkout, secret, now);
|
|
5500
5612
|
const evidence = { ...target, seatId: String(input.seatId), eventId: String(input.eventId) };
|
|
5501
5613
|
if (action === "seat-payments") {
|
|
5502
5614
|
const receipt = await confirmNamedMembershipSeatPayment(db, evidence, now);
|
|
@@ -5807,13 +5919,13 @@ async function requestNamedSeatDecline(options) {
|
|
|
5807
5919
|
verifiedRecipientEmail: options.verifiedRecipientEmail
|
|
5808
5920
|
});
|
|
5809
5921
|
const seat = result.seat;
|
|
5810
|
-
if (!seat || seat.seatId !== options.seatId || seat.status !== "refunded" || seat.subscriptionCanceled !== false || !Number.isSafeInteger(seat.refundedCents) || seat.refundedCents
|
|
5922
|
+
if (!seat || seat.seatId !== options.seatId || seat.status !== "refunded" || seat.subscriptionCanceled !== false || !Number.isSafeInteger(seat.refundedCents) || seat.refundedCents < 0) throw new Error("invalid named seat refund receipt");
|
|
5811
5923
|
return seat;
|
|
5812
5924
|
}
|
|
5813
5925
|
async function requestNamedSeatOperatorRefund(options) {
|
|
5814
5926
|
const result = await requestAuthority(options, "seat-operator-refund", { applicationId: options.applicationId, skuId: options.skuId, seatId: options.seatId });
|
|
5815
5927
|
const seat = result.seat;
|
|
5816
|
-
if (!seat || seat.seatId !== options.seatId || seat.status !== "refunded" || seat.subscriptionCanceled !== false || !Number.isSafeInteger(seat.refundedCents) || seat.refundedCents
|
|
5928
|
+
if (!seat || seat.seatId !== options.seatId || seat.status !== "refunded" || seat.subscriptionCanceled !== false || !Number.isSafeInteger(seat.refundedCents) || seat.refundedCents < 0) throw new Error("invalid named seat refund receipt");
|
|
5817
5929
|
return seat;
|
|
5818
5930
|
}
|
|
5819
5931
|
async function requestNamedSeatReservation(options) {
|
|
@@ -5827,13 +5939,13 @@ async function requestNamedSeatReservation(options) {
|
|
|
5827
5939
|
});
|
|
5828
5940
|
const seat = result.seat;
|
|
5829
5941
|
const expected = (await membershipOfferIdForRequest({ ...options, requestId: "named_seat" })).replace(/^offer_/, "seat_");
|
|
5830
|
-
if (!seat || seat.seatId !== expected || !Number.isSafeInteger(seat.amountCents) || seat.amountCents
|
|
5942
|
+
if (!seat || seat.seatId !== expected || !Number.isSafeInteger(seat.amountCents) || seat.amountCents < 0 || !/^[a-z]{3}$/.test(seat.currency) || !Number.isSafeInteger(seat.termEndsAt)) throw new Error("invalid seat reservation receipt");
|
|
5831
5943
|
return { seatId: seat.seatId, amountCents: seat.amountCents, currency: seat.currency, termEndsAt: seat.termEndsAt };
|
|
5832
5944
|
}
|
|
5833
5945
|
async function requestNamedSeatOffer(options) {
|
|
5834
5946
|
const result = await requestAuthority(options, "seat-offer", { applicationId: options.applicationId, skuId: options.skuId });
|
|
5835
5947
|
const offer = result.seat;
|
|
5836
|
-
if (!offer || offer.membershipId !== await networkMembershipId(options) || !Number.isSafeInteger(offer.amountCents) || offer.amountCents
|
|
5948
|
+
if (!offer || offer.membershipId !== await networkMembershipId(options) || !Number.isSafeInteger(offer.amountCents) || offer.amountCents < 0 || offer.included !== (offer.amountCents === 0) || !/^[a-z]{3}$/.test(offer.currency) || !["month", "year"].includes(offer.interval) || typeof offer.eligible !== "boolean" || typeof offer.autoRenew !== "boolean" || !Number.isSafeInteger(offer.termEndsAt) || !Number.isSafeInteger(offer.purchaseEndsAt) || offer.purchaseWindowed !== void 0 && typeof offer.purchaseWindowed !== "boolean" || offer.digest !== await namedSeatOfferDigest(offer)) throw new Error("invalid named seat offer");
|
|
5837
5949
|
return offer;
|
|
5838
5950
|
}
|
|
5839
5951
|
async function requestNamedSeatInvitation(options) {
|
|
@@ -5869,7 +5981,8 @@ async function requestNamedSeatCheckout(options) {
|
|
|
5869
5981
|
});
|
|
5870
5982
|
const seat = result.seat;
|
|
5871
5983
|
const expected = (await membershipOfferIdForRequest({ ...options, requestId: "named_seat" })).replace(/^offer_/, "seat_");
|
|
5872
|
-
|
|
5984
|
+
const included = seat?.amountCents === 0;
|
|
5985
|
+
if (!seat || seat.seatId !== expected || !Number.isSafeInteger(seat.amountCents) || seat.amountCents < 0 || !/^[a-z]{3}$/.test(seat.currency) || !Number.isSafeInteger(seat.termEndsAt) || (included ? seat.included !== true || seat.paymentIntentId !== void 0 || seat.clientSecret !== void 0 || !["pending_acceptance", "pending_approval", "pending_primary", "active"].includes(seat.status) : seat.included !== void 0 || !/^pi_[A-Za-z0-9_]+$/.test(String(seat.paymentIntentId)) || typeof seat.clientSecret !== "string" || !["payment_processing", "renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status))) {
|
|
5873
5986
|
throw new Error("invalid named seat checkout receipt");
|
|
5874
5987
|
}
|
|
5875
5988
|
return seat;
|