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:
parent
49cfb47fff
commit
fc09760a41
2 changed files with 38 additions and 0 deletions
|
|
@ -12,7 +12,9 @@ import {
|
|||
updateHelcimSubscription,
|
||||
cancelHelcimSubscription,
|
||||
generateIdempotencyKey,
|
||||
listHelcimCustomerTransactions,
|
||||
} from "../../utils/helcim.js";
|
||||
import { upsertPaymentFromHelcim } from "../../utils/payments.js";
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
|
|
@ -118,6 +120,27 @@ export default defineEventHandler(async (event) => {
|
|||
{ runValidators: false }
|
||||
);
|
||||
|
||||
try {
|
||||
const txs = await listHelcimCustomerTransactions(customerCode);
|
||||
const latestPaid = txs.find((t) => t.status === 'paid');
|
||||
if (latestPaid) {
|
||||
await upsertPaymentFromHelcim(
|
||||
{
|
||||
_id: member._id,
|
||||
email: member.email,
|
||||
name: member.name,
|
||||
helcimCustomerId: member.helcimCustomerId,
|
||||
helcimSubscriptionId: subscription.id,
|
||||
billingCadence: cadence,
|
||||
},
|
||||
latestPaid,
|
||||
{ paymentType: cadence, sendConfirmation: true }
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[payments] free→paid charge log failed, will be picked up by reconciliation:', err?.message || err);
|
||||
}
|
||||
|
||||
logContributionChange();
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue