Data validation

Learn about built-in validation and implement additional scenarios.

Overview

The PayPal component includes comprehensive validation to ensure data integrity and compliance with PayPal's requirements. All built-in validation is performed before creating orders. If it fails, the SDK will throw a PaypalValidationException with detailed error information.

You can also easily build custom validation, depending on your business needs.

Built-in validation

By default, the PayPal Component validates that:

  • Fields marked as required are provided.
  • Maximum length constraints are respected.
  • All email, currency code, country code, and date fields are formatted properly.
  • Valid values are provided for every enum.

Error codes

The PayPal component returns structured error codes for different validation failures:

Error CodeDescriptionCommon Causes
REQUIRED_FIELDRequired field is missingMissing mandatory configuration
MAX_LENGTH_EXCEEDEDField exceeds maximum lengthText too long for PayPal limits
INVALID_LENGTHField has incorrect lengthCurrency/country code format
INVALID_EMAIL_FORMATEmail format is invalidMalformed email address
INVALID_DATE_FORMATDate format is invalidNon-ISO 8601 date format
INVALID_ENUM_VALUEInvalid enum valueUnsupported option value
INVALID_SHIPPING_PREFERENCEShipping preference mismatchWrong preference for shipping options
NO_SELECTION_MADENo shipping option selectedMissing selected option
MULTIPLE_SELECTIONS_NOT_ALLOWEDMultiple shipping options selectedMore than one selected
CURRENCY_CODE_INVALIDCurrency code inconsistencyDifferent currencies in same request

Validation example

// Example of handling validation errors
const paypalConfig = {
  // ... configuration
  onSubmitError: (error) => {
    if (error._validationResult) {
      const { errors } = error._validationResult;    
      // Handle specific validation errors
      if (errors['fundingData.paypal.payeeEmailAddress']) {
        console.error('Invalid email address');
      }
            
      if (errors['amounts.currencyCode']) {
        console.error('Invalid currency code');
      }
            
      // Display user-friendly error messages
      Object.entries(errors).forEach(([field, errorInfo]) => {
        console.error(`${field}: ${errorInfo.message}`);
      });
    }
  }
};

Custom validation

Pre-authorisation validation

Run a validation before the PayPal authorisation to catch issues early and prevent failed payments.

const paypalComponent = pxpSdk.create('paypal-button', {
  onApprove: async (data, actions) => {
    try {
      // 1. Business logic validation
      const orderValidation = await validateOrder({
        cartItems: getCartItems(),
        customerLocation: getCustomerLocation(),
        paymentAmount: getOrderTotal()
      });
      
      if (!orderValidation.valid) {
        throw new Error(orderValidation.reason);
      }
      
      // 2. Security validation
      const securityCheck = await performSecurityValidation({
        customerIP: getClientIP(),
        deviceFingerprint: getDeviceFingerprint(),
        paymentHistory: getCustomerPaymentHistory()
      });
      
      if (securityCheck.riskLevel === 'HIGH') {
        // Require additional verification
        const verified = await requestAdditionalVerification();
        if (!verified) {
          throw new Error('Additional verification required');
        }
      }
      
      // 3. Inventory validation
      const inventoryCheck = await validateInventory(getCartItems());
      if (!inventoryCheck.allAvailable) {
        throw new Error('Some items are no longer available');
      }
      
      // 4. Regulatory compliance
      const complianceCheck = await validateCompliance({
        customerCountry: getCustomerCountry(),
        orderAmount: getOrderTotal(),
        productTypes: getProductTypes()
      });
      
      if (!complianceCheck.compliant) {
        throw new Error('Order does not meet regulatory requirements');
      }
      
      // If all validations pass, proceed with authorisation
      await processPayPalAuthorization(data);
      
    } catch (error) {
      console.error('Validation failed:', error);
      showError(error.message);
    }
  }
});

Confirmation page validation

Run validation on the confirmation page before capturing funds, to ensure order integrity and prevent capture failures.

async function confirmAndCaptureOrder(orderID) {
  try {
    // 1. Re-validate order (things might have changed)
    const currentOrderState = await validateCurrentOrderState(orderID);
    if (!currentOrderState.valid) {
      throw new Error('Order state has changed since authorisation');
    }
    
    // 2. Final inventory check
    const finalInventoryCheck = await reserveInventory(orderID);
    if (!finalInventoryCheck.success) {
      throw new Error('Inventory no longer available');
    }
    
    // 3. Calculate final amounts (shipping, taxes, fees)
    const finalCalculation = await calculateFinalAmounts(orderID);
    
    // 4. Validate amount changes are within acceptable limits
    const authData = getAuthorizationData(orderID);
    const amountDifference = Math.abs(finalCalculation.total - authData.authorizedAmount);
    const maxAllowedDifference = authData.authorizedAmount * 0.15; // 15% tolerance
    
    if (amountDifference > maxAllowedDifference) {
      throw new Error('Final amount differs too much from authorised amount');
    }
    
    // 5. Capture the payment
    await captureAuthorizedPayment(orderID, finalCalculation.total);
    
  } catch (error) {
    handleCaptureValidationError(error);
  }
}

Validate geographic restrictions

Check in real-time if any cart items are restricted in the customer's country to prevent compliance violations.

function validateGeographicRestrictions(customerCountry, cartItems) {
  const restrictedItems = cartItems.filter(item => 
    item.restrictions?.countries?.includes(customerCountry)
  );
  
  return {
    valid: restrictedItems.length === 0,
    restrictedItems: restrictedItems
  };
}

Calculate a risk score

Calculate a comprehensive fraud risk score based on multiple behavioural and historical factors.

async function calculateFraudScore(transactionData) {
  const factors = {
    velocityScore: await checkPaymentVelocity(transactionData.customerEmail),
    locationScore: await validateLocation(transactionData.ipAddress),
    deviceScore: await analyzeDeviceFingerprint(transactionData.deviceData),
    historyScore: await analyzePaymentHistory(transactionData.customerId)
  };
  
  const totalScore = Object.values(factors).reduce((sum, score) => sum + score, 0) / 4;
  
  return {
    score: totalScore,
    riskLevel: totalScore > 0.8 ? 'HIGH' : totalScore > 0.5 ? 'MEDIUM' : 'LOW',
    factors: factors
  };
}