Compare commits
No commits in common. "426f233ccd92943bfe31f4f3a79462458e0a4f7b" and "10a28ac5ef00b432fb2cb77a5089c108cd60fb52" have entirely different histories.
426f233ccd
...
10a28ac5ef
7 changed files with 0 additions and 129 deletions
|
|
@ -288,7 +288,6 @@ const handleAccept = async () => {
|
||||||
circle: form.circle,
|
circle: form.circle,
|
||||||
motivation: form.motivation || undefined,
|
motivation: form.motivation || undefined,
|
||||||
contributionAmount: form.contributionAmount,
|
contributionAmount: form.contributionAmount,
|
||||||
cadence: cadence.value,
|
|
||||||
agreedToGuidelines: form.agreedToGuidelines,
|
agreedToGuidelines: form.agreedToGuidelines,
|
||||||
token: token.value,
|
token: token.value,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -432,7 +432,6 @@ const handleSubmit = async () => {
|
||||||
email: form.email,
|
email: form.email,
|
||||||
circle: form.circle,
|
circle: form.circle,
|
||||||
contributionAmount: form.contributionAmount,
|
contributionAmount: form.contributionAmount,
|
||||||
cadence: cadence.value,
|
|
||||||
agreedToGuidelines: form.agreedToGuidelines,
|
agreedToGuidelines: form.agreedToGuidelines,
|
||||||
billingAddress: form.billingAddress,
|
billingAddress: form.billingAddress,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,6 @@ export default defineEventHandler(async (event) => {
|
||||||
name: body.name,
|
name: body.name,
|
||||||
circle: body.circle,
|
circle: body.circle,
|
||||||
contributionAmount: body.contributionAmount,
|
contributionAmount: body.contributionAmount,
|
||||||
billingCadence: body.contributionAmount === 0 ? 'monthly' : body.cadence,
|
|
||||||
helcimCustomerId: customerData.id,
|
helcimCustomerId: customerData.id,
|
||||||
helcimCustomerCode: customerData.customerCode,
|
helcimCustomerCode: customerData.customerCode,
|
||||||
status: 'pending_payment',
|
status: 'pending_payment',
|
||||||
|
|
@ -77,7 +76,6 @@ export default defineEventHandler(async (event) => {
|
||||||
name: body.name,
|
name: body.name,
|
||||||
circle: body.circle,
|
circle: body.circle,
|
||||||
contributionAmount: body.contributionAmount,
|
contributionAmount: body.contributionAmount,
|
||||||
billingCadence: body.contributionAmount === 0 ? 'monthly' : body.cadence,
|
|
||||||
helcimCustomerId: customerData.id,
|
helcimCustomerId: customerData.id,
|
||||||
helcimCustomerCode: customerData.customerCode,
|
helcimCustomerCode: customerData.customerCode,
|
||||||
status: 'pending_payment',
|
status: 'pending_payment',
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,6 @@ export default defineEventHandler(async (event) => {
|
||||||
location: body.location || undefined,
|
location: body.location || undefined,
|
||||||
circle: body.circle,
|
circle: body.circle,
|
||||||
contributionAmount: body.contributionAmount,
|
contributionAmount: body.contributionAmount,
|
||||||
billingCadence: body.contributionAmount === 0 ? 'monthly' : body.cadence,
|
|
||||||
bio: body.motivation || undefined,
|
bio: body.motivation || undefined,
|
||||||
status: body.contributionAmount === 0 ? 'active' : 'pending_payment',
|
status: body.contributionAmount === 0 ? 'active' : 'pending_payment',
|
||||||
helcimCustomerId: helcimCustomer?.id,
|
helcimCustomerId: helcimCustomer?.id,
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,6 @@ export const helcimCustomerSchema = z.object({
|
||||||
email: z.string().trim().toLowerCase().email(),
|
email: z.string().trim().toLowerCase().email(),
|
||||||
circle: z.enum(['community', 'founder', 'practitioner']).optional().default('community'),
|
circle: z.enum(['community', 'founder', 'practitioner']).optional().default('community'),
|
||||||
contributionAmount: z.number().int().min(0).optional(),
|
contributionAmount: z.number().int().min(0).optional(),
|
||||||
cadence: z.enum(['monthly', 'annual']).default('monthly'),
|
|
||||||
agreedToGuidelines: z.literal(true)
|
agreedToGuidelines: z.literal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -364,7 +363,6 @@ export const inviteAcceptSchema = z.object({
|
||||||
circle: z.enum(['community', 'founder', 'practitioner']),
|
circle: z.enum(['community', 'founder', 'practitioner']),
|
||||||
motivation: z.string().max(5000).optional(),
|
motivation: z.string().max(5000).optional(),
|
||||||
contributionAmount: z.number().int().min(0),
|
contributionAmount: z.number().int().min(0),
|
||||||
cadence: z.enum(['monthly', 'annual']).default('monthly'),
|
|
||||||
agreedToGuidelines: z.literal(true),
|
agreedToGuidelines: z.literal(true),
|
||||||
token: z.string().min(1)
|
token: z.string().min(1)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -209,62 +209,3 @@ describe('POST /api/members/create — auto-flag wiring (3.8)', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// billingCadence persistence on /api/invite/accept
|
|
||||||
//
|
|
||||||
// Regression: an annual-choosing invitee who abandoned before payment was left
|
|
||||||
// with billingCadence defaulting to 'monthly' while contributionAmount held an
|
|
||||||
// annual-unit value (e.g. 180), rendering "$180/mo" in admin views. The handler
|
|
||||||
// must persist the chosen cadence at Member.create time.
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
describe('POST /api/invite/accept — billingCadence persistence', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
vi.clearAllMocks()
|
|
||||||
PreRegistration.findById.mockResolvedValue({
|
|
||||||
_id: 'prereg-1',
|
|
||||||
email: 'invitee@example.com',
|
|
||||||
status: 'pending'
|
|
||||||
})
|
|
||||||
PreRegistration.findByIdAndUpdate.mockResolvedValue(undefined)
|
|
||||||
Member.findOne.mockResolvedValue(null)
|
|
||||||
Member.create.mockImplementation(async (data) => ({ _id: 'new-member-via-invite', ...data }))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('persists billingCadence "annual" for an annual paid invite', async () => {
|
|
||||||
globalThis.validateBody.mockResolvedValue({
|
|
||||||
token: 'tok',
|
|
||||||
preRegistrationId: 'prereg-1',
|
|
||||||
name: 'Annual Invitee',
|
|
||||||
circle: 'community',
|
|
||||||
contributionAmount: 180,
|
|
||||||
cadence: 'annual'
|
|
||||||
})
|
|
||||||
createHelcimCustomer.mockResolvedValue({ id: 'cust-annual', customerCode: 'code-annual' })
|
|
||||||
|
|
||||||
const event = createMockEvent({ method: 'POST', path: '/api/invite/accept' })
|
|
||||||
await inviteAcceptHandler(event)
|
|
||||||
|
|
||||||
expect(Member.create).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({ contributionAmount: 180, billingCadence: 'annual' })
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('forces billingCadence "monthly" for a $0 invite even when cadence is annual', async () => {
|
|
||||||
globalThis.validateBody.mockResolvedValue({
|
|
||||||
token: 'tok',
|
|
||||||
preRegistrationId: 'prereg-1',
|
|
||||||
name: 'Free Invitee',
|
|
||||||
circle: 'community',
|
|
||||||
contributionAmount: 0,
|
|
||||||
cadence: 'annual'
|
|
||||||
})
|
|
||||||
|
|
||||||
const event = createMockEvent({ method: 'POST', path: '/api/invite/accept' })
|
|
||||||
await inviteAcceptHandler(event)
|
|
||||||
|
|
||||||
expect(Member.create).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({ contributionAmount: 0, billingCadence: 'monthly' })
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
|
||||||
|
|
@ -385,67 +385,4 @@ describe('POST /api/helcim/customer', () => {
|
||||||
expect(setAuthCookie).not.toHaveBeenCalled()
|
expect(setAuthCookie).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Regression: a joiner who picks annual but abandons before
|
|
||||||
// /api/helcim/subscription runs must already have billingCadence persisted,
|
|
||||||
// otherwise contributionAmount (cadence-unit, e.g. 180) renders as "$180/mo".
|
|
||||||
describe('billingCadence persistence', () => {
|
|
||||||
it('persists billingCadence "annual" when a new member signs up annual', async () => {
|
|
||||||
Member.findOne.mockResolvedValue(null)
|
|
||||||
const event = build({
|
|
||||||
body: {
|
|
||||||
name: 'Annual Joiner',
|
|
||||||
email: 'annual@example.com',
|
|
||||||
circle: 'community',
|
|
||||||
contributionAmount: 180,
|
|
||||||
cadence: 'annual',
|
|
||||||
agreedToGuidelines: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await customerHandler(event)
|
|
||||||
expect(Member.create).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({ contributionAmount: 180, billingCadence: 'annual' })
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('persists billingCadence "annual" when upgrading a guest who signs up annual', async () => {
|
|
||||||
Member.findOne.mockResolvedValue({
|
|
||||||
_id: 'guest-annual',
|
|
||||||
email: 'guest@example.com',
|
|
||||||
status: 'guest'
|
|
||||||
})
|
|
||||||
const event = build({
|
|
||||||
body: {
|
|
||||||
name: 'Guest Annual',
|
|
||||||
email: 'guest@example.com',
|
|
||||||
circle: 'founder',
|
|
||||||
contributionAmount: 180,
|
|
||||||
cadence: 'annual',
|
|
||||||
agreedToGuidelines: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await customerHandler(event)
|
|
||||||
expect(Member.create).not.toHaveBeenCalled()
|
|
||||||
const [, updatePayload] = Member.findByIdAndUpdate.mock.calls[0]
|
|
||||||
expect(updatePayload.$set).toMatchObject({ contributionAmount: 180, billingCadence: 'annual' })
|
|
||||||
})
|
|
||||||
|
|
||||||
it('forces billingCadence "monthly" for a $0 signup even when cadence is annual', async () => {
|
|
||||||
Member.findOne.mockResolvedValue(null)
|
|
||||||
const event = build({
|
|
||||||
body: {
|
|
||||||
name: 'Free Joiner',
|
|
||||||
email: 'free@example.com',
|
|
||||||
circle: 'community',
|
|
||||||
contributionAmount: 0,
|
|
||||||
cadence: 'annual',
|
|
||||||
agreedToGuidelines: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await customerHandler(event)
|
|
||||||
expect(Member.create).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({ contributionAmount: 0, billingCadence: 'monthly' })
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue