Skip to content

Commit f83ecf2

Browse files
committed
feat: ✨ Add automatic accent removal for PIX codes
- Add removeAccents() method to clean special characters - Remove accents from recipientName, recipientCity, and description - Normalize text using NFD and remove diacritical marks - Clean special characters that could cause bank rejections - Apply cleaning in both payload generation and response - Ensures maximum compatibility with banking systems - Fixes issues with characters like á, ã, ç, í, etc.
1 parent 0186f1e commit f83ecf2

File tree

1 file changed

+56
-30
lines changed

1 file changed

+56
-30
lines changed

src/services/StaticPixService.ts

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,20 @@ export class StaticPixService {
3535
*/
3636
private calculateCRC16(payload: string): string {
3737
const polynomial = 0x1021;
38-
let crc = 0xFFFF;
39-
38+
let crc = 0xffff;
39+
4040
for (let i = 0; i < payload.length; i++) {
41-
crc ^= (payload.charCodeAt(i) << 8);
41+
crc ^= payload.charCodeAt(i) << 8;
4242
for (let j = 0; j < 8; j++) {
4343
if (crc & 0x8000) {
4444
crc = (crc << 1) ^ polynomial;
4545
} else {
4646
crc = crc << 1;
4747
}
48-
crc &= 0xFFFF;
48+
crc &= 0xffff;
4949
}
5050
}
51-
51+
5252
return crc.toString(16).toUpperCase().padStart(4, '0');
5353
}
5454

@@ -68,34 +68,52 @@ export class StaticPixService {
6868
if (pixKey.includes('@') && pixKey.includes('.')) {
6969
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(pixKey);
7070
}
71-
71+
7272
// Phone format (+5511999999999)
7373
if (pixKey.startsWith('+55')) {
7474
return /^\+55\d{10,11}$/.test(pixKey);
7575
}
76-
76+
7777
// CPF format (11 digits)
7878
if (/^\d{11}$/.test(pixKey)) {
7979
return true;
8080
}
81-
81+
8282
// CNPJ format (14 digits)
8383
if (/^\d{14}$/.test(pixKey)) {
8484
return true;
8585
}
86-
86+
8787
// Random key (UUID format)
8888
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(pixKey)) {
8989
return true;
9090
}
91-
91+
9292
return false;
9393
}
9494

95+
/**
96+
* Removes accents and special characters from text
97+
*/
98+
private removeAccents(text: string): string {
99+
return text
100+
.normalize('NFD')
101+
.replace(/[\u0300-\u036f]/g, '')
102+
.replace(/[^\w\s]/gi, ' ')
103+
.replace(/\s+/g, ' ')
104+
.trim();
105+
}
106+
95107
/**
96108
* Generates static Pix payload according to BACEN EMV 4.0 standard
97109
*/
98-
private generatePixPayload({ pixKey, amount, recipientName, recipientCity, description }: StaticPixRequest): string {
110+
private generatePixPayload({
111+
pixKey,
112+
amount,
113+
recipientName,
114+
recipientCity,
115+
description,
116+
}: StaticPixRequest): string {
99117
// Validate inputs
100118
if (!this.validatePixKey(pixKey)) {
101119
throw new Error('Invalid Pix key format');
@@ -105,9 +123,11 @@ export class StaticPixService {
105123
throw new Error('Amount must be between 0.01 and 999,999.99');
106124
}
107125

108-
// Truncate names if they exceed limits
109-
const truncatedRecipientName = recipientName.substring(0, 25);
110-
const truncatedRecipientCity = recipientCity.substring(0, 15);
126+
// Remove accents and truncate names if they exceed limits
127+
const cleanRecipientName = this.removeAccents(recipientName);
128+
const cleanRecipientCity = this.removeAccents(recipientCity);
129+
const truncatedRecipientName = cleanRecipientName.substring(0, 25);
130+
const truncatedRecipientCity = cleanRecipientCity.substring(0, 15);
111131

112132
// Build EMV payload
113133
let payload = '';
@@ -119,9 +139,8 @@ export class StaticPixService {
119139
payload += this.formatEMVField('01', '12');
120140

121141
// Merchant Account Information (26)
122-
const merchantInfo =
123-
this.formatEMVField('00', 'BR.GOV.BCB.PIX') +
124-
this.formatEMVField('01', pixKey);
142+
const merchantInfo =
143+
this.formatEMVField('00', 'BR.GOV.BCB.PIX') + this.formatEMVField('01', pixKey);
125144
payload += this.formatEMVField('26', merchantInfo);
126145

127146
// Merchant Category Code (52) - Generic
@@ -144,7 +163,8 @@ export class StaticPixService {
144163

145164
// Additional Data Field Template (62)
146165
if (description) {
147-
const additionalData = this.formatEMVField('05', description.substring(0, 25));
166+
const cleanDescription = this.removeAccents(description);
167+
const additionalData = this.formatEMVField('05', cleanDescription.substring(0, 25));
148168
payload += this.formatEMVField('62', additionalData);
149169
}
150170

@@ -166,14 +186,16 @@ export class StaticPixService {
166186
margin: 1,
167187
color: {
168188
dark: '#000000',
169-
light: '#FFFFFF'
189+
light: '#FFFFFF',
170190
},
171-
width: 256
191+
width: 256,
172192
});
173-
193+
174194
return qrCodeDataUrl;
175195
} catch (error) {
176-
throw new Error(`Failed to generate QR code: ${error instanceof Error ? error.message : 'Unknown error'}`);
196+
throw new Error(
197+
`Failed to generate QR code: ${error instanceof Error ? error.message : 'Unknown error'}`
198+
);
177199
}
178200
}
179201

@@ -184,14 +206,16 @@ export class StaticPixService {
184206
try {
185207
// Generate Pix payload
186208
const pixCode = this.generatePixPayload(request);
187-
209+
188210
// Generate QR Code
189211
const qrCodeDataUrl = await this.generateQRCode(pixCode);
190212

191-
// Apply same truncation as in payload generation
192-
const truncatedRecipientName = request.recipientName.substring(0, 25);
193-
const truncatedRecipientCity = request.recipientCity.substring(0, 15);
194-
213+
// Apply same cleaning and truncation as in payload generation
214+
const cleanRecipientName = this.removeAccents(request.recipientName);
215+
const cleanRecipientCity = this.removeAccents(request.recipientCity);
216+
const truncatedRecipientName = cleanRecipientName.substring(0, 25);
217+
const truncatedRecipientCity = cleanRecipientCity.substring(0, 15);
218+
195219
return {
196220
success: true,
197221
message: 'Static Pix QR code generated successfully!',
@@ -201,13 +225,15 @@ export class StaticPixService {
201225
amountFormatted: `R$ ${request.amount.toFixed(2)}`,
202226
recipient: truncatedRecipientName,
203227
city: truncatedRecipientCity,
204-
description: request.description || ''
228+
description: request.description ? this.removeAccents(request.description) : '',
205229
},
206230
pixCode,
207-
qrCodeDataUrl
231+
qrCodeDataUrl,
208232
};
209233
} catch (error) {
210-
throw new Error(`Failed to create static Pix: ${error instanceof Error ? error.message : 'Unknown error'}`);
234+
throw new Error(
235+
`Failed to create static Pix: ${error instanceof Error ? error.message : 'Unknown error'}`
236+
);
211237
}
212238
}
213239
}

0 commit comments

Comments
 (0)