fix(server): treat contributionAmount as cadence-unit (drop ×12)
ContributionAmountField now emits cadence-unit values (180 for $180/yr,
15 for $15/mo). Server endpoints were still multiplying annual by 12,
which would have charged $2160/yr instead of $180/yr after the form
ports in Tasks 2–3.
- helcim/subscription.post.js: recurringAmount = contributionAmount
(no more × 12 for annual)
- members/update-contribution.post.js: same drop in both Case 1
(free→paid) and Case 3 (paid→paid)
- slack.ts notifyNewMember: new positional `cadence` param so the
Slack notification suffix renders /yr or /mo instead of hardcoded
/month; all three call sites updated to pass member.billingCadence
- tests updated to match the new contract:
- helcim-subscription.test.js: annual tests now send the cadence-
unit amount (180, 600) and expect the same recurringAmount
- update-contribution.test.js: annual Case 1 and Case 3 tests
updated likewise
This commit is contained in:
parent
e0e7da5cca
commit
5023fb14ad
6 changed files with 24 additions and 22 deletions
|
|
@ -47,6 +47,7 @@ export default defineEventHandler(async (event) => {
|
|||
member.email,
|
||||
member.circle,
|
||||
member.contributionAmount,
|
||||
member.billingCadence,
|
||||
'manual_invitation_required'
|
||||
)
|
||||
}
|
||||
|
|
@ -79,9 +80,7 @@ export default defineEventHandler(async (event) => {
|
|||
|
||||
const cadence = body.cadence
|
||||
const paymentPlanId = getHelcimPlanId(cadence)
|
||||
const recurringAmount = cadence === 'annual'
|
||||
? body.contributionAmount * 12
|
||||
: body.contributionAmount
|
||||
const recurringAmount = body.contributionAmount
|
||||
|
||||
if (!paymentPlanId) {
|
||||
throw createError({
|
||||
|
|
@ -159,6 +158,7 @@ export default defineEventHandler(async (event) => {
|
|||
member.email,
|
||||
member.circle,
|
||||
member.contributionAmount,
|
||||
member.billingCadence,
|
||||
'manual_invitation_required'
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ export default defineEventHandler(async (event) => {
|
|||
member.email,
|
||||
member.circle,
|
||||
member.contributionAmount,
|
||||
member.billingCadence,
|
||||
'manual_invitation_required'
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ export default defineEventHandler(async (event) => {
|
|||
dateActivated: new Date().toISOString().split("T")[0],
|
||||
paymentPlanId: parseInt(paymentPlanId),
|
||||
customerCode,
|
||||
recurringAmount: cadence === 'annual' ? newAmount * 12 : newAmount,
|
||||
recurringAmount: newAmount,
|
||||
paymentMethod: "card",
|
||||
},
|
||||
idempotencyKey,
|
||||
|
|
@ -217,7 +217,7 @@ export default defineEventHandler(async (event) => {
|
|||
try {
|
||||
const subscriptionData = await updateHelcimSubscription(
|
||||
member.helcimSubscriptionId,
|
||||
{ recurringAmount: memberCadence === 'annual' ? newAmount * 12 : newAmount }
|
||||
{ recurringAmount: newAmount }
|
||||
);
|
||||
|
||||
await Member.findByIdAndUpdate(
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ export class SlackService {
|
|||
memberEmail: string,
|
||||
circle: string,
|
||||
contributionAmount: number,
|
||||
cadence: string = 'monthly',
|
||||
invitationStatus: string = "manual_invitation_required",
|
||||
): Promise<void> {
|
||||
try {
|
||||
|
|
@ -148,7 +149,7 @@ export class SlackService {
|
|||
},
|
||||
{
|
||||
type: "mrkdwn",
|
||||
text: `*Contribution:*\n$${contributionAmount}/month`,
|
||||
text: `*Contribution:*\n$${contributionAmount}/${cadence === 'annual' ? 'yr' : 'mo'}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ describe('helcim subscription endpoint', () => {
|
|||
expect(result.subscription.nextBillingDate).toBe('2026-06-01')
|
||||
})
|
||||
|
||||
it('annual $15 tier creates subscription with correct paymentPlanId and recurringAmount', async () => {
|
||||
it('annual $180 tier creates subscription with correct paymentPlanId and recurringAmount', async () => {
|
||||
requireAuth.mockResolvedValue(undefined)
|
||||
requiresPayment.mockReturnValue(true)
|
||||
getHelcimPlanId.mockReturnValue('88888')
|
||||
|
|
@ -211,7 +211,7 @@ describe('helcim subscription endpoint', () => {
|
|||
email: 'annual@example.com',
|
||||
name: 'Annual User',
|
||||
circle: 'founder',
|
||||
contributionAmount: 15,
|
||||
contributionAmount: 180,
|
||||
status: 'active',
|
||||
}
|
||||
Member.findOneAndUpdate.mockResolvedValue({ _id: 'member-3', status: 'pending_payment' })
|
||||
|
|
@ -223,7 +223,7 @@ describe('helcim subscription endpoint', () => {
|
|||
const event = createMockEvent({
|
||||
method: 'POST',
|
||||
path: '/api/helcim/subscription',
|
||||
body: { customerId: 'cust-1', contributionAmount: 15, customerCode: 'code-1', cardToken: 'tok-123', cadence: 'annual' }
|
||||
body: { customerId: 'cust-1', contributionAmount: 180, customerCode: 'code-1', cardToken: 'tok-123', cadence: 'annual' }
|
||||
})
|
||||
|
||||
const result = await subscriptionHandler(event)
|
||||
|
|
@ -235,13 +235,13 @@ describe('helcim subscription endpoint', () => {
|
|||
)
|
||||
expect(Member.findOneAndUpdate).toHaveBeenCalledWith(
|
||||
{ helcimCustomerId: 'cust-1' },
|
||||
{ $set: expect.objectContaining({ billingCadence: 'annual', contributionAmount: 15, status: 'active' }) },
|
||||
{ $set: expect.objectContaining({ billingCadence: 'annual', contributionAmount: 180, status: 'active' }) },
|
||||
{ new: false, runValidators: false, projection: { status: 1 } }
|
||||
)
|
||||
expect(Member.findById).toHaveBeenCalledWith('member-3')
|
||||
})
|
||||
|
||||
it('annual $50 tier recurringAmount is 600', async () => {
|
||||
it('annual $600 tier recurringAmount is 600', async () => {
|
||||
requireAuth.mockResolvedValue(undefined)
|
||||
requiresPayment.mockReturnValue(true)
|
||||
getHelcimPlanId.mockReturnValue('88888')
|
||||
|
|
@ -251,7 +251,7 @@ describe('helcim subscription endpoint', () => {
|
|||
email: 'top@example.com',
|
||||
name: 'Top Tier',
|
||||
circle: 'practitioner',
|
||||
contributionAmount: 50,
|
||||
contributionAmount: 600,
|
||||
status: 'active',
|
||||
}
|
||||
Member.findOneAndUpdate.mockResolvedValue({ _id: 'member-4', status: 'pending_payment' })
|
||||
|
|
@ -263,7 +263,7 @@ describe('helcim subscription endpoint', () => {
|
|||
const event = createMockEvent({
|
||||
method: 'POST',
|
||||
path: '/api/helcim/subscription',
|
||||
body: { customerId: 'cust-2', contributionAmount: 50, customerCode: 'code-2', cardToken: 'tok-456', cadence: 'annual' }
|
||||
body: { customerId: 'cust-2', contributionAmount: 600, customerCode: 'code-2', cardToken: 'tok-456', cadence: 'annual' }
|
||||
})
|
||||
|
||||
await subscriptionHandler(event)
|
||||
|
|
|
|||
|
|
@ -83,10 +83,10 @@ describe('update-contribution endpoint — Case 3 (paid→paid)', () => {
|
|||
expect(result.message).toBe('Successfully updated contribution level')
|
||||
})
|
||||
|
||||
it('annual $5 → $15: calls updateHelcimSubscription with recurringAmount 180', async () => {
|
||||
it('annual $60 → $180: calls updateHelcimSubscription with recurringAmount 180', async () => {
|
||||
const mockMember = {
|
||||
_id: 'member-2',
|
||||
contributionAmount: 5,
|
||||
contributionAmount: 60,
|
||||
helcimSubscriptionId: 'sub-2',
|
||||
billingCadence: 'annual',
|
||||
}
|
||||
|
|
@ -97,7 +97,7 @@ describe('update-contribution endpoint — Case 3 (paid→paid)', () => {
|
|||
const event = createMockEvent({
|
||||
method: 'POST',
|
||||
path: '/api/members/update-contribution',
|
||||
body: { contributionAmount: 15, cadence: 'annual' },
|
||||
body: { contributionAmount: 180, cadence: 'annual' },
|
||||
})
|
||||
|
||||
const result = await handler(event)
|
||||
|
|
@ -105,16 +105,16 @@ describe('update-contribution endpoint — Case 3 (paid→paid)', () => {
|
|||
expect(updateHelcimSubscription).toHaveBeenCalledWith('sub-2', { recurringAmount: 180 })
|
||||
expect(Member.findByIdAndUpdate).toHaveBeenCalledWith(
|
||||
'member-2',
|
||||
{ $set: { contributionAmount: 15 } },
|
||||
{ $set: { contributionAmount: 180 } },
|
||||
{ runValidators: false }
|
||||
)
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it('annual $15 → $50: calls updateHelcimSubscription with recurringAmount 600', async () => {
|
||||
it('annual $180 → $600: calls updateHelcimSubscription with recurringAmount 600', async () => {
|
||||
const mockMember = {
|
||||
_id: 'member-3',
|
||||
contributionAmount: 15,
|
||||
contributionAmount: 180,
|
||||
helcimSubscriptionId: 'sub-3',
|
||||
billingCadence: 'annual',
|
||||
}
|
||||
|
|
@ -125,7 +125,7 @@ describe('update-contribution endpoint — Case 3 (paid→paid)', () => {
|
|||
const event = createMockEvent({
|
||||
method: 'POST',
|
||||
path: '/api/members/update-contribution',
|
||||
body: { contributionAmount: 50, cadence: 'annual' },
|
||||
body: { contributionAmount: 600, cadence: 'annual' },
|
||||
})
|
||||
|
||||
await handler(event)
|
||||
|
|
@ -296,7 +296,7 @@ describe('update-contribution endpoint — Case 1 (free→paid)', () => {
|
|||
const event = createMockEvent({
|
||||
method: 'POST',
|
||||
path: '/api/members/update-contribution',
|
||||
body: { contributionAmount: 15, cadence: 'annual' },
|
||||
body: { contributionAmount: 180, cadence: 'annual' },
|
||||
})
|
||||
|
||||
const result = await handler(event)
|
||||
|
|
@ -307,7 +307,7 @@ describe('update-contribution endpoint — Case 1 (free→paid)', () => {
|
|||
)
|
||||
expect(Member.findByIdAndUpdate).toHaveBeenCalledWith(
|
||||
'member-c1',
|
||||
{ $set: expect.objectContaining({ billingCadence: 'annual', contributionAmount: 15, helcimSubscriptionId: 'sub-annual' }) },
|
||||
{ $set: expect.objectContaining({ billingCadence: 'annual', contributionAmount: 180, helcimSubscriptionId: 'sub-annual' }) },
|
||||
{ runValidators: false }
|
||||
)
|
||||
expect(result.success).toBe(true)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue