Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
"wp-phpunit/wp-phpunit": "^7.0",
"wpackagist-plugin/woocommerce": "^11.0"
},
"autoload": {
"psr-4": {
"LibreSign\\WordPressTheme\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"LibreSign\\WordPressTheme\\Tests\\": "tests/"
Expand Down Expand Up @@ -55,8 +60,10 @@
"cs:fix": "phpcbf",
"stan": "phpstan analyse --memory-limit=2G",
"test": [
"@test:unit",
"@test:integration"
],
"test:unit": "phpunit",
"test:integration": "phpunit -c phpunit-integration.xml.dist",
"ci": [
"@lint",
Expand Down
118 changes: 15 additions & 103 deletions inc/cpf-cnpj.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@
* @package libresign
*/

use LibreSign\WordPressTheme\TaxId;

defined( 'ABSPATH' ) || exit;

require_once dirname( __DIR__ ) . '/src/TaxId.php';

/**
* Register the CPF/CNPJ field in the checkout address section.
*/
Expand All @@ -35,89 +39,6 @@ function libresign_theme_register_cpf_cnpj_field() {
}
add_action( 'woocommerce_init', 'libresign_theme_register_cpf_cnpj_field' );

/**
* Validate CPF — 11-digit Brazilian individual taxpayer ID.
*
* Uses the standard Módulo 11 algorithm with decreasing weights 10..2 / 11..2.
*
* @param string $cpf Raw or formatted CPF (dots and dash are stripped).
* @return bool
*/
function libresign_theme_validate_cpf( string $cpf ): bool {
$cpf = preg_replace( '/[^0-9]/', '', $cpf );
if ( strlen( $cpf ) !== 11 ) {
return false;
}
// All identical digits (e.g. 000.000.000-00) are never valid.
if ( preg_match( '/^(\d)\1{10}$/', $cpf ) ) {
return false;
}

$mod11 = function ( string $str, int $start_weight ): int {
$sum = 0;
$w = $start_weight;
for ( $i = 0; $i < strlen( $str ); $i++ ) {
$sum += (int) $str[ $i ] * $w--;
}
$rem = $sum % 11;
return $rem < 2 ? 0 : 11 - $rem;
};

$dv1 = $mod11( substr( $cpf, 0, 9 ), 10 );
if ( $dv1 !== (int) $cpf[9] ) {
return false;
}
return $mod11( substr( $cpf, 0, 10 ), 11 ) === (int) $cpf[10];
}

/**
* Validate CNPJ — 14-character Brazilian company taxpayer ID.
*
* Handles both the legacy numeric format and the new alphanumeric format
* introduced by Instrução Normativa RFB nº 2.119/2022 (Anexo Único):
* - Positions 1–12: alphanumeric (root 8 chars + order 4 chars)
* - Positions 13–14: numeric check digits
*
* Algorithm: Módulo 11 with weights 2–9 assigned right-to-left, cycling
* back to 2 after reaching 9. Character value = ord(char) − 48, which gives
* the digit value for '0'–'9' (ASCII 48–57) and 17–42 for 'A'–'Z'
* (ASCII 65–90), exactly as specified in the Receita Federal table.
*
* @param string $cnpj Raw or formatted CNPJ (dots, slash, dash are stripped).
* @return bool
*/
function libresign_theme_validate_cnpj( string $cnpj ): bool {
$cnpj = strtoupper( preg_replace( '/[\.\-\/]/', '', $cnpj ) );
if ( strlen( $cnpj ) !== 14 ) {
return false;
}
// All identical digits — applies to legacy numeric CNPJs.
if ( preg_match( '/^(\d)\1{13}$/', $cnpj ) ) {
return false;
}
// Positions 1–12: uppercase letters or digits; positions 13–14: digits.
if ( ! preg_match( '/^[A-Z0-9]{12}\d{2}$/', $cnpj ) ) {
return false;
}

$mod11 = function ( string $str ): int {
$sum = 0;
$w = 2;
for ( $i = strlen( $str ) - 1; $i >= 0; $i-- ) {
$sum += ( ord( $str[ $i ] ) - 48 ) * $w;
$w = 9 === $w ? 2 : $w + 1;
}
$rem = $sum % 11;
return $rem < 2 ? 0 : 11 - $rem;
};

$dv1 = $mod11( substr( $cnpj, 0, 12 ) );
if ( $dv1 !== (int) $cnpj[12] ) {
return false;
}
return $mod11( substr( $cnpj, 0, 13 ) ) === (int) $cnpj[13];
}

/**
* Server-side enforcement via the address-location validation hook.
*
Expand All @@ -135,25 +56,16 @@ function libresign_theme_validate_cnpj( string $cnpj ): bool {
return;
}

$value = isset( $fields['libresign/cpf-cnpj'] ) ? trim( (string) $fields['libresign/cpf-cnpj'] ) : '';
if ( '' === $value ) {
$errors->add( 'libresign_cpf_cnpj_required', __( 'Please enter your CPF or CNPJ.', 'libresign' ) );
return;
}

$digits_only = preg_replace( '/[^0-9]/', '', $value );
$stripped = strtoupper( preg_replace( '/[\.\-\/]/', '', $value ) );
$messages = array(
'libresign_cpf_cnpj_required' => __( 'Please enter your CPF or CNPJ.', 'libresign' ),
'invalid_cnpj' => __( 'Please enter a valid CNPJ.', 'libresign' ),
'invalid_cpf' => __( 'Please enter a valid CPF.', 'libresign' ),
'invalid_cpf_cnpj' => __( 'Please enter a valid CPF (11 digits) or CNPJ (14 characters).', 'libresign' ),
);

if ( 14 === strlen( $stripped ) ) {
if ( ! libresign_theme_validate_cnpj( $value ) ) {
$errors->add( 'invalid_cnpj', __( 'Please enter a valid CNPJ.', 'libresign' ) );
}
} elseif ( 11 === strlen( $digits_only ) ) {
if ( ! libresign_theme_validate_cpf( $value ) ) {
$errors->add( 'invalid_cpf', __( 'Please enter a valid CPF.', 'libresign' ) );
}
} else {
$errors->add( 'invalid_cpf_cnpj', __( 'Please enter a valid CPF (11 digits) or CNPJ (14 characters).', 'libresign' ) );
$error_code = TaxId::error_code( isset( $fields['libresign/cpf-cnpj'] ) ? (string) $fields['libresign/cpf-cnpj'] : '' );
if ( '' !== $error_code ) {
$errors->add( $error_code, $messages[ $error_code ] );
}
}, 10, 3 );

Expand Down Expand Up @@ -190,7 +102,7 @@ function libresign_theme_strip_irrelevant_cpf_cnpj( $wc_object ) {
* 1. Visibility — hides the field for non-BR customers and reveals it when
* the billing country is Brazil, surviving React re-renders via a
* MutationObserver.
* 2. Validation — mirrors the PHP Módulo 11 algorithms above so the user
* 2. Validation — mirrors the Módulo 11 algorithms of src/TaxId.php so the user
* gets native WooCommerce Blocks error feedback (red border + message
* below the field) without a server round-trip. A re-entrancy guard
* prevents the wp.data subscriber from triggering itself in an infinite
Expand Down Expand Up @@ -249,7 +161,7 @@ function updateVisibility() {
}

// -----------------------------------------------------------------------
// Módulo 11 validation — mirrors the PHP functions in this file.
// Módulo 11 validation — mirrors src/TaxId.php.
// -----------------------------------------------------------------------

/**
Expand Down
Loading
Loading