From 94f3a59c449f3bdccc0142db11417056ea236dd9 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Thu, 30 Jul 2026 14:40:25 +0200 Subject: [PATCH] fix(viban): only reuse a personal IBAN whose bank still receives (#4501) A customer can hold several active rows for one currency - a retired Yapeal EUR IBAN next to a working Frick one. The lookup is a findOne without an ORDER BY, so the retired row can win. The caller then sees an IBAN whose bank does not receive, skips the branch, and since the collection account is no longer a fallback the request ends in PersonalIbanIssuanceFailed. 1678 customers hold such a retired Yapeal EUR row and could not buy by transfer. Filter the lookup on a receiving bank so the working row is found, or none is and issuance proceeds. --- .../virtual-iban/__tests__/virtual-iban.service.spec.ts | 7 ++++++- .../supporting/bank/virtual-iban/virtual-iban.service.ts | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/subdomains/supporting/bank/virtual-iban/__tests__/virtual-iban.service.spec.ts b/src/subdomains/supporting/bank/virtual-iban/__tests__/virtual-iban.service.spec.ts index 33aed14c9c..a5727856d1 100644 --- a/src/subdomains/supporting/bank/virtual-iban/__tests__/virtual-iban.service.spec.ts +++ b/src/subdomains/supporting/bank/virtual-iban/__tests__/virtual-iban.service.spec.ts @@ -1377,7 +1377,11 @@ describe('VirtualIbanService', () => { expect(yapealVibanProvider.reserveViban).not.toHaveBeenCalled(); }); - it('getActiveForUserAndCurrency keeps the merge-base selection semantics without excluding Frick', async () => { + it('getActiveForUserAndCurrency only considers rows whose bank still receives', async () => { + // A customer can hold several active rows per currency - a retired Yapeal EUR IBAN next to a + // working Frick one. findOne has no ORDER BY, so without this filter the retired row can win, + // the caller sees "found, but the bank does not receive", and with no collection-account + // fallback left the request fails outright. That hit every holder of a retired Yapeal EUR IBAN. jest.spyOn(virtualIbanRepo, 'findOne').mockResolvedValue(null); await service.getActiveForUserAndCurrency(userData, 'CHF'); @@ -1386,6 +1390,7 @@ describe('VirtualIbanService', () => { where: { userData: { id: 7 }, currency: { name: 'CHF' }, + bank: { receive: true }, active: true, status: VirtualIbanStatus.ACTIVE, }, diff --git a/src/subdomains/supporting/bank/virtual-iban/virtual-iban.service.ts b/src/subdomains/supporting/bank/virtual-iban/virtual-iban.service.ts index 8a9f6902ba..a74b64f7e1 100644 --- a/src/subdomains/supporting/bank/virtual-iban/virtual-iban.service.ts +++ b/src/subdomains/supporting/bank/virtual-iban/virtual-iban.service.ts @@ -126,6 +126,13 @@ export class VirtualIbanService { where: { userData: { id: userData.id }, currency: { name: currencyName }, + // A customer can hold several active rows for one currency - e.g. an old Yapeal EUR IBAN + // alongside a newer Frick one. Rows whose bank no longer receives must not be returned: this + // is a findOne without an ORDER BY, so a retired row can win over a working one, and the + // caller then sees "IBAN found, bank does not receive" and gives up. Since the collection + // account is no longer a fallback, that surfaced as PersonalIbanIssuanceFailed for every + // customer holding a retired Yapeal EUR IBAN. + bank: { receive: true }, active: true, status: VirtualIbanStatus.ACTIVE, },