/** * Return Shipment Address Validation * * Enhanced validation utilities specifically for return shipment scenarios, * including address swapping validation, domestic shipment checks, and * return-specific address requirements. */ import { __ } from '@wordpress/i18n'; import { validateRequiredFields, validatePostalCode, validateCountryAndState, validateEmail, validatePhone, validateEmojiString, } from './validators'; import { createLocalErrors, createValidationResult } from './validation'; import { AddressValidationInput, OriginAddress } from 'types'; export interface ReturnShipmentAddress extends OriginAddress { isVerified?: boolean; verificationStatus?: 'verified' | 'unverified' | 'failed' | 'pending'; addressType?: 'origin' | 'destination'; } export interface ReturnShipmentValidationContext { isReturn: boolean; isDomestic: boolean; originAddress: ReturnShipmentAddress; destinationAddress: ReturnShipmentAddress; operation?: 'create' | 'purchase' | 'validate'; allowUnverifiedAddresses?: boolean; } export interface ReturnShipmentValidationResult { isValid: boolean; errors: string[]; warnings: string[]; addressErrors: { origin: Partial< Record< keyof ReturnShipmentAddress, string > >; destination: Partial< Record< keyof ReturnShipmentAddress, string > >; }; canProceed: boolean; requiresConfirmation: boolean; } /** * Validates that return shipment addresses meet domestic-only requirements */ export const validateDomesticReturnShipment = ( context: ReturnShipmentValidationContext ): ReturnShipmentValidationResult => { const errors: string[] = []; const warnings: string[] = []; const addressErrors: { origin: Partial< Record< keyof ReturnShipmentAddress, string > >; destination: Partial< Record< keyof ReturnShipmentAddress, string > >; } = { origin: {}, destination: {} }; if ( ! context.isReturn ) { return { isValid: true, errors: [], warnings: [], addressErrors, canProceed: true, requiresConfirmation: false, }; } // Check domestic-only requirement for returns const originCountry = context.originAddress.country; const destinationCountry = context.destinationAddress.country; if ( originCountry !== destinationCountry ) { errors.push( __( 'Return labels are currently only available for domestic shipments (same country).', 'woocommerce-shipping' ) ); } // US-specific validation for return shipments if ( originCountry === 'US' && destinationCountry === 'US' ) { // Both addresses should be in US context.isDomestic = true; } else if ( originCountry !== 'US' || destinationCountry !== 'US' ) { errors.push( __( 'Return labels are currently only supported for US domestic shipments.', 'woocommerce-shipping' ) ); } return { isValid: errors.length === 0, errors, warnings, addressErrors, canProceed: errors.length === 0, requiresConfirmation: false, }; }; /** * Validates return shipment address swapping is correct */ export const validateReturnAddressSwapping = ( context: ReturnShipmentValidationContext ): ReturnShipmentValidationResult => { const errors: string[] = []; const warnings: string[] = []; const addressErrors: { origin: Partial< Record< keyof ReturnShipmentAddress, string > >; destination: Partial< Record< keyof ReturnShipmentAddress, string > >; } = { origin: {}, destination: {} }; if ( ! context.isReturn ) { return { isValid: true, errors: [], warnings: [], addressErrors, canProceed: true, requiresConfirmation: false, }; } // For return shipments, validate that addresses make sense // Origin (customer) should have residential characteristics // Destination (merchant) should be the business/return address // Check if origin address looks residential vs business const originLooksResidential = ! context.originAddress.company || context.originAddress.company.trim().length === 0; const destinationLooksBusiness = context.destinationAddress.company && context.destinationAddress.company.trim().length > 0; if ( ! originLooksResidential && ! destinationLooksBusiness ) { warnings.push( __( 'Return shipment addresses may be swapped. Please verify the customer is shipping from their address to your business address.', 'woocommerce-shipping' ) ); } // Validate required fields for return addresses if ( ! context.originAddress.name && ! context.originAddress.company ) { addressErrors.origin.name = __( 'Customer name or company is required for return shipments.', 'woocommerce-shipping' ); errors.push( addressErrors.origin.name ); } if ( ! context.destinationAddress.name && ! context.destinationAddress.company ) { addressErrors.destination.name = __( 'Return-to business name or company is required.', 'woocommerce-shipping' ); errors.push( addressErrors.destination.name ); } return { isValid: errors.length === 0, errors, warnings, addressErrors, canProceed: true, // Warnings don't prevent proceeding requiresConfirmation: warnings.length > 0, }; }; /** * Validates address verification status for return shipments */ export const validateReturnAddressVerification = ( context: ReturnShipmentValidationContext ): ReturnShipmentValidationResult => { const errors: string[] = []; const warnings: string[] = []; const addressErrors: { origin: Partial< Record< keyof ReturnShipmentAddress, string > >; destination: Partial< Record< keyof ReturnShipmentAddress, string > >; } = { origin: {}, destination: {} }; if ( ! context.isReturn ) { return { isValid: true, errors: [], warnings: [], addressErrors, canProceed: true, requiresConfirmation: false, }; } // Check origin address verification if ( context.originAddress.verificationStatus === 'failed' ) { warnings.push( __( 'Customer address could not be verified. This may cause delivery issues.', 'woocommerce-shipping' ) ); } else if ( context.originAddress.verificationStatus === 'unverified' ) { if ( ! context.allowUnverifiedAddresses ) { warnings.push( __( 'Customer address has not been verified. Consider verifying before creating return label.', 'woocommerce-shipping' ) ); } } // Check destination address verification (return-to address) if ( context.destinationAddress.verificationStatus === 'failed' ) { errors.push( __( 'Return-to address verification failed. Please update the address before proceeding.', 'woocommerce-shipping' ) ); addressErrors.destination.address = __( 'Address verification failed', 'woocommerce-shipping' ); } else if ( context.destinationAddress.verificationStatus === 'unverified' ) { if ( context.operation === 'purchase' ) { warnings.push( __( 'Return-to address is not verified. This may cause delivery issues.', 'woocommerce-shipping' ) ); } } return { isValid: errors.length === 0, errors, warnings, addressErrors, canProceed: errors.length === 0, requiresConfirmation: warnings.length > 0, }; }; /** * Validates complete return shipment address requirements */ export const validateReturnShipmentAddresses = ( context: ReturnShipmentValidationContext ): ReturnShipmentValidationResult => { if ( ! context.isReturn ) { return { isValid: true, errors: [], warnings: [], addressErrors: { origin: {}, destination: {} }, canProceed: true, requiresConfirmation: false, }; } // Run all validation checks const domesticValidation = validateDomesticReturnShipment( context ); const swappingValidation = validateReturnAddressSwapping( context ); const verificationValidation = validateReturnAddressVerification( context ); // Combine results const allErrors = [ ...domesticValidation.errors, ...swappingValidation.errors, ...verificationValidation.errors, ]; const allWarnings = [ ...domesticValidation.warnings, ...swappingValidation.warnings, ...verificationValidation.warnings, ]; const combinedAddressErrors = { origin: { ...domesticValidation.addressErrors.origin, ...swappingValidation.addressErrors.origin, ...verificationValidation.addressErrors.origin, }, destination: { ...domesticValidation.addressErrors.destination, ...swappingValidation.addressErrors.destination, ...verificationValidation.addressErrors.destination, }, }; return { isValid: allErrors.length === 0, errors: allErrors, warnings: allWarnings, addressErrors: combinedAddressErrors, canProceed: domesticValidation.canProceed && swappingValidation.canProceed && verificationValidation.canProceed, requiresConfirmation: domesticValidation.requiresConfirmation || swappingValidation.requiresConfirmation || verificationValidation.requiresConfirmation, }; }; /** * Enhanced address field validation for return shipments */ export const validateReturnShipmentAddressFields = ( address: ReturnShipmentAddress, addressType: 'origin' | 'destination', context: ReturnShipmentValidationContext ): AddressValidationInput => { // Start with base validation let validationInput: AddressValidationInput = { values: address, errors: {}, }; // Apply standard validators const validators = [ validateRequiredFields( false ), // Returns are domestic, so no cross-border requirements validatePostalCode, validateCountryAndState, validateEmojiString, ]; // Apply email and phone validation based on address type if ( addressType === 'origin' && context.isReturn ) { // Customer address for return - may need contact info if ( address.email ) { validators.push( validateEmail ); } if ( address.phone ) { validators.push( validatePhone ); } } else if ( addressType === 'destination' && context.isReturn ) { // Business return address - should have contact info validators.push( validateEmail ); validators.push( validatePhone ); } // Apply all validators in sequence for ( const validator of validators ) { validationInput = validator( validationInput ); } // Add return-specific validation const localErrors = createLocalErrors(); if ( context.isReturn ) { // Return-specific validations if ( addressType === 'destination' ) { // Return-to address should be a business address if ( ! address.company && address.name?.includes( 'LLC' ) ) { localErrors.company = __( 'Please enter the company name in the Company field.', 'woocommerce-shipping' ); } // Require email for return-to address if ( ! address.email ) { localErrors.email = __( 'Email is required for the return-to address.', 'woocommerce-shipping' ); } // Require phone for return-to address if ( ! address.phone ) { localErrors.phone = __( 'Phone number is required for the return-to address.', 'woocommerce-shipping' ); } } // Validate domestic requirement if ( address.country !== 'US' ) { localErrors.country = __( 'Return shipments are currently only available for US addresses.', 'woocommerce-shipping' ); } } return createValidationResult( validationInput.values, validationInput.errors, localErrors ); }; /** * Quick validation check for return shipment feasibility */ export const canCreateReturnShipment = ( originAddress: ReturnShipmentAddress, destinationAddress: ReturnShipmentAddress ): { canCreate: boolean; reason?: string } => { // Check basic requirements if ( ! originAddress || ! destinationAddress ) { return { canCreate: false, reason: __( 'Both origin and destination addresses are required.', 'woocommerce-shipping' ), }; } // Check domestic requirement if ( originAddress.country !== destinationAddress.country ) { return { canCreate: false, reason: __( 'Return labels are only available for domestic shipments.', 'woocommerce-shipping' ), }; } // Check US requirement if ( originAddress.country !== 'US' || destinationAddress.country !== 'US' ) { return { canCreate: false, reason: __( 'Return labels are currently only available for US shipments.', 'woocommerce-shipping' ), }; } // Check minimum address requirements if ( ! originAddress.address || ! originAddress.city || ! originAddress.postcode ) { return { canCreate: false, reason: __( 'Origin address is incomplete.', 'woocommerce-shipping' ), }; } if ( ! destinationAddress.address || ! destinationAddress.city || ! destinationAddress.postcode ) { return { canCreate: false, reason: __( 'Return-to address is incomplete.', 'woocommerce-shipping' ), }; } return { canCreate: true }; }; /** * Get user-friendly error messages for return address validation */ export const getReturnAddressErrorMessage = ( field: keyof ReturnShipmentAddress, addressType: 'origin' | 'destination' ): string => { const fieldMessages: { origin: Partial< Record< keyof ReturnShipmentAddress, string > >; destination: Partial< Record< keyof ReturnShipmentAddress, string > >; } = { origin: { name: __( 'Customer name is required for return shipments.', 'woocommerce-shipping' ), company: __( 'Customer company name (if applicable).', 'woocommerce-shipping' ), address: __( 'Customer address is required for return pickup.', 'woocommerce-shipping' ), city: __( 'Customer city is required.', 'woocommerce-shipping' ), state: __( 'Customer state is required.', 'woocommerce-shipping' ), postcode: __( 'Customer postal code is required.', 'woocommerce-shipping' ), country: __( 'Return shipments require US addresses.', 'woocommerce-shipping' ), email: __( 'Customer email may be required for delivery notifications.', 'woocommerce-shipping' ), phone: __( 'Customer phone number may be required for delivery.', 'woocommerce-shipping' ), }, destination: { name: __( 'Business contact name is required for returns.', 'woocommerce-shipping' ), company: __( 'Business name is required for return address.', 'woocommerce-shipping' ), address: __( 'Return-to address is required.', 'woocommerce-shipping' ), city: __( 'Return-to city is required.', 'woocommerce-shipping' ), state: __( 'Return-to state is required.', 'woocommerce-shipping' ), postcode: __( 'Return-to postal code is required.', 'woocommerce-shipping' ), country: __( 'Return-to address must be in the US.', 'woocommerce-shipping' ), email: __( 'Business email is required for return notifications.', 'woocommerce-shipping' ), phone: __( 'Business phone number is required for returns.', 'woocommerce-shipping' ), }, }; return ( fieldMessages[ addressType ][ field ] ?? __( 'This field is required for return shipments.', 'woocommerce-shipping' ) ); };