fix: send memberid on checkout
This commit is contained in:
@@ -15,6 +15,9 @@ import {
|
|||||||
getOrganizationAudienceMember,
|
getOrganizationAudienceMember,
|
||||||
getOrganizationById,
|
getOrganizationById,
|
||||||
getOrganizationBySiteName,
|
getOrganizationBySiteName,
|
||||||
|
getOrganizationMemberByIdAndOrg,
|
||||||
|
getOrganizationMemberIdByOrgAndProfile,
|
||||||
|
getOrganizationPlanBySubscriptionId,
|
||||||
getOrganizationTeam,
|
getOrganizationTeam,
|
||||||
getOrganizations,
|
getOrganizations,
|
||||||
revokeActiveOrganizationInvitesByEmails,
|
revokeActiveOrganizationInvitesByEmails,
|
||||||
@@ -41,7 +44,7 @@ import { PLAN } from '@cio/utils/plans';
|
|||||||
import { ROLE } from '@cio/utils/constants';
|
import { ROLE } from '@cio/utils/constants';
|
||||||
import { createOrganizationWithOwner } from '@api/services/onboarding';
|
import { createOrganizationWithOwner } from '@api/services/onboarding';
|
||||||
import { deriveAudienceMemberStatus } from '@api/utils/audience-member-status';
|
import { deriveAudienceMemberStatus } from '@api/utils/audience-member-status';
|
||||||
import { getProfileById } from '@cio/db/queries/auth';
|
import { getProfileById, getProfileByEmail } from '@cio/db/queries/auth';
|
||||||
import { inviteTeamMembers as inviteTeamMembersSecure } from './organization/invite';
|
import { inviteTeamMembers as inviteTeamMembersSecure } from './organization/invite';
|
||||||
import { trustCustomDomainHostname, untrustCustomDomainHostname } from '@cio/db/utils';
|
import { trustCustomDomainHostname, untrustCustomDomainHostname } from '@cio/db/utils';
|
||||||
|
|
||||||
@@ -520,6 +523,45 @@ export async function getOrgSetupData(siteName: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function resolveOrgPlanTriggeredBy(params: {
|
||||||
|
checkoutOrgId: string;
|
||||||
|
targetOrgId: string;
|
||||||
|
triggeredBy: number;
|
||||||
|
payload: Record<string, unknown> | null | undefined;
|
||||||
|
}): Promise<number> {
|
||||||
|
const { checkoutOrgId, targetOrgId, triggeredBy, payload } = params;
|
||||||
|
const checkoutMember = await getOrganizationMemberByIdAndOrg(triggeredBy, checkoutOrgId);
|
||||||
|
|
||||||
|
if (checkoutMember?.profileId) {
|
||||||
|
if (targetOrgId === checkoutOrgId) {
|
||||||
|
return triggeredBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
const primaryMemberId = await getOrganizationMemberIdByOrgAndProfile(targetOrgId, checkoutMember.profileId);
|
||||||
|
|
||||||
|
if (primaryMemberId) {
|
||||||
|
return primaryMemberId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const customer = payload?.customer;
|
||||||
|
|
||||||
|
if (customer && typeof customer === 'object' && 'email' in customer && typeof customer.email === 'string') {
|
||||||
|
const profile = await getProfileByEmail(customer.email);
|
||||||
|
const memberId = profile ? await getOrganizationMemberIdByOrgAndProfile(targetOrgId, profile.id) : null;
|
||||||
|
|
||||||
|
if (memberId) {
|
||||||
|
return memberId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AppError(
|
||||||
|
'Could not resolve organization member for plan',
|
||||||
|
ErrorCodes.ORG_PLAN_CREATE_FAILED,
|
||||||
|
400
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new organization plan
|
* Creates a new organization plan
|
||||||
* @param data - Organization plan creation data
|
* @param data - Organization plan creation data
|
||||||
@@ -527,22 +569,42 @@ export async function getOrgSetupData(siteName: string) {
|
|||||||
*/
|
*/
|
||||||
export async function createOrgPlan(data: TNewOrganizationPlan) {
|
export async function createOrgPlan(data: TNewOrganizationPlan) {
|
||||||
try {
|
try {
|
||||||
|
if (!data.orgId || data.triggeredBy == null || !data.subscriptionId) {
|
||||||
|
throw new AppError('Missing organization plan fields', ErrorCodes.ORG_PLAN_CREATE_FAILED, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingPlan = await getOrganizationPlanBySubscriptionId(data.subscriptionId);
|
||||||
|
|
||||||
|
if (existingPlan) {
|
||||||
|
return existingPlan;
|
||||||
|
}
|
||||||
|
|
||||||
// Subscriptions always attach to the primary workspace, even if a
|
// Subscriptions always attach to the primary workspace, even if a
|
||||||
// secondary org id was supplied at checkout.
|
// secondary org id was supplied at checkout.
|
||||||
const primary = data.orgId ? await getAccountPrimary(data.orgId) : null;
|
const primary = await getAccountPrimary(data.orgId);
|
||||||
const targetOrgId = primary?.id ?? data.orgId;
|
const targetOrgId = primary?.id ?? data.orgId;
|
||||||
|
const triggeredBy = await resolveOrgPlanTriggeredBy({
|
||||||
|
checkoutOrgId: data.orgId,
|
||||||
|
targetOrgId,
|
||||||
|
triggeredBy: data.triggeredBy,
|
||||||
|
payload: data.payload as Record<string, unknown> | null | undefined
|
||||||
|
});
|
||||||
|
|
||||||
const plan = await createOrganizationPlan({
|
const plan = await createOrganizationPlan({
|
||||||
orgId: targetOrgId,
|
orgId: targetOrgId,
|
||||||
planName: data.planName,
|
planName: data.planName,
|
||||||
subscriptionId: data.subscriptionId,
|
subscriptionId: data.subscriptionId,
|
||||||
triggeredBy: data.triggeredBy,
|
triggeredBy,
|
||||||
payload: data.payload,
|
payload: data.payload,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
provider: 'polar'
|
provider: 'polar'
|
||||||
});
|
});
|
||||||
return plan;
|
return plan;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof AppError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
throw new AppError(
|
throw new AppError(
|
||||||
error instanceof Error ? error.message : 'Failed to create organization plan',
|
error instanceof Error ? error.message : 'Failed to create organization plan',
|
||||||
ErrorCodes.ORG_PLAN_CREATE_FAILED,
|
ErrorCodes.ORG_PLAN_CREATE_FAILED,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function handleClick(plan, planName: string) {
|
async function handleClick(plan, planName: string) {
|
||||||
if (plan.CTA.IS_DISABLED || !$profile.id || !$profile.email) {
|
if (plan.CTA.IS_DISABLED || !$profile.id || !$profile.email || !$currentOrg.memberId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
checkoutURL.searchParams.set(
|
checkoutURL.searchParams.set(
|
||||||
'metadata',
|
'metadata',
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
triggeredBy: $currentOrg.roleId,
|
triggeredBy: $currentOrg.memberId,
|
||||||
orgId: $currentOrg.id,
|
orgId: $currentOrg.id,
|
||||||
orgSlug: $currentOrg.siteName
|
orgSlug: $currentOrg.siteName
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export const currentOrg: Writable<AccountOrg> = writable({
|
|||||||
parentOrganizationId: null,
|
parentOrganizationId: null,
|
||||||
plans: [],
|
plans: [],
|
||||||
readOnlyUntil: null,
|
readOnlyUntil: null,
|
||||||
|
memberId: 0,
|
||||||
roleId: 0,
|
roleId: 0,
|
||||||
settings: {},
|
settings: {},
|
||||||
siteName: '',
|
siteName: '',
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export const getOrganizationByProfileId = async (profileId: string): Promise<Org
|
|||||||
const result = await db
|
const result = await db
|
||||||
.select({
|
.select({
|
||||||
organization: schema.organization,
|
organization: schema.organization,
|
||||||
|
memberId: schema.organizationmember.id,
|
||||||
roleId: schema.organizationmember.roleId,
|
roleId: schema.organizationmember.roleId,
|
||||||
plan: {
|
plan: {
|
||||||
planName: schema.organizationPlan.planName,
|
planName: schema.organizationPlan.planName,
|
||||||
@@ -61,6 +62,7 @@ export const getOrganizationByProfileId = async (profileId: string): Promise<Org
|
|||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
organization: typeof schema.organization.$inferSelect;
|
organization: typeof schema.organization.$inferSelect;
|
||||||
|
memberId: number | undefined;
|
||||||
roleId: number | undefined;
|
roleId: number | undefined;
|
||||||
plans: Array<OrganizationPlan>;
|
plans: Array<OrganizationPlan>;
|
||||||
}
|
}
|
||||||
@@ -72,6 +74,7 @@ export const getOrganizationByProfileId = async (profileId: string): Promise<Org
|
|||||||
if (!organizationMap.has(orgId)) {
|
if (!organizationMap.has(orgId)) {
|
||||||
organizationMap.set(orgId, {
|
organizationMap.set(orgId, {
|
||||||
organization: row.organization,
|
organization: row.organization,
|
||||||
|
memberId: row.memberId ?? undefined,
|
||||||
roleId: row.roleId ?? undefined,
|
roleId: row.roleId ?? undefined,
|
||||||
plans: []
|
plans: []
|
||||||
});
|
});
|
||||||
@@ -115,6 +118,32 @@ export const createOrganizationMember = async (data: TNewOrganizationmember, dbC
|
|||||||
return member;
|
return member;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export async function getOrganizationMemberByIdAndOrg(
|
||||||
|
memberId: number,
|
||||||
|
organizationId: string,
|
||||||
|
dbClient: DbOrTxClient = db
|
||||||
|
): Promise<{ id: number; profileId: string | null } | null> {
|
||||||
|
try {
|
||||||
|
const [row] = await dbClient
|
||||||
|
.select({
|
||||||
|
id: schema.organizationmember.id,
|
||||||
|
profileId: schema.organizationmember.profileId
|
||||||
|
})
|
||||||
|
.from(schema.organizationmember)
|
||||||
|
.where(
|
||||||
|
and(eq(schema.organizationmember.id, memberId), eq(schema.organizationmember.organizationId, organizationId))
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
return row ?? null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('getOrganizationMemberByIdAndOrg error:', error);
|
||||||
|
throw new Error(
|
||||||
|
`Failed to resolve organization membership: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function getOrganizationMemberIdByOrgAndProfile(
|
export async function getOrganizationMemberIdByOrgAndProfile(
|
||||||
organizationId: string,
|
organizationId: string,
|
||||||
profileId: string,
|
profileId: string,
|
||||||
@@ -870,6 +899,26 @@ export const getOrganizationCount = async (): Promise<number> => {
|
|||||||
* @param dbClient Optional DB or transaction client for use within transactions
|
* @param dbClient Optional DB or transaction client for use within transactions
|
||||||
* @returns Created organization plan record
|
* @returns Created organization plan record
|
||||||
*/
|
*/
|
||||||
|
export const getOrganizationPlanBySubscriptionId = async (
|
||||||
|
subscriptionId: string,
|
||||||
|
dbClient: DbOrTxClient = db
|
||||||
|
): Promise<TOrganizationPlan | null> => {
|
||||||
|
try {
|
||||||
|
const [plan] = await dbClient
|
||||||
|
.select()
|
||||||
|
.from(schema.organizationPlan)
|
||||||
|
.where(eq(schema.organizationPlan.subscriptionId, subscriptionId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
return plan ?? null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('getOrganizationPlanBySubscriptionId error:', error);
|
||||||
|
throw new Error(
|
||||||
|
`Failed to fetch organization plan: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const createOrganizationPlan = async (
|
export const createOrganizationPlan = async (
|
||||||
data: TNewOrganizationPlan,
|
data: TNewOrganizationPlan,
|
||||||
dbClient: DbOrTxClient = db
|
dbClient: DbOrTxClient = db
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export type OrganizationPlan = Pick<TOrganizationPlan, 'planName' | 'isActive' |
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type OrganizationWithMemberAndPlans = TOrganization & {
|
export type OrganizationWithMemberAndPlans = TOrganization & {
|
||||||
|
memberId?: number;
|
||||||
roleId?: number;
|
roleId?: number;
|
||||||
plans: OrganizationPlan[];
|
plans: OrganizationPlan[];
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user