Skip to content

Add NxtSupport::Iban - #172

Merged
nsommer merged 4 commits into
mainfrom
add-iban-normalization
Sep 16, 2026
Merged

nsommer merged 4 commits into
mainfrom
add-iban-normalization

Conversation

@nsommer

@nsommer nsommer commented Sep 16, 2026 •

Copy link
Copy Markdown
Member

References

Upstreamed from payment-service IX-3421 (payment-service#4898)

Problem

The iOS app sends IBANs with a space every four characters. In payment-service nothing between the controller and the PSP normalised them, so the stored value differed from the same IBAN typed without spaces — last4 was derived from the spaced string, and exact-string comparisons against the ban list silently missed those rows.

The normalisation itself is generic. Every service that stores an IBAN wants the same five lines, so it belongs in the gem rather than in one app.

Solution

NxtSupport::Iban.normalize(iban) upcases and strips all whitespace:

NxtSupport::Iban.normalize('de89 3704 0044 0532 0130 00') # => "DE89370400440532013000"

It does not validate — callers keep using their own IBAN validator (payment-service uses SEPA::IBANValidator). Non-strings are returned unchanged, and the input is not mutated.

#to_s, #country_code, #last4 and #redacted_s all read from the normalized form, so they answer the same for an IBAN typed with spaces as for one typed without:

iban = NxtSupport::Iban.new(iban: 'de89 3704 0044 0532 0130 00')

iban.to_s          # => "DE89370400440532013000"
iban.country_code  # => "DE"
iban.last4         # => "3000"
iban.redacted_s    # => "DE****3000"

#redacted_s is for anywhere an IBAN would otherwise reach a log line, an error message or a support tool. It keeps the country code and the last four characters — enough to tell two accounts apart — and masks everything between. On an IBAN of six characters or fewer, the last four would give the rest away, so only the country code survives.

#to_s and #redacted_s return '' for a missing IBAN; #country_code and #last4 return nil rather than '', so a blank value cannot silently reach a field that shows the last four digits.

Changes

  • NxtSupport::Iban under NxtSupport/Util, next to BirthDate
  • #to_s, #country_code, #last4 and #redacted_s derived from the normalized form
  • Specs ported from payment-service's IbanNormalizer spec, plus coverage for the four readers
  • README section, VERSION → 0.7.0, CHANGELOG entry

Once this is released, payment-service drops its own IbanNormalizer and points its 13 call sites here.

🤖 Generated with Claude Code

Upstreamed from payment-service, where the iOS app sends IBANs with a space every
four characters and nothing between the controller and the PSP normalised them.
The stored value then differed from the same IBAN entered without spaces, which
broke last4 derivation and exact-string comparisons against a ban list.

The normalisation itself is generic — upcase, drop whitespace — and every service
that stores an IBAN wants it, so it belongs here rather than in one app. It does
not validate: callers keep using their own IBAN validator.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nsommer
nsommer force-pushed the add-iban-normalization branch from 09330cc to d7006ca Compare September 16, 2026 08:13
@nsommer nsommer changed the title Add NxtSupport::Iban and the normalize_iban preprocessor Add NxtSupport::Iban Sep 16, 2026
nsommer and others added 3 commits September 16, 2026 10:16
All three read from the normalized form, so they answer the same for an IBAN
typed with spaces as for one typed without — which is the bug that motivated
the class: payment-service derived last4 from the raw string, and a German IBAN
ending "0130 00" yielded "0 00".

country_code and last4 return nil rather than an empty string for a missing
IBAN, so a blank value cannot silently reach a field that shows the last four
digits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An IBAN is personal data and regularly ends up in a log line, an error message
or a support tool. Masking it at the call site is easy to forget and easy to get
wrong, so give the class a reader that cannot leak.

It masks the whole value rather than the usual all-but-last-four when the IBAN
is four characters or shorter, where revealing the last four would reveal all of
it, and returns an empty string for a missing IBAN so that "****" never implies
a value that is not there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A redacted IBAN is mostly read by someone trying to tell two accounts apart in a
log or a support tool, and the country code does that work without giving up
anything that identifies the account.

The threshold for masking the last four characters as well moves up accordingly:
the country code is now visible, so four more characters would reveal the whole
of a six character IBAN rather than a four character one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nsommer
nsommer merged commit fd2980a into main Sep 16, 2026
3 checks passed
@nsommer
nsommer deleted the add-iban-normalization branch September 16, 2026 08:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants