Skip to content

Commit ff4659c

Browse files
committed
v. 4.48.0
* Melhoria: adicionado medidas de segurança, evitando que: 1) usuários adicionem mais de 5 cartões salvos; 2) bots realizem testes de cartão ao adicionar cartões em intervalos menores que 2 minutos ou sem estarem logados; 3) nonce adicionado ao formulário de cartão (área logado) evitando que bots explorem diretamente este endpoint * Correção: erro fatal poderia ocorrer ao finalizar uma compra com assinatura por conta de uma tentativa de inserir registros duplicados. Na ausência da chave única na tabela, assinatura e cobranças duplicadas poderiam ser geradas. (Reportado por Ricardo Alves B.) * Correção: mesmo com opção de salvar cartão desabilitada, ainda era possível salvar cartão em Minha Conta > Meios de Pagamento * Correção/Melhoria: Erro fatal poderia ocorrer ao tentar finalizar um pedido com Boleto (métodos getter retornam null (retorna string vazia)) e melhorado phpdoc de alguns métodos do objeto Address. * Correção: Erro Fatal poderia ocorrer na primeira vez a configuração de uma das áreas do plugin fosse salva, dependendo de como o plugin foi reinstalado/reconfigurado no passado. * Correção: em cenários específicos, o form de cartão de crédito não era exibido no checkout em blocos (TypeError). Merge branch 'release/4.48.0'
2 parents b13123d + b6639b2 commit ff4659c

File tree

11 files changed

+309
-112
lines changed

11 files changed

+309
-112
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "ricardomartins/pagbank-woocommerce",
33
"description": "Integração PagBank (PagSeguro) WooCommerce com desconto nas taxas oficiais",
44
"type": "wordpress-plugin",
5-
"version": "4.47.0",
5+
"version": "4.48.0",
66
"license": "GPL-3.0",
77
"autoload": {
88
"psr-4": {

public/js/blocks/checkout-blocks-cc.js

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import RetryInput from './components/RetryInput';
1212
import SavedCardInstallments from './components/SavedCardInstallments';
1313
import SavedCreditCardToken from './components/SavedCreditCardToken';
1414
const { useSelect } = window.wp.data;
15-
const { checkoutStore } = window.wc.wcBlocksData;
15+
const { checkoutStore } = window.wc?.wcBlocksData || {};
1616
const settings = getSetting('rm-pagbank-cc_data', {});
1717
const label = decodeEntities( settings.title ) || window.wp.i18n.__( 'PagBank Connect Cartão de Crédito', 'rm-pagbank-pix' );
1818
let showRetryInput = false;
@@ -48,9 +48,24 @@ const Label = ( props ) => {
4848
*/
4949
const Content = ( props ) => {
5050
const { eventRegistration, emitResponse, billing } = props;
51+
52+
// Safety check: ensure required props exist
53+
if (!eventRegistration || !emitResponse || !billing) {
54+
return null;
55+
}
56+
5157
const { onPaymentSetup, onCheckoutValidation: onCheckoutValidation, onCheckoutSuccess, onCheckoutFail } = eventRegistration;
52-
const isCalculating = useSelect((select) => select(checkoutStore).isCalculating());
53-
const cartTotalRef = useRef(billing.cartTotal.value ?? 0);
58+
const isCalculating = useSelect((select) => {
59+
if (!checkoutStore) {
60+
return false;
61+
}
62+
try {
63+
return select(checkoutStore)?.isCalculating() || false;
64+
} catch (e) {
65+
return false;
66+
}
67+
});
68+
const cartTotalRef = useRef(billing?.cartTotal?.value ?? 0);
5469
if (settings.paymentUnavailable) {
5570
useEffect( () => {
5671
const unsubscribe = onPaymentSetup(() => {
@@ -318,35 +333,37 @@ const Content = ( props ) => {
318333
}
319334
}
320335

321-
let customerName = billing.billingData.first_name + ' ' + billing.billingData.last_name;
336+
let customerName = (billing?.billingData?.first_name || '') + ' ' + (billing?.billingData?.last_name || '');
322337
if(customerName.trim() === '') {
323-
customerName = document.getElementById('billing-first_name').value.replace(/\s/g, '')
324-
+ ' ' + document.getElementById('billing-last_name').value.replace(/\s/g, '');
338+
const firstNameEl = document.getElementById('billing-first_name');
339+
const lastNameEl = document.getElementById('billing-last_name');
340+
customerName = (firstNameEl?.value || '').replace(/\s/g, '')
341+
+ ' ' + (lastNameEl?.value || '').replace(/\s/g, '');
325342
}
326343
customerName = customerName.trim().replace(/\s+/g, ' '); //removing duplicated spaces in the middle
327344
customerName = customerName.replace(/[^A-Za-zÀ-ÖØ-öø-ÿ\s]/g, '').replace(/\s+/g, ' '); //removing specials
328345

329346

330-
let customerEmail = billing.billingData.email;
331-
customerEmail = customerEmail.trim() === '' ? document.getElementById('email').value : customerEmail;
347+
let customerEmail = billing?.billingData?.email || '';
348+
customerEmail = customerEmail.trim() === '' ? (document.getElementById('email')?.value || '') : customerEmail;
332349

333-
let customerPhone = billing.billingData.phone;
334-
customerPhone = customerPhone.trim() === '' ? document.getElementById('billing-phone').value : customerPhone;
350+
let customerPhone = billing?.billingData?.phone || '';
351+
customerPhone = customerPhone.trim() === '' ? (document.getElementById('billing-phone')?.value || '') : customerPhone;
335352

336-
let billingAddress1 = billing.billingData.address_1;
337-
billingAddress1 = billingAddress1.trim() === '' ? document.getElementById('billing-address_1').value : billingAddress1;
353+
let billingAddress1 = billing?.billingData?.address_1 || '';
354+
billingAddress1 = billingAddress1.trim() === '' ? (document.getElementById('billing-address_1')?.value || '') : billingAddress1;
338355

339-
let billingAddress2 = billing.billingData.address_2;
340-
billingAddress2 = billingAddress2.trim() === '' ? document.getElementById('billing-address_2').value : billingAddress2;
356+
let billingAddress2 = billing?.billingData?.address_2 || '';
357+
billingAddress2 = billingAddress2.trim() === '' ? (document.getElementById('billing-address_2')?.value || '') : billingAddress2;
341358

342-
let regionCode = billing.billingData.state;
343-
regionCode = regionCode.trim() === '' ? document.getElementById('billing-state').value : regionCode;
359+
let regionCode = billing?.billingData?.state || '';
360+
regionCode = regionCode.trim() === '' ? (document.getElementById('billing-state')?.value || '') : regionCode;
344361

345-
let billingAddressCity = billing.billingData.city;
346-
billingAddressCity = billingAddressCity.trim() === '' ? document.getElementById('billing-city').value : billingAddressCity;
362+
let billingAddressCity = billing?.billingData?.city || '';
363+
billingAddressCity = billingAddressCity.trim() === '' ? (document.getElementById('billing-city')?.value || '') : billingAddressCity;
347364

348-
let billingAddressPostcode = billing.billingData.postcode;
349-
billingAddressPostcode = billingAddressPostcode.trim() === '' ? document.getElementById('billing-postcode').value : billingAddressPostcode;
365+
let billingAddressPostcode = billing?.billingData?.postcode || '';
366+
billingAddressPostcode = billingAddressPostcode.trim() === '' ? (document.getElementById('billing-postcode')?.value || '') : billingAddressPostcode;
350367

351368
let orderData = {
352369
customer: {
@@ -453,7 +470,7 @@ const Content = ( props ) => {
453470

454471
// When the bin changes or total recalculates
455472
useEffect(() => {
456-
if (!isCalculating) {
473+
if (!isCalculating && billing?.cartTotal?.value) {
457474
cartTotalRef.current = billing.cartTotal.value;
458475
}
459476
}, [isCalculating]);

public/js/blocks/components/CreditCardForm.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { getSetting } from '@woocommerce/settings';
55
import MaskedInput from './MaskedInput';
66
import InstallmentsOptions from './InstallmentsOptions';
77
const { useSelect } = window.wp.data;
8-
const { checkoutStore } = window.wc.wcBlocksData;
8+
const { checkoutStore } = window.wc?.wcBlocksData || {};
99
const PaymentInstructions = () => {
1010
const settings = getSetting('rm-pagbank-cc_data', {});
1111
const defaultInstallments = settings.defaultInstallments || [];
@@ -16,7 +16,16 @@ const PaymentInstructions = () => {
1616
const [installments, setInstallments] = useState(defaultInstallments);
1717
window.ps_cc_installments = installments;
1818

19-
const isCalculating = useSelect((select) => select(checkoutStore).isCalculating());
19+
const isCalculating = useSelect((select) => {
20+
if (!checkoutStore) {
21+
return false;
22+
}
23+
try {
24+
return select(checkoutStore)?.isCalculating() || false;
25+
} catch (e) {
26+
return false;
27+
}
28+
});
2029
const detectCardBrand = (number) => {
2130
const visaRegex = /^4[0-9]{0,}$/;
2231
const mastercardRegex = /^(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}$/;

public/js/blocks/components/SavedCardInstallments.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ const SavedCardInstallments = (props) => {
77
const { token, emitResponse, eventRegistration, billing } = props;
88
const total = billing?.cartTotal?.value || 0;
99
const showInstallments = Number(total) > 0;
10+
11+
// Safety check: ensure eventRegistration exists
12+
if (!eventRegistration) {
13+
return null;
14+
}
15+
1016
const { onPaymentSetup, onCheckoutValidation: onCheckoutValidation, onCheckoutSuccess, onCheckoutFail } = eventRegistration;
1117
// Example: fetch installments by saved card BIN
1218
const [installments, setInstallments] = useState([]);

readme.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Donate link: https://github.com/sponsors/r-martins
55
Requires at least: 4.0
66
Tested up to: 6.9
77
Requires PHP: 7.4
8-
Stable tag: 4.47.0
8+
Stable tag: 4.48.0
99
License: GPLv3
1010
License URI: https://www.gnu.org/licenses/gpl-3.0.html
1111
PagBank com PIX, Cartão de Crédito, Boleto, Recorrência + Envio Fácil e com Menos Taxas no PagSeguro.
@@ -239,6 +239,13 @@ A confirmação é exibida ainda na tela de sucesso, e pode opcionalmente dispar
239239
Sim! Você pode [configurar descontos percentuais ou fixos](https://ajuda.pbintegracoes.com/hc/pt-br/articles/19945430928909-Oferecer-Desconto-Pix-e-Boleto) para PIX e Boleto diretamente nas configurações do plugin.
240240

241241
== Changelog ==
242+
= 4.48.0 =
243+
* Melhoria: adicionado medidas de segurança, evitando que: 1) usuários adicionem mais de 5 cartões salvos; 2) bots realizem testes de cartão ao adicionar cartões em intervalos menores que 2 minutos ou sem estarem logados; 3) nonce adicionado ao formulário de cartão (área logado) evitando que bots explorem diretamente este endpoint
244+
* Correção: erro fatal poderia ocorrer ao finalizar uma compra com assinatura por conta de uma tentativa de inserir registros duplicados. Na ausência da chave única na tabela, assinatura e cobranças duplicadas poderiam ser geradas. (Reportado por Ricardo Alves B.)
245+
* Correção: mesmo com opção de salvar cartão desabilitada, ainda era possível salvar cartão em Minha Conta > Meios de Pagamento
246+
* Correção/Melhoria: Erro fatal poderia ocorrer ao tentar finalizar um pedido com Boleto (métodos getter retornam null (retorna string vazia)) e melhorado phpdoc de alguns métodos do objeto Address.
247+
* Correção: Erro Fatal poderia ocorrer na primeira vez a configuração de uma das áreas do plugin fosse salva, dependendo de como o plugin foi reinstalado/reconfigurado no passado.
248+
* Correção: em cenários específicos, o form de cartão de crédito não era exibido no checkout em blocos (TypeError).
242249

243250
= 4.47.0 =
244251
* Adicionado suporte a divisão de pagamentos (split) nativa.

rm-pagbank.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* Plugin Name: PagBank Connect
1212
* Plugin URI: https://pbintegracoes.com
1313
* Description: Integra seu WooCommerce com as APIs PagSeguro v4 através da aplicação de Ricardo Martins (com descontos nas taxas oficiais), com suporte a PIX transparente e muito mais.
14-
* Version: 4.47.0
14+
* Version: 4.48.0
1515
* Requires at least: 5.2
1616
* Tested up to: 6.9
1717
* Requires PHP: 7.4
@@ -33,7 +33,7 @@
3333
defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
3434

3535
// Plugin constants.
36-
define( 'WC_PAGSEGURO_CONNECT_VERSION', '4.47.0' );
36+
define( 'WC_PAGSEGURO_CONNECT_VERSION', '4.48.0' );
3737
define( 'WC_PAGSEGURO_CONNECT_PLUGIN_FILE', __FILE__ );
3838
define( 'WC_PAGSEGURO_CONNECT_BASE_DIR', __DIR__ );
3939
define( 'WC_PAGSEGURO_CONNECT_TEMPLATES_DIR', WC_PAGSEGURO_CONNECT_BASE_DIR . '/src/templates/' );

src/Connect.php

Lines changed: 56 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ public static function activate()
314314
global $wpdb;
315315
$recurringTable = $wpdb->prefix . 'pagbank_recurring';
316316

317-
$sql = "CREATE TABLE $recurringTable
317+
$sql = "CREATE TABLE IF NOT EXISTS $recurringTable
318318
(
319319
id int auto_increment,
320320
initial_order_id int not null UNIQUE comment 'Order that generated the subscription profiler',
@@ -340,22 +340,42 @@ public static function activate()
340340
add_option('pagbank_db_version', '4.0');
341341
}
342342

343+
/**
344+
* Check if a table exists in the database
345+
*
346+
* @param string $table_name The table name to check
347+
* @return bool True if table exists, false otherwise
348+
*/
349+
private static function tableExists($table_name)
350+
{
351+
global $wpdb;
352+
$table_name = esc_sql($table_name);
353+
// Suppress errors to avoid warnings if table doesn't exist
354+
$wpdb->suppress_errors();
355+
$result = $wpdb->get_var("SHOW TABLES LIKE '$table_name'");
356+
$wpdb->suppress_errors(false);
357+
return $result === $table_name;
358+
}
359+
343360
public static function upgrade()
344361
{
345362
global $wpdb;
346363
$recurringTable = $wpdb->prefix . 'pagbank_recurring';
347364
$stored_version = get_option('pagbank_db_version');
348365

349366
if (version_compare($stored_version, '4.12', '<')) {
350-
if ($wpdb->get_var("SHOW COLUMNS FROM $recurringTable LIKE 'recurring_initial_fee'") !== 'recurring_initial_fee') { //if column recurring_initial_fee does not exist
351-
$sql = "ALTER TABLE $recurringTable
352-
ADD COLUMN recurring_initial_fee float(8, 2) null comment 'Initial fee to be charged on the first payment' AFTER recurring_amount,
353-
ADD COLUMN recurring_trial_period int null comment 'Number of days to wait before charging the first fee' AFTER recurring_initial_fee,
354-
ADD COLUMN recurring_discount_amount float(8, 2) null comment 'Discount amount to be applied to the recurring amount' AFTER recurring_trial_period,
355-
ADD COLUMN recurring_discount_cycles int null comment 'Number of cycles to apply the discount' AFTER recurring_discount_amount;
356-
";
357-
358-
$wpdb->query($sql);
367+
// Check if table exists before trying to modify it
368+
if (self::tableExists($recurringTable)) {
369+
if ($wpdb->get_var("SHOW COLUMNS FROM $recurringTable LIKE 'recurring_initial_fee'") !== 'recurring_initial_fee') { //if column recurring_initial_fee does not exist
370+
$sql = "ALTER TABLE $recurringTable
371+
ADD COLUMN recurring_initial_fee float(8, 2) null comment 'Initial fee to be charged on the first payment' AFTER recurring_amount,
372+
ADD COLUMN recurring_trial_period int null comment 'Number of days to wait before charging the first fee' AFTER recurring_initial_fee,
373+
ADD COLUMN recurring_discount_amount float(8, 2) null comment 'Discount amount to be applied to the recurring amount' AFTER recurring_trial_period,
374+
ADD COLUMN recurring_discount_cycles int null comment 'Number of cycles to apply the discount' AFTER recurring_discount_amount;
375+
";
376+
377+
$wpdb->query($sql);
378+
}
359379
}
360380
update_option('pagbank_db_version', '4.12');
361381
}
@@ -428,54 +448,21 @@ public static function upgrade()
428448
}
429449

430450
$generalSettings = serialize($generalSettings);
431-
$ccSettings = serialize($ccSettings);
432-
$pixSettings = serialize($pixSettings);
433-
$boletoSettings = serialize($boletoSettings);
451+
$ccSettings = $ccSettings;
452+
$pixSettings = $pixSettings;
453+
$boletoSettings = $boletoSettings;
434454

435455
$wpdb->update(
436456
$settingsTable,
437457
['option_value' => $generalSettings],
438458
['option_name' => 'woocommerce_rm-pagbank_settings']
439459
);
440460

441-
if (get_option('woocommerce_rm-pagbank-cc_settings')) {
442-
$wpdb->update(
443-
$settingsTable,
444-
['option_value' => $ccSettings],
445-
['option_name' => 'woocommerce_rm-pagbank-cc_settings']
446-
);
447-
} else {
448-
$wpdb->insert(
449-
$settingsTable,
450-
['option_name' => 'woocommerce_rm-pagbank-cc_settings', 'option_value' => $ccSettings]
451-
);
452-
}
453-
454-
if (get_option('woocommerce_rm-pagbank-pix_settings')) {
455-
$wpdb->update(
456-
$settingsTable,
457-
['option_value' => $pixSettings],
458-
['option_name' => 'woocommerce_rm-pagbank-pix_settings']
459-
);
460-
} else {
461-
$wpdb->insert(
462-
$settingsTable,
463-
['option_name' => 'woocommerce_rm-pagbank-pix_settings', 'option_value' => $pixSettings]
464-
);
465-
}
466-
467-
if (get_option('woocommerce_rm-pagbank-boleto_settings')) {
468-
$wpdb->update(
469-
$settingsTable,
470-
['option_value' => $boletoSettings],
471-
['option_name' => 'woocommerce_rm-pagbank-boleto_settings']
472-
);
473-
} else {
474-
$wpdb->insert(
475-
$settingsTable,
476-
['option_name' => 'woocommerce_rm-pagbank-boleto_settings', 'option_value' => $boletoSettings]
477-
);
478-
}
461+
// Use update_option which automatically handles INSERT or UPDATE
462+
// This prevents duplicate key errors
463+
update_option('woocommerce_rm-pagbank-cc_settings', $ccSettings);
464+
update_option('woocommerce_rm-pagbank-pix_settings', $pixSettings);
465+
update_option('woocommerce_rm-pagbank-boleto_settings', $boletoSettings);
479466

480467
foreach ($recurringSettings as $key => $setting) {
481468
$key = 'woocommerce_rm-pagbank-' . $key;
@@ -502,20 +489,29 @@ public static function upgrade()
502489
}
503490

504491
if (version_compare($stored_version, '4.27', '<')) {
505-
if ($wpdb->get_var("SHOW COLUMNS FROM $recurringTable LIKE 'recurring_max_cycles'") !== 'recurring_max_cycles') {
506-
$sql = "ALTER TABLE $recurringTable
507-
ADD COLUMN recurring_max_cycles int null comment 'Maximum number of billing cycles' AFTER recurring_discount_cycles;";
508-
$wpdb->query($sql);
492+
// Check if table exists before trying to modify it
493+
if (self::tableExists($recurringTable)) {
494+
if ($wpdb->get_var("SHOW COLUMNS FROM $recurringTable LIKE 'recurring_max_cycles'") !== 'recurring_max_cycles') {
495+
$sql = "ALTER TABLE $recurringTable
496+
ADD COLUMN recurring_max_cycles int null comment 'Maximum number of billing cycles' AFTER recurring_discount_cycles;";
497+
$wpdb->query($sql);
498+
}
509499
}
510500
update_option('pagbank_db_version', '4.27');
511501
}
512502

513503
if (version_compare($stored_version, '4.28', '<')) {
514-
$sql = "ALTER TABLE $recurringTable
515-
ADD COLUMN retry_attempts_remaining int null comment 'Number of billing attempts remaining on suspended subscriptions' AFTER suspended_at;
516-
";
517-
518-
$wpdb->query($sql);
504+
// Check if table exists before trying to modify it
505+
if (self::tableExists($recurringTable)) {
506+
// Check if column already exists to avoid errors
507+
if ($wpdb->get_var("SHOW COLUMNS FROM $recurringTable LIKE 'retry_attempts_remaining'") !== 'retry_attempts_remaining') {
508+
$sql = "ALTER TABLE $recurringTable
509+
ADD COLUMN retry_attempts_remaining int null comment 'Number of billing attempts remaining on suspended subscriptions' AFTER suspended_at;
510+
";
511+
512+
$wpdb->query($sql);
513+
}
514+
}
519515
update_option('pagbank_db_version', '4.28');
520516
}
521517

0 commit comments

Comments
 (0)