fix(invite): persist billingCadence at invite-accept time
Annual-choosing invitees who abandoned between accept and payment were left at billingCadence:'monthly' (the model default) while contributionAmount held an annual-unit value, rendering $180/mo in admin views. Persist the chosen cadence at Member.create time. accept-invite.vue now sends cadence in the accept POST body; inviteAcceptSchema accepts cadence (defaults 'monthly'); accept.post.js sets billingCadence on create, forced to 'monthly' for $0 members since a free member has no billing relationship.
This commit is contained in:
parent
10a28ac5ef
commit
c3b1c59779
4 changed files with 62 additions and 0 deletions
|
|
@ -288,6 +288,7 @@ const handleAccept = async () => {
|
|||
circle: form.circle,
|
||||
motivation: form.motivation || undefined,
|
||||
contributionAmount: form.contributionAmount,
|
||||
cadence: cadence.value,
|
||||
agreedToGuidelines: form.agreedToGuidelines,
|
||||
token: token.value,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ export default defineEventHandler(async (event) => {
|
|||
location: body.location || undefined,
|
||||
circle: body.circle,
|
||||
contributionAmount: body.contributionAmount,
|
||||
billingCadence: body.contributionAmount === 0 ? 'monthly' : body.cadence,
|
||||
bio: body.motivation || undefined,
|
||||
status: body.contributionAmount === 0 ? 'active' : 'pending_payment',
|
||||
helcimCustomerId: helcimCustomer?.id,
|
||||
|
|
|
|||
|
|
@ -363,6 +363,7 @@ export const inviteAcceptSchema = z.object({
|
|||
circle: z.enum(['community', 'founder', 'practitioner']),
|
||||
motivation: z.string().max(5000).optional(),
|
||||
contributionAmount: z.number().int().min(0),
|
||||
cadence: z.enum(['monthly', 'annual']).default('monthly'),
|
||||
agreedToGuidelines: z.literal(true),
|
||||
token: z.string().min(1)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -209,3 +209,62 @@ 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' })
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue