fix(reconcile): pass customerCode (not helcimCustomerId) to Helcim transactions API
Some checks failed
Test / vitest (push) Successful in 11m5s
Test / playwright (push) Has been cancelled
Test / Notify on failure (push) Blocked by required conditions
Test / visual (push) Blocked by required conditions

This commit is contained in:
Jennie Robinson Faber 2026-04-27 19:31:59 +01:00
parent 4d44e7045c
commit 3c38333dd1
2 changed files with 16 additions and 5 deletions

View file

@ -69,10 +69,12 @@ export default defineEventHandler(async (event) => {
// Opportunistic backfill: members predating the helcimCustomerCode field
// get it filled in here so the daily cron acts as the migration. Only on
// the missing path — no overwrite, no extra API call once populated.
if (!member.helcimCustomerCode) {
let customerCode = member.helcimCustomerCode
if (!customerCode) {
try {
const customer = await getHelcimCustomer(member.helcimCustomerId)
if (customer?.customerCode) {
customerCode = customer.customerCode
await Member.findByIdAndUpdate(
member._id,
{ $set: { helcimCustomerCode: customer.customerCode } },
@ -85,9 +87,14 @@ export default defineEventHandler(async (event) => {
}
}
if (!customerCode) {
console.warn(`[reconcile] no customerCode for member=${member._id}; skipping`)
return { error: false, txExamined: 0, created: 0, existed: 0, skipped: 0 }
}
let txs
try {
txs = await listTransactionsWithRetry(member.helcimCustomerId)
txs = await listTransactionsWithRetry(customerCode)
} catch (err) {
console.error(`[reconcile] member=${member._id}: ${err?.message || err}`)
return { error: true }

View file

@ -167,8 +167,8 @@ describe('POST /api/internal/reconcile-payments', () => {
// m1 succeeds first try, m2 fails all 3 retries, m3 succeeds first try.
// Keyed by customerCode so it works regardless of call order (chunked Promise.all).
listHelcimCustomerTransactions.mockImplementation((customerCode) => {
if (customerCode === 'cust-1') return Promise.resolve([{ id: 'tx1', status: 'paid', amount: 5 }])
if (customerCode === 'cust-3') return Promise.resolve([{ id: 'tx3', status: 'paid', amount: 7 }])
if (customerCode === 'CST-1') return Promise.resolve([{ id: 'tx1', status: 'paid', amount: 5 }])
if (customerCode === 'CST-3') return Promise.resolve([{ id: 'tx3', status: 'paid', amount: 7 }])
return Promise.reject(new Error('helcim 503'))
})
upsertPaymentFromHelcim.mockResolvedValue({ created: true, payment: { _id: 'p' } })
@ -291,6 +291,7 @@ describe('POST /api/internal/reconcile-payments', () => {
{ $set: { helcimCustomerCode: 'CST-NEW' } },
{ runValidators: false }
)
expect(listHelcimCustomerTransactions).toHaveBeenCalledWith('CST-NEW')
})
it('skips backfill when helcimCustomerCode is already present', async () => {
@ -308,6 +309,7 @@ describe('POST /api/internal/reconcile-payments', () => {
expect(getHelcimCustomer).not.toHaveBeenCalled()
expect(Member.findByIdAndUpdate).not.toHaveBeenCalled()
expect(listHelcimCustomerTransactions).toHaveBeenCalledWith('CST-EXISTING')
})
it('does not fail the run when getHelcimCustomer throws during backfill', async () => {
@ -325,7 +327,9 @@ describe('POST /api/internal/reconcile-payments', () => {
const result = await reconcileHandler(event)
expect(Member.findByIdAndUpdate).not.toHaveBeenCalled()
expect(result.memberErrors).toBe(0) // backfill failure is best-effort, not fatal
// Without a customerCode, we skip the transactions API entirely (best-effort, not error).
expect(listHelcimCustomerTransactions).not.toHaveBeenCalled()
expect(result.memberErrors).toBe(0)
})
})
})