feat(payments): log Helcim charge on free-to-paid upgrade

In the Case 1 (free→paid) branch of update-contribution, after the
subscription is created and the member row is updated, fetch the
newest paid Helcim transaction and upsert a Payment with
paymentType=cadence and sendConfirmation=true.

Paid→paid (Case 3) is intentionally NOT wired — no new transaction
occurs at amount change; the next recurring charge is captured by the
reconciliation script.
This commit is contained in:
Jennie Robinson Faber 2026-04-20 13:19:21 +01:00
parent 49cfb47fff
commit fc09760a41
2 changed files with 38 additions and 0 deletions

View file

@ -11,7 +11,9 @@ import {
listHelcimCustomerCards,
createHelcimSubscription,
cancelHelcimSubscription,
listHelcimCustomerTransactions,
} from '../../../server/utils/helcim.js'
import { upsertPaymentFromHelcim } from '../../../server/utils/payments.js'
import handler from '../../../server/api/members/update-contribution.post.js'
import { createMockEvent } from '../helpers/createMockEvent.js'
@ -31,6 +33,10 @@ vi.mock('../../../server/utils/helcim.js', () => ({
updateHelcimSubscription: vi.fn(),
cancelHelcimSubscription: vi.fn(),
generateIdempotencyKey: vi.fn().mockReturnValue('idem-key-123'),
listHelcimCustomerTransactions: vi.fn().mockResolvedValue([]),
}))
vi.mock('../../../server/utils/payments.js', () => ({
upsertPaymentFromHelcim: vi.fn().mockResolvedValue({ created: true, payment: { _id: 'p1' } })
}))
// Nitro auto-imports
@ -224,6 +230,9 @@ describe('update-contribution endpoint — Case 1 (free→paid)', () => {
listHelcimCustomerCards.mockResolvedValue([{ id: 'card-1' }])
createHelcimSubscription.mockResolvedValue({ data: [{ id: 'sub-new', status: 'active', nextBillingDate: '2026-05-18' }] })
Member.findByIdAndUpdate.mockResolvedValue({})
listHelcimCustomerTransactions.mockResolvedValueOnce([
{ id: 'tx-upgrade', date: '2026-04-20', amount: 15, status: 'paid', currency: 'CAD' }
])
const event = createMockEvent({
method: 'POST',
@ -242,6 +251,12 @@ describe('update-contribution endpoint — Case 1 (free→paid)', () => {
{ $set: expect.objectContaining({ billingCadence: 'monthly', contributionAmount: 15, helcimSubscriptionId: 'sub-new' }) },
{ runValidators: false }
)
expect(listHelcimCustomerTransactions).toHaveBeenCalledWith('code-1')
expect(upsertPaymentFromHelcim).toHaveBeenCalledWith(
expect.objectContaining({ _id: 'member-c1', helcimSubscriptionId: 'sub-new', billingCadence: 'monthly' }),
expect.objectContaining({ id: 'tx-upgrade' }),
{ paymentType: 'monthly', sendConfirmation: true }
)
expect(result.success).toBe(true)
expect(result.message).toBe('Successfully upgraded to paid tier')
})