
MobileTopUPPhone-number validation looks easy until an application becomes international. At first, the form...
Phone-number validation looks easy until an application becomes international.
At first, the form may accept something like:
5551234567
Then international users arrive.
Suddenly the system needs to understand:
07700 900123
+44 7700 900123
0044 7700 900123
(07700) 900 123
The problem becomes even more interesting when the number is not just a contact field but the destination of a transaction.
A mobile recharge system is a good example because a syntactically valid number is not enough. The system may also need to determine whether the number is supported, which operator serves it, and which products can be delivered.
The key engineering lesson is to avoid collapsing all of those checks into a single boolean called valid.
Consider these questions:
Those are seven different questions.
A typical frontend library may help answer the first few.
It cannot necessarily answer the last four.
This is why:
{
"valid": true
}
is often a poor API model.
A richer response is easier to reason about:
{
"parseable": true,
"plausible": true,
"normalized": "+447700900123",
"eligibility": "supported",
"operator": {
"id": "op_123",
"name": "Example Mobile"
}
}
Internally, choose one canonical representation.
For international mobile numbers, an E.164-style form is commonly useful:
+447700900123
E.164 numbers contain the country calling code and national significant number and are limited to 15 digits.
The normalized form is useful for:
The user-facing representation can be formatted differently:
+44 7700 900123
That is presentation.
Do not store presentation formatting as identity.
A good rule is:
Normalize for machines. Format for humans.
The input:
07700900123
does not carry an explicit international country code.
Your parser needs context.
If the user selected United Kingdom, the application can interpret the local format in that context.
If no country is known, guessing becomes dangerous.
This is why many international phone forms work best with:
Country selector
+
Phone number input
instead of trying to infer everything from one text field.
The selected country is parsing context. It should not automatically be treated as the user's physical location. This distinction becomes particularly important in international mobile top-up, where the sender may be in one country while the prepaid number being recharged belongs to another market.
A regex can enforce simple syntax.
For example:
^\+[1-9]\d{7,14}$
can help check a normalized international representation.
It cannot tell you whether a number is plausible for a specific country's numbering plan.
It certainly cannot tell you whether the number currently belongs to a supported operator.
A dedicated phone-number library is usually safer for parsing and country-specific structural checks.
Regex still has a role.
It just should not pretend to be a numbering-plan database.
Historically, number ranges often indicated the original operator.
That makes prefix tables tempting.
For example:
if number starts with X:
operator = Carrier A
The problem is mobile number portability.
A subscriber may move to another network while keeping the same number.
A prefix can therefore be useful metadata without being authoritative current-routing information.
If your business process needs the actual operator, use an authoritative lookup available through your provider or another appropriate source.
This distinction matters in recharge systems because available products are commonly tied to the current receiving network.
A perfectly formatted number can belong to someone else.
Phone-number validation and phone-number verification are different processes.
Validation asks:
Does this number look structurally valid?
Verification asks:
Can this user prove control of this number?
Verification commonly requires a separate mechanism such as an OTP.
Not every product flow requires ownership verification.
For example, a user may legitimately be sending a recharge to a family member.
The application should therefore decide intentionally whether it needs:
Do not implement one and assume you have all three.
Suppose this number parses correctly:
+XXXXXXXXXXXX
The recharge provider may still respond:
{
"supported": false
}
Possible reasons include:
The user message should reflect the actual problem.
Bad:
Invalid phone number.
Better:
This number appears valid, but recharge is not currently available for this network.
Error semantics matter.
A recharge transaction might snapshot:
{
"recipient": {
"input": "07700 900123",
"normalized": "+447700900123",
"country": "GB",
"operator_id": "op_123",
"operator_name": "Example Mobile"
}
}
Do you need to store the original user input?
Maybe.
It can help with support and debugging.
But downstream transaction logic should generally use the normalized form.
Also consider whether the operator should be stored as a transaction snapshot.
If operator metadata changes later, historical transactions should still describe what the application believed at execution time.
A clean API can expose different failure classes.
For example:
NUMBER_PARSE_ERROR
NUMBER_NOT_PLAUSIBLE
COUNTRY_NOT_SUPPORTED
OPERATOR_NOT_SUPPORTED
NUMBER_NOT_ELIGIBLE
PROVIDER_LOOKUP_FAILED
That makes frontend messages clearer and observability much better.
If every problem becomes:
400 Invalid number
you lose information.
Support teams lose information too.
Frontend validation exists for user experience.
Server-side validation exists for correctness.
A malicious or simply outdated client can bypass browser checks.
The execution path should validate the normalized number and current eligibility again before creating the final transaction.
Especially when the number is the destination of something with monetary value.
A useful mental model is:
Raw input
↓
Parsing
↓
Normalization
↓
Structural plausibility
↓
Provider/operator lookup
↓
Recharge eligibility
↓
Product eligibility
Each layer answers a different question.
Keeping those questions separate avoids both false confidence and confusing error messages.
International phone numbers are not unusually difficult.
They simply expose a broader software-design principle:
Do not use one validation flag to represent several independent business facts.
AI disclosure: This article was prepared with AI assistance. The publishing editor should verify the technical details and factual accuracy before publication.