E-commerce Checkout Validation
Four validators, one checkout flow. Here's how to validate a credit card, IBAN, VAT number, and postal code at checkout — adapting dynamically based on payment method and customer type (B2B vs B2C, SEPA vs card).
In this guide
- 1. The checkout validation problem
- 2. What validators are needed
- 3. Step 1: Validate the credit card
- 4. Step 2: Validate the IBAN (SEPA)
- 5. Step 3: Validate the VAT number (B2B)
- 6. Step 4: Validate the postal code
- 7. Putting it all together — adaptive parallel validation
- 8. cURL examples
- 9. Handling edge cases
- 10. Summary checklist
1. The checkout validation problem
E-commerce checkout is where bad data costs real money. An invalid credit card number triggers a failed charge. A wrong IBAN causes a SEPA direct debit rejection — and those come with bank fees. A missing or incorrect VAT number for an EU B2B transaction means you cannot apply the VAT reverse charge, so your customer pays tax they should not owe and you deal with the complaint.
The challenge is that checkout flows are not uniform. A B2C customer paying by card needs card + postal validation. A B2B EU customer paying by SEPA needs IBAN + VAT + postal. Your validation layer needs to adapt based on payment method and customer type.
The solution: validate the right combination of fields in parallel, before the payment processor sees the data. Catch errors early, reduce chargebacks, and handle VAT exemptions correctly.
2. What validators are needed
The checkout flow uses up to four validators, depending on the payment method and customer type:
| Validator | When to use | What it proves | API endpoint |
|---|---|---|---|
| Credit Card | Always (card payments) | Luhn checksum valid, BIN identifies issuer and card type | GET /v0/credit-card |
| IBAN | SEPA payments | MOD-97 valid, bank identified, SEPA membership confirmed | GET /v0/iban |
| VAT | B2B EU orders | Checksum valid, VIES confirms active registration | GET /v0/vat |
| Postal Code | Always | Format valid, exists, resolves to city and coordinates | GET /v0/postal-code |
3. Step 1: Validate the credit card
The credit card endpoint performs multi-layer validation:
- Luhn algorithm — the ISO/IEC 7812 checksum that catches typos and transpositions
- BIN lookup — identifies the card network (Visa, Mastercard, Amex), issuing bank, and card type (credit/debit/prepaid)
- Card number length — validates the expected length for the identified network
Example response
{ "valid": true, "number": "4532015112830366", "network": "Visa", "cardType": "credit", "issuer": "JPMORGAN CHASE BANK N.A.", "country": "US", "luhn": true }
What to check in your code
valid === true — the card number passes Luhn and format validation
network is supported — reject networks your payment processor does not handle (e.g., Amex in some EU acquirers)
cardType — some merchants block prepaid cards due to higher chargeback rates
4. Step 2: Validate the IBAN (SEPA)
For SEPA Direct Debit or SEPA Credit Transfer, the IBAN endpoint validates:
- MOD-97 checksum — the ISO 13616 algorithm that catches transcription errors
- Country code + BBAN extraction — validates the country-specific length and format
- Bank identification — returns bank name and BIC for SEPA routing
- SEPA membership — confirms the account is in a SEPA-participating country
Example response
{ "valid": true, "countryCode": "NL", "countryName": "Netherlands", "bban": "ABNA0417164300", "isEU": true, "isSEPA": true, "bankCode": "ABNA", "bankName": "ABN AMRO BANK N.V.", "bankBic": "ABNANL2AXXX" }
What to check in your code
valid === true — the IBAN passes format, length, and mod-97 checksum
isSEPA === true — the account is in a SEPA country, required for SEPA Direct Debit
bankBic is present — you need the BIC for SEPA transaction routing
isSEPA is false, the account cannot be debited via SEPA — offer card payment instead.5. Step 3: Validate the VAT number (B2B)
For B2B EU transactions, VAT validation determines whether you can apply the reverse charge mechanism (zero-rate VAT):
- Country-specific checksum — each EU country has its own format and check digit algorithm
- VIES lookup — queries the EU VAT Information Exchange System to confirm the number is actively registered
- Company name and address — VIES returns the registered business details for cross-referencing
Example response
{ "valid": true, "countryCode": "NL", "vatNumber": "123456789B01", "normalized": "NL123456789B01", "vies": { "checked": true, "valid": true, "name": "EXAMPLE B.V.", "address": "HERENGRACHT 100, 1017 BS AMSTERDAM" } }
What to check in your code
vies.valid === true — the VAT number is confirmed active in VIES, enabling reverse charge
vies.name — matches the company name on the order to detect misuse of others' VAT numbers
countryCode — the VAT country should match the billing address country
6. Step 4: Validate the postal code
The postal code endpoint validates the shipping or billing address:
- Country-specific format — validates the correct format per country
- Existence check — confirms the postal code exists in the national registry
- City and state resolution — returns the primary city and administrative region
- Geolocation — provides latitude/longitude for shipping cost calculations
Example response
{ "valid": true, "postalCode": "1017 BS", "countryCode": "NL", "city": "Amsterdam", "state": "North Holland", "stateCode": "NH", "latitude": 52.3676, "longitude": 4.9041 }
7. Putting it all together — adaptive parallel validation
The key insight: not every checkout needs all four validators. The following code adapts based on payment_method and customer_type. All applicable validations run in parallel via asyncio.gather (SDK) or ThreadPoolExecutor (requests). Install with pip install isvalid-sdk or pip install requests.
import asyncio import os from isvalid_sdk import IsValidConfig, create_client iv = create_client(IsValidConfig(api_key=os.environ["ISVALID_API_KEY"])) async def validate_checkout( card_number: str | None = None, iban: str | None = None, vat_number: str | None = None, postal_code: str = "", country: str = "", payment_method: str = "card", customer_type: str = "b2c", ) -> dict: tasks = {} # Always validate postal code tasks["postal"] = iv.postal_code(postal_code, country=country) # Card payment → validate card number if payment_method == "card" and card_number: tasks["card"] = iv.credit_card(card_number) # SEPA payment → validate IBAN if payment_method == "sepa" and iban: tasks["iban"] = iv.iban(iban) # B2B EU order → validate VAT if customer_type == "b2b" and vat_number: tasks["vat"] = iv.vat(vat_number, check_vies=True) # Run all applicable validations in parallel keys = list(tasks.keys()) results = await asyncio.gather(*tasks.values()) validated = dict(zip(keys, results)) return { "postal": { "valid": validated["postal"]["valid"], "city": validated["postal"].get("city"), }, "card": { "valid": validated["card"]["valid"], "network": validated["card"].get("network"), "card_type": validated["card"].get("cardType"), } if "card" in validated else None, "iban": { "valid": validated["iban"]["valid"], "is_sepa": validated["iban"].get("isSEPA"), "bank_name": validated["iban"].get("bankName"), "bank_bic": validated["iban"].get("bankBic"), } if "iban" in validated else None, "vat": { "valid": validated["vat"]["valid"], "confirmed": (validated["vat"].get("vies") or {}).get("valid"), "name": (validated["vat"].get("vies") or {}).get("name"), } if "vat" in validated else None, } # ── Example: B2B SEPA checkout ───────────────────────────────────────────── result = asyncio.run(validate_checkout( iban="NL91ABNA0417164300", vat_number="NL123456789B01", postal_code="1017 BS", country="NL", payment_method="sepa", customer_type="b2b", )) print("IBAN:", f"✓ {result['iban']['bank_name']}" if result.get("iban", {}).get("valid") else "✗ invalid") print("VAT :", f"✓ {result['vat']['name']}" if result.get("vat", {}).get("confirmed") else "✗ not confirmed") print("ZIP :", f"✓ {result['postal']['city']}" if result["postal"]["valid"] else "✗ invalid") can_apply_reverse_charge = result.get("vat", {}).get("confirmed") is True print("VAT reverse charge:", "YES" if can_apply_reverse_charge else "NO")
asyncio.gather (SDK) or ThreadPoolExecutor (requests).8. cURL examples
Validate a credit card number:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/credit-card?value=4532015112830366"
Validate an IBAN (Dutch account):
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/iban?value=NL91ABNA0417164300"
Validate a VAT number with VIES check:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/vat?value=NL123456789B01&checkVies=true"
Validate a postal code (Netherlands):
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/postal-code?value=1017BS&country=NL"
9. Handling edge cases
E-commerce checkout encounters specific edge cases around payment methods, VAT handling, and cross-border transactions:
VIES unavailable — cannot confirm VAT
VIES has regular downtimes. When vies.checked is false, the VAT number passed local checksum validation but VIES confirmation is unavailable. For checkout: charge VAT and refund later when confirmed, or accept provisionally and re-check.
vies = vat_result.get("vies") or {} if vat_result["valid"] and vies and not vies.get("checked"): # VIES is down — format valid, but cannot confirm for reverse charge # Safe approach: charge VAT now, refund when VIES confirms return { "vat_exempt": False, "reason": "vies_unavailable", "message": "VAT will be charged. Refund issued once registration is confirmed.", }
IBAN valid but not in SEPA zone
The IBAN passes checksum validation but isSEPA is false. This account cannot be used for SEPA Direct Debit. Fall back to card payment or international wire transfer.
if iban_result["valid"] and not iban_result.get("isSEPA"): # IBAN is valid but outside SEPA zone — cannot use SEPA Direct Debit return { "status": "payment_method_unavailable", "reason": f"IBAN country {iban_result['countryCode']} is not in the SEPA zone", "suggestion": "Please use a card payment instead", }
Prepaid card detected
The card number is valid but the BIN lookup identifies it as a prepaid card. Prepaid cards have higher chargeback rates and are commonly used for fraud. Decide whether to block, flag for review, or accept with additional verification.
if card_result["valid"] and card_result.get("cardType") == "prepaid": # Valid card, but prepaid — higher fraud risk # Option A: block for high-value orders if order_total > 500: return {"status": "rejected", "reason": "Prepaid cards not accepted for orders over €500"} # Option B: accept but flag for manual review order.risk_flags.append("prepaid_card")
VAT country does not match billing country
The VAT number is valid and confirmed by VIES, but the country code does not match the billing address country. This may indicate VAT fraud — using another company's VAT number from a different country. Log the mismatch and consider requiring additional verification.
if vat_result["valid"] and vat_result.get("countryCode") != billing_country: # VAT country does not match billing address country logger.warning(f"VAT mismatch: VAT={vat_result['countryCode']}, billing={billing_country}") return { "status": "requires_review", "reason": "VAT registration country does not match billing address", }
10. Summary checklist
See also
Validate checkout data before charging
Free tier includes 100 API calls per day. No credit card required. Card, IBAN, VAT, and postal code validation all included.