countries->get_base_country() === 'MX' && get_option( 'alegra_fields_visible', 'yes' ) === 'yes'; } private function get_regime_options() { return [ '' => __( 'Selecciona un régimen fiscal', 'alegra-invoicing-for-woocommerce' ), '601' => __( '601 - General de Ley Personas Morales', 'alegra-invoicing-for-woocommerce' ), '603' => __( '603 - Personas Morales con Fines no Lucrativos', 'alegra-invoicing-for-woocommerce' ), '605' => __( '605 - Sueldos y Salarios e Ingresos Asimilados a Salarios', 'alegra-invoicing-for-woocommerce' ), '606' => __( '606 - Arrendamiento', 'alegra-invoicing-for-woocommerce' ), '612' => __( '612 - Personas Físicas con Actividades Empresariales y Profesionales', 'alegra-invoicing-for-woocommerce' ), '616' => __( '616 - Sin obligaciones fiscales', 'alegra-invoicing-for-woocommerce' ), '620' => __( '620 - Sociedades Cooperativas de Producción que optan por diferir sus ingresos', 'alegra-invoicing-for-woocommerce' ), '621' => __( '621 - Incorporación Fiscal', 'alegra-invoicing-for-woocommerce' ), '622' => __( '622 - Actividades Agrícolas, Ganaderas, Silvícolas y Pesqueras', 'alegra-invoicing-for-woocommerce' ), '623' => __( '623 - Opcional para Grupos de Sociedades', 'alegra-invoicing-for-woocommerce' ), '624' => __( '624 - Coordinados', 'alegra-invoicing-for-woocommerce' ), '625' => __( '625 - Actividades Empresariales con ingresos a través de Plataformas Tecnológicas', 'alegra-invoicing-for-woocommerce' ), '626' => __( '626 - Régimen Simplificado de Confianza', 'alegra-invoicing-for-woocommerce' ), ]; } private function get_cfdi_use_options() { return [ '' => __( 'Selecciona el uso del CFDI', 'alegra-invoicing-for-woocommerce' ), 'G01' => __( 'G01 - Adquisición de mercancías', 'alegra-invoicing-for-woocommerce' ), 'G02' => __( 'G02 - Devoluciones, descuentos o bonificaciones', 'alegra-invoicing-for-woocommerce' ), 'G03' => __( 'G03 - Gastos en general', 'alegra-invoicing-for-woocommerce' ), 'I01' => __( 'I01 - Construcciones', 'alegra-invoicing-for-woocommerce' ), 'I02' => __( 'I02 - Mobiliario y equipo de oficina por inversiones', 'alegra-invoicing-for-woocommerce' ), 'I03' => __( 'I03 - Equipo de transporte', 'alegra-invoicing-for-woocommerce' ), 'I04' => __( 'I04 - Equipo de cómputo y accesorios', 'alegra-invoicing-for-woocommerce' ), 'I05' => __( 'I05 - Dados, troqueles, moldes, matrices y herramental', 'alegra-invoicing-for-woocommerce' ), 'I06' => __( 'I06 - Comunicaciones telefónicas', 'alegra-invoicing-for-woocommerce' ), 'I07' => __( 'I07 - Comunicaciones satelitales', 'alegra-invoicing-for-woocommerce' ), 'I08' => __( 'I08 - Otra maquinaria y equipo', 'alegra-invoicing-for-woocommerce' ), 'D01' => __( 'D01 - Honorarios médicos, dentales y gastos hospitalarios', 'alegra-invoicing-for-woocommerce' ), 'D02' => __( 'D02 - Gastos médicos por incapacidad o discapacidad', 'alegra-invoicing-for-woocommerce' ), 'D03' => __( 'D03 - Gastos funerales', 'alegra-invoicing-for-woocommerce' ), 'D04' => __( 'D04 - Donativos', 'alegra-invoicing-for-woocommerce' ), 'D05' => __( 'D05 - Intereses reales pagados por créditos hipotecarios', 'alegra-invoicing-for-woocommerce' ), 'D06' => __( 'D06 - Aportaciones voluntarias al SAR', 'alegra-invoicing-for-woocommerce' ), 'D07' => __( 'D07 - Primas por seguros de gastos médicos', 'alegra-invoicing-for-woocommerce' ), 'D08' => __( 'D08 - Gastos de transportación escolar obligatoria', 'alegra-invoicing-for-woocommerce' ), 'D09' => __( 'D09 - Depósitos en cuentas para el ahorro o planes de pensiones', 'alegra-invoicing-for-woocommerce' ), 'D10' => __( 'D10 - Pagos por servicios educativos (colegiaturas)', 'alegra-invoicing-for-woocommerce' ), 'S01' => __( 'S01 - Sin efectos fiscales', 'alegra-invoicing-for-woocommerce' ), ]; } private function to_block_options( array $options ) { $block_options = []; foreach ( $options as $value => $label ) { if ( $value === '' ) { continue; } $block_options[] = [ 'label' => $label, 'value' => (string) $value ]; } return $block_options; } public function register_fields() { if ( ! self::is_enabled() ) { return; } if ( function_exists( 'woocommerce_register_additional_checkout_field' ) ) { woocommerce_register_additional_checkout_field( [ 'id' => 'alegra/fiscal_regime', 'label' => __( 'Régimen fiscal para facturar con tu RFC', 'alegra-invoicing-for-woocommerce' ), 'location' => 'contact', 'type' => 'select', 'options' => $this->to_block_options( $this->get_regime_options() ), 'required' => false, ] ); woocommerce_register_additional_checkout_field( [ 'id' => 'alegra/fiscal_zip_code', 'label' => __( 'Código postal fiscal', 'alegra-invoicing-for-woocommerce' ), 'location' => 'contact', 'type' => 'text', 'required' => false, 'attributes' => [ 'maxLength' => 5, ], ] ); woocommerce_register_additional_checkout_field( [ 'id' => 'alegra/cfdi_use', 'label' => __( 'Uso del CFDI', 'alegra-invoicing-for-woocommerce' ), 'location' => 'contact', 'type' => 'select', 'options' => $this->to_block_options( $this->get_cfdi_use_options() ), 'required' => false, ] ); add_action( 'woocommerce_blocks_validate_location_contact_fields', [ $this, 'validate_block_fields' ], 10, 2 ); } add_filter( 'woocommerce_checkout_fields', [ $this, 'add_classic_checkout_fields' ] ); add_action( 'woocommerce_after_checkout_validation', [ $this, 'validate_classic_fields' ], 10, 2 ); add_action( 'woocommerce_checkout_create_order', [ $this, 'save_classic_checkout_fields' ], 10, 2 ); } public function add_classic_checkout_fields( $fields ) { $user_id = get_current_user_id(); $default = function ( $key ) use ( $user_id ) { return $user_id ? get_user_meta( $user_id, 'alegra_' . $key, true ) : ''; }; $fields['billing']['alegra_fiscal_regime'] = [ 'type' => 'select', 'label' => __( 'Régimen fiscal para facturar con tu RFC', 'alegra-invoicing-for-woocommerce' ), 'required' => false, 'options' => $this->get_regime_options(), 'class' => [ 'form-row-wide' ], 'priority' => 27, 'default' => $default( 'fiscal_regime' ), ]; $fields['billing']['alegra_fiscal_zip_code'] = [ 'type' => 'text', 'label' => __( 'Código postal fiscal', 'alegra-invoicing-for-woocommerce' ), 'required' => false, 'class' => [ 'form-row-first' ], 'priority' => 28, 'default' => $default( 'fiscal_zip_code' ), 'custom_attributes' => [ 'maxlength' => 5, ], ]; $fields['billing']['alegra_cfdi_use'] = [ 'type' => 'select', 'label' => __( 'Uso del CFDI', 'alegra-invoicing-for-woocommerce' ), 'required' => false, 'options' => $this->get_cfdi_use_options(), 'class' => [ 'form-row-last' ], 'priority' => 29, 'default' => $default( 'cfdi_use' ), ]; return $fields; } public static function normalize_rfc( $value ) { $compact = preg_replace( '/[\s-]+/u', '', (string) $value ); return function_exists( 'mb_strtoupper' ) ? mb_strtoupper( $compact, 'UTF-8' ) : strtoupper( $compact ); } private function find_person_type( $rfc ) { if ( ! preg_match( '/^([A-ZÑ&]{3,4})(\d{2})(\d{2})(\d{2})[A-Z0-9]{3}$/u', $rfc, $parts ) ) { return ''; } if ( ! checkdate( (int) $parts[3], (int) $parts[4], 2000 + (int) $parts[2] ) ) { return ''; } return preg_match( '/^[A-ZÑ&]{3}$/u', $parts[1] ) ? 'moral' : 'natural'; } private function has_fiscal_data( $regime, $zip_code, $cfdi_use ) { return $regime !== '' || $zip_code !== '' || $cfdi_use !== ''; } private function is_generic_rfc( $rfc ) { return $rfc === self::GENERIC_RFC || $rfc === self::FOREIGN_GENERIC_RFC; } private function find_errors( $document_type, $document_number, $regime, $zip_code, $cfdi_use ) { if ( ! $this->has_fiscal_data( $regime, $zip_code, $cfdi_use ) ) { return []; } $rfc = self::normalize_rfc( $document_number ); if ( $document_type !== 'NATIONAL' || $rfc === '' || $this->is_generic_rfc( $rfc ) ) { return [ 'alegra_rfc' => __( 'Para facturar con tus datos fiscales elige el tipo Nacional y escribe tu RFC. Si no necesitas factura a tu nombre, deja vacíos el régimen, el código postal fiscal y el uso del CFDI.', 'alegra-invoicing-for-woocommerce' ), ]; } $person_type = $this->find_person_type( $rfc ); if ( $person_type === '' ) { return [ 'alegra_rfc' => __( 'El RFC no es válido. Debe tener 12 caracteres (persona moral) o 13 (persona física) y una fecha válida. Si no necesitas factura a tu nombre, deja vacíos los datos fiscales.', 'alegra-invoicing-for-woocommerce' ), ]; } $errors = []; if ( $regime === '' ) { $errors['alegra_fiscal_regime'] = __( 'Selecciona tu régimen fiscal para emitir la factura con tu RFC.', 'alegra-invoicing-for-woocommerce' ); } elseif ( ! array_key_exists( $regime, $this->get_regime_options() ) ) { $errors['alegra_fiscal_regime'] = __( 'El régimen fiscal seleccionado no es válido.', 'alegra-invoicing-for-woocommerce' ); } else { $allowed = $person_type === 'moral' ? self::MORAL_PERSON_REGIMES : self::NATURAL_PERSON_REGIMES; if ( ! in_array( $regime, $allowed, true ) ) { $errors['alegra_fiscal_regime'] = __( 'El régimen fiscal no corresponde al tipo de persona de tu RFC.', 'alegra-invoicing-for-woocommerce' ); } } if ( ! preg_match( '/^\d{5}$/', $zip_code ) ) { $errors['alegra_fiscal_zip_code'] = __( 'Escribe el código postal fiscal de 5 dígitos que aparece en tu Constancia de Situación Fiscal.', 'alegra-invoicing-for-woocommerce' ); } if ( $cfdi_use !== '' && ! array_key_exists( $cfdi_use, $this->get_cfdi_use_options() ) ) { $errors['alegra_cfdi_use'] = __( 'El uso del CFDI seleccionado no es válido.', 'alegra-invoicing-for-woocommerce' ); } return $errors; } public function validate_classic_fields( $data, $errors ) { $read = function ( $key ) use ( $data ) { return isset( $data[ $key ] ) ? sanitize_text_field( $data[ $key ] ) : ''; }; foreach ( $this->find_errors( $read( 'alegra_document_type' ), $read( 'alegra_document_number' ), $read( 'alegra_fiscal_regime' ), $read( 'alegra_fiscal_zip_code' ), $read( 'alegra_cfdi_use' ) ) as $code => $message ) { $errors->add( $code, $message ); } } public function validate_block_fields( $errors, $fields ) { $read = function ( $key ) use ( $fields ) { return isset( $fields[ $key ] ) ? sanitize_text_field( (string) $fields[ $key ] ) : ''; }; foreach ( $this->find_errors( $read( 'alegra/document_type' ), $read( 'alegra/document_number' ), $read( 'alegra/fiscal_regime' ), $read( 'alegra/fiscal_zip_code' ), $read( 'alegra/cfdi_use' ) ) as $code => $message ) { $errors->add( $code, $message ); } } public function save_classic_checkout_fields( $order, $data ) { foreach ( self::FIELD_KEYS as $key ) { $posted_key = 'alegra_' . $key; if ( isset( $data[ $posted_key ] ) && $data[ $posted_key ] !== '' ) { $order->update_meta_data( $posted_key, sanitize_text_field( $data[ $posted_key ] ) ); } } } public function save_fields_to_user_profile( $customer_id, $posted ) { if ( ! $customer_id || ! self::is_enabled() ) { return; } foreach ( self::FIELD_KEYS as $key ) { $posted_key = 'alegra_' . $key; if ( isset( $posted[ $posted_key ] ) ) { update_user_meta( $customer_id, $posted_key, sanitize_text_field( $posted[ $posted_key ] ) ); } } } }