Guide · Python · SDK · Compliance

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).

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.

ℹ️Pre-validation at checkout reduces payment failures by 15-30% and eliminates most SEPA direct debit rejections. It costs a fraction of a failed transaction fee.

2. What validators are needed

The checkout flow uses up to four validators, depending on the payment method and customer type:

ValidatorWhen to useWhat it provesAPI endpoint
Credit CardAlways (card payments)Luhn checksum valid, BIN identifies issuer and card typeGET /v0/credit-card
IBANSEPA paymentsMOD-97 valid, bank identified, SEPA membership confirmedGET /v0/iban
VATB2B EU ordersChecksum valid, VIES confirms active registrationGET /v0/vat
Postal CodeAlwaysFormat valid, exists, resolves to city and coordinatesGET /v0/postal-code
⚠️Not every checkout needs all four validators. A B2C card payment only needs credit card + postal code. Design your flow to validate only what applies to the current transaction.

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

1

valid === true — the card number passes Luhn and format validation

2

network is supported — reject networks your payment processor does not handle (e.g., Amex in some EU acquirers)

3

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

1

valid === true — the IBAN passes format, length, and mod-97 checksum

2

isSEPA === true — the account is in a SEPA country, required for SEPA Direct Debit

3

bankBic is present — you need the BIC for SEPA transaction routing

ℹ️SEPA Direct Debit mandates require a valid IBAN and the associated BIC. If 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

1

vies.valid === true — the VAT number is confirmed active in VIES, enabling reverse charge

2

vies.name — matches the company name on the order to detect misuse of others' VAT numbers

3

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")
The adaptive pattern only calls the APIs you need. A B2C card checkout makes 2 calls (card + postal). A B2B SEPA checkout makes 3 calls (IBAN + VAT + postal). All run in parallel via 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

Validate card numbers with Luhn before charging — catches typos instantly
Check isSEPA for IBAN payments — non-SEPA accounts cannot be debited via SEPA
Confirm VAT via VIES before applying reverse charge — checksum alone is not enough
Adapt validation to payment method and customer type — do not over-validate
Do not apply VAT reverse charge without VIES confirmation — charge VAT and refund later
Do not ignore prepaid card detection — flag high-value orders for review

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.