fix(invoices): accept USD/GBP payment accounts without an IBAN (#1649)

Payment accounts per currency required an IBAN for every non-SEK
currency. USD (ABA routing number) and GBP (sort code) accounts have no
IBAN, so a Wise US or UK receiving account could only be saved by
pasting an IBAN from another currency, which then printed on the invoice
and misrouted the payment.

- InvoicePaymentAccount gains bank_code (routing number / sort code) and
  foreign_account_number; JSONB column, no migration.
- Rule, shared by the Zod schema, the client validation and
  hasUsableInvoicePaymentAccount: a foreign account is usable with an
  IBAN, or, only for NON_IBAN_CURRENCIES (USD, GBP), with bank_code +
  foreign_account_number + BIC. EUR/NOK/DKK still require IBAN.
- Settings: the two fields appear only for USD/GBP with the identifier
  named per currency (Routing number (ABA) / Sort code), a hint that IBAN
  may be left empty, and IBAN no longer marked required there.
- Invoice PDF renders the routing row with the same per-currency label
  plus the foreign account number, in both sv and en.

Reported via gnubok_feedback 2026-08-03.

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-08-17 22:23:36 +02:00
committed by GitHub
co-authored by Jakob Wennberg Claude Fable 5
parent 76b8d5c100
commit 798a76ed7a
10 changed files with 323 additions and 13 deletions
+48
View File
@@ -1400,6 +1400,54 @@ describe('UpdateSettingsSchema', () => {
expect(result.success).toBe(true)
})
it('accepts a USD payment account with routing number + account number + BIC and no IBAN', () => {
const result = UpdateSettingsSchema.safeParse({
invoice_payment_accounts: {
USD: {
bank_name: 'Wise US Inc',
bic: 'trwius35xxx',
bank_code: '084 009 519',
foreign_account_number: '9600 0012 3456 7890',
},
},
})
expect(result.success).toBe(true)
if (result.success) {
const usd = result.data.invoice_payment_accounts?.USD
expect(usd?.bic).toBe('TRWIUS35XXX')
expect(usd?.bank_code).toBe('084009519')
expect(usd?.foreign_account_number).toBe('9600001234567890')
}
})
it('accepts a GBP payment account with a dashed sort code', () => {
const result = UpdateSettingsSchema.safeParse({
invoice_payment_accounts: {
GBP: { bic: 'NWBKGB2L', bank_code: '60-16-13', foreign_account_number: '31926819' },
},
})
expect(result.success).toBe(true)
})
it('rejects a USD payment account with neither IBAN nor the full routing triple', () => {
const result = UpdateSettingsSchema.safeParse({
invoice_payment_accounts: {
USD: { bank_code: '084009519', foreign_account_number: '9600001234567890' },
},
})
expect(result.success).toBe(false)
})
it('still requires an IBAN for an EUR payment account even with a routing triple', () => {
const result = UpdateSettingsSchema.safeParse({
invoice_payment_accounts: {
EUR: { bic: 'DEUTDEFF', bank_code: '37040044', foreign_account_number: '0532013000' },
},
})
expect(result.success).toBe(false)
})
it('accepts null when clearing the legacy SEK bank account mirror', () => {
const result = UpdateSettingsSchema.safeParse({
bank_name: null,