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:
Jennie Robinson Faber 2026-05-23 15:37:52 +01:00
parent e0e7da5cca
commit 5023fb14ad
6 changed files with 24 additions and 22 deletions

View file

@ -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)